| | | 1 | | using System.Text.Json; |
| | | 2 | | using TeleFlow.Framework.Callbacks; |
| | | 3 | | using TeleFlow.Telegram.Internal; |
| | | 4 | | |
| | | 5 | | namespace TeleFlow.Telegram; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Serializes callback payloads for typed callback handlers, using compact |
| | | 9 | | /// <see cref="TeleFlow.Annotations.CallbackDataAttribute"/> metadata when available and JSON otherwise. |
| | | 10 | | /// </summary> |
| | | 11 | | public sealed class JsonCallbackDataSerializer : ICallbackDataRouteDeserializer |
| | | 12 | | { |
| | | 13 | | private readonly JsonSerializerOptions _jsonSerializerOptions; |
| | | 14 | | |
| | | 15 | | public JsonCallbackDataSerializer(TelegramJsonOptions jsonOptions) |
| | | 16 | | { |
| | 266 | 17 | | ArgumentNullException.ThrowIfNull(jsonOptions); |
| | 266 | 18 | | _jsonSerializerOptions = jsonOptions.SerializerOptions; |
| | 266 | 19 | | } |
| | | 20 | | |
| | | 21 | | public string Serialize<TPayload>(TPayload payload) |
| | | 22 | | { |
| | 7 | 23 | | ArgumentNullException.ThrowIfNull(payload); |
| | | 24 | | |
| | 7 | 25 | | var serializedPayload = CallbackDataCodec.TryGetGenerated(typeof(TPayload), out var generatedCodec) |
| | 7 | 26 | | ? generatedCodec.Pack(payload!) |
| | 7 | 27 | | : CallbackDataMetadata.TryCreate(typeof(TPayload), out var metadata) |
| | 7 | 28 | | ? CallbackDataCodec.Pack(payload!, metadata) |
| | 7 | 29 | | : JsonSerializer.Serialize(payload, _jsonSerializerOptions); |
| | | 30 | | |
| | 7 | 31 | | CallbackDataCodec.ValidateCallbackData(serializedPayload, "Serialized Telegram callback data"); |
| | | 32 | | |
| | 6 | 33 | | return serializedPayload; |
| | | 34 | | } |
| | | 35 | | |
| | | 36 | | public TPayload Deserialize<TPayload>(string serializedPayload) |
| | | 37 | | { |
| | 1 | 38 | | ArgumentNullException.ThrowIfNull(serializedPayload); |
| | | 39 | | |
| | 1 | 40 | | if (CallbackDataCodec.TryGetGenerated(typeof(TPayload), out var generatedCodec)) |
| | | 41 | | { |
| | 0 | 42 | | return (TPayload)generatedCodec.Unpack(serializedPayload); |
| | | 43 | | } |
| | | 44 | | |
| | 1 | 45 | | if (CallbackDataMetadata.TryCreate(typeof(TPayload), out var metadata)) |
| | | 46 | | { |
| | 0 | 47 | | return (TPayload)CallbackDataCodec.Unpack(serializedPayload, metadata); |
| | | 48 | | } |
| | | 49 | | |
| | 1 | 50 | | return JsonSerializer.Deserialize<TPayload>(serializedPayload, _jsonSerializerOptions) |
| | 1 | 51 | | ?? throw new JsonException("Telegram callback data deserialized to null."); |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | bool ICallbackDataRouteDeserializer.TryDeserializeForRoute( |
| | | 55 | | Type payloadType, |
| | | 56 | | string serializedPayload, |
| | | 57 | | out object? payload) |
| | | 58 | | { |
| | 30 | 59 | | ArgumentNullException.ThrowIfNull(payloadType); |
| | 30 | 60 | | ArgumentNullException.ThrowIfNull(serializedPayload); |
| | | 61 | | |
| | 30 | 62 | | if (CallbackDataCodec.TryGetGenerated(payloadType, out var generatedCodec)) |
| | | 63 | | { |
| | 2 | 64 | | if (!generatedCodec.MatchesSerializedPayload(serializedPayload)) |
| | | 65 | | { |
| | 0 | 66 | | payload = null; |
| | 0 | 67 | | return false; |
| | | 68 | | } |
| | | 69 | | |
| | 2 | 70 | | payload = UnpackForMatchedRoute( |
| | 2 | 71 | | payloadType, |
| | 2 | 72 | | serializedPayload, |
| | 2 | 73 | | generatedCodec.Unpack); |
| | 1 | 74 | | return true; |
| | | 75 | | } |
| | | 76 | | |
| | 28 | 77 | | if (CallbackDataMetadata.TryCreate(payloadType, out var metadata)) |
| | | 78 | | { |
| | 10 | 79 | | if (!metadata.MatchesSerializedPayload(serializedPayload)) |
| | | 80 | | { |
| | 5 | 81 | | payload = null; |
| | 5 | 82 | | return false; |
| | | 83 | | } |
| | | 84 | | |
| | 5 | 85 | | payload = UnpackForMatchedRoute( |
| | 5 | 86 | | payloadType, |
| | 5 | 87 | | serializedPayload, |
| | 10 | 88 | | value => CallbackDataCodec.Unpack(value, metadata)); |
| | 4 | 89 | | return true; |
| | | 90 | | } |
| | | 91 | | |
| | 18 | 92 | | payload = DeserializeJson(payloadType, serializedPayload); |
| | 4 | 93 | | return true; |
| | | 94 | | } |
| | | 95 | | |
| | | 96 | | private object DeserializeJson(Type payloadType, string serializedPayload) |
| | | 97 | | { |
| | 18 | 98 | | return JsonSerializer.Deserialize(serializedPayload, payloadType, _jsonSerializerOptions) |
| | 18 | 99 | | ?? throw new JsonException("Telegram callback data deserialized to null."); |
| | | 100 | | } |
| | | 101 | | |
| | | 102 | | private static object UnpackForMatchedRoute( |
| | | 103 | | Type payloadType, |
| | | 104 | | string serializedPayload, |
| | | 105 | | Func<string, object> unpack) |
| | | 106 | | { |
| | | 107 | | try |
| | | 108 | | { |
| | 7 | 109 | | return unpack(serializedPayload); |
| | | 110 | | } |
| | 2 | 111 | | catch (Exception exception) when (CallbackDataCodec.IsPayloadDeserializationFailure(exception)) |
| | | 112 | | { |
| | 2 | 113 | | throw new CallbackDataRouteDeserializationException(payloadType, serializedPayload, exception); |
| | | 114 | | } |
| | 5 | 115 | | } |
| | | 116 | | } |