File: ApplicationModel\ReferenceExpression.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
 
namespace Aspire.Hosting.ApplicationModel;
 
/// <summary>
/// Represents an expression that might be made up of multiple resource properties. For example,
/// a connection string might be made up of a host, port, and password from different endpoints.
/// </summary>
public class ReferenceExpression : IManifestExpressionProvider, IValueProvider, IValueWithReferences
{
    private readonly string[] _manifestExpressions;
 
    private ReferenceExpression(string format, IValueProvider[] valueProviders, string[] manifestExpressions)
    {
        ArgumentNullException.ThrowIfNull(format);
        ArgumentNullException.ThrowIfNull(valueProviders);
        ArgumentNullException.ThrowIfNull(manifestExpressions);
 
        Format = format;
        ValueProviders = valueProviders;
        _manifestExpressions = manifestExpressions;
    }
 
    /// <summary>
    /// The format string for this expression.
    /// </summary>
    public string Format { get; }
 
    /// <summary>
    /// The manifest expressions for the parameters for the format string.
    /// </summary>
    public IReadOnlyList<string> ManifestExpressions => _manifestExpressions;
 
    /// <summary>
    /// The list of <see cref="IValueProvider"/> that will be used to resolve parameters for the format string.
    /// </summary>
    public IReadOnlyList<IValueProvider> ValueProviders { get; }
 
    IEnumerable<object> IValueWithReferences.References => ValueProviders;
 
    /// <summary>
    /// The value expression for the format string.
    /// </summary>
    public string ValueExpression =>
        string.Format(CultureInfo.InvariantCulture, Format, _manifestExpressions);
 
    /// <summary>
    /// Gets the value of the expression. The final string value after evaluating the format string and its parameters.
    /// </summary>
    /// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
    /// <returns></returns>
    public async ValueTask<string?> GetValueAsync(CancellationToken cancellationToken)
    {
        if (Format.Length == 0)
        {
            return null;
        }
 
        var args = new object?[ValueProviders.Count];
        for (var i = 0; i < ValueProviders.Count; i++)
        {
            args[i] = await ValueProviders[i].GetValueAsync(cancellationToken).ConfigureAwait(false);
        }
 
        return string.Format(CultureInfo.InvariantCulture, Format, args);
    }
 
    internal static ReferenceExpression Create(string format, IValueProvider[] valueProviders, string[] manifestExpressions)
    {
        return new(format, valueProviders, manifestExpressions);
    }
 
    /// <summary>
    /// Creates a new instance of <see cref="ReferenceExpression"/> with the specified format and value providers.
    /// </summary>
    /// <param name="handler">The handler that contains the format and value providers.</param>
    /// <returns>A new instance of <see cref="ReferenceExpression"/> with the specified format and value providers.</returns>
    public static ReferenceExpression Create(in ExpressionInterpolatedStringHandler handler)
    {
        return handler.GetExpression();
    }
 
    /// <summary>
    /// Represents a handler for interpolated strings that contain expressions. Those expressions will either be literal strings or
    /// instances of types that implement both <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.
    /// </summary>
    /// <param name="literalLength">The length of the literal part of the interpolated string.</param>
    /// <param name="formattedCount">The number of formatted parts in the interpolated string.</param>
    [InterpolatedStringHandler]
    public ref struct ExpressionInterpolatedStringHandler(int literalLength, int formattedCount)
    {
        private readonly StringBuilder _builder = new(literalLength * 2);
        private readonly List<IValueProvider> _valueProviders = new(formattedCount);
        private readonly List<string> _manifestExpressions = new(formattedCount);
 
        /// <summary>
        /// Appends a literal value to the expression.
        /// </summary>
        /// <param name="value">The literal string value to be appended to the interpolated string.</param>
        public readonly void AppendLiteral(string value)
        {
            _builder.Append(value);
        }
 
        /// <summary>
        /// Appends a formatted value to the expression.
        /// </summary>
        /// <param name="value">The formatted string to be appended to the interpolated string.</param>
        public readonly void AppendFormatted(string? value)
        {
            // The value that comes in is a literal string that is not meant to be interpreted.
            // But the _builder later gets treated as a format string, so we just need to escape the braces.
            _builder.Append(value?.Replace("{", "{{").Replace("}", "}}"));
        }
 
        /// <summary>
        /// Appends a formatted value to the expression. The value must implement <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.
        /// </summary>
        /// <param name="valueProvider">An instance of an object which implements <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public void AppendFormatted<T>(T valueProvider) where T : IValueProvider, IManifestExpressionProvider
        {
            var index = _valueProviders.Count;
            _builder.Append(CultureInfo.InvariantCulture, $"{{{index}}}");
 
            _valueProviders.Add(valueProvider);
            _manifestExpressions.Add(valueProvider.ValueExpression);
        }
 
        internal readonly ReferenceExpression GetExpression() =>
            new(_builder.ToString(), [.. _valueProviders], [.. _manifestExpressions]);
    }
}
 
