up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,230 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Serialization;
|
||||
|
||||
internal static class OrchestratorEventSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions CompactOptions = CreateOptions(writeIndented: false);
|
||||
private static readonly JsonSerializerOptions PrettyOptions = CreateOptions(writeIndented: true);
|
||||
|
||||
public static string Serialize(OrchestratorEvent @event)
|
||||
=> JsonSerializer.Serialize(@event, CompactOptions);
|
||||
|
||||
public static string SerializeIndented(OrchestratorEvent @event)
|
||||
=> JsonSerializer.Serialize(@event, PrettyOptions);
|
||||
|
||||
private static JsonSerializerOptions CreateOptions(bool writeIndented)
|
||||
{
|
||||
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = writeIndented,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
var baselineResolver = options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver();
|
||||
options.TypeInfoResolver = new DeterministicTypeInfoResolver(baselineResolver);
|
||||
return options;
|
||||
}
|
||||
|
||||
private sealed class DeterministicTypeInfoResolver : IJsonTypeInfoResolver
|
||||
{
|
||||
private static readonly ImmutableDictionary<Type, string[]> PropertyOrder = new Dictionary<Type, string[]>
|
||||
{
|
||||
[typeof(OrchestratorEvent)] = new[]
|
||||
{
|
||||
"eventId",
|
||||
"kind",
|
||||
"version",
|
||||
"tenant",
|
||||
"occurredAt",
|
||||
"recordedAt",
|
||||
"source",
|
||||
"idempotencyKey",
|
||||
"correlationId",
|
||||
"traceId",
|
||||
"spanId",
|
||||
"scope",
|
||||
"payload",
|
||||
"attributes"
|
||||
},
|
||||
[typeof(OrchestratorEventScope)] = new[]
|
||||
{
|
||||
"namespace",
|
||||
"repo",
|
||||
"digest",
|
||||
"component",
|
||||
"image"
|
||||
},
|
||||
[typeof(ReportReadyEventPayload)] = new[]
|
||||
{
|
||||
"reportId",
|
||||
"scanId",
|
||||
"imageDigest",
|
||||
"generatedAt",
|
||||
"verdict",
|
||||
"summary",
|
||||
"delta",
|
||||
"quietedFindingCount",
|
||||
"policy",
|
||||
"links",
|
||||
"dsse",
|
||||
"report"
|
||||
},
|
||||
[typeof(ScanCompletedEventPayload)] = new[]
|
||||
{
|
||||
"reportId",
|
||||
"scanId",
|
||||
"imageDigest",
|
||||
"verdict",
|
||||
"summary",
|
||||
"delta",
|
||||
"policy",
|
||||
"findings",
|
||||
"links",
|
||||
"dsse",
|
||||
"report"
|
||||
},
|
||||
[typeof(ReportDeltaPayload)] = new[]
|
||||
{
|
||||
"newCritical",
|
||||
"newHigh",
|
||||
"kev"
|
||||
},
|
||||
[typeof(ReportLinksPayload)] = new[]
|
||||
{
|
||||
"report",
|
||||
"policy",
|
||||
"attestation"
|
||||
},
|
||||
[typeof(LinkTarget)] = new[]
|
||||
{
|
||||
"ui",
|
||||
"api"
|
||||
},
|
||||
[typeof(FindingSummaryPayload)] = new[]
|
||||
{
|
||||
"id",
|
||||
"severity",
|
||||
"cve",
|
||||
"purl",
|
||||
"reachability"
|
||||
},
|
||||
[typeof(ReportPolicyDto)] = new[]
|
||||
{
|
||||
"revisionId",
|
||||
"digest"
|
||||
},
|
||||
[typeof(ReportSummaryDto)] = new[]
|
||||
{
|
||||
"total",
|
||||
"blocked",
|
||||
"warned",
|
||||
"ignored",
|
||||
"quieted"
|
||||
},
|
||||
[typeof(ReportDocumentDto)] = new[]
|
||||
{
|
||||
"reportId",
|
||||
"imageDigest",
|
||||
"generatedAt",
|
||||
"verdict",
|
||||
"policy",
|
||||
"summary",
|
||||
"verdicts",
|
||||
"issues"
|
||||
},
|
||||
[typeof(DsseEnvelopeDto)] = new[]
|
||||
{
|
||||
"payloadType",
|
||||
"payload",
|
||||
"signatures"
|
||||
},
|
||||
[typeof(DsseSignatureDto)] = new[]
|
||||
{
|
||||
"keyId",
|
||||
"algorithm",
|
||||
"signature"
|
||||
}
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private readonly IJsonTypeInfoResolver _inner;
|
||||
|
||||
public DeterministicTypeInfoResolver(IJsonTypeInfoResolver inner)
|
||||
{
|
||||
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
||||
}
|
||||
|
||||
public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
|
||||
{
|
||||
var info = _inner.GetTypeInfo(type, options)
|
||||
?? throw new InvalidOperationException($"Unable to resolve JsonTypeInfo for '{type}'.");
|
||||
|
||||
if (info.Kind is JsonTypeInfoKind.Object && info.Properties is { Count: > 1 })
|
||||
{
|
||||
var ordered = info.Properties
|
||||
.OrderBy(property => GetOrder(type, property.Name))
|
||||
.ThenBy(property => property.Name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
info.Properties.Clear();
|
||||
foreach (var property in ordered)
|
||||
{
|
||||
info.Properties.Add(property);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigurePolymorphism(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
private static int GetOrder(Type type, string propertyName)
|
||||
{
|
||||
if (PropertyOrder.TryGetValue(type, out var order) && Array.IndexOf(order, propertyName) is { } index and >= 0)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
if (type.BaseType is not null)
|
||||
{
|
||||
return GetOrder(type.BaseType, propertyName);
|
||||
}
|
||||
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
private static void ConfigurePolymorphism(JsonTypeInfo info)
|
||||
{
|
||||
if (info.Type != typeof(OrchestratorEventPayload))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info.PolymorphismOptions ??= new JsonPolymorphismOptions();
|
||||
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ReportReadyEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ScanCompletedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ScanStartedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ScanFailedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(SbomGeneratedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(VulnerabilityDetectedEventPayload));
|
||||
}
|
||||
|
||||
private static void AddDerivedType(JsonPolymorphismOptions options, Type derivedType)
|
||||
{
|
||||
if (options.DerivedTypes.Any(d => d.DerivedType == derivedType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.DerivedTypes.Add(new JsonDerivedType(derivedType));
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Serialization;
|
||||
|
||||
internal static class OrchestratorEventSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions CompactOptions = CreateOptions(writeIndented: false);
|
||||
private static readonly JsonSerializerOptions PrettyOptions = CreateOptions(writeIndented: true);
|
||||
|
||||
public static string Serialize(OrchestratorEvent @event)
|
||||
=> JsonSerializer.Serialize(@event, CompactOptions);
|
||||
|
||||
public static string SerializeIndented(OrchestratorEvent @event)
|
||||
=> JsonSerializer.Serialize(@event, PrettyOptions);
|
||||
|
||||
private static JsonSerializerOptions CreateOptions(bool writeIndented)
|
||||
{
|
||||
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = writeIndented,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
var baselineResolver = options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver();
|
||||
options.TypeInfoResolver = new DeterministicTypeInfoResolver(baselineResolver);
|
||||
return options;
|
||||
}
|
||||
|
||||
private sealed class DeterministicTypeInfoResolver : IJsonTypeInfoResolver
|
||||
{
|
||||
private static readonly ImmutableDictionary<Type, string[]> PropertyOrder = new Dictionary<Type, string[]>
|
||||
{
|
||||
[typeof(OrchestratorEvent)] = new[]
|
||||
{
|
||||
"eventId",
|
||||
"kind",
|
||||
"version",
|
||||
"tenant",
|
||||
"occurredAt",
|
||||
"recordedAt",
|
||||
"source",
|
||||
"idempotencyKey",
|
||||
"correlationId",
|
||||
"traceId",
|
||||
"spanId",
|
||||
"scope",
|
||||
"payload",
|
||||
"attributes"
|
||||
},
|
||||
[typeof(OrchestratorEventScope)] = new[]
|
||||
{
|
||||
"namespace",
|
||||
"repo",
|
||||
"digest",
|
||||
"component",
|
||||
"image"
|
||||
},
|
||||
[typeof(ReportReadyEventPayload)] = new[]
|
||||
{
|
||||
"reportId",
|
||||
"scanId",
|
||||
"imageDigest",
|
||||
"generatedAt",
|
||||
"verdict",
|
||||
"summary",
|
||||
"delta",
|
||||
"quietedFindingCount",
|
||||
"policy",
|
||||
"links",
|
||||
"dsse",
|
||||
"report"
|
||||
},
|
||||
[typeof(ScanCompletedEventPayload)] = new[]
|
||||
{
|
||||
"reportId",
|
||||
"scanId",
|
||||
"imageDigest",
|
||||
"verdict",
|
||||
"summary",
|
||||
"delta",
|
||||
"policy",
|
||||
"findings",
|
||||
"links",
|
||||
"dsse",
|
||||
"report"
|
||||
},
|
||||
[typeof(ReportDeltaPayload)] = new[]
|
||||
{
|
||||
"newCritical",
|
||||
"newHigh",
|
||||
"kev"
|
||||
},
|
||||
[typeof(ReportLinksPayload)] = new[]
|
||||
{
|
||||
"report",
|
||||
"policy",
|
||||
"attestation"
|
||||
},
|
||||
[typeof(LinkTarget)] = new[]
|
||||
{
|
||||
"ui",
|
||||
"api"
|
||||
},
|
||||
[typeof(FindingSummaryPayload)] = new[]
|
||||
{
|
||||
"id",
|
||||
"severity",
|
||||
"cve",
|
||||
"purl",
|
||||
"reachability"
|
||||
},
|
||||
[typeof(ReportPolicyDto)] = new[]
|
||||
{
|
||||
"revisionId",
|
||||
"digest"
|
||||
},
|
||||
[typeof(ReportSummaryDto)] = new[]
|
||||
{
|
||||
"total",
|
||||
"blocked",
|
||||
"warned",
|
||||
"ignored",
|
||||
"quieted"
|
||||
},
|
||||
[typeof(ReportDocumentDto)] = new[]
|
||||
{
|
||||
"reportId",
|
||||
"imageDigest",
|
||||
"generatedAt",
|
||||
"verdict",
|
||||
"policy",
|
||||
"summary",
|
||||
"verdicts",
|
||||
"issues"
|
||||
},
|
||||
[typeof(DsseEnvelopeDto)] = new[]
|
||||
{
|
||||
"payloadType",
|
||||
"payload",
|
||||
"signatures"
|
||||
},
|
||||
[typeof(DsseSignatureDto)] = new[]
|
||||
{
|
||||
"keyId",
|
||||
"algorithm",
|
||||
"signature"
|
||||
}
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
private readonly IJsonTypeInfoResolver _inner;
|
||||
|
||||
public DeterministicTypeInfoResolver(IJsonTypeInfoResolver inner)
|
||||
{
|
||||
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
||||
}
|
||||
|
||||
public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
|
||||
{
|
||||
var info = _inner.GetTypeInfo(type, options)
|
||||
?? throw new InvalidOperationException($"Unable to resolve JsonTypeInfo for '{type}'.");
|
||||
|
||||
if (info.Kind is JsonTypeInfoKind.Object && info.Properties is { Count: > 1 })
|
||||
{
|
||||
var ordered = info.Properties
|
||||
.OrderBy(property => GetOrder(type, property.Name))
|
||||
.ThenBy(property => property.Name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
info.Properties.Clear();
|
||||
foreach (var property in ordered)
|
||||
{
|
||||
info.Properties.Add(property);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigurePolymorphism(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
private static int GetOrder(Type type, string propertyName)
|
||||
{
|
||||
if (PropertyOrder.TryGetValue(type, out var order) && Array.IndexOf(order, propertyName) is { } index and >= 0)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
if (type.BaseType is not null)
|
||||
{
|
||||
return GetOrder(type.BaseType, propertyName);
|
||||
}
|
||||
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
private static void ConfigurePolymorphism(JsonTypeInfo info)
|
||||
{
|
||||
if (info.Type != typeof(OrchestratorEventPayload))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info.PolymorphismOptions ??= new JsonPolymorphismOptions();
|
||||
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ReportReadyEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ScanCompletedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ScanStartedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(ScanFailedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(SbomGeneratedEventPayload));
|
||||
AddDerivedType(info.PolymorphismOptions, typeof(VulnerabilityDetectedEventPayload));
|
||||
}
|
||||
|
||||
private static void AddDerivedType(JsonPolymorphismOptions options, Type derivedType)
|
||||
{
|
||||
if (options.DerivedTypes.Any(d => d.DerivedType == derivedType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.DerivedTypes.Add(new JsonDerivedType(derivedType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user