File: Publishing\RadiusUnresolvableValueException.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;
 
namespace Aspire.Hosting.Radius.Publishing;
 
/// <summary>
/// Thrown when one specific fragment of a container environment value cannot be produced at publish
/// time, so the publisher can skip that single variable without also swallowing unrelated failures.
/// </summary>
/// <remarks>
/// <para>
/// The env-var resolution loop in <c>RadiusInfrastructureBuilder</c> used to wrap every value in
/// <c>catch (InvalidOperationException)</c>. That is far broader than intended: it also swallowed
/// genuine publish errors, so whether a bug surfaced depended on the exception's <em>type</em>
/// rather than on the publisher having decided the value was legitimately unavailable.
/// </para>
/// <para>
/// The two cases the publisher does intend to skip both raise <see cref="InvalidOperationException"/>
/// from framework code it does not own:
/// </para>
/// <list type="number">
/// <item>A reference to an endpoint that is not defined on the target resource
/// (<c>EndpointReference.EndpointAnnotation</c>).</item>
/// <item>A reference to an output of a resource whose values are only known after its own
/// deployment — for example an Azure Bicep output, which throws
/// <c>"...has no value..."</c> until the deployment that produces it has run. Radius cannot
/// deploy those resources, so the value is genuinely unavailable at publish time. The skip is
/// gated on the value positively declaring deployment-substituted semantics by implementing
/// <see cref="IManifestExpressionProvider"/>; a plain <see cref="IValueProvider"/> that raises
/// <see cref="InvalidOperationException"/> for a genuine invalid state fails the publish.</item>
/// </list>
/// <para>
/// Both are detected at their exact call site and re-thrown as this type, so the loop's catch names
/// the condition rather than a type that any other failure could also share.
/// </para>
/// <para>
/// It derives from <see cref="InvalidOperationException"/> to match the package's publish-time
/// failure convention (the same convention <c>RadiusBackingResourceProjectionException</c>
/// documents), so that a path which does not intend to skip the value — and therefore lets this
/// escape — still fails the publish as the publisher's own exception family rather than as a bare
/// <see cref="Exception"/>.
/// </para>
/// </remarks>
internal sealed class RadiusUnresolvableValueException : InvalidOperationException
{
    public RadiusUnresolvableValueException(IResource owner, string reason, Exception? innerException = null)
        : base(reason, innerException)
    {
        Owner = owner;
    }
 
    /// <summary>Gets the resource whose environment value could not be resolved.</summary>
    public IResource Owner { get; }
}