From ba6617673b093646b4bbf4310aef64c06ccfb251 Mon Sep 17 00:00:00 2001 From: Matthias Gernand Date: Sat, 8 Jun 2024 21:25:44 +0200 Subject: [PATCH] Added static TryParse/Create methods to strongly-typed IDs. (#45) --- GitVersion.yml | 2 +- .../IStronglyTypedId.cs | 19 ++++- .../StronglyTypedId.cs | 60 +++++++++++++- src/Fluxera.StronglyTypedId/TypeExtensions.cs | 13 +-- .../CreationTests.cs | 30 +++++++ .../TryParseTests.cs | 82 +++++++++++++++++++ 6 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 tests/Fluxera.StronglyTypedId.UnitTests/TryParseTests.cs diff --git a/GitVersion.yml b/GitVersion.yml index 47c3fc8..cc09407 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1 +1 @@ -next-version: 8.0.0 \ No newline at end of file +next-version: 8.2.0 \ No newline at end of file diff --git a/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs b/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs index c851aa9..8085aee 100644 --- a/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs +++ b/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs @@ -9,12 +9,29 @@ /// /// [PublicAPI] - public interface IStronglyTypedId : IComparable, IEquatable + public interface IStronglyTypedId : IComparable, IEquatable where TKey : notnull, IComparable { /// /// Gets the underlying value of the strongly-typed ID. /// public TKey Value { get; } + +#if NET7_0_OR_GREATER + /// + /// Parses the from the given string value. + /// + /// + /// + /// + static abstract bool TryParse(string value, out TStronglyTypedId id); + + /// + /// Creates a new instance of the with the given value. + /// + /// + /// + static abstract TStronglyTypedId Create(TKey value); +#endif } } diff --git a/src/Fluxera.StronglyTypedId/StronglyTypedId.cs b/src/Fluxera.StronglyTypedId/StronglyTypedId.cs index ec857e4..a52f30f 100644 --- a/src/Fluxera.StronglyTypedId/StronglyTypedId.cs +++ b/src/Fluxera.StronglyTypedId/StronglyTypedId.cs @@ -31,7 +31,7 @@ static StronglyTypedId() Type valueType = typeof(TValue); bool isIdentifierType = valueType.IsNumeric() || valueType == typeof(string) || valueType == typeof(Guid); - Guard.ThrowIfFalse(isIdentifierType, nameof(Value), "The value of a strongly-typed ID must be a numeric, string or Guid type."); + Guard.ThrowIfFalse(isIdentifierType, nameof(Value), "The value of a strongly-typed ID must be int, long, string or Guid."); } /// @@ -70,6 +70,62 @@ private set } } +#if NET7_0_OR_GREATER + /// + public static bool TryParse(string value, out TStronglyTypedId id) + { + Type valueType = typeof(TStronglyTypedId).GetStronglyTypedIdValueType(); + bool isIdentifierType = valueType.IsNumeric() || valueType == typeof(string) || valueType == typeof(Guid); + + if(!isIdentifierType) + { + id = null; + return false; + } + + if(valueType == typeof(string)) + { + if(!string.IsNullOrWhiteSpace(value)) + { + id = (TStronglyTypedId)Activator.CreateInstance(typeof(TStronglyTypedId), [value]); + return true; + } + } + + if(valueType.IsNumeric()) + { + try + { + object numericValue = Convert.ChangeType(value, valueType); + id = (TStronglyTypedId)Activator.CreateInstance(typeof(TStronglyTypedId), [numericValue]); + return true; + } + catch + { + // Note: Intentionally left blank. + } + } + + if(valueType == typeof(Guid)) + { + if(Guid.TryParse(value, out Guid guidValue)) + { + id = (TStronglyTypedId)Activator.CreateInstance(typeof(TStronglyTypedId), [guidValue]); + return true; + } + } + + id = null; + return false; + } + + /// + public static TStronglyTypedId Create(TValue value) + { + return (TStronglyTypedId)Activator.CreateInstance(typeof(TStronglyTypedId), [value]); + } +#endif + /// public bool Equals(TStronglyTypedId other) { @@ -115,7 +171,7 @@ public int CompareTo(TStronglyTypedId other) /// public static explicit operator StronglyTypedId(TValue value) { - object instance = Activator.CreateInstance(typeof(TStronglyTypedId), new object[] { value }); + object instance = Activator.CreateInstance(typeof(TStronglyTypedId), [value]); return (TStronglyTypedId)instance; } diff --git a/src/Fluxera.StronglyTypedId/TypeExtensions.cs b/src/Fluxera.StronglyTypedId/TypeExtensions.cs index abf91f6..4aed178 100644 --- a/src/Fluxera.StronglyTypedId/TypeExtensions.cs +++ b/src/Fluxera.StronglyTypedId/TypeExtensions.cs @@ -15,17 +15,8 @@ public static bool IsNumeric(this Type type) { type = type.UnwrapNullableType(); return - (type == typeof(sbyte)) || - (type == typeof(byte)) || - (type == typeof(short)) || - (type == typeof(ushort)) || - (type == typeof(int)) || - (type == typeof(uint)) || - (type == typeof(long)) || - (type == typeof(ulong)) || - (type == typeof(decimal)) || - (type == typeof(float)) || - (type == typeof(double)); + type == typeof(int) || + type == typeof(long); } /// diff --git a/tests/Fluxera.StronglyTypedId.UnitTests/CreationTests.cs b/tests/Fluxera.StronglyTypedId.UnitTests/CreationTests.cs index 23cd4bd..cf282df 100644 --- a/tests/Fluxera.StronglyTypedId.UnitTests/CreationTests.cs +++ b/tests/Fluxera.StronglyTypedId.UnitTests/CreationTests.cs @@ -62,5 +62,35 @@ public void ShouldNotAllowNegativeValue_Numeric() action.Should().Throw(); } + +#if NET7_0_OR_GREATER + [Test] + public void ShouldCreateStringId() + { + StringId id = StringId.Create("1234"); + + id.Should().NotBeNull(); + id.Value.Should().Be("1234"); + } + + [Test] + public void ShouldCreateIntegerId() + { + IntegerId id = IntegerId.Create(1234); + + id.Should().NotBeNull(); + id.Value.Should().Be(1234); + } + + [Test] + public void ShouldCreateGuidId() + { + Guid value = Guid.NewGuid(); + GuidId id = GuidId.Create(value); + + id.Should().NotBeNull(); + id.Value.Should().Be(value); + } +#endif } } diff --git a/tests/Fluxera.StronglyTypedId.UnitTests/TryParseTests.cs b/tests/Fluxera.StronglyTypedId.UnitTests/TryParseTests.cs new file mode 100644 index 0000000..10c8997 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.UnitTests/TryParseTests.cs @@ -0,0 +1,82 @@ +#if NET7_0_OR_GREATER +namespace Fluxera.StronglyTypedId.UnitTests +{ + using System; + using FluentAssertions; + using Fluxera.StronglyTypedId.UnitTests.Model; + using NUnit.Framework; + + [TestFixture] + public class TryParseTests + { + [Test] + public void ShouldParseStringId() + { + bool success = StringId.TryParse("1234asdf", out StringId id); + + success.Should().BeTrue(); + id.Should().NotBeNull(); + id.Value.Should().Be("1234asdf"); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void ShouldNotParseStringId(string value) + { + bool success = StringId.TryParse(value, out StringId id); + + success.Should().BeFalse(); + id.Should().BeNull(); + } + + [Test] + public void ShouldParseNumericId() + { + bool success = IntegerId.TryParse("123", out IntegerId id); + + success.Should().BeTrue(); + id.Should().NotBeNull(); + id.Value.Should().Be(123); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + [TestCase("asdf")] + [TestCase("1243-asdf")] + public void ShouldNotParseNumericId(string value) + { + bool success = IntegerId.TryParse(value, out IntegerId id); + + success.Should().BeFalse(); + id.Should().BeNull(); + } + + [Test] + public void ShouldParseGuidId() + { + bool success = GuidId.TryParse("0f445ea8c8884f47ab75bf7127a7f00d", out GuidId id); + + success.Should().BeTrue(); + id.Should().NotBeNull(); + id.Value.Should().Be(Guid.Parse("0f445ea8c8884f47ab75bf7127a7f00d")); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + [TestCase("0f445ea8f47f7127a7f00d")] + public void ShouldNotParseGuidId(string value) + { + bool success = GuidId.TryParse(value, out GuidId id); + + success.Should().BeFalse(); + id.Should().BeNull(); + } + } +} +#endif