using System.Text.Json;
namespace ZB.MOM.WW.Auth.ApiKeys.Sqlite;
///
/// Serializes API-key scope sets to a canonical JSON array. Scopes are sorted with
/// so that equal sets always produce identical
/// column text, regardless of insertion order.
///
public static class ScopeSerializer
{
/// Serializes scopes to an ordinal-sorted JSON array.
/// The scopes to serialize.
/// A JSON array string with elements sorted ordinally.
public static string Serialize(IReadOnlySet scopes)
{
ArgumentNullException.ThrowIfNull(scopes);
return JsonSerializer.Serialize(scopes.Order(StringComparer.Ordinal));
}
/// Deserializes scopes from a JSON array string.
/// The JSON string to deserialize; may be null or empty.
///
/// An ordinal-compared set of scopes; empty when the input is null/blank. A malformed or
/// non-array column (operator tampering, a partial write, a format change, or a buggy writer)
/// fails closed to an EMPTY set rather than throwing, so a single poisoned row degrades to a
/// zero-scope identity on the auth path instead of an unhandled .
///
public static IReadOnlySet Deserialize(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return new HashSet(StringComparer.Ordinal);
}
string[]? scopes;
try
{
scopes = JsonSerializer.Deserialize(value);
}
catch (JsonException)
{
// Fail closed: a corrupt scopes column yields no scopes rather than an exception on the
// verification hot path. The verifier's "only exception path is cancellation" contract
// is preserved, and a key with an unreadable scope set is left with zero authority.
return new HashSet(StringComparer.Ordinal);
}
return new HashSet(scopes ?? [], StringComparer.Ordinal);
}
}