using System; using System.Collections.Generic; namespace StellaOps.Concelier.Models; /// /// Canonical normalized version rule emitted by range builders for analytical queries. /// public sealed record NormalizedVersionRule { public NormalizedVersionRule( string scheme, string type, string? min = null, bool? minInclusive = null, string? max = null, bool? maxInclusive = null, string? value = null, string? notes = null) { Scheme = Validation.EnsureNotNullOrWhiteSpace(scheme, nameof(scheme)).ToLowerInvariant(); Type = Validation.EnsureNotNullOrWhiteSpace(type, nameof(type)).Replace('_', '-').ToLowerInvariant(); Min = Validation.TrimToNull(min); MinInclusive = minInclusive; Max = Validation.TrimToNull(max); MaxInclusive = maxInclusive; Value = Validation.TrimToNull(value); Notes = Validation.TrimToNull(notes); } public string Scheme { get; } public string Type { get; } public string? Min { get; } public bool? MinInclusive { get; } public string? Max { get; } public bool? MaxInclusive { get; } public string? Value { get; } public string? Notes { get; } } public sealed class NormalizedVersionRuleEqualityComparer : IEqualityComparer { public static NormalizedVersionRuleEqualityComparer Instance { get; } = new(); public bool Equals(NormalizedVersionRule? x, NormalizedVersionRule? y) { if (ReferenceEquals(x, y)) { return true; } if (x is null || y is null) { return false; } return string.Equals(x.Scheme, y.Scheme, StringComparison.Ordinal) && string.Equals(x.Type, y.Type, StringComparison.Ordinal) && string.Equals(x.Min, y.Min, StringComparison.Ordinal) && x.MinInclusive == y.MinInclusive && string.Equals(x.Max, y.Max, StringComparison.Ordinal) && x.MaxInclusive == y.MaxInclusive && string.Equals(x.Value, y.Value, StringComparison.Ordinal) && string.Equals(x.Notes, y.Notes, StringComparison.Ordinal); } public int GetHashCode(NormalizedVersionRule obj) => HashCode.Combine( obj.Scheme, obj.Type, obj.Min, obj.MinInclusive, obj.Max, obj.MaxInclusive, obj.Value, obj.Notes); } public sealed class NormalizedVersionRuleComparer : IComparer { public static NormalizedVersionRuleComparer Instance { get; } = new(); public int Compare(NormalizedVersionRule? x, NormalizedVersionRule? y) { if (ReferenceEquals(x, y)) { return 0; } if (x is null) { return -1; } if (y is null) { return 1; } var schemeComparison = string.Compare(x.Scheme, y.Scheme, StringComparison.Ordinal); if (schemeComparison != 0) { return schemeComparison; } var typeComparison = string.Compare(x.Type, y.Type, StringComparison.Ordinal); if (typeComparison != 0) { return typeComparison; } var minComparison = string.Compare(x.Min, y.Min, StringComparison.Ordinal); if (minComparison != 0) { return minComparison; } var minInclusiveComparison = NullableBoolCompare(x.MinInclusive, y.MinInclusive); if (minInclusiveComparison != 0) { return minInclusiveComparison; } var maxComparison = string.Compare(x.Max, y.Max, StringComparison.Ordinal); if (maxComparison != 0) { return maxComparison; } var maxInclusiveComparison = NullableBoolCompare(x.MaxInclusive, y.MaxInclusive); if (maxInclusiveComparison != 0) { return maxInclusiveComparison; } var valueComparison = string.Compare(x.Value, y.Value, StringComparison.Ordinal); if (valueComparison != 0) { return valueComparison; } return string.Compare(x.Notes, y.Notes, StringComparison.Ordinal); } private static int NullableBoolCompare(bool? x, bool? y) { if (x == y) { return 0; } return (x, y) switch { (null, not null) => -1, (not null, null) => 1, (false, true) => -1, (true, false) => 1, _ => 0, }; } } public static class NormalizedVersionSchemes { public const string SemVer = "semver"; public const string Nevra = "nevra"; public const string Evr = "evr"; } public static class NormalizedVersionRuleTypes { public const string Range = "range"; public const string Exact = "exact"; public const string LessThan = "lt"; public const string LessThanOrEqual = "lte"; public const string GreaterThan = "gt"; public const string GreaterThanOrEqual = "gte"; }