Skip to content

Commit

Permalink
Added a type converter for primitive value-objects. (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgernand authored Jun 1, 2022
1 parent 8c2631e commit d3e7bf1
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Fluxera.ValueObject/PrimitiveValueObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Fluxera.Guards;
using Fluxera.Utilities.Extensions;
using JetBrains.Annotations;
Expand All @@ -18,6 +19,7 @@
/// <typeparam name="TValueObject">The type of the value object.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
[PublicAPI]
[TypeConverter(typeof(PrimitiveValueObjectConverter))]
public abstract class PrimitiveValueObject<TValueObject, TValue> : ValueObject<TValueObject>, IComparable<TValueObject>
where TValueObject : PrimitiveValueObject<TValueObject, TValue>
where TValue : IComparable
Expand Down
126 changes: 126 additions & 0 deletions src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
namespace Fluxera.ValueObject
{
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Globalization;
using Fluxera.Guards;

internal sealed class PrimitiveValueObjectConverter : TypeConverter
{
private static readonly ConcurrentDictionary<Type, TypeConverter> ActualConverters = new ConcurrentDictionary<Type, TypeConverter>();

private readonly TypeConverter innerConverter;

public PrimitiveValueObjectConverter(Type primitiveValueObjectType)
{
this.innerConverter = ActualConverters.GetOrAdd(primitiveValueObjectType, CreateActualConverter);
}

public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return this.innerConverter.CanConvertFrom(context, sourceType);
}

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return this.innerConverter.CanConvertTo(context, destinationType);
}

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return this.innerConverter.ConvertFrom(context, culture, value);
}

public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return this.innerConverter.ConvertTo(context, culture, value, destinationType);
}

private static TypeConverter CreateActualConverter(Type primitiveValueObjectType)
{
if(!primitiveValueObjectType.IsPrimitiveValueObject())
{
throw new InvalidOperationException($"The type '{primitiveValueObjectType}' is not a primitive value-object.");
}

Type valueType = primitiveValueObjectType.GetValueType();
Type actualConverterType = typeof(PrimitiveValueObjectConverter<,>).MakeGenericType(primitiveValueObjectType, valueType);
return (TypeConverter)Activator.CreateInstance(actualConverterType);
}
}

internal sealed class PrimitiveValueObjectConverter<TValueObject, TValue> : TypeConverter
where TValueObject : PrimitiveValueObject<TValueObject, TValue>
where TValue : IComparable
{
private static TypeConverter ValueConverter { get; } = GetIdValueConverter();

private static TypeConverter GetIdValueConverter()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(TValue));

if(!converter.CanConvertFrom(typeof(string)))
{
throw new InvalidOperationException(
$"The type '{typeof(TValue)}' doesn't have a converter that can convert from string.");
}

return converter;
}

/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string)
|| sourceType == typeof(TValue)
|| base.CanConvertFrom(context, sourceType);
}

/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string)
|| destinationType == typeof(TValue)
|| base.CanConvertTo(context, destinationType);
}

/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if(value is string str)
{
value = ValueConverter.ConvertFrom(str);
}

if(value is TValue idValue)
{
object instance = Activator.CreateInstance(typeof(TValueObject), new object[] { idValue });
return instance;
}

return base.ConvertFrom(context, culture, value);
}

/// <inheritdoc />
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
Guard.Against.Null(value);

PrimitiveValueObject<TValueObject, TValue> primitiveValueObject = (PrimitiveValueObject<TValueObject, TValue>)value;

TValue objectValue = primitiveValueObject.Value;
if(destinationType == typeof(string))
{
return objectValue.ToString();
}

if(destinationType == typeof(TValue))
{
return objectValue;
}

return base.ConvertTo(context, culture, value, destinationType);
}
}
}
112 changes: 112 additions & 0 deletions tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace Fluxera.ValueObject.UnitTests
{
using System;
using System.ComponentModel;
using FluentAssertions;
using Fluxera.ValueObject.UnitTests.Model;
using NUnit.Framework;

[TestFixture]
public class TypeConverterTests
{
[Test]
public void ShouldConvertFromGuidStringValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));

GuidValue result = (GuidValue)converter.ConvertFromString("4c3668ce-3aaa-4adb-bece-05baa708e20f");
result.Should().NotBeNull();
result.Value.Should().NotBeEmpty().And.Be("4c3668ce-3aaa-4adb-bece-05baa708e20f");
}

[Test]
public void ShouldConvertFromGuidValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));

GuidValue result = (GuidValue)converter.ConvertFrom(Guid.Parse("4c3668ce-3aaa-4adb-bece-05baa708e20f"));
result.Should().NotBeNull();
result.Value.Should().NotBeEmpty().And.Be(Guid.Parse("4c3668ce-3aaa-4adb-bece-05baa708e20f"));
}

[Test]
public void ShouldConvertFromIntegerStringValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));

IntValue result = (IntValue)converter.ConvertFromString("999");
result.Should().NotBeNull();
result.Value.Should().Be(999);
}

[Test]
public void ShouldConvertFromIntegerValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));

IntValue result = (IntValue)converter.ConvertFrom(999);
result.Should().NotBeNull();
result.Value.Should().Be(999);
}

[Test]
public void ShouldConvertFromStringValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(StringValue));

StringValue result = (StringValue)converter.ConvertFromString("12345");
result.Should().NotBeNull();
result.Value.Should().NotBeEmpty().And.Be("12345");
}

[Test]
public void ShouldConvertToGuidStringValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));

GuidValue id = new GuidValue(Guid.Parse("2ca3459d-794e-4d25-9594-bc3849972e1f"));
string result = converter.ConvertToString(id);
result.Should().Be("2ca3459d-794e-4d25-9594-bc3849972e1f");
}

[Test]
public void ShouldConvertToGuidValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));

GuidValue id = new GuidValue(Guid.Parse("2ca3459d-794e-4d25-9594-bc3849972e1f"));
Guid result = (Guid)converter.ConvertTo(null, null, id, typeof(Guid));
result.Should().Be(Guid.Parse("2ca3459d-794e-4d25-9594-bc3849972e1f"));
}

[Test]
public void ShouldConvertToIntegerStringValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));

IntValue id = new IntValue(999);
string result = converter.ConvertToString(id);
result.Should().Be("999");
}

[Test]
public void ShouldConvertToIntegerValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));

IntValue id = new IntValue(999);
int result = (int)converter.ConvertTo(null, null, id, typeof(int));
result.Should().Be(999);
}

[Test]
public void ShouldConvertToStringValue()
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(StringValue));

StringValue id = new StringValue("12345");
string result = converter.ConvertToString(id);
result.Should().Be("12345");
}
}
}

0 comments on commit d3e7bf1

Please sign in to comment.