diff --git a/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs b/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs index 7ebe195..9d58b72 100644 --- a/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs +++ b/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs @@ -37,7 +37,7 @@ public MutationHistoryEntry HistoryEntry_FromGovernedExecution() StateId = _fixture.Result.Request.StateId, Intent = _fixture.Mutation.Intent, Context = _fixture.Mutation.Context, - Changes = _fixture.Result.MutationResult!.Changes, + Changes = _fixture.Result.MutationResult!.Value.Changes, SideEffects = _fixture.Result.Request.SideEffects.ToList(), Timestamp = _fixture.Result.Request.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow, ExecutionTime = TimeSpan.FromMilliseconds(2) @@ -50,6 +50,7 @@ public MutationHistoryEntry HistoryEntry_FromGovernedExecution() [Benchmark] public MutationAuditEntry AuditEntry_FromGovernedExecution() { + var mr = _fixture.Result.MutationResult!.Value; return new MutationAuditEntry { ExecutionId = _fixture.Result.Request.RequestId, @@ -57,10 +58,10 @@ public MutationAuditEntry AuditEntry_FromGovernedExecution() StateType = _fixture.Result.Request.StateType, MutationIntent = _fixture.Mutation.Intent, Context = _fixture.Mutation.Context, - Changes = _fixture.Result.MutationResult!.Changes, - IsSuccess = _fixture.Result.MutationResult.IsSuccess, + Changes = mr.Changes, + IsSuccess = mr.IsSuccess, ErrorMessage = null, - PolicyDecisions = _fixture.Result.MutationResult.PolicyDecisions, + PolicyDecisions = mr.PolicyDecisions, SideEffects = _fixture.Result.Request.SideEffects.ToList(), Timestamp = _fixture.Result.Request.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow, Duration = TimeSpan.FromMilliseconds(2), diff --git a/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs b/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs index 68a6694..d39dc17 100644 --- a/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs +++ b/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs @@ -1,37 +1,45 @@ using BenchmarkDotNet.Attributes; using ModularityKit.Mutator.Abstractions.Audit; +using ModularityKit.Mutator.Abstractions.Context; +using ModularityKit.Mutator.Abstractions.Effects; using ModularityKit.Mutator.Abstractions.History; +using ModularityKit.Mutator.Abstractions.Intent; using ModularityKit.Mutator.Abstractions.Results; using ModularityKit.Mutator.Benchmarks.Results.Support; namespace ModularityKit.Mutator.Benchmarks.Results; -/// -/// Benchmarks materialization of history and audit output from an executed mutation result. -/// [BenchmarkCategory("Results")] [MemoryDiagnoser] [InProcess] public class MutationOutputMaterializationBenchmarks { - private MutationResult _result = null!; + private MutationResult _result = default!; private string _executionId = string.Empty; private TimeSpan _duration; + private IReadOnlyList _sideEffectList = null!; + private MutationIntent _historyIntent = null!; + private MutationIntent _auditIntent = null!; + private MutationContext _historyContext = null!; + private MutationContext _auditContext = null!; - /// - /// Prepares a representative executed mutation result for output materialization benchmarks. - /// [GlobalSetup] public void Setup() { _result = ResultsBenchmarkSupport.CreateExecutedResult(sideEffectCount: 3, changeCount: 4); _executionId = "results-benchmark-execution"; _duration = TimeSpan.FromMilliseconds(2); + _sideEffectList = _result.SideEffects.ToList(); + _historyIntent = ResultsBenchmarkSupport.CreateIntent( + "ResultHistoryMaterialization", + "Materialize history output for benchmark results."); + _auditIntent = ResultsBenchmarkSupport.CreateIntent( + "ResultAuditMaterialization", + "Materialize audit output for benchmark results."); + _historyContext = ResultsBenchmarkSupport.CreateContext("history"); + _auditContext = ResultsBenchmarkSupport.CreateContext("audit"); } - /// - /// Measures materialization of the mutation history entry, including change and side effect copying. - /// [Benchmark(Baseline = true)] public MutationHistoryEntry HistoryEntry_Materialization() { @@ -39,20 +47,15 @@ public MutationHistoryEntry HistoryEntry_Materialization() { ExecutionId = _executionId, StateId = ResultsBenchmarkSupport.StateId, - Intent = ResultsBenchmarkSupport.CreateIntent( - "ResultHistoryMaterialization", - "Materialize history output for benchmark results."), - Context = ResultsBenchmarkSupport.CreateContext("history"), + Intent = _historyIntent, + Context = _historyContext, Changes = _result.Changes, - SideEffects = _result.SideEffects.ToList(), + SideEffects = _sideEffectList, Timestamp = DateTimeOffset.UtcNow, ExecutionTime = _duration }; } - /// - /// Measures materialization of the audit entry produced from the same executed mutation result. - /// [Benchmark] public MutationAuditEntry AuditEntry_Materialization() { @@ -60,11 +63,9 @@ public MutationAuditEntry AuditEntry_Materialization() { ExecutionId = _executionId, StateId = ResultsBenchmarkSupport.StateId, - StateType = nameof(ResultsBenchmarkSupport.ResultBenchmarkState), - MutationIntent = ResultsBenchmarkSupport.CreateIntent( - "ResultAuditMaterialization", - "Materialize audit output for benchmark results."), - Context = ResultsBenchmarkSupport.CreateContext("audit"), + StateType = "ResultBenchmarkState", + MutationIntent = _auditIntent, + Context = _auditContext, Changes = _result.Changes, IsSuccess = _result.IsSuccess, ErrorMessage = null, diff --git a/Benchmarks/Results/MutationResultCreationBenchmarks.cs b/Benchmarks/Results/MutationResultCreationBenchmarks.cs index b79b009..789c2ce 100644 --- a/Benchmarks/Results/MutationResultCreationBenchmarks.cs +++ b/Benchmarks/Results/MutationResultCreationBenchmarks.cs @@ -14,17 +14,21 @@ namespace ModularityKit.Mutator.Benchmarks.Results; [InProcess] public class MutationResultCreationBenchmarks { - private ResultsBenchmarkSupport.ResultBenchmarkState _state = null!; + private ResultsBenchmarkSupport.ResultBenchmarkState _state = default!; private ChangeSet _changes = null!; + private IReadOnlyList _singleSideEffect = null!; + private IReadOnlyList _multipleSideEffects = null!; /// - /// Prepares the shared state and change set used by the result creation cases. + /// Prepares the shared state, change set, and side effect lists used by the result creation cases. /// [GlobalSetup] public void Setup() { _state = new ResultsBenchmarkSupport.ResultBenchmarkState(0, 42); _changes = ResultsBenchmarkSupport.CreateChangeSet(_state.Revision, 2); + _singleSideEffect = ResultsBenchmarkSupport.CreateSideEffects(1); + _multipleSideEffects = ResultsBenchmarkSupport.CreateSideEffects(4); } /// @@ -45,22 +49,14 @@ _state with /// [Benchmark] public MutationResult Success_SingleSideEffect() - { - var sideEffect = SideEffect.Create( - "ResultMaterialization", - "Single side effect", - new ResultsBenchmarkSupport.SideEffectPayload(1, "single"), - SideEffectSeverity.Info); - - return MutationResult.Success( + => MutationResult.Success( _state with { Revision = _state.Revision + 1, Value = _state.Value + 1 }, _changes, - [sideEffect]); - } + _singleSideEffect); /// /// Measures creation of a successful mutation result with several side effects. @@ -74,5 +70,5 @@ _state with Value = _state.Value + 1 }, _changes, - ResultsBenchmarkSupport.CreateSideEffects(4)); + _multipleSideEffects); } diff --git a/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs b/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs index 98056e4..848eb99 100644 --- a/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs +++ b/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs @@ -119,12 +119,12 @@ public static MutationResult CreateExecutedResult( /// /// The revision counter advanced on each benchmark mutation. /// The mutable numeric value exercised by the benchmark mutation. - public sealed record ResultBenchmarkState(int Revision, int Value); + public readonly record struct ResultBenchmarkState(int Revision, int Value); /// /// Typed payload used to give side effects realistic materialization shape. /// /// The ordinal of the side effect. /// A stable payload token. - public sealed record SideEffectPayload(int Index, string Token); + public readonly record struct SideEffectPayload(int Index, string Token); } diff --git a/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs b/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs index 02dfdb7..38bddf6 100644 --- a/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs +++ b/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs @@ -73,7 +73,7 @@ public async Task ExecuteApproved_executes_operator_rollback_compensation_and_li var compensationResult = await executionManager.ExecuteApproved( compensationRequest.RequestId, compensationMutation, - originalResult.MutationResult!.NewState!, + originalResult.MutationResult!.Value.NewState!, governanceContext: MutationContext.Service("governance-runtime", "Execute operator rollback"), strategy: VersionedRequestResolutionStrategy.RejectStale); diff --git a/src/Abstractions/Audit/MutationAuditEntry.cs b/src/Abstractions/Audit/MutationAuditEntry.cs index f61762d..81efb55 100644 --- a/src/Abstractions/Audit/MutationAuditEntry.cs +++ b/src/Abstractions/Audit/MutationAuditEntry.cs @@ -7,7 +7,7 @@ namespace ModularityKit.Mutator.Abstractions.Audit; /// -/// Represents a single audit record for a mutation operation. +/// Represents single audit record for a mutation operation. /// Captures the intent, context, changes, policy decisions, and metadata of the mutation. /// /// @@ -15,12 +15,12 @@ namespace ModularityKit.Mutator.Abstractions.Audit; /// Each entry is immutable once created. Typical consumers include auditors, logging systems, /// or analytics pipelines. /// -public sealed class MutationAuditEntry +public readonly record struct MutationAuditEntry { /// /// Unique execution identifier for this mutation. /// - public string ExecutionId { get; init; } = string.Empty; + public string ExecutionId { get; init; } /// /// Identifier of the state object that was mutated. @@ -35,17 +35,17 @@ public sealed class MutationAuditEntry /// /// The intent describing what the mutation is trying to achieve. /// - public MutationIntent MutationIntent { get; init; } = null!; + public MutationIntent MutationIntent { get; init; } /// /// Context of the mutation (e.g., correlation data, user context). /// - public MutationContext Context { get; init; } = null!; + public MutationContext Context { get; init; } /// /// Changes applied by the mutation. /// - public ChangeSet Changes { get; init; } = ChangeSet.Empty; + public ChangeSet Changes { get; init; } /// /// Indicates whether the mutation was successful. @@ -60,12 +60,12 @@ public sealed class MutationAuditEntry /// /// Decisions made by policies during the mutation evaluation. /// - public IReadOnlyList PolicyDecisions { get; init; } = []; + public IReadOnlyList PolicyDecisions { get; init; } /// /// Side effects produced during the mutation. /// - public IReadOnlyList SideEffects { get; init; } = []; + public IReadOnlyList SideEffects { get; init; } /// /// Timestamp when the mutation started. diff --git a/src/Abstractions/Changes/ChangeSet.cs b/src/Abstractions/Changes/ChangeSet.cs index bd38869..345cd7d 100644 --- a/src/Abstractions/Changes/ChangeSet.cs +++ b/src/Abstractions/Changes/ChangeSet.cs @@ -1,11 +1,12 @@ namespace ModularityKit.Mutator.Abstractions.Changes; /// -/// Represents a collection of state changes introduced by a mutation. -/// This is a **primary feature** of a mutation and not an optional addition. +/// Represents collection of state changes introduced by a mutation. +/// This is primary feature of mutation and not an optional addition. /// public sealed class ChangeSet { + private static readonly ChangeSet _empty = new(); private readonly List _changes = []; /// @@ -34,7 +35,7 @@ public sealed class ChangeSet public string? Checksum { get; init; } /// - /// Adds a new state change to the changeset. + /// Adds new state change to the changeset. /// /// The state change to add. /// Thrown if is null. @@ -45,7 +46,7 @@ public void Add(StateChange change) } /// - /// Retrieves all changes corresponding to a specific path. + /// Retrieves all changes corresponding to specific path. /// /// The path to filter changes. /// Enumerable of matching the path. @@ -53,7 +54,7 @@ public IEnumerable GetChanges(string path) => _changes.Where(c => c.Path == path); /// - /// Determines whether a specific path has been changed. + /// Determines whether specific path has been changed. /// /// The path to check. /// True if the path has been changed; otherwise, false. @@ -70,7 +71,7 @@ public IEnumerable GetChangedPaths() /// /// Returns an empty changeset. /// - public static ChangeSet Empty => new(); + public static ChangeSet Empty => _empty; /// /// Creates changeset containing single state change. diff --git a/src/Abstractions/Effects/SideEffect.cs b/src/Abstractions/Effects/SideEffect.cs index 5b511a9..fada0cf 100644 --- a/src/Abstractions/Effects/SideEffect.cs +++ b/src/Abstractions/Effects/SideEffect.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Text.Json.Serialization; namespace ModularityKit.Mutator.Abstractions.Effects; @@ -189,20 +190,33 @@ private static SideEffect CreateCore( }; } + private static readonly ConcurrentDictionary _contractCache = new(); + private static (string? ContractType, int? ContractVersion) ResolveContract(object? data) { if (data is null) return (null, null); var dataType = data.GetType(); + if (_contractCache.TryGetValue(dataType, out var cached)) + return cached; + var contract = dataType.GetCustomAttributes(typeof(SideEffectDataContractAttribute), inherit: false) .OfType() .SingleOrDefault(); + (string?, int?) result; if (contract is null) - return (null, null); + { + result = (null, null); + } + else + { + SideEffectDataContractRegistry.Register(dataType); + result = (contract.ContractType, contract.ContractVersion); + } - SideEffectDataContractRegistry.Register(dataType); - return (contract.ContractType, contract.ContractVersion); + _contractCache.TryAdd(dataType, result); + return result; } } diff --git a/src/Abstractions/History/MutationHistory.cs b/src/Abstractions/History/MutationHistory.cs index 79058b1..cb53869 100644 --- a/src/Abstractions/History/MutationHistory.cs +++ b/src/Abstractions/History/MutationHistory.cs @@ -3,10 +3,10 @@ namespace ModularityKit.Mutator.Abstractions.History; /// -/// Represents the full mutation history of a specific state object. +/// Represents the full mutation history of specific state object. /// /// -/// MutationHistory stores a chronological sequence of entries. +/// MutationHistory stores chronological sequence of entries. /// It allows replaying state changes, querying timelines for specific paths, and computing statistics. /// This is typically used in combination with to persist and retrieve histories. /// @@ -32,13 +32,13 @@ public sealed class MutationHistory /// Timestamp of the first mutation in the history. /// public DateTimeOffset? FirstMutationAt - => Entries.FirstOrDefault()?.Timestamp; + => Entries.Count > 0 ? Entries[0].Timestamp : null; /// /// Timestamp of the last mutation in the history. /// public DateTimeOffset? LastMutationAt - => Entries.LastOrDefault()?.Timestamp; + => Entries.Count > 0 ? Entries[^1].Timestamp : null; /// /// Total number of mutations recorded. diff --git a/src/Abstractions/History/MutationHistoryEntry.cs b/src/Abstractions/History/MutationHistoryEntry.cs index 94b38f5..c7690bd 100644 --- a/src/Abstractions/History/MutationHistoryEntry.cs +++ b/src/Abstractions/History/MutationHistoryEntry.cs @@ -11,39 +11,39 @@ namespace ModularityKit.Mutator.Abstractions.History; /// /// Each captures the details of a single mutation, /// including its intent, context, state changes, side effects, execution timing, and integrity hashes. -/// This class is used by to store a chronological sequence of mutations. +/// This type is used by to store a chronological sequence of mutations. /// -public sealed class MutationHistoryEntry +public readonly record struct MutationHistoryEntry { /// /// Unique identifier for the execution of this mutation. /// - public string ExecutionId { get; init; } = string.Empty; + public string ExecutionId { get; init; } /// /// Identifier of the state this mutation was applied to. /// - public string StateId { get; init; } = string.Empty; + public string StateId { get; init; } /// /// The intent behind the mutation. /// - public MutationIntent Intent { get; init; } = null!; + public MutationIntent Intent { get; init; } /// /// Contextual information about the mutation execution. /// - public MutationContext Context { get; init; } = null!; + public MutationContext Context { get; init; } /// /// Set of changes applied by this mutation. /// - public ChangeSet Changes { get; init; } = ChangeSet.Empty; + public ChangeSet Changes { get; init; } /// /// Side effects produced by this mutation. /// - public IReadOnlyList SideEffects { get; init; } = []; + public IReadOnlyList SideEffects { get; init; } /// /// Timestamp indicating when the mutation occurred. diff --git a/src/Abstractions/Metrics/MutationMetrics.cs b/src/Abstractions/Metrics/MutationMetrics.cs index 1e966db..63660c9 100644 --- a/src/Abstractions/Metrics/MutationMetrics.cs +++ b/src/Abstractions/Metrics/MutationMetrics.cs @@ -12,6 +12,7 @@ namespace ModularityKit.Mutator.Abstractions.Metrics; /// /// Key considerations: /// +/// timestamp of the mutation recording. /// measures the total duration of the mutation. /// measures time spent validating the mutation. /// measures time spent evaluating policies. @@ -25,6 +26,11 @@ namespace ModularityKit.Mutator.Abstractions.Metrics; /// public sealed record MutationMetrics { + private static readonly IReadOnlyDictionary _emptyAdditionalMetrics + = new Dictionary(); + + internal static readonly MutationMetrics Empty = new(); + /// /// Timestamp when the mutation was recorded. /// @@ -46,12 +52,12 @@ public sealed record MutationMetrics public TimeSpan PolicyEvaluationTime { get; init; } /// - /// Number of rules validated during the mutation. + /// Number of validated rules. /// public int ValidatedRules { get; init; } /// - /// Number of policies evaluated during the mutation. + /// Number of evaluated policies. /// public int EvaluatedPolicies { get; init; } @@ -61,23 +67,23 @@ public sealed record MutationMetrics public int ChangesCount { get; init; } /// - /// Size of the state before the mutation (in bytes, if applicable). + /// Size of the state object in bytes, if available. /// public long? StateSize { get; init; } /// - /// Memory used during mutation execution (in bytes). + /// Memory used during mutation execution in bytes, if available. /// public long? MemoryUsed { get; init; } /// - /// Indicates whether a cache was used. + /// Indicates whether caching was used during the mutation. /// public bool UsedCache { get; init; } /// - /// Additional custom metrics. + /// Additional metrics for extensibility. /// public IReadOnlyDictionary AdditionalMetrics { get; init; } - = new Dictionary(); + = _emptyAdditionalMetrics; } diff --git a/src/Abstractions/Results/MutationResult.cs b/src/Abstractions/Results/MutationResult.cs index a86229a..330a0d5 100644 --- a/src/Abstractions/Results/MutationResult.cs +++ b/src/Abstractions/Results/MutationResult.cs @@ -6,35 +6,35 @@ namespace ModularityKit.Mutator.Abstractions.Results; /// -/// Represents the outcome of applying a mutation to a state. -/// Always contains a trace of changes, even if the mutation fails. +/// Represents the outcome of mutation operation for a given state type. /// -/// The type of the state being mutated. -public sealed record MutationResult +/// The type of the state object being mutated. +public readonly record struct MutationResult { + public MutationResult() { } + /// - /// Indicates whether the mutation was successfully applied. + /// Indicates whether the mutation completed successfully. /// public bool IsSuccess { get; init; } /// - /// The new state after mutation, if successful; otherwise null. + /// The new state after a successful mutation, or default if unsuccessful. /// public TState? NewState { get; init; } /// - /// The set of changes describing what exactly was modified during the mutation. - /// Always populated, even on failure. + /// The set of changes applied by the mutation. /// - public ChangeSet Changes { get; init; } = ChangeSet.Empty; + public ChangeSet Changes { get; init; } /// - /// Result of validation checks performed during mutation. + /// Validation result indicating any validation errors or warnings. /// - public ValidationResult ValidationResult { get; init; } = ValidationResult.Success(); + public ValidationResult ValidationResult { get; init; } /// - /// Decisions from policy evaluation that influenced the mutation outcome. + /// Policy decisions that were made during mutation evaluation. /// public IReadOnlyList PolicyDecisions { get; init; } = []; @@ -44,27 +44,27 @@ public sealed record MutationResult public IReadOnlyList SideEffects { get; init; } = []; /// - /// Metrics related to the execution of the mutation (e.g., duration, performance counters). + /// Metrics collected during the mutation execution. /// - public MutationMetrics Metrics { get; init; } = new(); + public MutationMetrics Metrics { get; init; } /// - /// Exception thrown during mutation, if any, for diagnostic purposes. + /// Exception thrown during the mutation, if any. /// public Exception? Exception { get; init; } /// - /// Timestamp indicating when the mutation completed. + /// Timestamp when the mutation completed. /// - public DateTimeOffset CompletedAt { get; init; } = DateTimeOffset.UtcNow; + public DateTimeOffset CompletedAt { get; init; } /// - /// Creates a successful mutation result. + /// Creates successful mutation result with the given state and changes. /// - /// The new state after the mutation. + /// The new state after mutation. /// The set of changes applied. /// Optional list of side effects. - /// A representing success. + /// A successful . public static MutationResult Success( TState newState, ChangeSet changes, @@ -74,16 +74,19 @@ public static MutationResult Success( IsSuccess = true, NewState = newState, Changes = changes, - SideEffects = sideEffects ?? [] + SideEffects = sideEffects ?? [], + ValidationResult = ValidationResult.Success(), + Metrics = MutationMetrics.Empty, + CompletedAt = DateTimeOffset.UtcNow }; /// - /// Creates a successful mutation result from a single state change. + /// Creates successful mutation result with single state change. /// - /// The new state after the mutation. - /// The single change applied. + /// The new state after mutation. + /// The single state change applied. /// Optional list of side effects. - /// A representing success. + /// A successful . public static MutationResult Success( TState newState, StateChange change, @@ -91,22 +94,24 @@ public static MutationResult Success( => Success(newState, ChangeSet.Single(change), sideEffects); /// - /// Creates a failed mutation result due to validation errors. + /// Creates failed mutation result with validation errors. /// - /// The validation result explaining the failure. - /// A representing failure. + /// The validation result describing the failure. + /// A failed . public static MutationResult Failure(ValidationResult validation) => new() { IsSuccess = false, - ValidationResult = validation + ValidationResult = validation, + Metrics = MutationMetrics.Empty, + CompletedAt = DateTimeOffset.UtcNow }; /// - /// Creates a failed mutation result due to policy enforcement. + /// Creates mutation result blocked by policy decision. /// /// The policy decision that blocked the mutation. - /// A representing a policy-blocked mutation. + /// A blocked . public static MutationResult PolicyBlocked(PolicyDecision decision) => new() { @@ -114,6 +119,8 @@ public static MutationResult PolicyBlocked(PolicyDecision decision) PolicyDecisions = [decision], ValidationResult = ValidationResult.WithError( "Policy", - decision.Reason ?? "Blocked by policy") + decision.Reason ?? "Blocked by policy"), + Metrics = MutationMetrics.Empty, + CompletedAt = DateTimeOffset.UtcNow }; } diff --git a/src/Abstractions/Results/ValidationResult.cs b/src/Abstractions/Results/ValidationResult.cs index 57b24c0..636b119 100644 --- a/src/Abstractions/Results/ValidationResult.cs +++ b/src/Abstractions/Results/ValidationResult.cs @@ -1,95 +1,108 @@ namespace ModularityKit.Mutator.Abstractions.Results; /// -/// Represents the result of validating a mutation or state change. -/// Contains errors, warnings, and informational messages produced during validation. +/// Encapsulates the result of a mutation validation, including errors, warnings, and informational messages. /// +/// +/// +/// A collects detailed validation feedback during mutation processing. +/// It distinguishes between three severity levels: +/// +/// - critical issues that prevent mutation execution. +/// - non-blocking issues that may indicate potential problems. +/// - informational messages about validation context or suggestions. +/// +/// +/// +/// The property returns true only when no errors are present. +/// Static factory methods (, , ) +/// provide convenient creation patterns. +/// +/// public sealed class ValidationResult { + private static readonly ValidationResult _success = new(); private readonly List _errors = []; private readonly List _warnings = []; private readonly List _info = []; /// - /// Indicates whether the validation passed successfully. - /// True if there are no errors; false otherwise. + /// Indicates whether the validation passed (no errors). /// public bool IsValid => _errors.Count == 0; /// - /// Read-only list of validation errors. - /// Each error indicates a violation of rules that prevents the mutation from being applied. + /// List of validation errors that prevent mutation execution. /// public IReadOnlyList Errors => _errors; /// - /// Read-only list of validation warnings. - /// Warnings indicate potential issues that do not block mutation execution. + /// List of validation warnings (non-blocking issues). /// public IReadOnlyList Warnings => _warnings; /// - /// Read-only list of informational messages produced during validation. + /// List of informational validation messages. /// public IReadOnlyList Info => _info; /// - /// Adds a new validation error. + /// Adds validation error with the specified path, message, and optional code. /// - /// The path or property associated with the error. - /// Human-readable description of the error. - /// Optional error code for categorization. + /// Path to the invalid property or field. + /// Description of the validation error. + /// Optional error code for categorization or localization. public void AddError(string path, string message, string? code = null) => _errors.Add(new ValidationError(path, message, code)); /// - /// Adds a new validation warning. + /// Adds validation warning with the specified path, message, and optional code. /// - /// The path or property associated with the warning. - /// Human-readable description of the warning. - /// Optional warning code for categorization. + /// Path to the property or field causing the warning. + /// Description of the warning. + /// Optional code for categorization or localization. public void AddWarning(string path, string message, string? code = null) => _warnings.Add(new ValidationWarning(path, message, code)); /// /// Adds an informational validation message. /// - /// The path or property associated with the info. - /// Human-readable informational message. + /// Path to the property or concept for this informational message. + /// The informational message. public void AddInfo(string path, string message) => _info.Add(new ValidationInfo(path, message)); /// - /// Adds an existing instance. + /// Adds a instance directly. /// - /// The error to add. + /// The validation error to add. public void AddError(ValidationError error) => _errors.Add(error); /// - /// Adds an existing instance. + /// Adds a instance directly. /// - /// The warning to add. + /// The validation warning to add. public void AddWarning(ValidationWarning warning) => _warnings.Add(warning); /// - /// Adds an existing instance. + /// Adds a instance directly. /// /// The informational message to add. public void AddInfo(ValidationInfo info) => _info.Add(info); /// - /// Creates a successful validation result with no errors. + /// Returns a successful (empty) validation result. /// - /// A indicating success. - public static ValidationResult Success() => new(); + /// A with no errors, warnings, or info. + public static ValidationResult Success() => _success; /// /// Creates a validation result with a single error. /// - /// The path or property associated with the error. - /// Human-readable description of the error. - /// Optional error code. - /// A containing one error. + /// Path to the invalid property or field. + /// Description of the error. + /// Optional error code for categorization or localization. + /// A with the specified error. public static ValidationResult WithError(string path, string message, string? code = null) { var result = new ValidationResult(); @@ -98,10 +111,10 @@ public static ValidationResult WithError(string path, string message, string? co } /// - /// Creates a validation result containing multiple errors. + /// Creates a validation result with multiple errors. /// - /// Array of to include. - /// A containing the specified errors. + /// The validation errors to include. + /// A with the specified errors. public static ValidationResult WithErrors(params ValidationError[] errors) { var result = new ValidationResult(); diff --git a/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs b/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs index 1174463..a6fb168 100644 --- a/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs +++ b/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs @@ -182,7 +182,7 @@ public async Task> HandleMutationResult( mutationResult.SideEffects, cancellationToken).ConfigureAwait(false); - return BuildNonExecutedResult( + return BuildNonExecutedResult( execution.Resolution with { Request = rejectedRequest }, mutationResult); } diff --git a/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs b/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs index dfb3194..3f7ef40 100644 --- a/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs +++ b/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs @@ -13,9 +13,9 @@ namespace ModularityKit.Mutator.Runtime.Internal.Execution; /// /// /// -/// The pipeline runs policy evaluation, validation, mode-specific execution, and +/// The pipeline runs policy evaluation, validation, mode specific execution, and /// outcome processing in fixed order. Each stage can short circuit: -/// a blocking policy decision skips execution; a validation failure skips execution; +/// blocking policy decision skips execution validation failure skips execution; /// otherwise the mutation runs through the configured mode runner and the result /// is finalized by the outcome processor. /// @@ -81,9 +81,9 @@ await _interceptorPipeline.OnBeforeMutationAsync( .ConfigureAwait(false); var validationFailureResult = ValidateIfRequired(executionContext); - if (validationFailureResult is not null) + if (validationFailureResult is MutationResult failure) return await _outcomeProcessor - .HandleValidationFailureAsync(executionContext, validationFailureResult) + .HandleValidationFailureAsync(executionContext, failure) .ConfigureAwait(false); var mutationResult = await _modeRunner.ExecuteAsync(executionContext).ConfigureAwait(false);