176 lines
5.6 KiB
C#
176 lines
5.6 KiB
C#
// -----------------------------------------------------------------------------
|
|
// HlcTimestampJsonConverter.cs
|
|
// Sprint: SPRINT_20260105_002_001_LB_hlc_core_library
|
|
// Task: HLC-006 - Add HlcTimestampJsonConverter for System.Text.Json serialization
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.HybridLogicalClock;
|
|
|
|
/// <summary>
|
|
/// JSON converter for HlcTimestamp using the sortable string format.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Serializes HlcTimestamp to/from the sortable string format (e.g., "1704067200000-scheduler-east-1-000042").
|
|
/// This format is both human-readable and lexicographically sortable.
|
|
/// </remarks>
|
|
public sealed class HlcTimestampJsonConverter : JsonConverter<HlcTimestamp>
|
|
{
|
|
/// <inheritdoc/>
|
|
public override HlcTimestamp Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
if (reader.TokenType == JsonTokenType.Null)
|
|
{
|
|
throw new JsonException("Cannot deserialize null to HlcTimestamp. Use HlcTimestamp? with NullableHlcTimestampJsonConverter for nullable timestamps.");
|
|
}
|
|
|
|
if (reader.TokenType != JsonTokenType.String)
|
|
{
|
|
throw new JsonException($"Expected string but got {reader.TokenType}");
|
|
}
|
|
|
|
var value = reader.GetString();
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
throw new JsonException("Cannot convert empty string to HlcTimestamp");
|
|
}
|
|
|
|
try
|
|
{
|
|
return HlcTimestamp.Parse(value);
|
|
}
|
|
catch (FormatException ex)
|
|
{
|
|
throw new JsonException($"Invalid HlcTimestamp format: {value}", ex);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Write(Utf8JsonWriter writer, HlcTimestamp value, JsonSerializerOptions options)
|
|
{
|
|
writer.WriteStringValue(value.ToSortableString());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON converter for nullable HlcTimestamp.
|
|
/// </summary>
|
|
public sealed class NullableHlcTimestampJsonConverter : JsonConverter<HlcTimestamp?>
|
|
{
|
|
private readonly HlcTimestampJsonConverter _inner = new();
|
|
|
|
/// <inheritdoc/>
|
|
public override HlcTimestamp? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
if (reader.TokenType == JsonTokenType.Null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return _inner.Read(ref reader, typeof(HlcTimestamp), options);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Write(Utf8JsonWriter writer, HlcTimestamp? value, JsonSerializerOptions options)
|
|
{
|
|
if (!value.HasValue)
|
|
{
|
|
writer.WriteNullValue();
|
|
return;
|
|
}
|
|
|
|
_inner.Write(writer, value.Value, options);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON converter for HlcTimestamp using object format with individual properties.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Alternative converter that serializes HlcTimestamp as an object:
|
|
/// <code>
|
|
/// {
|
|
/// "physicalTime": 1704067200000,
|
|
/// "nodeId": "scheduler-east-1",
|
|
/// "logicalCounter": 42
|
|
/// }
|
|
/// </code>
|
|
/// Use this when you need to query individual fields in JSON storage.
|
|
/// </remarks>
|
|
public sealed class HlcTimestampObjectJsonConverter : JsonConverter<HlcTimestamp>
|
|
{
|
|
/// <inheritdoc/>
|
|
public override HlcTimestamp Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
if (reader.TokenType != JsonTokenType.StartObject)
|
|
{
|
|
throw new JsonException($"Expected StartObject but got {reader.TokenType}");
|
|
}
|
|
|
|
long? physicalTime = null;
|
|
string? nodeId = null;
|
|
int? logicalCounter = null;
|
|
|
|
while (reader.Read())
|
|
{
|
|
if (reader.TokenType == JsonTokenType.EndObject)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (reader.TokenType != JsonTokenType.PropertyName)
|
|
{
|
|
throw new JsonException($"Expected PropertyName but got {reader.TokenType}");
|
|
}
|
|
|
|
var propertyName = reader.GetString();
|
|
reader.Read();
|
|
|
|
switch (propertyName)
|
|
{
|
|
case "physicalTime":
|
|
case "PhysicalTime":
|
|
physicalTime = reader.GetInt64();
|
|
break;
|
|
case "nodeId":
|
|
case "NodeId":
|
|
nodeId = reader.GetString();
|
|
break;
|
|
case "logicalCounter":
|
|
case "LogicalCounter":
|
|
logicalCounter = reader.GetInt32();
|
|
break;
|
|
default:
|
|
reader.Skip();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!physicalTime.HasValue)
|
|
throw new JsonException("Missing required property 'physicalTime'");
|
|
if (string.IsNullOrEmpty(nodeId))
|
|
throw new JsonException("Missing required property 'nodeId'");
|
|
if (!logicalCounter.HasValue)
|
|
throw new JsonException("Missing required property 'logicalCounter'");
|
|
|
|
return new HlcTimestamp
|
|
{
|
|
PhysicalTime = physicalTime.Value,
|
|
NodeId = nodeId,
|
|
LogicalCounter = logicalCounter.Value
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override void Write(Utf8JsonWriter writer, HlcTimestamp value, JsonSerializerOptions options)
|
|
{
|
|
writer.WriteStartObject();
|
|
writer.WriteNumber("physicalTime", value.PhysicalTime);
|
|
writer.WriteString("nodeId", value.NodeId);
|
|
writer.WriteNumber("logicalCounter", value.LogicalCounter);
|
|
writer.WriteEndObject();
|
|
}
|
|
}
|