88 lines
3.2 KiB
C#
88 lines
3.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Feedser.Models;
|
|
|
|
/// <summary>
|
|
/// Canonical affected package descriptor with deterministic ordering of ranges and provenance.
|
|
/// </summary>
|
|
public sealed record AffectedPackage
|
|
{
|
|
public static AffectedPackage Empty { get; } = new(
|
|
AffectedPackageTypes.SemVer,
|
|
identifier: "unknown",
|
|
platform: null,
|
|
versionRanges: Array.Empty<AffectedVersionRange>(),
|
|
statuses: Array.Empty<AffectedPackageStatus>(),
|
|
provenance: Array.Empty<AdvisoryProvenance>());
|
|
|
|
[JsonConstructor]
|
|
public AffectedPackage(
|
|
string type,
|
|
string identifier,
|
|
string? platform = null,
|
|
IEnumerable<AffectedVersionRange>? versionRanges = null,
|
|
IEnumerable<AffectedPackageStatus>? statuses = null,
|
|
IEnumerable<AdvisoryProvenance>? provenance = null)
|
|
{
|
|
Type = Validation.EnsureNotNullOrWhiteSpace(type, nameof(type)).ToLowerInvariant();
|
|
Identifier = Validation.EnsureNotNullOrWhiteSpace(identifier, nameof(identifier));
|
|
Platform = Validation.TrimToNull(platform);
|
|
|
|
VersionRanges = (versionRanges ?? Array.Empty<AffectedVersionRange>())
|
|
.Distinct(AffectedVersionRangeEqualityComparer.Instance)
|
|
.OrderBy(static range => range, AffectedVersionRangeComparer.Instance)
|
|
.ToImmutableArray();
|
|
|
|
Statuses = (statuses ?? Array.Empty<AffectedPackageStatus>())
|
|
.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<AdvisoryProvenance>())
|
|
.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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Semantic type of the coordinates (rpm, deb, cpe, semver, vendor, ics-vendor).
|
|
/// </summary>
|
|
public string Type { get; }
|
|
|
|
/// <summary>
|
|
/// Canonical identifier for the package (NEVRA, PackageURL, CPE string, vendor slug, etc.).
|
|
/// </summary>
|
|
public string Identifier { get; }
|
|
|
|
public string? Platform { get; }
|
|
|
|
public ImmutableArray<AffectedVersionRange> VersionRanges { get; }
|
|
|
|
public ImmutableArray<AffectedPackageStatus> Statuses { get; }
|
|
|
|
public ImmutableArray<AdvisoryProvenance> Provenance { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Known values for <see cref="AffectedPackage.Type"/>.
|
|
/// </summary>
|
|
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";
|
|
}
|