diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.IdSerializer.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.IdSerializer.cs index eedbb0a0ae0..3c0c9f47e4d 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.IdSerializer.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.IdSerializer.cs @@ -50,6 +50,9 @@ public static IRequestExecutorBuilder AddDefaultNodeIdSerializer( builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(new GuidNodeIdValueSerializer(compress: outputNewIdFormat)); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); } builder.Services.TryAddSingleton(sp => diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DecimalNodeIdValueSerializer.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DecimalNodeIdValueSerializer.cs new file mode 100644 index 00000000000..746f12fd7a3 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DecimalNodeIdValueSerializer.cs @@ -0,0 +1,35 @@ +#nullable enable +using System.Buffers.Text; +using System.Diagnostics.CodeAnalysis; + +namespace HotChocolate.Types.Relay; + +internal sealed class DecimalNodeIdValueSerializer : INodeIdValueSerializer +{ + public bool IsSupported(Type type) => type == typeof(decimal) || type == typeof(decimal?); + + public NodeIdFormatterResult Format(Span buffer, object value, out int written) + { + if (value is decimal i) + { + return Utf8Formatter.TryFormat(i, buffer, out written) + ? NodeIdFormatterResult.Success + : NodeIdFormatterResult.BufferTooSmall; + } + + written = 0; + return NodeIdFormatterResult.InvalidValue; + } + + public bool TryParse(ReadOnlySpan buffer, [NotNullWhen(true)] out object? value) + { + if (Utf8Parser.TryParse(buffer, out decimal parsedValue, out _)) + { + value = parsedValue; + return true; + } + + value = null; + return false; + } +} diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DoubleNodeIdValueSerializer.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DoubleNodeIdValueSerializer.cs new file mode 100644 index 00000000000..a6bd49c5fc8 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/DoubleNodeIdValueSerializer.cs @@ -0,0 +1,35 @@ +#nullable enable +using System.Buffers.Text; +using System.Diagnostics.CodeAnalysis; + +namespace HotChocolate.Types.Relay; + +internal sealed class DoubleNodeIdValueSerializer : INodeIdValueSerializer +{ + public bool IsSupported(Type type) => type == typeof(double) || type == typeof(double?); + + public NodeIdFormatterResult Format(Span buffer, object value, out int written) + { + if (value is double i) + { + return Utf8Formatter.TryFormat(i, buffer, out written) + ? NodeIdFormatterResult.Success + : NodeIdFormatterResult.BufferTooSmall; + } + + written = 0; + return NodeIdFormatterResult.InvalidValue; + } + + public bool TryParse(ReadOnlySpan buffer, [NotNullWhen(true)] out object? value) + { + if (Utf8Parser.TryParse(buffer, out double parsedValue, out _)) + { + value = parsedValue; + return true; + } + + value = null; + return false; + } +} diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/SingleNodeIdValueSerializer.cs b/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/SingleNodeIdValueSerializer.cs new file mode 100644 index 00000000000..5b96cbc50fa --- /dev/null +++ b/src/HotChocolate/Core/src/Types/Types/Relay/Serialization/SingleNodeIdValueSerializer.cs @@ -0,0 +1,35 @@ +#nullable enable +using System.Buffers.Text; +using System.Diagnostics.CodeAnalysis; + +namespace HotChocolate.Types.Relay; + +internal sealed class SingleNodeIdValueSerializer : INodeIdValueSerializer +{ + public bool IsSupported(Type type) => type == typeof(float) || type == typeof(float?); + + public NodeIdFormatterResult Format(Span buffer, object value, out int written) + { + if (value is float i) + { + return Utf8Formatter.TryFormat(i, buffer, out written) + ? NodeIdFormatterResult.Success + : NodeIdFormatterResult.BufferTooSmall; + } + + written = 0; + return NodeIdFormatterResult.InvalidValue; + } + + public bool TryParse(ReadOnlySpan buffer, [NotNullWhen(true)] out object? value) + { + if (Utf8Parser.TryParse(buffer, out float parsedValue, out _)) + { + value = parsedValue; + return true; + } + + value = null; + return false; + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/OptimizedNodeIdSerializerTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/OptimizedNodeIdSerializerTests.cs index 787ebeeb0ad..adf73503872 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Relay/OptimizedNodeIdSerializerTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Relay/OptimizedNodeIdSerializerTests.cs @@ -154,6 +154,36 @@ public void Format_Int64Id_Legacy_Format() Assert.Equal("Rm9vCmw2NA==", id); } + [Fact] + public void Format_DecimalId() + { + var serializer = CreateSerializer("Foo", new DecimalNodeIdValueSerializer()); + + var id = serializer.Format("Foo", (decimal)6); + + Assert.Equal("Rm9vOjY=", id); + } + + [Fact] + public void Format_FloatId() + { + var serializer = CreateSerializer("Foo", new SingleNodeIdValueSerializer()); + + var id = serializer.Format("Foo", (float)6); + + Assert.Equal("Rm9vOjY=", id); + } + + [Fact] + public void Format_DoubleId() + { + var serializer = CreateSerializer("Foo", new DoubleNodeIdValueSerializer()); + + var id = serializer.Format("Foo", (double)6); + + Assert.Equal("Rm9vOjY=", id); + } + [Fact] public void Format_Empty_Guid() { @@ -363,6 +393,39 @@ public void Parse_Legacy_Int64Id() Assert.Equal((long)123, id.InternalId); } + [Fact] + public void Parse_DecimalId() + { + var serializer = CreateSerializer("Foo", new DecimalNodeIdValueSerializer()); + + var id = serializer.Parse("Rm9vOjEyMw==", typeof(decimal)); + + Assert.Equal("Foo", id.TypeName); + Assert.Equal((decimal)123, id.InternalId); + } + + [Fact] + public void Parse_SingleId() + { + var serializer = CreateSerializer("Foo", new SingleNodeIdValueSerializer()); + + var id = serializer.Parse("Rm9vOjEyMw==", typeof(float)); + + Assert.Equal("Foo", id.TypeName); + Assert.Equal((float)123, id.InternalId); + } + + [Fact] + public void Parse_DoublelId() + { + var serializer = CreateSerializer("Foo", new DoubleNodeIdValueSerializer()); + + var id = serializer.Parse("Rm9vOjEyMw==", typeof(double)); + + Assert.Equal("Foo", id.TypeName); + Assert.Equal((double)123, id.InternalId); + } + [Fact] public void Parse_CompositeId() { @@ -497,7 +560,7 @@ protected override NodeIdFormatterResult Format(Span buffer, CompositeId v protected override bool TryParse(ReadOnlySpan buffer, out CompositeId value) { - if(TryParseIdPart(buffer, out string a, out var ac) && + if (TryParseIdPart(buffer, out string a, out var ac) && TryParseIdPart(buffer.Slice(ac), out int b, out var bc) && TryParseIdPart(buffer.Slice(ac + bc), out Guid c, out var cc) && TryParseIdPart(buffer.Slice(ac + bc + cc), out bool d, out _))