up
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Replay.Core;
|
||||
using StellaOps.Scanner.ProofSpine;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Serialization;
|
||||
using StellaOps.Scanner.WebService.Security;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Endpoints;
|
||||
@@ -17,6 +18,7 @@ internal static class ProofSpineEndpoints
|
||||
spines.MapGet("/{spineId}", HandleGetSpineAsync)
|
||||
.WithName("scanner.spines.get")
|
||||
.Produces<ProofSpineResponseDto>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status200OK, contentType: CborNegotiation.ContentType)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.ScansRead);
|
||||
|
||||
@@ -24,15 +26,18 @@ internal static class ProofSpineEndpoints
|
||||
scans.MapGet("/{scanId}/spines", HandleListSpinesAsync)
|
||||
.WithName("scanner.spines.list-by-scan")
|
||||
.Produces<ProofSpineListResponseDto>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status200OK, contentType: CborNegotiation.ContentType)
|
||||
.RequireAuthorization(ScannerPolicies.ScansRead);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleGetSpineAsync(
|
||||
HttpRequest request,
|
||||
string spineId,
|
||||
IProofSpineRepository repository,
|
||||
ProofSpineVerifier verifier,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(repository);
|
||||
ArgumentNullException.ThrowIfNull(verifier);
|
||||
|
||||
@@ -93,19 +98,36 @@ internal static class ProofSpineEndpoints
|
||||
}
|
||||
};
|
||||
|
||||
if (CborNegotiation.AcceptsCbor(request))
|
||||
{
|
||||
return Results.Bytes(
|
||||
DeterministicCborSerializer.Serialize(dto),
|
||||
contentType: CborNegotiation.ContentType);
|
||||
}
|
||||
|
||||
return Results.Ok(dto);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleListSpinesAsync(
|
||||
HttpRequest request,
|
||||
string scanId,
|
||||
IProofSpineRepository repository,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ArgumentNullException.ThrowIfNull(repository);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(scanId))
|
||||
{
|
||||
return Results.Ok(new ProofSpineListResponseDto { Items = Array.Empty<ProofSpineSummaryDto>(), Total = 0 });
|
||||
var empty = new ProofSpineListResponseDto { Items = Array.Empty<ProofSpineSummaryDto>(), Total = 0 };
|
||||
if (CborNegotiation.AcceptsCbor(request))
|
||||
{
|
||||
return Results.Bytes(
|
||||
DeterministicCborSerializer.Serialize(empty),
|
||||
contentType: CborNegotiation.ContentType);
|
||||
}
|
||||
|
||||
return Results.Ok(empty);
|
||||
}
|
||||
|
||||
var summaries = await repository.GetSummariesByScanRunAsync(scanId, cancellationToken).ConfigureAwait(false);
|
||||
@@ -119,11 +141,20 @@ internal static class ProofSpineEndpoints
|
||||
CreatedAt = summary.CreatedAt
|
||||
}).ToArray();
|
||||
|
||||
return Results.Ok(new ProofSpineListResponseDto
|
||||
var response = new ProofSpineListResponseDto
|
||||
{
|
||||
Items = items,
|
||||
Total = items.Length
|
||||
});
|
||||
};
|
||||
|
||||
if (CborNegotiation.AcceptsCbor(request))
|
||||
{
|
||||
return Results.Bytes(
|
||||
DeterministicCborSerializer.Serialize(response),
|
||||
contentType: CborNegotiation.ContentType);
|
||||
}
|
||||
|
||||
return Results.Ok(response);
|
||||
}
|
||||
|
||||
private static DsseEnvelopeDto MapEnvelope(DsseEnvelope envelope)
|
||||
@@ -163,4 +194,3 @@ internal static class ProofSpineEndpoints
|
||||
return "/" + trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Serialization;
|
||||
|
||||
internal static class CborNegotiation
|
||||
{
|
||||
public const string ContentType = "application/cbor";
|
||||
|
||||
public static bool AcceptsCbor(HttpRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (!request.Headers.TryGetValue("Accept", out var values))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (value.Contains(ContentType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Formats.Cbor;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Serialization;
|
||||
|
||||
internal static class DeterministicCborSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public static byte[] Serialize<T>(T value)
|
||||
{
|
||||
using var document = JsonSerializer.SerializeToDocument(value, JsonOptions);
|
||||
var writer = new CborWriter(CborConformanceMode.Canonical);
|
||||
|
||||
WriteElement(writer, document.RootElement);
|
||||
return writer.Encode();
|
||||
}
|
||||
|
||||
private static void WriteElement(CborWriter writer, JsonElement element)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
WriteObject(writer, element);
|
||||
return;
|
||||
case JsonValueKind.Array:
|
||||
writer.WriteStartArray(element.GetArrayLength());
|
||||
foreach (var item in element.EnumerateArray())
|
||||
{
|
||||
WriteElement(writer, item);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
return;
|
||||
case JsonValueKind.String:
|
||||
writer.WriteTextString(element.GetString() ?? string.Empty);
|
||||
return;
|
||||
case JsonValueKind.Number:
|
||||
if (element.TryGetInt64(out var int64))
|
||||
{
|
||||
writer.WriteInt64(int64);
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteDouble(element.GetDouble());
|
||||
return;
|
||||
case JsonValueKind.True:
|
||||
writer.WriteBoolean(true);
|
||||
return;
|
||||
case JsonValueKind.False:
|
||||
writer.WriteBoolean(false);
|
||||
return;
|
||||
case JsonValueKind.Null:
|
||||
case JsonValueKind.Undefined:
|
||||
writer.WriteNull();
|
||||
return;
|
||||
default:
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteObject(CborWriter writer, JsonElement element)
|
||||
{
|
||||
var properties = new List<(byte[] KeyBytes, string Key, JsonElement Value)>();
|
||||
|
||||
foreach (var property in element.EnumerateObject())
|
||||
{
|
||||
var keyBytes = Encoding.UTF8.GetBytes(property.Name);
|
||||
properties.Add((keyBytes, property.Name, property.Value));
|
||||
}
|
||||
|
||||
properties.Sort(static (left, right) =>
|
||||
{
|
||||
var lengthCompare = left.KeyBytes.Length.CompareTo(right.KeyBytes.Length);
|
||||
if (lengthCompare != 0)
|
||||
{
|
||||
return lengthCompare;
|
||||
}
|
||||
|
||||
for (var i = 0; i < left.KeyBytes.Length; i++)
|
||||
{
|
||||
var byteCompare = left.KeyBytes[i].CompareTo(right.KeyBytes[i]);
|
||||
if (byteCompare != 0)
|
||||
{
|
||||
return byteCompare;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
writer.WriteStartMap(properties.Count);
|
||||
|
||||
foreach (var (_, key, value) in properties)
|
||||
{
|
||||
writer.WriteTextString(key);
|
||||
WriteElement(writer, value);
|
||||
}
|
||||
|
||||
writer.WriteEndMap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user