File: Recipes\RadiusRecipeParameterExtensions.cs
Web Access
Project: src\src\Aspire.Hosting.Radius\Aspire.Hosting.Radius.csproj (Aspire.Hosting.Radius)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Radius;
using Aspire.Hosting.Radius.Annotations;
 
namespace Aspire.Hosting;
 
/// <summary>
/// Recipe-parameter configuration entry points for
/// <see cref="RadiusEnvironmentResource"/>. These extensions attach a recipe-parameter
/// annotation to the environment that the publisher consumes to emit a <c>parameters</c>
/// block on each recipe entry under <c>Radius.Core/recipePacks</c> (and the legacy
/// <c>Applications.Core/environments</c> inline-recipes shape).
/// </summary>
/// <remarks>
/// <para>
/// Parameter values may be literals (string, number, boolean, array, or string-keyed
/// object), an <see cref="IResourceBuilder{ParameterResource}"/> binding, or a
/// <see cref="Radius.RadiusProviderReference"/> (e.g. <c>RadiusProviderReference.AwsRegion</c>).
/// </para>
/// <para>
/// <b>Precedence</b>: a resource-type-scoped parameter overrides an environment-wide
/// parameter of the same key for recipe entries of that type. Keys that do not collide are
/// merged (union). Repeated calls for the same scope also merge, with the later call winning
/// per key.
/// </para>
/// </remarks>
public static class RadiusRecipeParameterExtensions
{
    /// <summary>
    /// Declares recipe parameters applied to <b>every</b> recipe entry in the
    /// environment's recipe pack.
    /// </summary>
    /// <param name="builder">The Radius environment resource builder.</param>
    /// <param name="configure">
    /// Callback that populates the environment-wide parameter set. Repeated calls merge,
    /// with the later call winning per key.
    /// </param>
    /// <returns>The same builder for chaining.</returns>
    /// <exception cref="ArgumentException">A parameter key is empty or whitespace.</exception>
    /// <remarks>
    /// Environment-wide parameters apply to every recipe entry in the environment's recipe pack.
    /// A parameter scoped to a specific resource type via
    /// <see cref="WithRecipeParameters(IResourceBuilder{RadiusEnvironmentResource}, string, Action{IDictionary{string, object}})"/>
    /// overrides an environment-wide parameter of the same key for entries of that type.
    /// </remarks>
    /// <example>
    /// Apply an environment-wide recipe parameter and a provider reference:
    /// <code>
    /// var builder = DistributedApplication.CreateBuilder(args);
    /// builder.AddRadiusEnvironment("radius")
    ///        .WithRecipeParameters(p =>
    ///        {
    ///            p["tier"] = "standard";
    ///            p["region"] = RadiusProviderReference.AwsRegion;
    ///        });
    /// </code>
    /// </example>
    // [AspireExportIgnore]: the configure callback over a mutable dictionary is not
    // representable in the Aspire type system catalog (ASPIREEXPORT008); the method
    // is part of the public C# API surface and the export is suppressed only for ATS.
    [AspireExportIgnore(Reason = "The configure callback over a mutable dictionary is not representable in the ATS catalog (ASPIREEXPORT008).")]
    public static IResourceBuilder<RadiusEnvironmentResource> WithRecipeParameters(
        this IResourceBuilder<RadiusEnvironmentResource> builder,
        Action<IDictionary<string, object>> configure)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(configure);
 
        var staging = new Dictionary<string, object>(StringComparer.Ordinal);
        configure(staging);
 
        var annotation = RadiusRecipeParametersAnnotation.GetOrAdd(builder.Resource);
        RadiusRecipeParametersAnnotation.Merge(annotation.EnvironmentWide, staging, nameof(configure));
        return builder;
    }
 
    /// <summary>
    /// Declares recipe parameters applied only to recipe entries of the given Radius
    /// resource type (e.g. <c>Radius.Data/redisCaches</c>). These take precedence over
    /// environment-wide parameters of the same key for that type.
    /// </summary>
    /// <param name="builder">The Radius environment resource builder.</param>
    /// <param name="resourceType">The Radius resource type to scope the parameters to.</param>
    /// <param name="configure">
    /// Callback that populates the resource-type-scoped parameter set. Repeated calls for
    /// the same type merge, with the later call winning per key.
    /// </param>
    /// <returns>The same builder for chaining.</returns>
    /// <exception cref="ArgumentException">
    /// <paramref name="resourceType"/> is empty/whitespace, or a parameter key is empty or whitespace.
    /// </exception>
    // [AspireExportIgnore]: see the environment-wide overload above (ASPIREEXPORT008).
    [AspireExportIgnore(Reason = "The configure callback over a mutable dictionary is not representable in the ATS catalog (ASPIREEXPORT008).")]
    public static IResourceBuilder<RadiusEnvironmentResource> WithRecipeParameters(
        this IResourceBuilder<RadiusEnvironmentResource> builder,
        string resourceType,
        Action<IDictionary<string, object>> configure)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentException.ThrowIfNullOrWhiteSpace(resourceType);
        ArgumentNullException.ThrowIfNull(configure);
 
        var staging = new Dictionary<string, object>(StringComparer.Ordinal);
        configure(staging);
 
        var annotation = RadiusRecipeParametersAnnotation.GetOrAdd(builder.Resource);
        if (!annotation.ByResourceType.TryGetValue(resourceType, out var target))
        {
            target = new Dictionary<string, object>(StringComparer.Ordinal);
            annotation.ByResourceType[resourceType] = target;
        }
 
        RadiusRecipeParametersAnnotation.Merge(target, staging, nameof(configure));
        return builder;
    }
}