Skip to content

Commit

Permalink
Added static TryParse/Create methods to strongly-typed IDs. (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgernand authored Jun 8, 2024
1 parent c21af35 commit ba66176
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 15 deletions.
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
next-version: 8.0.0
next-version: 8.2.0
19 changes: 18 additions & 1 deletion src/Fluxera.StronglyTypedId/IStronglyTypedId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@
/// <typeparam name="TStronglyTypedId"></typeparam>
/// <typeparam name="TKey"></typeparam>
[PublicAPI]
public interface IStronglyTypedId<TStronglyTypedId, out TKey> : IComparable<TStronglyTypedId>, IEquatable<TStronglyTypedId>
public interface IStronglyTypedId<TStronglyTypedId, TKey> : IComparable<TStronglyTypedId>, IEquatable<TStronglyTypedId>
where TKey : notnull, IComparable
{
/// <summary>
/// Gets the underlying value of the strongly-typed ID.
/// </summary>
public TKey Value { get; }

#if NET7_0_OR_GREATER
/// <summary>
/// Parses the <see cref="TStronglyTypedId"/> from the given string value.
/// </summary>
/// <param name="value"></param>
/// <param name="id"></param>
/// <returns></returns>
static abstract bool TryParse(string value, out TStronglyTypedId id);

/// <summary>
/// Creates a new instance of the <see cref="TStronglyTypedId"/> with the given value.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
static abstract TStronglyTypedId Create(TKey value);
#endif
}
}
60 changes: 58 additions & 2 deletions src/Fluxera.StronglyTypedId/StronglyTypedId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}

/// <summary>
Expand Down Expand Up @@ -70,6 +70,62 @@ private set
}
}

#if NET7_0_OR_GREATER
/// <inheritdoc />
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;
}

/// <inheritdoc />
public static TStronglyTypedId Create(TValue value)
{
return (TStronglyTypedId)Activator.CreateInstance(typeof(TStronglyTypedId), [value]);
}
#endif

/// <inheritdoc />
public bool Equals(TStronglyTypedId other)
{
Expand Down Expand Up @@ -115,7 +171,7 @@ public int CompareTo(TStronglyTypedId other)
/// <param name="value"></param>
public static explicit operator StronglyTypedId<TStronglyTypedId, TValue>(TValue value)
{
object instance = Activator.CreateInstance(typeof(TStronglyTypedId), new object[] { value });
object instance = Activator.CreateInstance(typeof(TStronglyTypedId), [value]);
return (TStronglyTypedId)instance;
}

Expand Down
13 changes: 2 additions & 11 deletions src/Fluxera.StronglyTypedId/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
Expand Down
30 changes: 30 additions & 0 deletions tests/Fluxera.StronglyTypedId.UnitTests/CreationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,35 @@ public void ShouldNotAllowNegativeValue_Numeric()

action.Should().Throw<ArgumentException>();
}

#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
}
}
82 changes: 82 additions & 0 deletions tests/Fluxera.StronglyTypedId.UnitTests/TryParseTests.cs
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ba66176

Please sign in to comment.