Skip to content

Commit

Permalink
Add support for IUtf8SpanFormattable and IUtf8SpanParsable (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewlock committed Dec 1, 2023
1 parent 63d6de0 commit e386666
Show file tree
Hide file tree
Showing 19 changed files with 384 additions and 13 deletions.
13 changes: 13 additions & 0 deletions src/StronglyTypedIds.Templates/guid-full.typedid
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<PLACEHOLDERID>, global::System.ISpanParsable<PLACEHOLDERID>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<PLACEHOLDERID>, global::System.IEquatable<PLACEHOLDERID>, global::System.IFormattable
{
Expand Down Expand Up @@ -203,6 +206,16 @@
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif
#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
global::System.ReadOnlySpan<char> format,
global::System.IFormatProvider? provider)
=> Value.TryFormat(utf8Destination, out bytesWritten, format);
#endif

public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler<PLACEHOLDERID>
{
Expand Down
36 changes: 33 additions & 3 deletions src/StronglyTypedIds.Templates/int-full.typedid
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<PLACEHOLDERID>, global::System.ISpanParsable<PLACEHOLDERID>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanParsable<PLACEHOLDERID>, global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<PLACEHOLDERID>, global::System.IEquatable<PLACEHOLDERID>, global::System.IFormattable
{
Expand Down Expand Up @@ -136,7 +139,7 @@
/// <inheritdoc cref="global::System.IFormattable"/>
public string ToString(
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
string? format,
global::System.IFormatProvider? formatProvider)
Expand Down Expand Up @@ -182,7 +185,7 @@
global::System.Span<char> destination,
out int charsWritten,
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
global::System.ReadOnlySpan<char> format,
global::System.IFormatProvider? provider)
Expand All @@ -193,11 +196,38 @@
global::System.Span<char> destination,
out int charsWritten,
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif
#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
global::System.ReadOnlySpan<char> format = default,
global::System.IFormatProvider? provider = null)
=> Value.TryFormat(utf8Destination, out bytesWritten, format, provider);

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.Parse(global::System.ReadOnlySpan{byte}, global::System.IFormatProvider?)" />
public static PLACEHOLDERID Parse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider)
=> new(int.Parse(utf8Text, provider));

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.TryParse(global::System.ReadOnlySpan{byte}, global::System.IFormatProvider?, out TSelf)" />
public static bool TryParse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider, out PLACEHOLDERID result)
{
if (int.TryParse(utf8Text, provider, out var intResult))
{
result = new PLACEHOLDERID(intResult);
return true;
}

result = default;
return false;
}
#endif

public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler<PLACEHOLDERID>
{
Expand Down
37 changes: 33 additions & 4 deletions src/StronglyTypedIds.Templates/long-full.typedid
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<PLACEHOLDERID>, global::System.ISpanParsable<PLACEHOLDERID>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanParsable<PLACEHOLDERID>, global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<PLACEHOLDERID>, global::System.IEquatable<PLACEHOLDERID>, global::System.IFormattable
{
Expand Down Expand Up @@ -136,7 +139,7 @@
/// <inheritdoc cref="global::System.IFormattable"/>
public string ToString(
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
string? format,
global::System.IFormatProvider? formatProvider)
Expand Down Expand Up @@ -182,7 +185,7 @@
global::System.Span<char> destination,
out int charsWritten,
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
global::System.ReadOnlySpan<char> format,
global::System.IFormatProvider? provider)
Expand All @@ -193,12 +196,38 @@
global::System.Span<char> destination,
out int charsWritten,
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif

#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
global::System.ReadOnlySpan<char> format = default,
global::System.IFormatProvider? provider = null)
=> Value.TryFormat(utf8Destination, out bytesWritten, format, provider);

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.Parse(global::System.ReadOnlySpan{byte}, global::System.IFormatProvider?)" />
public static PLACEHOLDERID Parse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider)
=> new(long.Parse(utf8Text, provider));

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.TryParse(global::System.ReadOnlySpan{byte}, global::System.IFormatProvider?, out TSelf)" />
public static bool TryParse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider, out PLACEHOLDERID result)
{
if (long.TryParse(utf8Text, provider, out var intResult))
{
result = new PLACEHOLDERID(intResult);
return true;
}

result = default;
return false;
}
#endif
public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler<PLACEHOLDERID>
{
public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value)
Expand Down
13 changes: 13 additions & 0 deletions src/StronglyTypedIds/EmbeddedSources.Guid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ partial struct PLACEHOLDERID :
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<PLACEHOLDERID>, global::System.ISpanParsable<PLACEHOLDERID>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<PLACEHOLDERID>, global::System.IEquatable<PLACEHOLDERID>, global::System.IFormattable
{
Expand Down Expand Up @@ -204,6 +207,16 @@ public bool TryFormat(
#endif
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif
#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
global::System.ReadOnlySpan<char> format,
global::System.IFormatProvider? provider)
=> Value.TryFormat(utf8Destination, out bytesWritten, format);
#endif
}
""";
Expand Down
34 changes: 32 additions & 2 deletions src/StronglyTypedIds/EmbeddedSources.Int.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ partial struct PLACEHOLDERID :
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<PLACEHOLDERID>, global::System.ISpanParsable<PLACEHOLDERID>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanParsable<PLACEHOLDERID>, global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<PLACEHOLDERID>, global::System.IEquatable<PLACEHOLDERID>, global::System.IFormattable
{
Expand Down Expand Up @@ -184,7 +187,7 @@ public bool TryFormat(
global::System.Span<char> destination,
out int charsWritten,
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
global::System.ReadOnlySpan<char> format,
global::System.IFormatProvider? provider)
Expand All @@ -195,10 +198,37 @@ public bool TryFormat(
global::System.Span<char> destination,
out int charsWritten,
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
#endif
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif
#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
global::System.ReadOnlySpan<char> format = default,
global::System.IFormatProvider? provider = null)
=> Value.TryFormat(utf8Destination, out bytesWritten, format, provider);

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.Parse(global::System.ReadOnlySpan{byte}, global::System.IFormatProvider?)" />
public static PLACEHOLDERID Parse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider)
=> new(int.Parse(utf8Text, provider));

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.TryParse(global::System.ReadOnlySpan{byte}, global::System.IFormatProvider?, out TSelf)" />
public static bool TryParse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider, out PLACEHOLDERID result)
{
if (int.TryParse(utf8Text, provider, out var intResult))
{
result = new PLACEHOLDERID(intResult);
return true;
}

result = default;
return false;
}
#endif
}
""";
Expand Down
30 changes: 30 additions & 0 deletions src/StronglyTypedIds/EmbeddedSources.Long.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ partial struct PLACEHOLDERID :
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<PLACEHOLDERID>, global::System.ISpanParsable<PLACEHOLDERID>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanParsable<PLACEHOLDERID>, global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<PLACEHOLDERID>, global::System.IEquatable<PLACEHOLDERID>, global::System.IFormattable
{
Expand Down Expand Up @@ -199,6 +202,33 @@ public bool TryFormat(
#endif
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif
#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
global::System.ReadOnlySpan<char> format = default,
global::System.IFormatProvider? provider = null)
=> Value.TryFormat(utf8Destination, out bytesWritten, format, provider);

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
public static PLACEHOLDERID Parse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider)
=> new(long.Parse(utf8Text, provider));

