Skip to content

Commit

Permalink
Create system for registering custom LiteralConverters (#269)
Browse files Browse the repository at this point in the history
Motivation
----------
Extensions may need to control serialization and deserialization of literals beyond the types including in-box.

Modifications
-------------
- Define `LiteralConverter` and `LiteralConverter<T>` abstract base types for converters
- Create `LiteralConverterRegistry` for registering converters
- Create converters for all currently supported types and register them automatically
- Rewrite `LiteralSerializer` to use the singleton from `LiteralConverterRegistry.Instance`
- Add `System.Collections.Immutable` as a dependency on generated SDKs when targeting downlevel frameworks to get access to `FrozenDictionary`

Breaking Changes
----------------
The namespace of `LiteralSerializer` is changed to `XXX.Serialization.Literals` in generated SDKs.

Results
-------
A more extensible framework for extensions that want to support additional types in headers, path parameters, and query strings. A potential future use is an extension for NodaTime type support.

SDK consumers that need to customize serialization may also do so by mutating the public `LiteralConverterRegistry.Instance`.

Also fixes a bug deserializing nullable enums from literals.

Resolves #268
Fixes #267
  • Loading branch information
brantburnett authored Oct 30, 2024
1 parent 81b3c2f commit f18e744
Show file tree
Hide file tree
Showing 35 changed files with 939 additions and 268 deletions.
1 change: 1 addition & 0 deletions src/main/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageVersion Include="NuGet.Commands" Version="6.11.1" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Net.Http.Json" Version="8.0.1" />
<PackageVersion Include="System.Net.Primitives" Version="4.3.1" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using FluentAssertions;
using RootNamespace.Serialization;
using RootNamespace.Serialization.Literals;
using Xunit;

namespace Yardarm.Client.UnitTests.Serialization
Expand Down Expand Up @@ -888,6 +888,18 @@ public void Deserialize_Enum_ReturnsValue()
result.Should().Be(StringComparison.Ordinal);
}

[Fact]
public void Deserialize_NullableEnum_ReturnsValue()
{
// Act

StringComparison? result = LiteralSerializer.Deserialize<StringComparison?>("Ordinal");

// Assert

result.Should().Be(StringComparison.Ordinal);
}

#endregion

#region JoinListT
Expand Down
1 change: 1 addition & 0 deletions src/main/Yardarm.Client/Serialization/HeaderSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using RootNamespace.Serialization.Literals;
using Yardarm.Client.Internal;

// ReSharper disable once CheckNamespace
Expand Down
262 changes: 0 additions & 262 deletions src/main/Yardarm.Client/Serialization/LiteralSerializer.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;

namespace RootNamespace.Serialization.Literals.Converters;

internal sealed class BooleanLiteralConverter : ValueTypeLiteralConverter<bool>
{
protected override bool ReadCore(string value, string? format) =>
bool.Parse(value);

public override string Write(bool value, string? format) => value ? "true" : "false";

#if NET6_0_OR_GREATER

public override bool TryWrite(bool value, ReadOnlySpan<char> format, Span<char> destination, out int charsWritten)
{
var boolString = value ? "true" : "false";
if (boolString.TryCopyTo(destination))
{
charsWritten = boolString.Length;
return true;
}

charsWritten = 0;
return false;
}

#endif
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace RootNamespace.Serialization.Literals.Converters;

internal sealed class ByteLiteralConverter : ValueTypeLiteralConverter<byte>
{
// CultureInfo.InvariantCulture is not needed for unsigned integers

protected override byte ReadCore(string value, string? format) =>
byte.Parse(value);

public override string Write(byte value, string? format) =>
value.ToString();

#if NET6_0_OR_GREATER

public override bool TryWrite(byte value, ReadOnlySpan<char> format, Span<char> destination, out int charsWritten) =>
value.TryFormat(destination, out charsWritten);

#endif
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Globalization;

namespace RootNamespace.Serialization.Literals.Converters;

internal sealed class DateTimeLiteralConverter : ValueTypeLiteralConverter<DateTime>
{
protected override DateTime ReadCore(string value, string? format) =>
format switch
{
"date" or "full-date" => DateTime.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture),
_ => DateTime.Parse(value, CultureInfo.InvariantCulture)
};

public override string Write(DateTime value, string? format) =>
format switch
{
"date" or "full-date" => value.ToString("yyyy-MM-dd"),
_ => value.ToString("O")
};

#if NET6_0_OR_GREATER

public override bool TryWrite(DateTime value, ReadOnlySpan<char> format, Span<char> destination, out int charsWritten) =>
format switch
{
"date" or "full-date" => value.TryFormat(destination, out charsWritten, format: "yyyy-MM-dd"),
_ => value.TryFormat(destination, out charsWritten, format: "O")
};

#endif
}
Loading

0 comments on commit f18e744

Please sign in to comment.