using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text.Json.Serialization; namespace StellaOps.Feedser.Models; /// /// Canonical affected package descriptor with deterministic ordering of ranges and provenance. /// public sealed record AffectedPackage { public static AffectedPackage Empty { get; } = new( AffectedPackageTypes.SemVer, identifier: "unknown", platform: null, versionRanges: Array.Empty(), statuses: Array.Empty(), provenance: Array.Empty()); [JsonConstructor] public AffectedPackage( string type, string identifier, string? platform = null, IEnumerable? versionRanges = null, IEnumerable? statuses = null, IEnumerable? provenance = null) { Type = Validation.EnsureNotNullOrWhiteSpace(type, nameof(type)).ToLowerInvariant(); Identifier = Validation.EnsureNotNullOrWhiteSpace(identifier, nameof(identifier)); Platform = Validation.TrimToNull(platform); VersionRanges = (versionRanges ?? Array.Empty()) .Distinct(AffectedVersionRangeEqualityComparer.Instance) .OrderBy(static range => range, AffectedVersionRangeComparer.Instance) .ToImmutableArray(); Statuses = (statuses ?? Array.Empty()) .Where(static status => status is not null) .Distinct(AffectedPackageStatusEqualityComparer.Instance) .OrderBy(static status => status.Status, StringComparer.Ordinal) .ThenBy(static status => status.Provenance.Source, StringComparer.Ordinal) .ThenBy(static status => status.Provenance.Kind, StringComparer.Ordinal) .ThenBy(static status => status.Provenance.RecordedAt) .ToImmutableArray(); Provenance = (provenance ?? Array.Empty()) .Where(static p => p is not null) .OrderBy(static p => p.Source, StringComparer.Ordinal) .ThenBy(static p => p.Kind, StringComparer.Ordinal) .ThenBy(static p => p.RecordedAt) .ToImmutableArray(); } /// /// Semantic type of the coordinates (rpm, deb, cpe, semver, vendor, ics-vendor). /// public string Type { get; } /// /// Canonical identifier for the package (NEVRA, PackageURL, CPE string, vendor slug, etc.). /// public string Identifier { get; } public string? Platform { get; } public ImmutableArray VersionRanges { get; } public ImmutableArray Statuses { get; } public ImmutableArray Provenance { get; } } /// /// Known values for . /// public static class AffectedPackageTypes { public const string Rpm = "rpm"; public const string Deb = "deb"; public const string Cpe = "cpe"; public const string SemVer = "semver"; public const string Vendor = "vendor"; public const string IcsVendor = "ics-vendor"; }