part #2
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
namespace StellaOps.Replay.Loaders;
|
||||
|
||||
public sealed class DigestMismatchException : Exception
|
||||
{
|
||||
public DigestMismatchException(string message) : base(message) { }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace StellaOps.Replay.Loaders;
|
||||
|
||||
public sealed class FeedNotFoundException : Exception
|
||||
{
|
||||
public FeedNotFoundException(string message) : base(message) { }
|
||||
}
|
||||
@@ -21,24 +21,25 @@ public sealed class FeedSnapshotLoader : IFeedLoader
|
||||
|
||||
public async Task<FeedSnapshot> LoadByDigestAsync(string digest, CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogDebug("Loading feed snapshot with digest {Digest}", digest);
|
||||
var normalizedDigest = SnapshotDigestGuard.EnsureSha256Hex(digest, nameof(digest));
|
||||
_logger.LogDebug("Loading feed snapshot with digest {Digest}", normalizedDigest);
|
||||
|
||||
var localPath = GetLocalPath(digest);
|
||||
var localPath = GetLocalPath(normalizedDigest);
|
||||
if (File.Exists(localPath))
|
||||
{
|
||||
var feed = await LoadFromFileAsync(localPath, ct).ConfigureAwait(false);
|
||||
VerifyDigest(feed, digest);
|
||||
VerifyDigest(feed, normalizedDigest);
|
||||
return feed;
|
||||
}
|
||||
|
||||
var storedFeed = await _storage.GetByDigestAsync(digest, ct).ConfigureAwait(false);
|
||||
var storedFeed = await _storage.GetByDigestAsync(normalizedDigest, ct).ConfigureAwait(false);
|
||||
if (storedFeed is not null)
|
||||
{
|
||||
VerifyDigest(storedFeed, digest);
|
||||
VerifyDigest(storedFeed, normalizedDigest);
|
||||
return storedFeed;
|
||||
}
|
||||
|
||||
throw new FeedNotFoundException($"Feed snapshot not found: {digest}");
|
||||
throw new FeedNotFoundException($"Feed snapshot not found: {normalizedDigest}");
|
||||
}
|
||||
|
||||
private static void VerifyDigest(FeedSnapshot feed, string expected)
|
||||
@@ -66,18 +67,3 @@ public sealed class FeedSnapshotLoader : IFeedLoader
|
||||
return CanonicalJsonSerializer.Deserialize<FeedSnapshot>(json);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IFeedStorage
|
||||
{
|
||||
Task<FeedSnapshot?> GetByDigestAsync(string digest, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public sealed class FeedNotFoundException : Exception
|
||||
{
|
||||
public FeedNotFoundException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public sealed class DigestMismatchException : Exception
|
||||
{
|
||||
public DigestMismatchException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
8
src/__Libraries/StellaOps.Replay/Loaders/IFeedStorage.cs
Normal file
8
src/__Libraries/StellaOps.Replay/Loaders/IFeedStorage.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using StellaOps.Testing.Manifests.Models;
|
||||
|
||||
namespace StellaOps.Replay.Loaders;
|
||||
|
||||
public interface IFeedStorage
|
||||
{
|
||||
Task<FeedSnapshot?> GetByDigestAsync(string digest, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using StellaOps.Testing.Manifests.Models;
|
||||
|
||||
namespace StellaOps.Replay.Loaders;
|
||||
|
||||
public interface IPolicyStorage
|
||||
{
|
||||
Task<PolicySnapshot?> GetByDigestAsync(string digest, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace StellaOps.Replay.Loaders;
|
||||
|
||||
public sealed class PolicyNotFoundException : Exception
|
||||
{
|
||||
public PolicyNotFoundException(string message) : base(message) { }
|
||||
}
|
||||
@@ -21,24 +21,25 @@ public sealed class PolicySnapshotLoader : IPolicyLoader
|
||||
|
||||
public async Task<PolicySnapshot> LoadByDigestAsync(string digest, CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogDebug("Loading policy snapshot with digest {Digest}", digest);
|
||||
var normalizedDigest = SnapshotDigestGuard.EnsureSha256Hex(digest, nameof(digest));
|
||||
_logger.LogDebug("Loading policy snapshot with digest {Digest}", normalizedDigest);
|
||||
|
||||
var localPath = GetLocalPath(digest);
|
||||
var localPath = GetLocalPath(normalizedDigest);
|
||||
if (File.Exists(localPath))
|
||||
{
|
||||
var policy = await LoadFromFileAsync(localPath, ct).ConfigureAwait(false);
|
||||
VerifyDigest(policy, digest);
|
||||
VerifyDigest(policy, normalizedDigest);
|
||||
return policy;
|
||||
}
|
||||
|
||||
var stored = await _storage.GetByDigestAsync(digest, ct).ConfigureAwait(false);
|
||||
var stored = await _storage.GetByDigestAsync(normalizedDigest, ct).ConfigureAwait(false);
|
||||
if (stored is not null)
|
||||
{
|
||||
VerifyDigest(stored, digest);
|
||||
VerifyDigest(stored, normalizedDigest);
|
||||
return stored;
|
||||
}
|
||||
|
||||
throw new PolicyNotFoundException($"Policy snapshot not found: {digest}");
|
||||
throw new PolicyNotFoundException($"Policy snapshot not found: {normalizedDigest}");
|
||||
}
|
||||
|
||||
private static void VerifyDigest(PolicySnapshot policy, string expected)
|
||||
@@ -66,13 +67,3 @@ public sealed class PolicySnapshotLoader : IPolicyLoader
|
||||
return CanonicalJsonSerializer.Deserialize<PolicySnapshot>(json);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPolicyStorage
|
||||
{
|
||||
Task<PolicySnapshot?> GetByDigestAsync(string digest, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public sealed class PolicyNotFoundException : Exception
|
||||
{
|
||||
public PolicyNotFoundException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace StellaOps.Replay.Loaders;
|
||||
|
||||
internal static class SnapshotDigestGuard
|
||||
{
|
||||
private const int HexLength = 64;
|
||||
|
||||
internal static string EnsureSha256Hex(string digest, string parameterName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(digest))
|
||||
throw new ArgumentException($"{parameterName} is required.", parameterName);
|
||||
|
||||
var trimmed = digest.Trim();
|
||||
if (trimmed.Length != HexLength || !IsHex(trimmed))
|
||||
throw new FormatException($"{parameterName} must be {HexLength} hexadecimal characters.");
|
||||
|
||||
return trimmed.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static bool IsHex(ReadOnlySpan<char> value)
|
||||
{
|
||||
foreach (var c in value)
|
||||
{
|
||||
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user