| File: BicepValueProxy.cs | Web Access |
| Project: src\src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj (Aspire.Hosting.Azure.Provisioning) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Net; using Azure.Provisioning; using Azure.Provisioning.Expressions; using Azure.Provisioning.Primitives; namespace Aspire.Hosting.Azure.Provisioning; /// <summary> /// Represents a literal, resource reference, or expression-backed Azure Provisioning value. /// </summary> [AspireExport] public sealed class BicepValueProxy { private readonly IBicepValue _value; private readonly Type _valueType; private readonly bool _forcedSecure; private BicepValueProxy(IBicepValue value, Type valueType, bool forcedSecure) { _value = value; _valueType = valueType; _forcedSecure = forcedSecure; } /// <summary> /// Gets whether the value is unset, literal, or expression-backed. /// </summary> [AspireExport] internal BicepValueKind Kind => _value.Kind; /// <summary> /// Gets whether the value contains secure data. /// </summary> [AspireExport] internal bool IsSecure => _forcedSecure || _value.IsSecure; /// <summary> /// Creates a proxy for generated provisioning integration code. /// </summary> /// <typeparam name="T">The literal type represented by the Azure Provisioning value.</typeparam> /// <param name="value">The Azure Provisioning value to wrap.</param> /// <returns>A proxy that preserves the value's literal, expression, reference, and security metadata.</returns> [EditorBrowsable(EditorBrowsableState.Never)] [AspireExportIgnore(Reason = "Used by generated provisioning proxy code.")] public static BicepValueProxy Create<T>(BicepValue<T> value) { ArgumentNullException.ThrowIfNull(value); return new(value, typeof(T), forcedSecure: false); } internal static BicepValueProxy Create<T>(BicepValue<T> value, bool isSecure) { ArgumentNullException.ThrowIfNull(value); return new(value, typeof(T), isSecure); } internal static BicepValueProxy Create(IBicepValue value) { ArgumentNullException.ThrowIfNull(value); return Create(value, value.LiteralValue?.GetType() ?? typeof(object)); } internal static BicepValueProxy Create(IBicepValue value, Type valueType) { ArgumentNullException.ThrowIfNull(value); ArgumentNullException.ThrowIfNull(valueType); return new(value, valueType, forcedSecure: false); } /// <summary> /// Assigns this value to a generated Azure Provisioning property. /// </summary> /// <typeparam name="T">The literal type accepted by the target property.</typeparam> /// <param name="target">The target Azure Provisioning value.</param> [EditorBrowsable(EditorBrowsableState.Never)] [AspireExportIgnore(Reason = "Used by generated provisioning proxy code.")] public void AssignTo<T>(BicepValue<T> target) { ArgumentNullException.ThrowIfNull(target); EnsureLiteralType<T>(); if (_value.Kind == BicepValueKind.Literal) { // The shared string factory also supplies IP literals. Only parse literals; // expression-backed strings must retain their SDK reference and secure metadata. var literal = typeof(T) == typeof(IPAddress) && _value.LiteralValue is string address ? (T)(object)IPAddress.Parse(address) : (T)_value.LiteralValue!; var literalValue = new BicepValue<T>(literal); target.Assign(literalValue); if (IsSecure && !((IBicepValue)target).IsSecure) { // Azure Provisioning only copies the typed literal when the source is a BicepValue<T>, // but it only propagates secure metadata through IBicepValue.Assign. Apply the typed // literal first, then re-apply the secure flag without disturbing the assigned value. ((IBicepValue)target).Assign(new SecureBicepValue(literalValue)); } return; } ((IBicepValue)target).Assign(GetAssignableValue()); } internal void AssignTo(BicepValue<object> target, Type literalType) { ArgumentNullException.ThrowIfNull(target); ArgumentNullException.ThrowIfNull(literalType); EnsureLiteralType(literalType); AssignTo(target); } /// <summary> /// Converts a literal or proxy value to the requested Azure Provisioning value type. /// </summary> /// <typeparam name="T">The expected literal type.</typeparam> /// <param name="value">A literal value, an IP address string when <typeparamref name="T"/> is <see cref="IPAddress"/>, or <see cref="BicepValueProxy"/>.</param> /// <returns>An Azure Provisioning value that preserves expression and reference metadata.</returns> [EditorBrowsable(EditorBrowsableState.Never)] [AspireExportIgnore(Reason = "Used by generated provisioning proxy code.")] public static BicepValue<T> Convert<T>(object value) { ArgumentNullException.ThrowIfNull(value); if (value is BicepValueProxy proxy) { proxy.EnsureLiteralType<T>(); var converted = new BicepValue<T>((T)default!); proxy.AssignTo(converted); return converted; } if (value is T literal) { return literal; } // IP addresses travel over ATS as strings, but the SDK requires typed literals. // Keep proxy conversion above this branch so expressions and references retain their metadata. if (typeof(T) == typeof(IPAddress) && value is string address) { return (T)(object)IPAddress.Parse(address); } throw new ArgumentException($"Expected a {typeof(T).Name} literal or {nameof(BicepValueProxy)}.", nameof(value)); } internal static BicepValueProxy CreateExpression(BicepExpression expression, bool isSecure) { ArgumentNullException.ThrowIfNull(expression); return new(new BicepValue<object>(expression), typeof(object), isSecure); } internal BicepExpression ToBicepExpression() { return _value.ToBicepExpression(); } internal BicepValue<object> ToObjectBicepValue() { return new(ToBicepExpression()); } private void EnsureLiteralType<T>() { EnsureLiteralType(typeof(T)); } private void EnsureLiteralType(Type targetType) { if (_value.Kind != BicepValueKind.Literal && _valueType == typeof(object)) { return; } if (targetType.IsAssignableFrom(_valueType)) { return; } if (targetType == typeof(IPAddress) && _valueType == typeof(string)) { return; } var sourceUnderlyingType = Nullable.GetUnderlyingType(_valueType) ?? _valueType; var targetUnderlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; if (sourceUnderlyingType != targetUnderlyingType) { var valueDescription = _value.Kind == BicepValueKind.Literal ? "literal" : "value"; throw new ArgumentException( $"A {valueDescription} of type {_valueType.Name} cannot be assigned to a BicepValue<{targetType.Name}>."); } } private IBicepValue GetAssignableValue() { // Azure Provisioning copies secure metadata from the source IBicepValue during assignment. // Composing expressions creates a new SDK value that no longer carries that metadata, so // expose the propagated state through an adapter without changing the emitted expression. return IsSecure && !_value.IsSecure ? new SecureBicepValue(_value) : _value; } private sealed class SecureBicepValue(IBicepValue inner) : IBicepValue { public BicepValueKind Kind => inner.Kind; public BicepExpression? Expression { get => inner.Expression; set => inner.Expression = value; } public object? LiteralValue => inner.LiteralValue; public BicepValueReference? Self { get => inner.Self; set => inner.Self = value; } public BicepValueReference? Source => inner.Source; public bool IsOutput => inner.IsOutput; public bool IsRequired => inner.IsRequired; public bool IsSecure => true; public bool IsEmpty => inner.IsEmpty; public void Assign(IBicepValue source) { inner.Assign(source); } public BicepExpression Compile() { return inner.Compile(); } public void SetReadOnly() { inner.SetReadOnly(); } } }