< Summary

Information
Class: ProjectTemplate.Infrastructure.Data.Auditing.CanonicalApplicationMutationManifestBuilder
Assembly: ProjectTemplate.Infrastructure
File(s): /home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Infrastructure/Data/Auditing/CanonicalApplicationMutationManifestBuilder.cs
Line coverage
94%
Covered lines: 109
Uncovered lines: 6
Coverable lines: 115
Total lines: 189
Line coverage: 94.7%
Branch coverage
79%
Covered branches: 34
Total branches: 43
Branch coverage: 79%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Build(...)62.5%8892.85%
CreateCanonicalRecord(...)83.33%1818100%
CanonicalizeValueObject(...)75%4490.9%
WriteCanonicalElement(...)84.61%131387.5%
WriteRecord(...)100%11100%
WriteCanonicalObjectProperty(...)100%11100%
.ctor(...)100%11100%

File(s)

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Infrastructure/Data/Auditing/CanonicalApplicationMutationManifestBuilder.cs

#LineLine coverage
 1using System.Text;
 2using System.Text.Json;
 3using ProjectTemplate.Infrastructure.Data.Entities;
 4
 5namespace ProjectTemplate.Infrastructure.Data.Auditing;
 6
 7/// <summary>
 8/// Builds deterministic version 1.0 mutation manifests using ordinal ordering rules.
 9/// </summary>
 10public sealed class CanonicalApplicationMutationManifestBuilder : IApplicationMutationManifestBuilder
 11{
 12    /// <inheritdoc />
 13    public ApplicationMutationManifest Build(IReadOnlyCollection<AuditRecord> auditRecords)
 14    {
 5415        ArgumentNullException.ThrowIfNull(auditRecords);
 16
 5417        if (auditRecords.Count == 0)
 18        {
 019            throw new ArgumentException("At least one audit record is required to build a mutation manifest.", nameof(au
 20        }
 21
 5422        string mutationBatchId = auditRecords.First().MutationBatchId;
 5423        if (string.IsNullOrWhiteSpace(mutationBatchId))
 24        {
 025            throw new InvalidOperationException("Mutation audit records require a mutation batch identifier.");
 26        }
 27
 5428        if (auditRecords.Any(record => !string.Equals(record.MutationBatchId, mutationBatchId, StringComparison.Ordinal)
 29        {
 030            throw new InvalidOperationException("A canonical mutation manifest can represent only one mutation batch.");
 31        }
 32
 5433        var canonicalRecords = auditRecords
 5434            .Select(CreateCanonicalRecord)
 5435            .OrderBy(record => record.Entity, StringComparer.Ordinal)
 5436            .ThenBy(record => record.State, StringComparer.Ordinal)
 5437            .ThenBy(record => record.KeyValues, StringComparer.Ordinal)
 5438            .ThenBy(record => record.OriginalValues, StringComparer.Ordinal)
 5439            .ThenBy(record => record.CurrentValues, StringComparer.Ordinal)
 5440            .ThenBy(record => record.OperationExecutionId, StringComparer.Ordinal)
 5441            .ThenBy(record => record.ExecutionAttemptId, StringComparer.Ordinal)
 5442            .ThenBy(record => record.DecisionAuditRecordId, StringComparer.Ordinal)
 5443            .ThenBy(record => record.CorrelationId, StringComparer.Ordinal)
 5444            .ThenBy(record => record.TraceId, StringComparer.Ordinal)
 5445            .ThenBy(record => record.SpanId, StringComparer.Ordinal)
 5446            .ThenBy(record => record.SchemaVersion, StringComparer.Ordinal)
 5447            .ToList();
 48
 5449        using var stream = new MemoryStream();
 5450        using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }))
 51        {
 5452            writer.WriteStartObject();
 5453            writer.WriteString("schemaVersion", ApplicationMutationManifest.CurrentSchemaVersion);
 5454            writer.WriteString("mutationBatchId", mutationBatchId);
 5455            writer.WriteNumber("auditRecordCount", canonicalRecords.Count);
 5456            writer.WritePropertyName("records");
 5457            writer.WriteStartArray();
 58
 24059            foreach (CanonicalAuditRecord record in canonicalRecords)
 60            {
 6661                WriteRecord(writer, record);
 62            }
 63
 5464            writer.WriteEndArray();
 5465            writer.WriteEndObject();
 5466        }
 67
 5468        return new ApplicationMutationManifest(
 5469            ApplicationMutationManifest.CurrentSchemaVersion,
 5470            mutationBatchId,
 5471            canonicalRecords.Count,
 5472            Encoding.UTF8.GetString(stream.ToArray()));
 5473    }
 74
 75    private static CanonicalAuditRecord CreateCanonicalRecord(AuditRecord record)
 76    {
 6677        ArgumentNullException.ThrowIfNull(record);
 78
 6679        return new CanonicalAuditRecord(
 6680            record.SchemaVersion ?? string.Empty,
 6681            record.Entity ?? string.Empty,
 6682            record.State ?? string.Empty,
 6683            CanonicalizeValueObject(record.KeyValues),
 6684            CanonicalizeValueObject(record.OriginalValues),
 6685            CanonicalizeValueObject(record.CurrentValues),
 6686            record.OperationExecutionId ?? string.Empty,
 6687            record.ExecutionAttemptId ?? string.Empty,
 6688            record.DecisionAuditRecordId ?? string.Empty,
 6689            record.CorrelationId ?? string.Empty,
 6690            record.TraceId ?? string.Empty,
 6691            record.SpanId ?? string.Empty);
 92    }
 93
 94    private static string CanonicalizeValueObject(string? json)
 95    {
 19896        if (string.IsNullOrWhiteSpace(json))
 97        {
 4598            return "{}";
 99        }
 100
 153101        using var document = JsonDocument.Parse(json);
 153102        if (document.RootElement.ValueKind != JsonValueKind.Object)
 103        {
 0104            throw new InvalidOperationException("Audit value payloads must be JSON objects.");
 105        }
 106
 153107        using var stream = new MemoryStream();
 153108        using (var writer = new Utf8JsonWriter(stream))
 109        {
 153110            WriteCanonicalElement(writer, document.RootElement);
 153111        }
 112
 153113        return Encoding.UTF8.GetString(stream.ToArray());
 153114    }
 115
 116    private static void WriteCanonicalElement(Utf8JsonWriter writer, JsonElement element)
 117    {
 634118        switch (element.ValueKind)
 119        {
 120            case JsonValueKind.Object:
 159121                writer.WriteStartObject();
 1252122                foreach (JsonProperty property in element.EnumerateObject().OrderBy(property => property.Name, StringCom
 123                {
 467124                    writer.WritePropertyName(property.Name);
 467125                    WriteCanonicalElement(writer, property.Value);
 126                }
 159127                writer.WriteEndObject();
 159128                break;
 129            case JsonValueKind.Array:
 6130                writer.WriteStartArray();
 40131                foreach (JsonElement item in element.EnumerateArray())
 132                {
 14133                    WriteCanonicalElement(writer, item);
 134                }
 6135                writer.WriteEndArray();
 6136                break;
 137            case JsonValueKind.String:
 138            case JsonValueKind.Number:
 139            case JsonValueKind.True:
 140            case JsonValueKind.False:
 141            case JsonValueKind.Null:
 469142                element.WriteTo(writer);
 469143                break;
 144            case JsonValueKind.Undefined:
 0145                throw new InvalidOperationException("Undefined JSON values cannot be represented in a canonical mutation
 146            default:
 0147                throw new InvalidOperationException($"Unsupported JSON value kind '{element.ValueKind}'.");
 148        }
 149    }
 150
 151    private static void WriteRecord(Utf8JsonWriter writer, CanonicalAuditRecord record)
 152    {
 66153        writer.WriteStartObject();
 66154        writer.WriteString("schemaVersion", record.SchemaVersion);
 66155        writer.WriteString("entity", record.Entity);
 66156        writer.WriteString("state", record.State);
 66157        WriteCanonicalObjectProperty(writer, "keyValues", record.KeyValues);
 66158        WriteCanonicalObjectProperty(writer, "originalValues", record.OriginalValues);
 66159        WriteCanonicalObjectProperty(writer, "currentValues", record.CurrentValues);
 66160        writer.WriteString("operationExecutionId", record.OperationExecutionId);
 66161        writer.WriteString("executionAttemptId", record.ExecutionAttemptId);
 66162        writer.WriteString("decisionAuditRecordId", record.DecisionAuditRecordId);
 66163        writer.WriteString("correlationId", record.CorrelationId);
 66164        writer.WriteString("traceId", record.TraceId);
 66165        writer.WriteString("spanId", record.SpanId);
 66166        writer.WriteEndObject();
 66167    }
 168
 169    private static void WriteCanonicalObjectProperty(Utf8JsonWriter writer, string propertyName, string canonicalJson)
 170    {
 198171        writer.WritePropertyName(propertyName);
 198172        using var document = JsonDocument.Parse(canonicalJson);
 198173        document.RootElement.WriteTo(writer);
 396174    }
 175
 66176    private sealed record CanonicalAuditRecord(
 66177        string SchemaVersion,
 66178        string Entity,
 66179        string State,
 66180        string KeyValues,
 66181        string OriginalValues,
 66182        string CurrentValues,
 66183        string OperationExecutionId,
 66184        string ExecutionAttemptId,
 66185        string DecisionAuditRecordId,
 66186        string CorrelationId,
 66187        string TraceId,
 66188        string SpanId);
 189}