/// <inheritdoc cref="global::System.IUtf8SpanParsable{TSelf}.TryParse(ReadOnlySpan{byte}, IFormatProvider?, out TSelf)" />
public static bool TryParse(global::System.ReadOnlySpan<byte> utf8Text, global::System.IFormatProvider? provider, out PLACEHOLDERID result)
{
if (long.TryParse(utf8Text, provider, out var intResult))
{
result = new PLACEHOLDERID(intResult);
return true;
}

result = default;
return false;
}
#endif
}
""";
Expand Down
17 changes: 17 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/GuidIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ public void CantCreateEmptyGeneratedId1()
Assert.NotEqual((object)bar, (object)foo);
}

#if NET8_0_OR_GREATER
[Fact]
public void CanFormatAsUtf8()
{
var expected = "17b4c96b-023a-4050-8ab5-4511c0e7fd09"u8;
var id = GuidId1.Parse("17B4C96B-023A-4050-8AB5-4511C0E7FD09");

var format = "D"; // in expected format
var actual = new byte[expected.Length].AsSpan();
var success = id.TryFormat(actual, out var charsWritten, format, provider: null);

Assert.True(success);
Assert.Equal(expected.Length, charsWritten);
Assert.True(actual.SequenceEqual(expected));
}
#endif

[Fact]
public void CanSerializeToGuid_WithTypeConverter()
{
Expand Down
16 changes: 16 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/IntIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ public void DifferentTypesAreUnequal()
Assert.NotEqual((object) bar, (object) foo);
}

#if NET8_0_OR_GREATER
[Fact]
public void CanRoundTripUtf8()
{
var id = new IntId(123);

var actual = new byte[16].AsSpan();
Assert.True(id.TryFormat(actual, out var charsWritten));

var success = IntId.TryParse(actual.Slice(0, charsWritten), provider: null, out var result);

Assert.True(success);
Assert.Equal(id, result);
}
#endif

[Fact]
public void CanSerializeToNullableInt_WithNewtonsoftJsonProvider()
{
Expand Down
16 changes: 16 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/LongIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ public void DifferentTypesAreUnequal()
Assert.NotEqual((object)bar, (object)foo);
}

#if NET8_0_OR_GREATER
[Fact]
public void CanRoundTripUtf8()
{
var id = new LongId(123L);

var actual = new byte[16].AsSpan();
Assert.True(id.TryFormat(actual, out var charsWritten));

var success = LongId.TryParse(actual.Slice(0, charsWritten), provider: null, out var result);

Assert.True(success);
Assert.Equal(id, result);
}
#endif

[Fact]
public void CanSerializeToNullableInt_WithNewtonsoftJsonProvider()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
#endif
#if NET7_0_OR_GREATER
global::System.IParsable<MyId>, global::System.ISpanParsable<MyId>,
#endif
#if NET8_0_OR_GREATER
global::System.IUtf8SpanFormattable,
#endif
global::System.IComparable<MyId>, global::System.IEquatable<MyId>, global::System.IFormattable
{
Expand Down Expand Up @@ -213,5 +216,15 @@
#endif
global::System.ReadOnlySpan<char> format = default)
=> Value.TryFormat(destination, out charsWritten, format);
#endif
#if NET8_0_OR_GREATER
/// <inheritdoc cref="global::System.IUtf8SpanFormattable.TryFormat" />
public bool TryFormat(
global::System.Span<byte> utf8Destination,
out int bytesWritten,
[global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
global::System.ReadOnlySpan<char> format,
global::System.IFormatProvider? provider)
=> Value.TryFormat(utf8Destination, out bytesWritten, format);
#endif
}
Loading

0 comments on commit e386666

Please sign in to comment.