< Summary

Information
Class: AsiBackbone.Core.Evaluation.DefaultAsiBackbonePolicyEvaluator<T>
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Evaluation/DefaultAsiBackbonePolicyEvaluator.cs
Line coverage
100%
Covered lines: 75
Uncovered lines: 0
Coverable lines: 75
Total lines: 152
Line coverage: 100%
Branch coverage
100%
Covered branches: 26
Total branches: 26
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%22100%
EvaluateAsync()100%1616100%
Compose(...)100%88100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Evaluation/DefaultAsiBackbonePolicyEvaluator.cs

#LineLine coverage
 1using AsiBackbone.Core.Constraints;
 2using AsiBackbone.Core.Decisions;
 3using AsiBackbone.Core.Results;
 4
 5namespace AsiBackbone.Core.Evaluation;
 6
 7/// <summary>
 8/// Default policy evaluator that runs the active constraint structure and composes the result into a governance decisio
 9/// </summary>
 10/// <typeparam name="TContext">The framework-neutral evaluation context type.</typeparam>
 11public sealed class DefaultAsiBackbonePolicyEvaluator<TContext> : IAsiBackbonePolicyEvaluator<TContext>
 12    where TContext : IAsiBackboneConstraintEvaluationContext
 13{
 14    private readonly IAsiBackboneConstraint<TContext>[] constraints;
 15    private readonly IAsiBackboneDecisionPolicy<TContext>? decisionPolicy;
 16    private readonly AsiBackbonePolicyEvaluatorOptions options;
 17
 18    /// <summary>
 19    /// Initializes a new instance of the <see cref="DefaultAsiBackbonePolicyEvaluator{TContext}"/> class.
 20    /// </summary>
 21    /// <param name="constraints">The constraints that make up the active policy structure.</param>
 22    /// <param name="decisionPolicy">Optional decision policy applied after constraint composition.</param>
 23    public DefaultAsiBackbonePolicyEvaluator(
 24        IEnumerable<IAsiBackboneConstraint<TContext>> constraints,
 25        IAsiBackboneDecisionPolicy<TContext>? decisionPolicy = null)
 2526        : this(constraints, decisionPolicy, options: null)
 27    {
 2428    }
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="DefaultAsiBackbonePolicyEvaluator{TContext}"/> class.
 32    /// </summary>
 33    /// <param name="constraints">The constraints that make up the active policy structure.</param>
 34    /// <param name="decisionPolicy">Optional decision policy applied after constraint composition.</param>
 35    /// <param name="options">Evaluator options applied during constraint composition.</param>
 3936    public DefaultAsiBackbonePolicyEvaluator(
 3937        IEnumerable<IAsiBackboneConstraint<TContext>> constraints,
 3938        IAsiBackboneDecisionPolicy<TContext>? decisionPolicy,
 3939        AsiBackbonePolicyEvaluatorOptions? options)
 40    {
 3941        ArgumentNullException.ThrowIfNull(constraints);
 42
 43        // Keep an exact-sized private snapshot rather than wrapping a caller-owned list.
 44        // This avoids a per-evaluator ReadOnlyCollection<T> wrapper and prevents later caller mutations
 45        // from changing the evaluator's deterministic constraint order or behavior.
 3846        this.constraints = [.. constraints];
 3847        this.decisionPolicy = decisionPolicy;
 3848        this.options = options ?? new AsiBackbonePolicyEvaluatorOptions();
 3849        this.options.Validate();
 3250    }
 51
 52    /// <inheritdoc />
 53    public async ValueTask<GovernanceDecision> EvaluateAsync(
 54        TContext context,
 55        CancellationToken cancellationToken = default)
 56    {
 3257        ArgumentNullException.ThrowIfNull(context);
 3158        cancellationToken.ThrowIfCancellationRequested();
 59
 3060        if (constraints.Length == 0 && options.DenyWhenNoConstraints)
 61        {
 362            var noConstraintsDecision = GovernanceDecision.Deny(
 363                options.NoConstraintsReasonCode,
 364                options.NoConstraintsReasonMessage,
 365                correlationId: context.CorrelationId,
 366                policyVersion: context.PolicyVersion,
 367                policyHash: context.PolicyHash);
 68
 369            return decisionPolicy is null
 370                ? noConstraintsDecision
 371                : await decisionPolicy
 372                .ApplyAsync(
 373                    context,
 374                    noConstraintsDecision,
 375                    Array.AsReadOnly(Array.Empty<ConstraintEvaluationResult>()),
 376                    cancellationToken)
 377                .ConfigureAwait(false);
 78        }
 79
 2780        List<ConstraintEvaluationResult> results = new(constraints.Length);
 2781        List<OperationReason> denials = [];
 2782        List<OperationReason> warnings = [];
 83
 16084        foreach (IAsiBackboneConstraint<TContext> constraint in constraints)
 85        {
 5586            cancellationToken.ThrowIfCancellationRequested();
 87
 5488            ConstraintEvaluationResult result = await constraint
 5489                .EvaluateAsync(context, cancellationToken)
 5490                .ConfigureAwait(false);
 91
 5492            results.Add(result);
 93
 5494            if (result.IsDenied)
 95            {
 1396                denials.AddRange(result.Reasons);
 97
 1398                if (options.ShortCircuitOnFirstDenial)
 99                {
 3100                    break;
 101                }
 102            }
 41103            else if (result.IsWarning)
 104            {
 15105                warnings.AddRange(result.Reasons);
 106            }
 107        }
 108
 26109        GovernanceDecision composedDecision = Compose(
 26110            context,
 26111            denials,
 26112            warnings,
 26113            includeWarningsWhenDenied: options.ShortCircuitOnFirstDenial);
 114
 26115        return decisionPolicy is null
 26116            ? composedDecision
 26117            : await decisionPolicy
 26118            .ApplyAsync(context, composedDecision, Array.AsReadOnly([.. results]), cancellationToken)
 26119            .ConfigureAwait(false);
 29120    }
 121
 122    private static GovernanceDecision Compose(
 123        TContext context,
 124        List<OperationReason> denials,
 125        List<OperationReason> warnings,
 126        bool includeWarningsWhenDenied)
 127    {
 26128        if (denials.Count > 0)
 129        {
 11130            IEnumerable<OperationReason> denialReasons = includeWarningsWhenDenied && warnings.Count > 0
 11131                ? warnings.Concat(denials)
 11132                : denials;
 133
 11134            return GovernanceDecision.Deny(
 11135                denialReasons,
 11136                correlationId: context.CorrelationId,
 11137                policyVersion: context.PolicyVersion,
 11138                policyHash: context.PolicyHash);
 139        }
 140
 15141        return warnings.Count > 0
 15142            ? GovernanceDecision.Warning(
 15143                warnings,
 15144                correlationId: context.CorrelationId,
 15145                policyVersion: context.PolicyVersion,
 15146                policyHash: context.PolicyHash)
 15147            : GovernanceDecision.Allow(
 15148            correlationId: context.CorrelationId,
 15149            policyVersion: context.PolicyVersion,
 15150            policyHash: context.PolicyHash);
 151    }
 152}