using System.Collections.Immutable; using System.Linq; using System.Text.Json.Serialization; namespace StellaOps.Concelier.Models; /// /// Describes the origin of a canonical field and how/when it was captured. /// public sealed record AdvisoryProvenance { public static AdvisoryProvenance Empty { get; } = new("unknown", "unspecified", string.Empty, DateTimeOffset.UnixEpoch); [JsonConstructor] public AdvisoryProvenance( string source, string kind, string value, string? decisionReason, DateTimeOffset recordedAt, ImmutableArray fieldMask) : this(source, kind, value, recordedAt, fieldMask.IsDefault ? null : fieldMask.AsEnumerable(), decisionReason) { } public AdvisoryProvenance( string source, string kind, string value, DateTimeOffset recordedAt, IEnumerable? fieldMask = null, string? decisionReason = null) { Source = Validation.EnsureNotNullOrWhiteSpace(source, nameof(source)); Kind = Validation.EnsureNotNullOrWhiteSpace(kind, nameof(kind)); Value = Validation.TrimToNull(value); DecisionReason = Validation.TrimToNull(decisionReason); RecordedAt = recordedAt.ToUniversalTime(); FieldMask = NormalizeFieldMask(fieldMask); } public string Source { get; } public string Kind { get; } public string? Value { get; } public string? DecisionReason { get; } public DateTimeOffset RecordedAt { get; } public ImmutableArray FieldMask { get; } private static ImmutableArray NormalizeFieldMask(IEnumerable? fieldMask) { if (fieldMask is null) { return ImmutableArray.Empty; } var buffer = fieldMask .Where(static value => !string.IsNullOrWhiteSpace(value)) .Select(static value => value.Trim().ToLowerInvariant()) .Distinct(StringComparer.Ordinal) .OrderBy(static value => value, StringComparer.Ordinal) .ToImmutableArray(); return buffer.IsDefault ? ImmutableArray.Empty : buffer; } }