/// <summary>
/// A builder for creating <see cref="ReferenceExpression"/> instances.
/// </summary>
public class ReferenceExpressionBuilder
{
    private readonly StringBuilder _builder = new();
    private readonly List<IValueProvider> _valueProviders = new();
    private readonly List<string> _manifestExpressions = new();
 
    /// <summary>
    /// Indicates whether the expression is empty.
    /// </summary>
    public bool IsEmpty => _builder.Length == 0;
 
    /// <summary>
    /// Appends an interpolated string to the expression.
    /// </summary>
    /// <param name="handler"></param>
    public void Append([InterpolatedStringHandlerArgument("")] in ReferenceExpressionBuilderInterpolatedStringHandler handler)
    {
    }
 
    /// <summary>
    /// Appends a literal value to the expression.
    /// </summary>
    /// <param name="value">The literal string value to be appended to the interpolated string.</param>
    public void AppendLiteral(string value)
    {
        _builder.Append(value);
    }
 
    /// <summary>
    /// Appends a formatted value to the expression.
    /// </summary>
    /// <param name="value">The formatted string to be appended to the interpolated string.</param>
    public void AppendFormatted(string? value)
    {
        _builder.Append(value);
    }
 
    /// <summary>
    /// Appends a formatted value to the expression. The value must implement <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.
    /// </summary>
    /// <param name="valueProvider">An instance of an object which implements <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.</param>
    /// <exception cref="InvalidOperationException"></exception>
    public void AppendFormatted<T>(T valueProvider) where T : IValueProvider, IManifestExpressionProvider
    {
        var index = _valueProviders.Count;
        _builder.Append(CultureInfo.InvariantCulture, $"{{{index}}}");
 
        _valueProviders.Add(valueProvider);
        _manifestExpressions.Add(valueProvider.ValueExpression);
    }
 
    /// <summary>
    /// Builds the <see cref="ReferenceExpression"/>.
    /// </summary>
    public ReferenceExpression Build() =>
        ReferenceExpression.Create(_builder.ToString(), [.. _valueProviders], [.. _manifestExpressions]);
 
    /// <summary>
    /// Represents a handler for interpolated strings that contain expressions. Those expressions will either be literal strings or
    /// instances of types that implement both <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.
    /// </summary>
    /// <param name="literalLength">The length of the literal part of the interpolated string.</param>
    /// <param name="formattedCount">The number of formatted parts in the interpolated string.</param>
    /// <param name="builder">The builder that will be used to create the <see cref="ReferenceExpression"/>.</param>
    [InterpolatedStringHandler]
#pragma warning disable CS9113 // Parameter is unread.
    public ref struct ReferenceExpressionBuilderInterpolatedStringHandler(int literalLength, int formattedCount, ReferenceExpressionBuilder builder)
#pragma warning restore CS9113 // Parameter is unread.
    {
        /// <summary>
        /// Appends a literal value to the expression.
        /// </summary>
        /// <param name="value">The literal string value to be appended to the interpolated string.</param>
        public readonly void AppendLiteral(string value)
        {
            builder.AppendLiteral(value);
        }
 
        /// <summary>
        /// Appends a formatted value to the expression.
        /// </summary>
        /// <param name="value">The formatted string to be appended to the interpolated string.</param>
        public readonly void AppendFormatted(string? value)
        {
            builder.AppendFormatted(value);
        }
 
        /// <summary>
        /// Appends a formatted value to the expression. The value must implement <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.
        /// </summary>
        /// <param name="valueProvider">An instance of an object which implements <see cref="IValueProvider"/> and <see cref="IManifestExpressionProvider"/>.</param>
        /// <exception cref="InvalidOperationException"></exception>
        public void AppendFormatted<T>(T valueProvider) where T : IValueProvider, IManifestExpressionProvider
        {
            builder.AppendFormatted(valueProvider);
        }
    }
}