Skip to content

Instantly share code, notes, and snippets.

@gongdo
Last active June 8, 2019 08:58
Show Gist options
  • Save gongdo/89222c1fc3b71e43f0b13b6f448bb9c0 to your computer and use it in GitHub Desktop.
Save gongdo/89222c1fc3b71e43f0b13b6f448bb9c0 to your computer and use it in GitHub Desktop.
General purpose string id struct.
using System;
using System.Text.RegularExpressions;
namespace Alpaca
{
/// <summary>
/// Represents an identity of a domain object that must be unique in certain system.
/// An empty Id should be treated as invalid. You must check if the id was empty.
/// It can be treated as a string when it used to outside the system.
/// </summary>
/// <remarks>
/// Unlike Guid, it doesn't provide uniqueness by itself.
/// Instead, you must choose uniqueness strategy by yourself.
/// This gives you more control what object id should be.
/// </remarks>
public readonly struct Id : IComparable<Id>, IEquatable<Id>
{
private readonly string id;
private string Value => id ?? "";
private static readonly Regex invalidCharacters = new Regex(@"[\s\r\n\t\f\v]", RegexOptions.Compiled);
public static Id Empty => new Id();
/// <summary>
/// Initiate the id with specified value.
/// The value must not be null, empty or white-space.
/// Also the value must not contain space or escape characters.
/// </summary>
/// <param name="value"></param>
public Id(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException($"Id should not be null, empty nor white-space.", nameof(value));
}
if (invalidCharacters.IsMatch(value))
{
throw new ArgumentException($"Id should not contain space or escape characters.", nameof(value));
}
id = value;
}
public int CompareTo(Id other) => string.Compare(Value, other.Value);
public bool Equals(Id other) => string.Equals(Value, other.Value);
public override bool Equals(object obj) => string.Equals(Value, ((Id)obj).Value);
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => Value;
static public implicit operator Id(string value) => new Id(value);
static public explicit operator string(Id id) => id.Value;
static public implicit operator Id(Guid value) => new Id(value.ToString());
public static bool operator ==(Id a, Id b) => a.Equals(b);
public static bool operator !=(Id a, Id b) => !a.Equals(b);
}
}
using Alpaca.Testing;
using System;
using Xunit;
namespace Alpaca.Abstract.Test
{
public class IdTest
{
[Fact]
public void IdShouldProvideEquatable()
{
new EqualityTester<Id>("a&", "a&", "a&").Test();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
[InlineData("\n")]
[InlineData(" id-starts-with-space")]
[InlineData("id-ends-with-space ")]
public void IdHasGuardClause(string value)
{
Assert.ThrowsAny<ArgumentException>(() => new Id(value));
}
[Fact]
public void IdShouldWorkWithEqualOperator()
{
var a = new Id("a");
var b = new Id("a");
Assert.True(a == b);
Assert.False(a != b);
}
[Fact]
public void IdShouldReturnSameHashCodeForSameString()
{
var id = new Id("string");
var str = "string";
Assert.Equal(str.GetHashCode(), id.GetHashCode());
}
[Fact]
public void DefaultIdShouldBeEmptyString()
{
Assert.Equal("", Id.Empty.ToString());
}
[Fact]
public void IdShouldBeConvertableForString()
{
Id id = "string";
string str = (string)id;
Assert.Equal("string", id.ToString());
Assert.Equal("string", str);
}
[Fact]
public void IdShouldBeConvertableFromGuid()
{
Guid guid = Guid.NewGuid();
Id id = guid;
Assert.Equal(guid.ToString(), id.ToString());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment