< Summary

Information
Class: Chronicler.StateJsonConverterFactory
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/Json/StateJsonConverterFactory.cs
Line coverage
100%
Covered lines: 33
Uncovered lines: 0
Coverable lines: 33
Total lines: 88
Line coverage: 100%
Branch coverage
95%
Covered branches: 23
Total branches: 24
Branch coverage: 95.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CanConvert(...)100%66100%
CreateConverter(...)75%44100%
HasStateBackedContract(...)100%44100%
GetSingleStateType(...)100%88100%
IsStateBackedInterface(...)100%22100%
CreateFactory(...)100%11100%

File(s)

/home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/Json/StateJsonConverterFactory.cs

#LineLine coverage
 1using System;
 2using System.Linq.Expressions;
 3using System.Reflection;
 4using System.Text.Json;
 5using System.Text.Json.Serialization;
 6
 7namespace Chronicler;
 8
 9/// <summary>
 10/// Creates JSON converters for types that explicitly implement <see cref="IStateBacked{TState}"/>.
 11/// </summary>
 12public sealed class StateJsonConverterFactory : JsonConverterFactory
 13{
 14    /// <inheritdoc />
 15    public override bool CanConvert(Type typeToConvert)
 16    {
 1917        return typeToConvert != null
 1918            && typeToConvert.IsClass
 1919            && !typeToConvert.IsAbstract
 1920            && HasStateBackedContract(typeToConvert);
 21    }
 22
 23    /// <inheritdoc />
 24    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
 25    {
 926        Type stateType = GetSingleStateType(typeToConvert);
 27
 728        ConstructorInfo constructor = typeToConvert.GetConstructor(new[] { stateType })
 729            ?? throw new InvalidOperationException(
 730                $"Type '{typeToConvert}' must define a public constructor accepting '{stateType}'.");
 31
 632        Type converterType = typeof(StateJsonConverter<,>).MakeGenericType(typeToConvert, stateType);
 633        Delegate factory = CreateFactory(typeToConvert, stateType, constructor);
 34
 635        return (JsonConverter)(Activator.CreateInstance(converterType, factory)
 636            ?? throw new InvalidOperationException($"Failed to create converter instance for type '{converterType}'."));
 37    }
 38
 39    private static bool HasStateBackedContract(Type type)
 40    {
 9041        foreach (Type interfaceType in type.GetInterfaces())
 42        {
 3443            if (IsStateBackedInterface(interfaceType))
 644                return true;
 45        }
 46
 847        return false;
 48    }
 49
 50    private static Type GetSingleStateType(Type type)
 51    {
 952        Type? stateType = null;
 53
 3554        foreach (Type interfaceType in type.GetInterfaces())
 55        {
 956            if (!IsStateBackedInterface(interfaceType))
 57                continue;
 58
 959            Type currentStateType = interfaceType.GetGenericArguments()[0];
 960            if (stateType != null)
 61            {
 162                throw new InvalidOperationException(
 163                    $"Type '{type}' must implement only one IStateBacked<TState> contract.");
 64            }
 65
 866            stateType = currentStateType;
 67        }
 68
 869        return stateType
 870            ?? throw new InvalidOperationException(
 871                $"Type '{type}' must implement IStateBacked<TState>.");
 72    }
 73
 74    private static bool IsStateBackedInterface(Type type)
 75    {
 4376        return type.IsGenericType
 4377            && type.GetGenericTypeDefinition() == typeof(IStateBacked<>);
 78    }
 79
 80    private static Delegate CreateFactory(Type recordType, Type stateType, ConstructorInfo constructor)
 81    {
 682        ParameterExpression stateParameter = Expression.Parameter(stateType, "state");
 683        NewExpression createRecord = Expression.New(constructor, stateParameter);
 684        Type factoryType = typeof(Func<,>).MakeGenericType(stateType, recordType);
 85
 686        return Expression.Lambda(factoryType, createRecord, stateParameter).Compile();
 87    }
 88}