< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.AsyncResponsePayloadReflection
Assembly: AsyncResponse.Abstractions
File(s): /_/src/AsyncResponse.Abstractions/AsyncResponsePayloadReflection.cs
Line coverage
88%
Covered lines: 15
Uncovered lines: 2
Coverable lines: 17
Total lines: 76
Line coverage: 88.2%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
OverridesOnRecovery(...)100%22100%
DetectOverride(...)100%8883.33%

File(s)

/_/src/AsyncResponse.Abstractions/AsyncResponsePayloadReflection.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Diagnostics.CodeAnalysis;
 3using System.Reflection;
 4
 5namespace AsyncResponse;
 6
 7/// <summary>
 8/// Reflection helpers over <see cref="IAsyncResponsePayload"/> implementations. Durable channels
 9/// use this to fail fast when a recovery-enabled flow's payload has not overridden
 10/// <see cref="IAsyncResponsePayload.OnRecovery"/> and would otherwise silently take the
 11/// conservative default (never resume).
 12/// </summary>
 13public static class AsyncResponsePayloadReflection
 14{
 1415    private static readonly ConcurrentDictionary<Type, bool> OverrideCache = new();
 16
 17    /// <summary>
 18    /// Returns <c>true</c> when <paramref name="payloadType"/> provides its own implementation of
 19    /// <see cref="IAsyncResponsePayload.OnRecovery"/> rather than inheriting the interface's
 20    /// default. The result is cached per type.
 21    /// </summary>
 22    public static bool OverridesOnRecovery(Type payloadType)
 23    {
 155124        ArgumentNullException.ThrowIfNull(payloadType);
 25
 26        // Collectible (plugin) payload types are detected per call: a strong Type-keyed cache
 27        // entry would pin the plugin's AssemblyLoadContext after unload. The detection is a cheap
 28        // reflection probe on a startup/registration path, so the cold cost is immaterial.
 154929        if (payloadType.Assembly.IsCollectible)
 230            return DetectOverride(payloadType);
 31
 154732        return OverrideCache.GetOrAdd(payloadType, DetectOverride);
 33    }
 34
 35    [UnconditionalSuppressMessage("Trimming", "IL2070",
 36        Justification = "Best-effort diagnostic: payload types reaching this check are statically referenced by the wait
 37                        "registration that triggers it (typeof(T)), so their methods are preserved; when the runtime sti
 38                        "cannot answer, the check fails open rather than failing a correct app.")]
 39    private static bool DetectOverride(Type type)
 40    {
 4641        if (type.IsInterface || !typeof(IAsyncResponsePayload).IsAssignableFrom(type))
 442            return false;
 43
 44        // Implicit implementations (the overwhelmingly common shape) declare a public
 45        // OnRecovery on the class itself — detectable without the interface map. The return type
 46        // must match too: GetMethod ignores it, but a same-name method returning anything else
 47        // (void, Task, …) cannot implicitly implement the interface member — the interface
 48        // default still applies, so reporting true here would wave through exactly the payload
 49        // this guard exists to reject. A wrong-return match falls through to the interface map,
 50        // which answers authoritatively (an explicit implementation may still exist alongside).
 4251        if (type.GetMethod(nameof(IAsyncResponsePayload.OnRecovery), BindingFlags.Instance | BindingFlags.Public, Type.E
 4252            && declared.ReturnType == typeof(RecoveryAction))
 53        {
 2454            return true;
 55        }
 56
 57        try
 58        {
 1859            var map = type.GetInterfaceMap(typeof(IAsyncResponsePayload));
 1860            var interfaceMethod = typeof(IAsyncResponsePayload).GetMethod(nameof(IAsyncResponsePayload.OnRecovery))!;
 1861            var index = Array.IndexOf(map.InterfaceMethods, interfaceMethod);
 62
 63            // When the type does not implement the method, the interface map points the target
 64            // back at the interface's own default implementation.
 1865            return map.TargetMethods[index].DeclaringType != typeof(IAsyncResponsePayload);
 66        }
 067        catch (NotSupportedException)
 68        {
 69            // Native AOT cannot compute the interface map for interfaces with default
 70            // implementations. This check exists to fail fast on a payload that silently
 71            // inherits the conservative default — when the runtime cannot answer, assume the
 72            // payload is fine instead of breaking a correct app (fail open).
 073            return true;
 74        }
 1875    }
 76}