A Packets/NosSmooth.PacketSerializer.Abstractions/OptionalWrapper.cs => Packets/NosSmooth.PacketSerializer.Abstractions/OptionalWrapper.cs +41 -0
@@ 0,0 1,41 @@
+//
+// OptionalWrapper.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.Diagnostics.CodeAnalysis;
+
+namespace NosSmooth.PacketSerializer.Abstractions;
+
+/// <summary>
+/// Wraps a compound value that may not be present
+/// and there will be nothing instead in the packet.
+/// The converter of underlying type will be called
+/// if and only if the value is not null.
+/// </summary>
+/// <param name="Value">The value.</param>
+/// <typeparam name="T">The underlying type.</typeparam>
+[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "Fix this, this should not happen.")]
+public record struct OptionalWrapper<T>(bool Present, T? Value)
+{
+ /// <summary>
+ /// Unwrap the underlying value.
+ /// </summary>
+ /// <param name="wrapper">The wrapper to unwrap.</param>
+ /// <returns>The unwrapped value.</returns>
+ public static implicit operator T?(OptionalWrapper<T> wrapper)
+ {
+ return wrapper.Value;
+ }
+
+ /// <summary>
+ /// wrap the value in optional wrapper.
+ /// </summary>
+ /// <param name="value">The value to wrap.</param>
+ /// <returns>The wrapped value.</returns>
+ public static implicit operator OptionalWrapper<T>(T? value)
+ {
+ return new OptionalWrapper<T>(true, value);
+ }
+}<
\ No newline at end of file
A Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverter.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverter.cs +82 -0
@@ 0,0 1,82 @@
+//
+// OptionalWrapperConverter.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.PacketSerializer.Abstractions;
+using Remora.Results;
+
+namespace NosSmooth.PacketSerializer.Converters.Common;
+
+/// <summary>
+/// Converter of <see cref="OptionalWrapper{T}"/>.
+/// </summary>
+/// <typeparam name="T">The underlying type.</typeparam>
+public class OptionalWrapperConverter<T> : BaseStringConverter<OptionalWrapper<T>>
+{
+ private readonly IStringConverterRepository _converterRepository;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OptionalWrapperConverter{T}"/> class.
+ /// </summary>
+ /// <param name="converterRepository">The converter repository.</param>
+ public OptionalWrapperConverter(IStringConverterRepository converterRepository)
+ {
+ _converterRepository = converterRepository;
+ }
+
+ /// <inheritdoc />
+ public override Result Serialize(OptionalWrapper<T> obj, PacketStringBuilder builder)
+ {
+ if (obj.Value is null)
+ {
+ return Result.FromSuccess();
+ }
+
+ var converterResult = _converterRepository.GetTypeConverter<T>();
+ if (!converterResult.IsDefined(out var converter))
+ {
+ return Result.FromError(converterResult);
+ }
+
+ return converter.Serialize(obj.Value, builder);
+
+ }
+
+ /// <inheritdoc />
+ public override Result<OptionalWrapper<T>> Deserialize(ref PacketStringEnumerator stringEnumerator, DeserializeOptions options)
+ {
+ if (stringEnumerator.IsOnLastToken() ?? false)
+ {
+ return Result<OptionalWrapper<T>>.FromSuccess(new OptionalWrapper<T>(false, default));
+ }
+
+ var tokenResult = stringEnumerator.GetNextToken(out var token, false);
+ if (!tokenResult.IsSuccess)
+ {
+ return Result<OptionalWrapper<T>>.FromError(tokenResult);
+ }
+
+ if (token.Token.Length == 0)
+ {
+ stringEnumerator.GetNextToken(out _); // seek
+ return Result<OptionalWrapper<T>>.FromSuccess(new OptionalWrapper<T>(false, default));
+ }
+
+ var converterResult = _converterRepository.GetTypeConverter<T>();
+ if (!converterResult.IsDefined(out var converter))
+ {
+ return Result<OptionalWrapper<T>>.FromError(converterResult);
+ }
+
+ var deserializationResult = converter.Deserialize(ref stringEnumerator, new DeserializeOptions(true));
+ if (!deserializationResult.IsDefined(out var deserialization))
+ {
+ return Result<OptionalWrapper<T>>.FromError(deserializationResult);
+ }
+
+ return new OptionalWrapper<T>(true, deserialization);
+ }
+}<
\ No newline at end of file
A Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverterFactory.cs => Packets/NosSmooth.PacketSerializer/Converters/Common/OptionalWrapperConverterFactory.cs +56 -0
@@ 0,0 1,56 @@
+//
+// OptionalWrapperConverterFactory.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.PacketSerializer.Abstractions;
+using NosSmooth.PacketSerializer.Converters.Special;
+using NosSmooth.PacketSerializer.Extensions;
+using Remora.Results;
+
+namespace NosSmooth.PacketSerializer.Converters.Common;
+
+/// <summary>
+/// Converts <see cref="OptionalWrapper{T}"/>.
+/// </summary>
+public class OptionalWrapperConverterFactory : IStringConverterFactory
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OptionalWrapperConverterFactory"/> class.
+ /// </summary>
+ /// <param name="serviceProvider">The service provider.</param>
+ public OptionalWrapperConverterFactory(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ /// <inheritdoc />
+ public bool ShouldHandle(Type type)
+ => type.GetGenericTypeDefinition() == typeof(OptionalWrapper<>);
+
+ /// <inheritdoc />
+ public Result<IStringConverter> CreateTypeSerializer(Type type)
+ {
+ var underlyingType = type.GetGenericArguments()[0];
+ var serializerType = typeof(OptionalWrapperConverter<>).MakeGenericType(underlyingType);
+
+ try
+ {
+ return Result<IStringConverter>.FromSuccess
+ ((IStringConverter)ActivatorUtilities.CreateInstance(_serviceProvider, serializerType));
+ }
+ 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
M Packets/NosSmooth.PacketSerializer/Extensions/ServiceCollectionExtensions.cs => Packets/NosSmooth.PacketSerializer/Extensions/ServiceCollectionExtensions.cs +1 -0
@@ 78,6 78,7 @@ public static class ServiceCollectionExtensions
.AddStringConverterFactory<NullableStringConverterFactory>()
.AddStringConverterFactory<EnumStringConverterFactory>()
.AddStringConverterFactory<NullableWrapperConverterFactory>()
+ .AddStringConverterFactory<OptionalWrapperConverterFactory>()
.AddStringConverter<IntStringConverter>()
.AddStringConverter<BoolStringConverter>()
.AddStringConverter<UIntStringConverter>()