~ruther/NosSmooth

7f45f96e9a0ff9e0c0110b6184eb41aee91667af — František Boháček 3 years ago 9ac41c2
refactor: split string converters
41 files changed, 700 insertions(+), 507 deletions(-)

M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/BasicInlineConverterGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/EnumInlineConverterGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/FallbackInlineConverterGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/ListInlineConverterGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs
R Core/NosSmooth.Packets/Converters/{BaseTypeConverter.cs => BaseStringConverter.cs}
M Core/NosSmooth.Packets/Converters/Basic/BasicTypeConverter.cs
R Core/NosSmooth.Packets/Converters/Basic/{BoolTypeConverter.cs => BoolStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{ByteTypeConverter.cs => ByteStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{CharTypeConverter.cs => CharStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{IntTypeConverter.cs => IntStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{LongTypeConverter.cs => LongStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{ShortTypeConverter.cs => ShortStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{UIntTypeConverter.cs => UIntStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{ULongTypeConverter.cs => ULongStringConverter.cs}
R Core/NosSmooth.Packets/Converters/Basic/{UShortTypeConverter.cs => UShortStringConverter.cs}
M Core/NosSmooth.Packets/Converters/Common/NameStringConverter.cs
R Core/NosSmooth.Packets/Converters/{ITypeConverter.cs => IStringConverter.cs}
A Core/NosSmooth.Packets/Converters/IStringConverterRepository.cs
R Core/NosSmooth.Packets/Converters/{ITypeConverterRepository.cs => IStringSerializer.cs}
M Core/NosSmooth.Packets/Converters/Packets/UpgradeRareSubPacketConverter.cs
A Core/NosSmooth.Packets/Converters/Special/Converters/EnumStringConverter.cs
R Core/NosSmooth.Packets/Converters/Special/{ListTypeConverter.cs => Converters/ListStringConverter.cs}
A Core/NosSmooth.Packets/Converters/Special/Converters/NullableStringConverter.cs
R Core/NosSmooth.Packets/Converters/Special/{EnumTypeConverter.cs => EnumStringConverterFactory.cs}
R Core/NosSmooth.Packets/Converters/Special/{ISpecialTypeConverter.cs => IStringConverterFactory.cs}
A Core/NosSmooth.Packets/Converters/Special/ListStringConverterFactory.cs
A Core/NosSmooth.Packets/Converters/Special/NullableStringConverterFactory.cs
D Core/NosSmooth.Packets/Converters/Special/NullableTypeConverter.cs
A Core/NosSmooth.Packets/Converters/Special/StringSerializer.cs
A Core/NosSmooth.Packets/Converters/StringConverterRepository.cs
D Core/NosSmooth.Packets/Converters/TypeConverterRepository.cs
M Core/NosSmooth.Packets/Errors/CouldNotConvertError.cs
M Core/NosSmooth.Packets/Errors/PacketEndNotExpectedError.cs
M Core/NosSmooth.Packets/Errors/PacketParameterSerializerError.cs
M Core/NosSmooth.Packets/Errors/WrongTypeError.cs
A Core/NosSmooth.Packets/Extensions/ConcurrentDictionaryExtensions.cs
A Core/NosSmooth.Packets/Extensions/ResultExtensions.cs
M Core/NosSmooth.Packets/Extensions/ServiceCollectionExtensions.cs
M Core/NosSmooth.Packets/Packets/PacketInfo.cs
M Core/NosSmooth.Packets/Packets/PacketTypesRepository.cs
M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/BasicInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/BasicInlineConverterGenerator.cs +1 -1
@@ 67,7 67,7 @@ public class BasicInlineConverterGenerator : IInlineConverterGenerator
        foreach (var type in HandleTypes)
        {
            textWriter.WriteMultiline($@"
public static Result<{type}?> ParseBasic{type}(ITypeConverter typeConverter, PacketStringEnumerator stringEnumerator)
public static Result<{type}?> ParseBasic{type}(IStringConverter typeConverter, PacketStringEnumerator stringEnumerator)
{{
    var tokenResult = stringEnumerator.GetNextToken();
    if (!tokenResult.IsSuccess)

M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/EnumInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/EnumInlineConverterGenerator.cs +1 -1
@@ 80,7 80,7 @@ public class EnumInlineConverterGenerator : IInlineConverterGenerator
            textWriter.WriteMultiline
            (
                $@"
public static Result<{type}?> ParseEnum{type.ToString().Replace('.', '_')}(ITypeConverter typeConverter, PacketStringEnumerator stringEnumerator)
public static Result<{type}?> ParseEnum{type.ToString().Replace('.', '_')}(IStringConverter typeConverter, PacketStringEnumerator stringEnumerator)
{{
    var tokenResult = stringEnumerator.GetNextToken();
    if (!tokenResult.IsSuccess)

M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/FallbackInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/FallbackInlineConverterGenerator.cs +2 -2
@@ 32,7 32,7 @@ public class FallbackInlineConverterGenerator : IInlineConverterGenerator
        var resultName = $"{variableName.Replace(".", string.Empty)}Result";
        textWriter.WriteLine
        (
            $"var {resultName} = _typeConverterRepository.Serialize<{(typeSyntax?.ToString() ?? typeSymbol!.ToString()).TrimEnd('?')}?>({variableName}, builder);"
            $"var {resultName} = _stringSerializer.Serialize<{(typeSyntax?.ToString() ?? typeSymbol!.ToString()).TrimEnd('?')}?>({variableName}, builder);"
        );
        textWriter.WriteLine($"if (!{resultName}.IsSuccess)");
        textWriter.WriteLine("{");


@@ 48,7 48,7 @@ public class FallbackInlineConverterGenerator : IInlineConverterGenerator
    {
        textWriter.WriteLine
        (
            $"_typeConverterRepository.Deserialize<{(typeSyntax?.ToString() ?? typeSymbol!.ToString()).TrimEnd('?')}?>(stringEnumerator);"
            $"_stringSerializer.Deserialize<{(typeSyntax?.ToString() ?? typeSymbol!.ToString()).TrimEnd('?')}?>(stringEnumerator);"
        );
        return null;
    }

M Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/ListInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/InlineConverterGenerators/ListInlineConverterGenerator.cs +2 -2
@@ 89,7 89,7 @@ public class ListInlineConverterGenerator : IInlineConverterGenerator
        }

        textWriter.WriteLine
            ($"{Constants.HelperClass}.{GetMethodName(genericArgument)}(typeConverter, _typeConverterRepository, stringEnumerator);");
            ($"{Constants.HelperClass}.{GetMethodName(genericArgument)}(typeConverter, _stringSerializer, stringEnumerator);");
        return null;
    }



@@ 107,7 107,7 @@ public class ListInlineConverterGenerator : IInlineConverterGenerator
            textWriter.WriteLine
            (
                @$"
public static Result<IReadOnlyList<{type.GetActualType()}>> {GetMethodName(type)}(ITypeConverter typeConverter, ITypeConverterRepository _typeConverterRepository, PacketStringEnumerator stringEnumerator)
public static Result<IReadOnlyList<{type.GetActualType()}>> {GetMethodName(type)}(IStringConverter typeConverter, IStringSerializer _stringSerializer, PacketStringEnumerator stringEnumerator)
{{
    var data = new List<{type.GetActualType()}>();


M Core/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs +4 -4
@@ 58,18 58,18 @@ using Remora.Results;

namespace {_packetInfo.Namespace}.Generated;

public class {_packetInfo.Name}Converter : BaseTypeConverter<{_packetInfo.Name}>
public class {_packetInfo.Name}Converter : BaseStringConverter<{_packetInfo.Name}>
{{"
        );
        textWriter.Indent++;
        textWriter.WriteLine
        (
            $@"
private readonly ITypeConverterRepository _typeConverterRepository;
private readonly IStringSerializer _stringSerializer;

public {_packetInfo.Name}Converter(ITypeConverterRepository typeConverterRepository)
public {_packetInfo.Name}Converter(IStringSerializer stringSerializer)
{{
    _typeConverterRepository = typeConverterRepository;
    _stringSerializer = stringSerializer;
}}

/// <inheritdoc />

R Core/NosSmooth.Packets/Converters/BaseTypeConverter.cs => Core/NosSmooth.Packets/Converters/BaseStringConverter.cs +4 -4
@@ 1,5 1,5 @@
//
//  BaseTypeConverter.cs
//  BaseStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 13,7 13,7 @@ namespace NosSmooth.Packets.Converters;
/// Base type for converting objects that maps object converting methods to the actual type.
/// </summary>
/// <typeparam name="TParseType">The type of the object that this converts.</typeparam>
public abstract class BaseTypeConverter<TParseType> : ITypeConverter<TParseType>
public abstract class BaseStringConverter<TParseType> : IStringConverter<TParseType>
{
    /// <inheritdoc />
    public abstract Result Serialize(TParseType? obj, PacketStringBuilder builder);


@@ 22,7 22,7 @@ public abstract class BaseTypeConverter<TParseType> : ITypeConverter<TParseType>
    public abstract Result<TParseType?> Deserialize(PacketStringEnumerator stringEnumerator);

    /// <inheritdoc/>
    Result<object?> ITypeConverter.Deserialize(PacketStringEnumerator stringEnumerator)
    Result<object?> IStringConverter.Deserialize(PacketStringEnumerator stringEnumerator)
    {
        var result = Deserialize(stringEnumerator);
        if (!result.IsSuccess)


@@ 34,7 34,7 @@ public abstract class BaseTypeConverter<TParseType> : ITypeConverter<TParseType>
    }

    /// <inheritdoc/>
    Result ITypeConverter.Serialize(object? obj, PacketStringBuilder builder)
    Result IStringConverter.Serialize(object? obj, PacketStringBuilder builder)
    {
        if (!(obj is TParseType parseType))
        {

M Core/NosSmooth.Packets/Converters/Basic/BasicTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/BasicTypeConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// Basic type converter for converting using <see cref="Convert"/>.
/// </summary>
/// <typeparam name="TBasicType">The basic type, that contains correct to string.</typeparam>
public abstract class BasicTypeConverter<TBasicType> : BaseTypeConverter<TBasicType>
public abstract class BasicTypeConverter<TBasicType> : BaseStringConverter<TBasicType>
{
    /// <inheritdoc />
    public override Result Serialize(TBasicType? obj, PacketStringBuilder builder)

R Core/NosSmooth.Packets/Converters/Basic/BoolTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/BoolStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  BoolTypeConverter.cs
//  BoolStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="bool"/>.
/// </summary>
public class BoolTypeConverter : BaseTypeConverter<bool>
public class BoolStringConverter : BaseStringConverter<bool>
{
    /// <inheritdoc />
    public override Result Serialize(bool obj, PacketStringBuilder builder)

R Core/NosSmooth.Packets/Converters/Basic/ByteTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/ByteStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  ByteTypeConverter.cs
//  ByteStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="byte"/>.
/// </summary>
public class ByteTypeConverter : BasicTypeConverter<byte>
public class ByteStringConverter : BasicTypeConverter<byte>
{
    /// <inheritdoc />
    protected override Result<byte> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/CharTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/CharStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  CharTypeConverter.cs
//  CharStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="char"/>.
/// </summary>
public class CharTypeConverter : BasicTypeConverter<char>
public class CharStringConverter : BasicTypeConverter<char>
{
    /// <inheritdoc />
    protected override Result<char> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/IntTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/IntStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  IntTypeConverter.cs
//  IntStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="int"/>.
/// </summary>
public class IntTypeConverter : BasicTypeConverter<int>
public class IntStringConverter : BasicTypeConverter<int>
{
    /// <inheritdoc />
    protected override Result<int> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/LongTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/LongStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  LongTypeConverter.cs
//  LongStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="long"/>.
/// </summary>
public class LongTypeConverter : BasicTypeConverter<long>
public class LongStringConverter : BasicTypeConverter<long>
{
    /// <inheritdoc />
    protected override Result<long> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/ShortTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/ShortStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  ShortTypeConverter.cs
//  ShortStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="short"/>.
/// </summary>
public class ShortTypeConverter : BasicTypeConverter<short>
public class ShortStringConverter : BasicTypeConverter<short>
{
    /// <inheritdoc />
    protected override Result<short> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/UIntTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/UIntStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  UIntTypeConverter.cs
//  UIntStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="uint"/>.
/// </summary>
public class UIntTypeConverter : BasicTypeConverter<uint>
public class UIntStringConverter : BasicTypeConverter<uint>
{
    /// <inheritdoc />
    protected override Result<uint> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/ULongTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/ULongStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  ULongTypeConverter.cs
//  ULongStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="ulong"/>.
/// </summary>
public class ULongTypeConverter : BasicTypeConverter<ulong>
public class ULongStringConverter : BasicTypeConverter<ulong>
{
    /// <inheritdoc />
    protected override Result<ulong> Deserialize(string value)

R Core/NosSmooth.Packets/Converters/Basic/UShortTypeConverter.cs => Core/NosSmooth.Packets/Converters/Basic/UShortStringConverter.cs +2 -2
@@ 1,5 1,5 @@
//
//  UShortTypeConverter.cs
//  UShortStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 12,7 12,7 @@ namespace NosSmooth.Packets.Converters.Basic;
/// <summary>
/// Converter of <see cref="ushort"/>.
/// </summary>
public class UShortTypeConverter : BasicTypeConverter<ushort>
public class UShortStringConverter : BasicTypeConverter<ushort>
{
    /// <inheritdoc />
    protected override Result<ushort> Deserialize(string value)

M Core/NosSmooth.Packets/Converters/Common/NameStringConverter.cs => Core/NosSmooth.Packets/Converters/Common/NameStringConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.Packets.Converters.Common;
/// <summary>
/// Converter of <see cref="NameString"/>.
/// </summary>
public class NameStringConverter : BaseTypeConverter<NameString>
public class NameStringConverter : BaseStringConverter<NameString>
{
    /// <inheritdoc />
    public override Result Serialize(NameString? obj, PacketStringBuilder builder)

R Core/NosSmooth.Packets/Converters/ITypeConverter.cs => Core/NosSmooth.Packets/Converters/IStringConverter.cs +3 -3
@@ 1,5 1,5 @@
//
//  ITypeConverter.cs
//  IStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 11,7 11,7 @@ namespace NosSmooth.Packets.Converters;
/// <summary>
/// Base type for converting types.
/// </summary>
public interface ITypeConverter
public interface IStringConverter
{
    /// <summary>
    /// Convert the data from the enumerator to the given type.


@@ 36,7 36,7 @@ public interface ITypeConverter
/// Used for converting packets.
/// </remarks>
/// <typeparam name="TParseType">The type that can be parsed.</typeparam>
public interface ITypeConverter<TParseType> : ITypeConverter
public interface IStringConverter<TParseType> : IStringConverter
{
    /// <summary>
    /// Convert the data from the enumerator to the given type.

A Core/NosSmooth.Packets/Converters/IStringConverterRepository.cs => Core/NosSmooth.Packets/Converters/IStringConverterRepository.cs +32 -0
@@ 0,0 1,32 @@
//
//  IStringConverterRepository.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using NosSmooth.Packets.Converters.Special;
using NosSmooth.Packets.Errors;
using Remora.Results;

namespace NosSmooth.Packets.Converters;

/// <summary>
/// Repository for <see cref="IStringConverter"/>.
/// </summary>
public interface IStringConverterRepository
{
    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <param name="type">The type to find converter for.</param>
    /// <returns>The type converter or an error.</returns>
    public Result<IStringConverter> GetTypeConverter(Type type);

    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <typeparam name="TParseType">The type to find converter for.</typeparam>
    /// <returns>The type converter or an error.</returns>
    public Result<IStringConverter<TParseType>> GetTypeConverter<TParseType>();
}
\ No newline at end of file

R Core/NosSmooth.Packets/Converters/ITypeConverterRepository.cs => Core/NosSmooth.Packets/Converters/IStringSerializer.cs +3 -19
@@ 1,36 1,20 @@
//
//  ITypeConverterRepository.cs
//  IStringSerializer.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using NosSmooth.Packets.Converters.Special;
using NosSmooth.Packets.Errors;
using Remora.Results;

namespace NosSmooth.Packets.Converters;

/// <summary>
/// Repository for <see cref="ITypeConverter"/>.
/// Serializer of values from NosTale packet strings.
/// </summary>
public interface ITypeConverterRepository
public interface IStringSerializer
{
    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <param name="type">The type to find converter for.</param>
    /// <returns>The type converter or an error.</returns>
    public Result<ITypeConverter> GetTypeConverter(Type type);

    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <typeparam name="TParseType">The type to find converter for.</typeparam>
    /// <returns>The type converter or an error.</returns>
    public Result<ITypeConverter<TParseType>> GetTypeConverter<TParseType>();

    /// <summary>
    /// Convert the data from the enumerator to the given type.
    /// </summary>
    /// <param name="parseType">The type of the object to serialize.</param>

M Core/NosSmooth.Packets/Converters/Packets/UpgradeRareSubPacketConverter.cs => Core/NosSmooth.Packets/Converters/Packets/UpgradeRareSubPacketConverter.cs +1 -1
@@ 13,7 13,7 @@ namespace NosSmooth.Packets.Converters.Packets;
/// <summary>
/// Converter for <see cref="UpgradeRareSubPacket"/>.
/// </summary>
public class UpgradeRareSubPacketConverter : BaseTypeConverter<UpgradeRareSubPacket>
public class UpgradeRareSubPacketConverter : BaseStringConverter<UpgradeRareSubPacket>
{
    /// <inheritdoc />
    public override Result Serialize(UpgradeRareSubPacket? obj, PacketStringBuilder builder)

A Core/NosSmooth.Packets/Converters/Special/Converters/EnumStringConverter.cs => Core/NosSmooth.Packets/Converters/Special/Converters/EnumStringConverter.cs +49 -0
@@ 0,0 1,49 @@
//
//  EnumStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special.Converters;

/// <summary>
/// Converts enum with the given underlying type.
/// </summary>
/// <typeparam name="TEnum">The enum.</typeparam>
/// <typeparam name="TUnderlyingType">The enum's underlying type.</typeparam>
public class EnumStringConverter<TEnum, TUnderlyingType> : BaseStringConverter<TEnum>
    where TEnum : TUnderlyingType
{
    private readonly IStringSerializer _serializer;

    /// <summary>
    /// Initializes a new instance of the <see cref="EnumStringConverter{TEnum, TUnderlyingType}"/> class.
    /// </summary>
    /// <param name="serializer">The string serializer.</param>
    public EnumStringConverter(IStringSerializer serializer)
    {
        _serializer = serializer;
    }

    /// <inheritdoc />
    public override Result Serialize(TEnum? obj, PacketStringBuilder builder)
    {
        builder.Append(((TUnderlyingType?)obj)?.ToString() ?? "-");
        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public override Result<TEnum?> Deserialize(PacketStringEnumerator stringEnumerator)
    {
        var result = _serializer.Deserialize<TUnderlyingType>(stringEnumerator);
        if (!result.IsSuccess)
        {
            return Result<TEnum?>.FromError(result);
        }

        return (TEnum?)result.Entity;
    }
}
\ No newline at end of file

R Core/NosSmooth.Packets/Converters/Special/ListTypeConverter.cs => Core/NosSmooth.Packets/Converters/Special/Converters/ListStringConverter.cs +42 -91
@@ 1,81 1,37 @@
//
//  ListTypeConverter.cs
//  ListStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using NosSmooth.Packets.Errors;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special;
namespace NosSmooth.Packets.Converters.Special.Converters;

/// <summary>
/// Converts lists.
/// Converter for list types.
/// </summary>
public class ListTypeConverter : ISpecialTypeConverter
/// <typeparam name="TGeneric">The generic type argument of the list.</typeparam>
public class ListStringConverter<TGeneric> : BaseStringConverter<IReadOnlyList<TGeneric>>
{
    private readonly ITypeConverterRepository _typeConverterRepository;
    private readonly ConcurrentDictionary<Type, Func<IEnumerable<object?>, object>> _fillFunctions;
    private readonly IStringSerializer _serializer;

    /// <summary>
    /// Initializes a new instance of the <see cref="ListTypeConverter"/> class.
    /// Initializes a new instance of the <see cref="ListStringConverter{TGeneric}"/> class.
    /// </summary>
    /// <param name="typeConverterRepository">The type converter repository.</param>
    public ListTypeConverter(ITypeConverterRepository typeConverterRepository)
    /// <param name="serializer">The string serializer.</param>
    public ListStringConverter(IStringSerializer serializer)
    {
        _typeConverterRepository = typeConverterRepository;
        _fillFunctions = new ConcurrentDictionary<Type, Func<IEnumerable<object?>, object>>();
    }

    /// <inheritdoc />
    public bool ShouldHandle(Type type)
        => type.IsGenericType && typeof(IEnumerable).IsAssignableFrom(type);

    /// <inheritdoc />
    public Result<object?> Deserialize(Type type, PacketStringEnumerator stringEnumerator)
    {
        var data = new List<object?>();
        var genericType = type.GetElementType() ?? type.GetGenericArguments()[0];

        while (!(stringEnumerator.IsOnLastToken() ?? false))
        {
            if (!stringEnumerator.PushPreparedLevel())
            {
                return new ArgumentInvalidError(nameof(stringEnumerator), "The string enumerator has to have a prepared level for all lists.");
            }

            var result = _typeConverterRepository.Deserialize(genericType, stringEnumerator);

            // If we know that we are not on the last token in the item level, just skip to the end of the item.
            // Note that if this is the case, then that means the converter is either corrupted
            // or the packet has more fields.
            while (stringEnumerator.IsOnLastToken() == false)
            {
                stringEnumerator.GetNextToken();
            }

            stringEnumerator.PopLevel();
            if (!result.IsSuccess)
            {
                return Result<object?>.FromError(new ListSerializerError(result, data.Count), result);
            }

            data.Add(result.Entity);
        }
        _serializer = serializer;

        return _fillFunctions.GetOrAdd(genericType, GetAndFillListMethod)(data);
    }

    /// <inheritdoc />
    public Result Serialize(Type type, object? obj, PacketStringBuilder builder)
    public override Result Serialize(IReadOnlyList<TGeneric>? obj, PacketStringBuilder builder)
    {
        if (obj is null)
        {


@@ 83,17 39,14 @@ public class ListTypeConverter : ISpecialTypeConverter
            return Result.FromSuccess();
        }

        var items = (IEnumerable)obj;
        var genericType = type.GetElementType() ?? type.GetGenericArguments()[0];

        foreach (var item in items)
        foreach (var item in obj)
        {
            if (!builder.PushPreparedLevel())
            {
                return new ArgumentInvalidError(nameof(builder), "The string builder has to have a prepared level for all lists.");
            }

            var serializeResult = _typeConverterRepository.Serialize(genericType, item, builder);
            var serializeResult = _serializer.Serialize(item, builder);
            builder.PopLevel();
            if (!serializeResult.IsSuccess)
            {


@@ 104,44 57,42 @@ public class ListTypeConverter : ISpecialTypeConverter
        return Result.FromSuccess();
    }

    // TODO: cache the functions?

    /// <summary>
    /// From https://stackoverflow.com/questions/35913495/moving-from-reflection-to-expression-tree.
    /// </summary>
    /// <param name="genericType">The generic type.</param>
    /// <returns>The function.</returns>
    private Func<IEnumerable<object?>, object> GetAndFillListMethod(Type genericType)
    /// <inheritdoc />
    public override Result<IReadOnlyList<TGeneric>?> Deserialize(PacketStringEnumerator stringEnumerator)
    {
        var listType = typeof(List<>);
        var listGenericType = listType.MakeGenericType(genericType);

        var values = Expression.Parameter(typeof(IEnumerable<object?>), "values");
        var list = new List<TGeneric>();

        var ctor = listGenericType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);

        // I prefer using Expression.Variable to Expression.Parameter
        // for internal variables
        var instance = Expression.Variable(listGenericType, "list");
        while (!(stringEnumerator.IsOnLastToken() ?? false))
        {
            if (!stringEnumerator.PushPreparedLevel())
            {
                return new ArgumentInvalidError(nameof(stringEnumerator), "The string enumerator has to have a prepared level for all lists.");
            }

        var assign = Expression.Assign(instance, Expression.New(ctor!));
            var result = _serializer.Deserialize<TGeneric>(stringEnumerator);

        var addMethod = listGenericType.GetMethod("AddRange", new[] { typeof(IEnumerable<>).MakeGenericType(genericType) });
            // If we know that we are not on the last token in the item level, just skip to the end of the item.
            // Note that if this is the case, then that means the converter is either corrupted
            // or the packet has more fields.
            while (stringEnumerator.IsOnLastToken() == false)
            {
                stringEnumerator.GetNextToken();
            }

        // Enumerable.Cast<T>
        var castMethod = typeof(Enumerable).GetMethod("Cast", new[] { typeof(IEnumerable) })!.MakeGenericMethod(genericType);
            stringEnumerator.PopLevel();
            if (!result.IsSuccess)
            {
                return Result<IReadOnlyList<TGeneric>?>.FromError(new ListSerializerError(result, list.Count), result);
            }

        // For the parameters there is a params Expression[], so no explicit array necessary
        var castCall = Expression.Call(castMethod, values);
        var addCall = Expression.Call(instance, addMethod!, castCall);
            if (result.Entity is null)
            {
                return new DeserializedValueNullError(typeof(IReadOnlyList<TGeneric>));
            }

        var block = Expression.Block(
            new[] { instance },
            assign,
            addCall,
            Expression.Convert(instance, typeof(object))
        );
            list.Add(result.Entity);
        }

        return (Func<IEnumerable<object?>, object>)Expression.Lambda(block, values).Compile();
        return list;
    }
}
\ No newline at end of file

A Core/NosSmooth.Packets/Converters/Special/Converters/NullableStringConverter.cs => Core/NosSmooth.Packets/Converters/Special/Converters/NullableStringConverter.cs +56 -0
@@ 0,0 1,56 @@
//
//  NullableStringConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special.Converters;

#pragma warning disable SA1125
/// <summary>
/// Converter of nullable types.
/// </summary>
/// <typeparam name="T">The nonnullable underlying type.</typeparam>
public class NullableStringConverter<T> : BaseStringConverter<Nullable<T>>
    where T : struct
{
    private readonly IStringSerializer _stringSerializer;

    /// <summary>
    /// Initializes a new instance of the <see cref="NullableStringConverter{T}"/> class.
    /// </summary>
    /// <param name="stringSerializer">The string serializer.</param>
    public NullableStringConverter(IStringSerializer stringSerializer)
    {
        _stringSerializer = stringSerializer;

    }

    /// <inheritdoc />
    public override Result Serialize(T? obj, PacketStringBuilder builder)
    {
        if (obj is null)
        {
            builder.Append('-');
            return Result.FromSuccess();
        }

        return _stringSerializer.Serialize<T>(obj.Value, builder);
    }

    /// <inheritdoc />
    public override Result<T?> Deserialize(PacketStringEnumerator stringEnumerator)
    {
        var result = _stringSerializer.Deserialize<T>(stringEnumerator);
        if (!result.IsSuccess)
        {
            return Result<T?>.FromError(result);
        }

        return Result<T?>.FromSuccess(result.Entity);
    }
}
#pragma warning restore SA1125

R Core/NosSmooth.Packets/Converters/Special/EnumTypeConverter.cs => Core/NosSmooth.Packets/Converters/Special/EnumStringConverterFactory.cs +30 -20
@@ 1,45 1,55 @@
//
//  EnumTypeConverter.cs
//  EnumStringConverterFactory.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets.Converters.Special.Converters;
using NosSmooth.Packets.Extensions;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special;

/// <summary>
/// Converts all enums.
/// Factory for all enum converters.
/// </summary>
public class EnumTypeConverter : ISpecialTypeConverter
public class EnumStringConverterFactory : IStringConverterFactory
{
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// Initializes a new instance of the <see cref="EnumStringConverterFactory"/> class.
    /// </summary>
    /// <param name="serviceProvider">The service provider.</param>
    public EnumStringConverterFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    /// <inheritdoc />
    public bool ShouldHandle(Type type)
        => type.IsEnum;

    /// <inheritdoc />
    public Result<object?> Deserialize(Type type, PacketStringEnumerator stringEnumerator)
    public Result<IStringConverter> CreateTypeSerializer(Type type)
    {
        var tokenResult = stringEnumerator.GetNextToken();
        if (!tokenResult.IsSuccess)
        var underlyingType = type.GetEnumUnderlyingType();
        var serializerType = typeof(EnumStringConverter<,>).MakeGenericType(type, underlyingType);

        try
        {
            return Result.FromError(tokenResult);
            return Result<IStringConverter>.FromSuccess
                ((IStringConverter)ActivatorUtilities.CreateInstance(_serviceProvider, serializerType));
        }

        return Enum.Parse(type, tokenResult.Entity.Token);
    }

    /// <inheritdoc />
    public Result Serialize(Type type, object? obj, PacketStringBuilder builder)
    {
        if (obj is null)
        catch (Exception e)
        {
            builder.Append('-');
            return Result.FromSuccess();
            return e;
        }

        builder.Append(Convert.ToInt64(obj));
        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result<IStringConverter<T>> CreateTypeSerializer<T>()
        => CreateTypeSerializer(typeof(T)).Cast<IStringConverter<T>, IStringConverter>();
}
\ No newline at end of file

R Core/NosSmooth.Packets/Converters/Special/ISpecialTypeConverter.cs => Core/NosSmooth.Packets/Converters/Special/IStringConverterFactory.cs +10 -13
@@ 1,5 1,5 @@
//
//  ISpecialTypeConverter.cs
//  IStringConverterFactory.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.


@@ 13,7 13,7 @@ namespace NosSmooth.Packets.Converters.Special;
/// <summary>
/// Converts special types such as enums or lists.
/// </summary>
public interface ISpecialTypeConverter
public interface IStringConverterFactory
{
    /// <summary>
    /// Whether this type converter should handle the given type.


@@ 23,19 23,16 @@ public interface ISpecialTypeConverter
    public bool ShouldHandle(Type type);

    /// <summary>
    /// Deserialize the given string to the object.
    /// Create converter for the given type.
    /// </summary>
    /// <param name="type">The type to deserialize.</param>
    /// <param name="stringEnumerator">The packets string enumerator.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result<object?> Deserialize(Type type, PacketStringEnumerator stringEnumerator);
    /// <param name="type">The type to create converter for.</param>
    /// <returns>The type converter or an error.</returns>
    public Result<IStringConverter> CreateTypeSerializer(Type type);

    /// <summary>
    /// Deserialize the given object into string.
    /// Create generic converter for the given type.
    /// </summary>
    /// <param name="type">The type to serialize.</param>
    /// <param name="obj">The object to serialize.</param>
    /// <param name="builder">The packet string builder.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize(Type type, object? obj, PacketStringBuilder builder);
    /// <typeparam name="T">The type to create converter for.</typeparam>
    /// <returns>The type converter or an error.</returns>
    public Result<IStringConverter<T>> CreateTypeSerializer<T>();
}
\ No newline at end of file

A Core/NosSmooth.Packets/Converters/Special/ListStringConverterFactory.cs => Core/NosSmooth.Packets/Converters/Special/ListStringConverterFactory.cs +62 -0
@@ 0,0 1,62 @@
//
//  ListStringConverterFactory.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets.Converters.Special.Converters;
using NosSmooth.Packets.Errors;
using NosSmooth.Packets.Extensions;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special;

/// <summary>
/// Converts lists.
/// </summary>
public class ListStringConverterFactory : IStringConverterFactory
{
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// Initializes a new instance of the <see cref="ListStringConverterFactory"/> class.
    /// </summary>
    /// <param name="serviceProvider">The service provider.</param>
    public ListStringConverterFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    /// <inheritdoc />
    public bool ShouldHandle(Type type)
        => type.IsGenericType && typeof(IEnumerable).IsAssignableFrom(type);

    /// <inheritdoc />
    public Result<IStringConverter> CreateTypeSerializer(Type type)
    {
        var elementType = type.GetElementType() ?? type.GetGenericArguments()[0];
        var converterType = typeof(ListStringConverter<>).MakeGenericType(elementType);
        try
        {
            return Result<IStringConverter>
                .FromSuccess((IStringConverter)ActivatorUtilities.CreateInstance(_serviceProvider, converterType));
        }
        catch (Exception e)
        {
            return e;
        }
    }

    /// <inheritdoc />
    public Result<IStringConverter<T>> CreateTypeSerializer<T>()
        => CreateTypeSerializer(typeof(T)).Cast<IStringConverter<T>, IStringConverter>();
}
\ No newline at end of file

A Core/NosSmooth.Packets/Converters/Special/NullableStringConverterFactory.cs => Core/NosSmooth.Packets/Converters/Special/NullableStringConverterFactory.cs +61 -0
@@ 0,0 1,61 @@
//
//  NullableStringConverterFactory.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets.Converters.Special.Converters;
using NosSmooth.Packets.Extensions;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special;

/// <inheritdoc />
public class NullableStringConverterFactory : IStringConverterFactory
{
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// Initializes a new instance of the <see cref="NullableStringConverterFactory"/> class.
    /// </summary>
    /// <param name="serviceProvider">The service provider.</param>
    public NullableStringConverterFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    /// <inheritdoc />
    public bool ShouldHandle(Type type)
        => Nullable.GetUnderlyingType(type) != null;

    /// <inheritdoc />
    public Result<IStringConverter> CreateTypeSerializer(Type type)
    {
        var underlyingType = Nullable.GetUnderlyingType(type);
        if (underlyingType is null)
        {
            throw new InvalidOperationException("Accepts only nullable types.");
        }

        var nullableConverterType = typeof(NullableStringConverter<>).MakeGenericType(underlyingType);
        try
        {
            return Result<IStringConverter>
                .FromSuccess
                    ((IStringConverter)ActivatorUtilities.CreateInstance(_serviceProvider, nullableConverterType));
        }
        catch (Exception e)
        {
            return e;
        }
    }

    /// <inheritdoc />
    public Result<IStringConverter<T>> CreateTypeSerializer<T>()
    {
        return CreateTypeSerializer(typeof(T))
            .Cast<IStringConverter<T>, IStringConverter>();
    }
}
\ No newline at end of file

D Core/NosSmooth.Packets/Converters/Special/NullableTypeConverter.cs => Core/NosSmooth.Packets/Converters/Special/NullableTypeConverter.cs +0 -37
@@ 1,37 0,0 @@
//
//  NullableTypeConverter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special;

/// <inheritdoc />
public class NullableTypeConverter : ISpecialTypeConverter
{
    private readonly ITypeConverterRepository _typeConverterRepository;

    /// <summary>
    /// Initializes a new instance of the <see cref="NullableTypeConverter"/> class.
    /// </summary>
    /// <param name="typeConverterRepository">The type converter repository.</param>
    public NullableTypeConverter(ITypeConverterRepository typeConverterRepository)
    {
        _typeConverterRepository = typeConverterRepository;
    }

    /// <inheritdoc />
    public bool ShouldHandle(Type type)
        => Nullable.GetUnderlyingType(type) != null;

    /// <inheritdoc />
    public Result<object?> Deserialize(Type type, PacketStringEnumerator stringEnumerator)
        => _typeConverterRepository.Deserialize(Nullable.GetUnderlyingType(type)!, stringEnumerator);

    /// <inheritdoc />
    public Result Serialize(Type type, object? obj, PacketStringBuilder builder)
        => _typeConverterRepository.Serialize(Nullable.GetUnderlyingType(type)!, obj, builder);
}
\ No newline at end of file

A Core/NosSmooth.Packets/Converters/Special/StringSerializer.cs => Core/NosSmooth.Packets/Converters/Special/StringSerializer.cs +80 -0
@@ 0,0 1,80 @@
//
//  StringSerializer.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using NosSmooth.Packets.Errors;
using Remora.Results;

namespace NosSmooth.Packets.Converters.Special;

/// <inheritdoc />
public class StringSerializer : IStringSerializer
{
    private readonly IStringConverterRepository _converterRepository;

    /// <summary>
    /// Initializes a new instance of the <see cref="StringSerializer"/> class.
    /// </summary>
    /// <param name="converterRepository">The repository of string converters.</param>
    public StringSerializer(IStringConverterRepository converterRepository)
    {
        _converterRepository = converterRepository;
    }

    /// <inheritdoc />
    public Result<object?> Deserialize(Type parseType, PacketStringEnumerator stringEnumerator)
    {
        var converterResult = _converterRepository.GetTypeConverter(parseType);
        if (!converterResult.IsSuccess)
        {
            return Result<object?>.FromError(converterResult);
        }

        var deserializedResult = converterResult.Entity.Deserialize(stringEnumerator);
        if (!deserializedResult.IsSuccess)
        {
            return Result<object?>.FromError(deserializedResult);
        }

        return Result<object?>.FromSuccess(deserializedResult.Entity);
    }

    /// <inheritdoc />
    public Result Serialize(Type parseType, object? obj, PacketStringBuilder builder)
    {
        var converterResult = _converterRepository.GetTypeConverter(parseType);
        if (!converterResult.IsSuccess)
        {
            return Result.FromError(converterResult);
        }

        return converterResult.Entity.Serialize(obj, builder);
    }

    /// <inheritdoc />
    public Result<TParseType?> Deserialize<TParseType>(PacketStringEnumerator stringEnumerator)
    {
        var converterResult = _converterRepository.GetTypeConverter<TParseType>();
        if (!converterResult.IsSuccess)
        {
            return Result<TParseType?>.FromError(converterResult);
        }

        return converterResult.Entity.Deserialize(stringEnumerator);
    }

    /// <inheritdoc />
    public Result Serialize<TParseType>(TParseType? obj, PacketStringBuilder builder)
    {
        var converterResult = _converterRepository.GetTypeConverter<TParseType>();
        if (!converterResult.IsSuccess)
        {
            return Result.FromError(converterResult);
        }

        return converterResult.Entity.Serialize(obj, builder);
    }
}
\ No newline at end of file

A Core/NosSmooth.Packets/Converters/StringConverterRepository.cs => Core/NosSmooth.Packets/Converters/StringConverterRepository.cs +106 -0
@@ 0,0 1,106 @@
//
//  StringConverterRepository.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets.Converters.Special;
using NosSmooth.Packets.Errors;
using NosSmooth.Packets.Extensions;
using Remora.Results;

namespace NosSmooth.Packets.Converters;

/// <summary>
/// Repository for <see cref="IStringConverter"/>.
/// </summary>
public class StringConverterRepository : IStringConverterRepository
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ConcurrentDictionary<Type, IStringConverter?> _typeConverters;
    private IReadOnlyList<IStringConverterFactory>? _converterFactories;

    /// <summary>
    /// Initializes a new instance of the <see cref="StringConverterRepository"/> class.
    /// </summary>
    /// <param name="serviceProvider">The dependency injection service provider.</param>
    public StringConverterRepository(IServiceProvider serviceProvider)
    {
        _typeConverters = new ConcurrentDictionary<Type, IStringConverter?>();
        _converterFactories = null;
        _serviceProvider = serviceProvider;
    }

    private IReadOnlyList<IStringConverterFactory> ConverterFactories
    {
        get
        {
            if (_converterFactories is null)
            {
                _converterFactories = _serviceProvider
                    .GetServices<IStringConverterFactory>()
                    .ToArray();
            }

            return _converterFactories;
        }
    }

    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <param name="type">The type to find converter for.</param>
    /// <returns>The type converter or an error.</returns>
    public Result<IStringConverter> GetTypeConverter(Type type)
    {
        var typeConverter = _typeConverters.GetOrAddResult
        (
            type,
            (getType) =>
            {
                foreach (var converterFactory in ConverterFactories)
                {
                    if (converterFactory.ShouldHandle(getType))
                    {
                        var result = converterFactory.CreateTypeSerializer(getType);
                        if (!result.IsSuccess)
                        {
                            return Result<IStringConverter?>.FromError(result);
                        }

                        return Result<IStringConverter?>.FromSuccess(result.Entity);
                    }
                }

                var converterType = typeof(IStringConverter<>).MakeGenericType(type);
                return Result<IStringConverter?>.FromSuccess
                    ((IStringConverter?)_serviceProvider.GetService(converterType));
            }
        );

        if (!typeConverter.IsSuccess)
        {
            return Result<IStringConverter>.FromError(typeConverter);
        }

        if (typeConverter.Entity is null)
        {
            return new TypeConverterNotFoundError(type);
        }

        return Result<IStringConverter>.FromSuccess(typeConverter.Entity);
    }

    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <typeparam name="TParseType">The type to find converter for.</typeparam>
    /// <returns>The type converter or an error.</returns>
    public Result<IStringConverter<TParseType>> GetTypeConverter<TParseType>()
        => GetTypeConverter(typeof(TParseType)).Cast<IStringConverter<TParseType>, IStringConverter>();
}
\ No newline at end of file

D Core/NosSmooth.Packets/Converters/TypeConverterRepository.cs => Core/NosSmooth.Packets/Converters/TypeConverterRepository.cs +0 -245
@@ 1,245 0,0 @@
//
//  TypeConverterRepository.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets.Converters.Special;
using NosSmooth.Packets.Errors;
using Remora.Results;

namespace NosSmooth.Packets.Converters;

/// <summary>
/// Repository for <see cref="ITypeConverter"/>.
/// </summary>
public class TypeConverterRepository : ITypeConverterRepository
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ConcurrentDictionary<Type, ITypeConverter?> _typeConverters;
    private readonly ConcurrentDictionary<Type, ISpecialTypeConverter?> _specialConverter;
    private IReadOnlyList<ISpecialTypeConverter>? _specialTypeConverters;

    /// <summary>
    /// Initializes a new instance of the <see cref="TypeConverterRepository"/> class.
    /// </summary>
    /// <param name="serviceProvider">The dependency injection service provider.</param>
    public TypeConverterRepository(IServiceProvider serviceProvider)
    {
        _typeConverters = new ConcurrentDictionary<Type, ITypeConverter?>();
        _specialConverter = new ConcurrentDictionary<Type, ISpecialTypeConverter?>();
        _specialTypeConverters = null;
        _serviceProvider = serviceProvider;
    }

    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <param name="type">The type to find converter for.</param>
    /// <returns>The type converter or an error.</returns>
    public Result<ITypeConverter> GetTypeConverter(Type type)
    {
        var typeConverter = _typeConverters.GetOrAdd
        (
            type,
            (getType) =>
            {
                var converterType = typeof(ITypeConverter<>).MakeGenericType(type);
                return (ITypeConverter?)_serviceProvider.GetService(converterType);
            }
        );

        if (typeConverter is null)
        {
            return new TypeConverterNotFoundError(type);
        }

        return Result<ITypeConverter>.FromSuccess(typeConverter);
    }

    /// <summary>
    /// Gets the type converter for the given type.
    /// </summary>
    /// <typeparam name="TParseType">The type to find converter for.</typeparam>
    /// <returns>The type converter or an error.</returns>
    public Result<ITypeConverter<TParseType>> GetTypeConverter<TParseType>()
    {
        var typeConverter = _typeConverters.GetOrAdd
        (
            typeof(TParseType),
            _ => _serviceProvider.GetService<ITypeConverter<TParseType>>()
        );

        if (typeConverter is null)
        {
            return new TypeConverterNotFoundError(typeof(TParseType));
        }

        return Result<ITypeConverter<TParseType>>.FromSuccess((ITypeConverter<TParseType>)typeConverter);
    }

    /// <summary>
    /// Convert the data from the enumerator to the given type.
    /// </summary>
    /// <param name="parseType">The type of the object to serialize.</param>
    /// <param name="stringEnumerator">The packet string enumerator with the current position.</param>
    /// <returns>The parsed object or an error.</returns>
    public Result<object?> Deserialize(Type parseType, PacketStringEnumerator stringEnumerator)
    {
        var specialConverter = GetSpecialConverter(parseType);
        if (specialConverter is not null)
        {
            var deserializeResult = specialConverter.Deserialize(parseType, stringEnumerator);
            if (!deserializeResult.IsSuccess)
            {
                return Result<object?>.FromError(deserializeResult);
            }

            if (deserializeResult.Entity is null)
            {
                if (parseType.DeclaringType == typeof(Nullable<>))
                {
                    return default;
                }

                return Result<object?>.FromError(new DeserializedValueNullError(parseType));
            }

            return deserializeResult.Entity;
        }

        var converterResult = GetTypeConverter(parseType);
        if (!converterResult.IsSuccess)
        {
            return Result<object?>.FromError(converterResult);
        }

        var deserializedResult = converterResult.Entity.Deserialize(stringEnumerator);
        if (!deserializedResult.IsSuccess)
        {
            return Result<object?>.FromError(deserializedResult);
        }

        return Result<object?>.FromSuccess(deserializedResult.Entity);
    }

    /// <summary>
    /// Serializes the given object to string by appending to the packet string builder.
    /// </summary>
    /// <param name="parseType">The type of the object to serialize.</param>
    /// <param name="obj">The object to serialize.</param>
    /// <param name="builder">The string builder to append to.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize(Type parseType, object? obj, PacketStringBuilder builder)
    {
        var specialConverter = GetSpecialConverter(parseType);
        if (specialConverter is not null)
        {
            return specialConverter.Serialize(parseType, obj, builder);
        }

        var converterResult = GetTypeConverter(parseType);
        if (!converterResult.IsSuccess)
        {
            return Result.FromError(converterResult);
        }

        return converterResult.Entity.Serialize(obj, builder);
    }

    /// <summary>
    /// Convert the data from the enumerator to the given type.
    /// </summary>
    /// <param name="stringEnumerator">The packet string enumerator with the current position.</param>
    /// <typeparam name="TParseType">The type of the object to serialize.</typeparam>
    /// <returns>The parsed object or an error.</returns>
    public Result<TParseType?> Deserialize<TParseType>(PacketStringEnumerator stringEnumerator)
    {
        var specialConverter = GetSpecialConverter(typeof(TParseType));
        if (specialConverter is not null)
        {
            var deserializeResult = specialConverter.Deserialize(typeof(TParseType), stringEnumerator);
            if (!deserializeResult.IsSuccess)
            {
                return Result<TParseType?>.FromError(deserializeResult);
            }

            if (deserializeResult.Entity is null)
            {
                if (typeof(TParseType).DeclaringType == typeof(Nullable<>))
                {
                    return default;
                }

                return Result<TParseType?>.FromError(new DeserializedValueNullError(typeof(TParseType)));
            }

            return (TParseType?)deserializeResult.Entity;
        }

        var converterResult = GetTypeConverter<TParseType>();
        if (!converterResult.IsSuccess)
        {
            return Result<TParseType?>.FromError(converterResult);
        }

        return converterResult.Entity.Deserialize(stringEnumerator);
    }

    /// <summary>
    /// Serializes the given object to string by appending to the packet string builder.
    /// </summary>
    /// <param name="obj">The object to serialize.</param>
    /// <param name="builder">The string builder to append to.</param>
    /// <typeparam name="TParseType">The type of the object to deserialize.</typeparam>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Serialize<TParseType>(TParseType? obj, PacketStringBuilder builder)
    {
        if (obj is null)
        {
            builder.Append("-");
            return Result.FromSuccess();
        }

        var specialConverter = GetSpecialConverter(typeof(TParseType));
        if (specialConverter is not null)
        {
            return specialConverter.Serialize(typeof(TParseType), obj, builder);
        }

        var converterResult = GetTypeConverter<TParseType>();
        if (!converterResult.IsSuccess)
        {
            return Result.FromError(converterResult);
        }

        return converterResult.Entity.Serialize(obj, builder);
    }

    private ISpecialTypeConverter? GetSpecialConverter(Type type)
    {
        return _specialConverter.GetOrAdd
        (
            type,
            (t) =>
            {
                _specialTypeConverters ??= _serviceProvider.GetServices<ISpecialTypeConverter>().ToList();

                foreach (var specialConverter in _specialTypeConverters)
                {
                    if (specialConverter.ShouldHandle(t))
                    {
                        return specialConverter;
                    }
                }

                return null;
            }
        );
    }
}
\ No newline at end of file

M Core/NosSmooth.Packets/Errors/CouldNotConvertError.cs => Core/NosSmooth.Packets/Errors/CouldNotConvertError.cs +1 -1
@@ 16,5 16,5 @@ namespace NosSmooth.Packets.Errors;
/// <param name="Converter">The converter that failed the parsing.</param>
/// <param name="Value">The value that failed to parse.</param>
/// <param name="Reason">The reason for the error.</param>
public record CouldNotConvertError(ITypeConverter Converter, string Value, string Reason)
public record CouldNotConvertError(IStringConverter Converter, string Value, string Reason)
    : ResultError($"Converter {Converter.GetType().FullName} could not convert value \"{Value}\" due to {Reason}.");
\ No newline at end of file

M Core/NosSmooth.Packets/Errors/PacketEndNotExpectedError.cs => Core/NosSmooth.Packets/Errors/PacketEndNotExpectedError.cs +1 -1
@@ 15,5 15,5 @@ namespace NosSmooth.Packets.Errors;
/// </summary>
/// <param name="Converter">The type converter.</param>
/// <param name="PropertyName">The property name.</param>
public record PacketEndNotExpectedError(ITypeConverter Converter, string PropertyName)
public record PacketEndNotExpectedError(IStringConverter Converter, string PropertyName)
    : ResultError($"Unexpected packet end reached in {Converter.GetType()} during deserializing the property {PropertyName}");
\ No newline at end of file

M Core/NosSmooth.Packets/Errors/PacketParameterSerializerError.cs => Core/NosSmooth.Packets/Errors/PacketParameterSerializerError.cs +1 -1
@@ 17,5 17,5 @@ namespace NosSmooth.Packets.Errors;
/// <param name="PropertyName">The name of the property.</param>
/// <param name="Result">The underlying result.</param>
/// <param name="Reason">The reason for the error, if known.</param>
public record PacketParameterSerializerError(ITypeConverter Converter, string PropertyName, IResult Result, string? Reason = null)
public record PacketParameterSerializerError(IStringConverter Converter, string PropertyName, IResult Result, string? Reason = null)
    : ResultError($"There was an error deserializing property {PropertyName} in converter {Converter.GetType().FullName}{(Reason is not null ? (", reason: " + Reason) : string.Empty)}");
\ No newline at end of file

M Core/NosSmooth.Packets/Errors/WrongTypeError.cs => Core/NosSmooth.Packets/Errors/WrongTypeError.cs +1 -1
@@ 16,5 16,5 @@ namespace NosSmooth.Packets.Errors;
/// <param name="TypeConverter">The converter that failed to convert the object.</param>
/// <param name="ExpectedType">The expected type of the converting object.</param>
/// <param name="ActualObject">The actual object the converter got.</param>
public record WrongTypeError(ITypeConverter TypeConverter, Type ExpectedType, object? ActualObject)
public record WrongTypeError(IStringConverter TypeConverter, Type ExpectedType, object? ActualObject)
    : ResultError($"{TypeConverter.GetType().FullName} expected type {ExpectedType.FullName}, but got {ActualObject?.GetType().FullName ?? "null"}");
\ No newline at end of file

A Core/NosSmooth.Packets/Extensions/ConcurrentDictionaryExtensions.cs => Core/NosSmooth.Packets/Extensions/ConcurrentDictionaryExtensions.cs +52 -0
@@ 0,0 1,52 @@
//
//  ConcurrentDictionaryExtensions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using Remora.Results;

namespace NosSmooth.Packets.Extensions;

/// <summary>
/// Extension methods for <see cref="ConcurrentDictionary{TKey,TValue}"/>.
/// </summary>
public static class ConcurrentDictionaryExtensions
{
    /// <summary>
    /// Adds the value from the factory if it doesn't exist already,
    /// otherwise returns the existing avlue.
    /// </summary>
    /// <param name="dictionary">The dictionary.</param>
    /// <param name="key">The key to add the value at.</param>
    /// <param name="valueFactory">The factory to obtain the value to add.</param>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <returns>The added value.</returns>
    public static Result<TValue> GetOrAddResult<TKey, TValue>
    (
        this ConcurrentDictionary<TKey, TValue> dictionary,
        TKey key,
        Func<TKey, Result<TValue>> valueFactory
    )
        where TKey : notnull
    {
        if (dictionary.TryGetValue(key, out var val))
        {
            return val;
        }

        var result = valueFactory(key);
        if (!result.IsSuccess)
        {
            return result;
        }

        dictionary.TryAdd(key, result.Entity);
        return result;
    }
}
\ No newline at end of file

A Core/NosSmooth.Packets/Extensions/ResultExtensions.cs => Core/NosSmooth.Packets/Extensions/ResultExtensions.cs +35 -0
@@ 0,0 1,35 @@
//
//  ResultExtensions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Remora.Results;

namespace NosSmooth.Packets.Extensions;

/// <summary>
/// Extensions for <see cref="Result{TEntity}"/> class.
/// </summary>
public static class ResultExtensions
{
    /// <summary>
    /// Cast the given non-nullable result type to another type.
    /// </summary>
    /// <param name="result">The result to cast.</param>
    /// <typeparam name="TTo">The type to cast to.</typeparam>
    /// <typeparam name="TFrom">The type to cast from.</typeparam>
    /// <returns>The casted result.</returns>
    public static Result<TTo> Cast<TTo, TFrom>(this Result<TFrom> result)
        where TTo : notnull
        where TFrom : notnull
    {
        if (!result.IsSuccess)
        {
            return Result<TTo>.FromError(result);
        }

        return Result<TTo>.FromSuccess((TTo)(object)result.Entity);
    }
}
\ No newline at end of file

M Core/NosSmooth.Packets/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Packets/Extensions/ServiceCollectionExtensions.cs +34 -34
@@ 14,7 14,6 @@ using NosSmooth.Packets.Converters.Common;
using NosSmooth.Packets.Converters.Packets;
using NosSmooth.Packets.Converters.Special;
using NosSmooth.Packets.Packets;
using NosSmooth.Packets.Packets.Server.Weapons;

namespace NosSmooth.Packets.Extensions;



@@ 27,7 26,7 @@ public static class ServiceCollectionExtensions
    /// Add packet serialization classes.
    /// </summary>
    /// <remarks>
    /// All generic implementations of ITypeConverter the class
    /// All generic implementations of IStringConverter the class
    /// implements will be registered.
    /// </remarks>
    /// <param name="serviceCollection">The service collection.</param>


@@ 35,11 34,12 @@ public static class ServiceCollectionExtensions
    public static IServiceCollection AddPacketSerialization(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddSingleton<ITypeConverterRepository, TypeConverterRepository>()
            .AddSingleton<IStringConverterRepository, StringConverterRepository>()
            .AddSingleton<IStringSerializer, StringSerializer>()
            .AddSingleton<IPacketSerializer, PacketSerializer>()
            .AddSingleton<IPacketTypesRepository>(p =>
            {
                var repository = new PacketTypesRepository(p.GetRequiredService<ITypeConverterRepository>());
                var repository = new PacketTypesRepository(p.GetRequiredService<IStringConverterRepository>());
                var packetTypes = typeof(ServiceCollectionExtensions).Assembly
                    .GetExportedTypes()
                    .Where(x => x != typeof(UnresolvedPacket) && !x.IsAbstract && typeof(IPacket).IsAssignableFrom(x));


@@ 70,11 70,11 @@ public static class ServiceCollectionExtensions
        var types = assembly.GetExportedTypes()
            .Where(x => x.Namespace?.Contains("Generated") ?? false)
            .Where(x => x.GetInterfaces().Any(
                i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeConverter<>)
                i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IStringConverter<>)
            ));
        foreach (var type in types)
        {
            serviceCollection.AddTypeConverter(type);
            serviceCollection.AddStringConverter(type);
        }

        return serviceCollection;


@@ 89,62 89,62 @@ public static class ServiceCollectionExtensions
    public static IServiceCollection AddBasicConverters(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddSpecialConverter<ListTypeConverter>()
            .AddSpecialConverter<NullableTypeConverter>()
            .AddSpecialConverter<EnumTypeConverter>()
            .AddTypeConverter<IntTypeConverter>()
            .AddTypeConverter<BoolTypeConverter>()
            .AddTypeConverter<UIntTypeConverter>()
            .AddTypeConverter<ShortTypeConverter>()
            .AddTypeConverter<UShortTypeConverter>()
            .AddTypeConverter<ByteTypeConverter>()
            .AddTypeConverter<ULongTypeConverter>()
            .AddTypeConverter<LongTypeConverter>()
            .AddTypeConverter<StringTypeConverter>()
            .AddTypeConverter<NameStringConverter>()
            .AddTypeConverter<CharTypeConverter>()
            .AddTypeConverter<UpgradeRareSubPacketConverter>();
            .AddStringConverterFactory<ListStringConverterFactory>()
            .AddStringConverterFactory<NullableStringConverterFactory>()
            .AddStringConverterFactory<EnumStringConverterFactory>()
            .AddStringConverter<IntStringConverter>()
            .AddStringConverter<BoolStringConverter>()
            .AddStringConverter<UIntStringConverter>()
            .AddStringConverter<ShortStringConverter>()
            .AddStringConverter<UShortStringConverter>()
            .AddStringConverter<ByteStringConverter>()
            .AddStringConverter<ULongStringConverter>()
            .AddStringConverter<LongStringConverter>()
            .AddStringConverter<StringTypeConverter>()
            .AddStringConverter<NameStringConverter>()
            .AddStringConverter<CharStringConverter>()
            .AddStringConverter<UpgradeRareSubPacketConverter>();
    }

    /// <summary>
    /// Add generic type converter.
    /// </summary>
    /// <remarks>
    /// All generic implementations of ITypeConverter the class
    /// All generic implementations of IStringConverter the class
    /// implements will be registered.
    /// </remarks>
    /// <param name="serviceCollection">The service collection.</param>
    /// <typeparam name="TConverter">The type of the converter.</typeparam>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddTypeConverter<TConverter>(this IServiceCollection serviceCollection)
        where TConverter : ITypeConverter
        => serviceCollection.AddTypeConverter(typeof(TConverter));
    public static IServiceCollection AddStringConverter<TConverter>(this IServiceCollection serviceCollection)
        where TConverter : IStringConverter
        => serviceCollection.AddStringConverter(typeof(TConverter));

    /// <summary>
    /// Add generic type converter.
    /// </summary>
    /// <remarks>
    /// All generic implementations of ITypeConverter the class
    /// All generic implementations of IStringConverter the class
    /// implements will be registered.
    /// </remarks>
    /// <param name="serviceCollection">The service collection.</param>
    /// <param name="converterType">The type of the converter.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddTypeConverter(this IServiceCollection serviceCollection, Type converterType)
    public static IServiceCollection AddStringConverter(this IServiceCollection serviceCollection, Type converterType)
    {
        if (!converterType.GetInterfaces().Any(
                i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeConverter<>)
                i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IStringConverter<>)
            ))
        {
            throw new ArgumentException(
                $"{nameof(converterType)} should implement ITypeConverter.",
                $"{nameof(converterType)} should implement IStringConverter.",
                nameof(converterType));
        }

        var handlerTypeInterfaces = converterType.GetInterfaces();
        var handlerInterfaces = handlerTypeInterfaces.Where
        (
            r => r.IsGenericType && r.GetGenericTypeDefinition() == typeof(ITypeConverter<>)
            r => r.IsGenericType && r.GetGenericTypeDefinition() == typeof(IStringConverter<>)
        );

        foreach (var handlerInterface in handlerInterfaces)


@@ 159,11 159,11 @@ public static class ServiceCollectionExtensions
    /// Add the specified converter as a special converter.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <typeparam name="TSpecialConverter">The type to add as a special converter.</typeparam>
    /// <typeparam name="TConverterFactory">The type to add as a special converter.</typeparam>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddSpecialConverter<TSpecialConverter>(this IServiceCollection serviceCollection)
        where TSpecialConverter : class, ISpecialTypeConverter
    public static IServiceCollection AddStringConverterFactory<TConverterFactory>(this IServiceCollection serviceCollection)
        where TConverterFactory : class, IStringConverterFactory
    {
        return serviceCollection.AddSingleton<ISpecialTypeConverter, TSpecialConverter>();
        return serviceCollection.AddSingleton<IStringConverterFactory, TConverterFactory>();
    }
}
\ No newline at end of file

M Core/NosSmooth.Packets/Packets/PacketInfo.cs => Core/NosSmooth.Packets/Packets/PacketInfo.cs +1 -1
@@ 15,4 15,4 @@ namespace NosSmooth.Packets.Packets;
/// <param name="Header">The packet's header, if any.</param>
/// <param name="PacketType">The packet's type.</param>
/// <param name="PacketConverter">The packet's converter.</param>
public record PacketInfo(string? Header, Type PacketType, ITypeConverter PacketConverter);
\ No newline at end of file
public record PacketInfo(string? Header, Type PacketType, IStringConverter PacketConverter);
\ No newline at end of file

M Core/NosSmooth.Packets/Packets/PacketTypesRepository.cs => Core/NosSmooth.Packets/Packets/PacketTypesRepository.cs +5 -5
@@ 21,17 21,17 @@ namespace NosSmooth.Packets.Packets;
/// </summary>
public class PacketTypesRepository : IPacketTypesRepository
{
    private readonly ITypeConverterRepository _typeConverterRepository;
    private readonly IStringConverterRepository _stringConverterRepository;
    private readonly Dictionary<PacketSource, Dictionary<string, PacketInfo>> _headerToPacket;
    private readonly Dictionary<string, PacketInfo> _typeToPacket;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketTypesRepository"/> class.
    /// </summary>
    /// <param name="typeConverterRepository">The type converter repository.</param>
    public PacketTypesRepository(ITypeConverterRepository typeConverterRepository)
    /// <param name="stringConverterRepository">The type converter repository.</param>
    public PacketTypesRepository(IStringConverterRepository stringConverterRepository)
    {
        _typeConverterRepository = typeConverterRepository;
        _stringConverterRepository = stringConverterRepository;
        _headerToPacket = new Dictionary<PacketSource, Dictionary<string, PacketInfo>>();
        _typeToPacket = new Dictionary<string, PacketInfo>();
    }


@@ 54,7 54,7 @@ public class PacketTypesRepository : IPacketTypesRepository
            return new ArgumentInvalidError(nameof(type), "Every packet has to specify the header.");
        }

        var converterResult = _typeConverterRepository.GetTypeConverter(type);
        var converterResult = _stringConverterRepository.GetTypeConverter(type);
        if (!converterResult.IsSuccess)
        {
            return Result.FromError(converterResult);

Do not follow this link