Skip to content

Commit

Permalink
Add legacy TypeConverter support from System.ComponentModel
Browse files Browse the repository at this point in the history
This shouldn't be needed in newer ASP.NET Core since the IParsable<T>/IFormattable is the only requirement nowadays. But might be useful in other scenarios.
  • Loading branch information
kzu committed Dec 23, 2024
1 parent 11b53d2 commit dad5468
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/StructId.FunctionalTests/Functional.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ public record User(UserId Id, string Name, Wallet Wallet);

public class FunctionalTests(ITestOutputHelper output)
{
[Fact]
public void TypeConverters()
{
var id = ProductId.New();
var converter = TypeDescriptor.GetConverter(id);

Assert.True(converter.CanConvertTo(typeof(string)));
Assert.True(converter.CanConvertFrom(typeof(string)));

var id2 = (ProductId?)converter.ConvertFromString(converter.ConvertToString(id)!);
Assert.Equal(id, id2);
}

[Fact]
public void JsonConversion()
{
Expand Down
14 changes: 14 additions & 0 deletions src/StructId.FunctionalTests/UlidTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Data;
using System.Text.Json;
using Dapper;
Expand Down Expand Up @@ -69,6 +70,19 @@ public UlidToStringConverter(ConverterMappingHints? mappingHints = null)

public class UlidTests
{
[Fact]
public void TypeConverters()
{
var id = UlidId.New();
var converter = TypeDescriptor.GetConverter(id);

Assert.True(converter.CanConvertTo(typeof(string)));
Assert.True(converter.CanConvertFrom(typeof(string)));

var id2 = (UlidId?)converter.ConvertFromString(converter.ConvertToString(id)!);
Assert.Equal(id, id2);
}

[Fact]
public void JsonConversion()
{
Expand Down
55 changes: 55 additions & 0 deletions src/StructId/Templates/TypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// <auto-generated/>
#nullable enable

using System;
using System.ComponentModel;
using System.Globalization;
using StructId;

[TStructId]
[TypeConverter(typeof(TSelf.StringConverter))]
file readonly partial record struct TSelf(string Value)
{
partial class StringConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
=> sourceType == typeof(string) || sourceType == typeof(TSelf);

/// <inheritdoc />
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value == null)
return default(TSelf);

if (value is string typedValue)
return TSelf.New(typedValue);

throw new ArgumentException($"Cannot convert '{value}' to {nameof(TSelf)}", nameof(value));
}

/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
=> destinationType == typeof(string) || destinationType == typeof(TSelf);

/// <inheritdoc />
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (value != null)
{
if (destinationType == typeof(string))
return ((TSelf)value).Value;

if (destinationType == typeof(TSelf))
return value;
}

throw new InvalidOperationException($"Cannot convert '{value}' to '{destinationType}'");
}
}
}

file partial record struct TSelf : INewable<TSelf, string>
{
public static TSelf New(string value) => throw new NotImplementedException();
}
64 changes: 64 additions & 0 deletions src/StructId/Templates/TypeConverterT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// <auto-generated/>
#nullable enable

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using StructId;

[TStructId]
[TypeConverter(typeof(TSelf.StringConverter))]
file readonly partial record struct TSelf(/*!string*/ TValue Value)
{
partial class StringConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
=> sourceType == typeof(string) || sourceType == typeof(TSelf);

/// <inheritdoc />
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value == null)
return default(TSelf);

if (value is string typedValue)
return TSelf.New(TValue.Parse(typedValue, culture));

throw new ArgumentException($"Cannot convert '{value}' to {nameof(TSelf)}", nameof(value));
}

/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
=> destinationType == typeof(string) || destinationType == typeof(TSelf);

/// <inheritdoc />
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (value != null)
{
if (destinationType == typeof(string))
return ((TSelf)value).Value.ToString(null, culture);

if (destinationType == typeof(TSelf))
return value;
}

throw new InvalidOperationException($"Cannot convert '{value}' to '{destinationType}'");
}
}
}

file partial record struct TSelf : INewable<TSelf, TValue>
{
public static TSelf New(TValue value) => throw new NotImplementedException();
}

// This will be removed when applying the template to each user-defined struct id.
file struct TValue : IParsable<TValue>, IFormattable
{
public static TValue Parse(string s, IFormatProvider? provider) => throw new NotImplementedException();
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TValue result) => throw new NotImplementedException();
public string ToString(string? format, IFormatProvider? formatProvider) => throw new NotImplementedException();
}

0 comments on commit dad5468

Please sign in to comment.