using System.Collections.Immutable; namespace Just.Railway; public sealed class ErrorJsonConverter : JsonConverter { public override Error? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.StartObject => ToExpectedError(ReadOne(ref reader)), JsonTokenType.StartArray => ReadMany(ref reader), JsonTokenType.None => null, JsonTokenType.Null => null, _ => throw new JsonException("Unexpected JSON token.") }; } public override void Write(Utf8JsonWriter writer, Error value, JsonSerializerOptions options) { if (value is ManyErrors manyErrors) { writer.WriteStartArray(); foreach (var err in manyErrors) { WriteOne(writer, err); } writer.WriteEndArray(); } else { WriteOne(writer, value); } } internal static ManyErrors ReadMany(ref Utf8JsonReader reader) { var errors = ImmutableArray.CreateBuilder(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.StartObject) { errors.Add(ToExpectedError(ReadOne(ref reader))); } } return new ManyErrors(errors.ToImmutable()); } internal static ExpectedError ToExpectedError(in (string Type, string Message, ImmutableDictionary ExtensionData) errorInfo) => new(errorInfo.Type, errorInfo.Message) { ExtensionData = errorInfo.ExtensionData }; internal static (string Type, string Message, ImmutableDictionary ExtensionData) ReadOne(ref Utf8JsonReader reader) { ImmutableDictionary.Builder? extensionData = null; string type = "error"; string message = ""; while (reader.Read()) { switch (reader.TokenType) { case JsonTokenType.PropertyName: { var propname = reader.GetString(); reader.Read(); if (reader.TokenType == JsonTokenType.Null) break; while (reader.TokenType == JsonTokenType.Comment) reader.Read(); if (!(reader.TokenType == JsonTokenType.String)) throw new JsonException("Unable to deserialize Error type."); var propvalue = reader.GetString(); if (string.IsNullOrEmpty(propvalue)) break; if (propname == "type" || string.Equals(propname, "type", StringComparison.InvariantCultureIgnoreCase)) { type = propvalue; } else if (propname == "msg" || string.Equals(propname, "msg", StringComparison.InvariantCultureIgnoreCase)) { message = propvalue; } else if (!string.IsNullOrEmpty(propname)) { extensionData ??= ImmutableDictionary.CreateBuilder(); extensionData[propname] = propvalue; } break; } case JsonTokenType.Comment: break; case JsonTokenType.EndObject: goto endLoop; default: throw new JsonException("Unable to deserialize Error type."); } } endLoop: return (type, message, extensionData?.ToImmutable() ?? ImmutableDictionary.Empty); } internal static void WriteOne(Utf8JsonWriter writer, Error value) { writer.WriteStartObject(); writer.WriteString("type", value.Type); writer.WriteString("msg", value.Message); if (value.ExtensionData?.Count > 0) { foreach (var (key, val) in value.ExtensionData) { writer.WriteString(key, val); } } writer.WriteEndObject(); } } public sealed class ExpectedErrorJsonConverter : JsonConverter { public override ExpectedError? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.StartObject => ErrorJsonConverter.ToExpectedError(ErrorJsonConverter.ReadOne(ref reader)), JsonTokenType.None => null, JsonTokenType.Null => null, _ => throw new JsonException("Unexpected JSON token.") }; } public override void Write(Utf8JsonWriter writer, ExpectedError value, JsonSerializerOptions options) { ErrorJsonConverter.WriteOne(writer, value); } } public sealed class ExceptionalErrorJsonConverter : JsonConverter { public override ExceptionalError? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.StartObject => ToExceptionalError(ErrorJsonConverter.ReadOne(ref reader)), JsonTokenType.None => null, JsonTokenType.Null => null, _ => throw new JsonException("Unexpected JSON token.") }; } public override void Write(Utf8JsonWriter writer, ExceptionalError value, JsonSerializerOptions options) { ErrorJsonConverter.WriteOne(writer, value); } private static ExceptionalError ToExceptionalError(in (string Type, string Message, ImmutableDictionary ExtensionData) errorInfo) => new(errorInfo.Type, errorInfo.Message) { ExtensionData = errorInfo.ExtensionData }; } public sealed class ManyErrorsJsonConverter : JsonConverter { public override ManyErrors? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.StartArray => ErrorJsonConverter.ReadMany(ref reader), JsonTokenType.None => null, JsonTokenType.Null => null, _ => throw new JsonException("Unexpected JSON token.") }; } public override void Write(Utf8JsonWriter writer, ManyErrors value, JsonSerializerOptions options) { writer.WriteStartArray(); foreach (var err in value) { ErrorJsonConverter.WriteOne(writer, err); } writer.WriteEndArray(); } }