diff --git a/src/Fluxera.ValueObject/PrimitiveValueObject.cs b/src/Fluxera.ValueObject/PrimitiveValueObject.cs index db59def..fc271a3 100644 --- a/src/Fluxera.ValueObject/PrimitiveValueObject.cs +++ b/src/Fluxera.ValueObject/PrimitiveValueObject.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.ComponentModel; using Fluxera.Guards; using Fluxera.Utilities.Extensions; using JetBrains.Annotations; @@ -18,6 +19,7 @@ /// The type of the value object. /// The type of the value. [PublicAPI] + [TypeConverter(typeof(PrimitiveValueObjectConverter))] public abstract class PrimitiveValueObject : ValueObject, IComparable where TValueObject : PrimitiveValueObject where TValue : IComparable diff --git a/src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs b/src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs new file mode 100644 index 0000000..0ff83bb --- /dev/null +++ b/src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs @@ -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 ActualConverters = new ConcurrentDictionary(); + + 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 : TypeConverter + where TValueObject : PrimitiveValueObject + 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; + } + + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string) + || sourceType == typeof(TValue) + || base.CanConvertFrom(context, sourceType); + } + + /// + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(string) + || destinationType == typeof(TValue) + || base.CanConvertTo(context, destinationType); + } + + /// + 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); + } + + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + Guard.Against.Null(value); + + PrimitiveValueObject primitiveValueObject = (PrimitiveValueObject)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); + } + } +} diff --git a/tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs b/tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs new file mode 100644 index 0000000..1077933 --- /dev/null +++ b/tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs @@ -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"); + } + } +}