| | | 1 | | using System; |
| | | 2 | | using System.Linq.Expressions; |
| | | 3 | | using System.Reflection; |
| | | 4 | | using System.Text.Json; |
| | | 5 | | using System.Text.Json.Serialization; |
| | | 6 | | |
| | | 7 | | namespace Chronicler; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// Creates JSON converters for types that explicitly implement <see cref="IStateBacked{TState}"/>. |
| | | 11 | | /// </summary> |
| | | 12 | | public sealed class StateJsonConverterFactory : JsonConverterFactory |
| | | 13 | | { |
| | | 14 | | /// <inheritdoc /> |
| | | 15 | | public override bool CanConvert(Type typeToConvert) |
| | | 16 | | { |
| | 5 | 17 | | return typeToConvert != null |
| | 5 | 18 | | && typeToConvert.IsClass |
| | 5 | 19 | | && !typeToConvert.IsAbstract |
| | 5 | 20 | | && HasStateBackedContract(typeToConvert); |
| | | 21 | | } |
| | | 22 | | |
| | | 23 | | /// <inheritdoc /> |
| | | 24 | | public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) |
| | | 25 | | { |
| | 2 | 26 | | Type stateType = GetSingleStateType(typeToConvert); |
| | | 27 | | |
| | 2 | 28 | | ConstructorInfo constructor = typeToConvert.GetConstructor(new[] { stateType }) |
| | 2 | 29 | | ?? throw new InvalidOperationException( |
| | 2 | 30 | | $"Type '{typeToConvert}' must define a public constructor accepting '{stateType}'."); |
| | | 31 | | |
| | 1 | 32 | | Type converterType = typeof(StateJsonConverter<,>).MakeGenericType(typeToConvert, stateType); |
| | 1 | 33 | | Delegate factory = CreateFactory(typeToConvert, stateType, constructor); |
| | | 34 | | |
| | 1 | 35 | | return (JsonConverter)(Activator.CreateInstance(converterType, factory) |
| | 1 | 36 | | ?? throw new InvalidOperationException($"Failed to create converter instance for type '{converterType}'.")); |
| | | 37 | | } |
| | | 38 | | |
| | | 39 | | private static bool HasStateBackedContract(Type type) |
| | | 40 | | { |
| | 27 | 41 | | foreach (Type interfaceType in type.GetInterfaces()) |
| | | 42 | | { |
| | 10 | 43 | | if (IsStateBackedInterface(interfaceType)) |
| | 1 | 44 | | return true; |
| | | 45 | | } |
| | | 46 | | |
| | 3 | 47 | | return false; |
| | | 48 | | } |
| | | 49 | | |
| | | 50 | | private static Type GetSingleStateType(Type type) |
| | | 51 | | { |
| | 2 | 52 | | Type? stateType = null; |
| | | 53 | | |
| | 8 | 54 | | foreach (Type interfaceType in type.GetInterfaces()) |
| | | 55 | | { |
| | 2 | 56 | | if (!IsStateBackedInterface(interfaceType)) |
| | | 57 | | continue; |
| | | 58 | | |
| | 2 | 59 | | Type currentStateType = interfaceType.GetGenericArguments()[0]; |
| | 2 | 60 | | if (stateType != null) |
| | | 61 | | { |
| | 0 | 62 | | throw new InvalidOperationException( |
| | 0 | 63 | | $"Type '{type}' must implement only one IStateBacked<TState> contract."); |
| | | 64 | | } |
| | | 65 | | |
| | 2 | 66 | | stateType = currentStateType; |
| | | 67 | | } |
| | | 68 | | |
| | 2 | 69 | | return stateType |
| | 2 | 70 | | ?? throw new InvalidOperationException( |
| | 2 | 71 | | $"Type '{type}' must implement IStateBacked<TState>."); |
| | | 72 | | } |
| | | 73 | | |
| | | 74 | | private static bool IsStateBackedInterface(Type type) |
| | | 75 | | { |
| | 12 | 76 | | return type.IsGenericType |
| | 12 | 77 | | && type.GetGenericTypeDefinition() == typeof(IStateBacked<>); |
| | | 78 | | } |
| | | 79 | | |
| | | 80 | | private static Delegate CreateFactory(Type recordType, Type stateType, ConstructorInfo constructor) |
| | | 81 | | { |
| | 1 | 82 | | ParameterExpression stateParameter = Expression.Parameter(stateType, "state"); |
| | 1 | 83 | | NewExpression createRecord = Expression.New(constructor, stateParameter); |
| | 1 | 84 | | Type factoryType = typeof(Func<,>).MakeGenericType(stateType, recordType); |
| | | 85 | | |
| | 1 | 86 | | return Expression.Lambda(factoryType, createRecord, stateParameter).Compile(); |
| | | 87 | | } |
| | | 88 | | } |