From 7879ba6afc2c462e17bbef590b827bc34f5980e3 Mon Sep 17 00:00:00 2001
From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com>
Date: Thu, 24 Oct 2024 12:32:56 -0400
Subject: [PATCH] Added GeneratedIdentifier.
---
.github/workflows/publish.yml | 5 +
.../ComparisonBenchmarks.csproj | 10 +-
LightResults.Extensions.sln | 20 +
README.md | 5 +
docfx/docs/generatedidentifier.md | 7 +
.../Common/Declaration.cs | 59 ++
.../Common/DeclarationExtensions.cs | 53 ++
.../Common/DeclarationType.cs | 23 +
.../Common/EquatableImmutableArray.cs | 15 +
.../Common/EquatableImmutableArray`1.cs | 87 +++
.../IncrementalValueProviderExtensions.cs | 13 +
.../Common/SymbolExtensions.cs | 100 ++++
.../GeneratedIdentifierSourceGenerator.cs | 527 ++++++++++++++++++
...ults.Extensions.GeneratedIdentifier.csproj | 85 +++
.../Properties/launchSettings.json | 9 +
.../Identifiers/TestGuidId.cs | 6 +
.../Identifiers/TestIntId.cs | 6 +
.../Identifiers/TestShortId.cs | 6 +
.../Interfaces/ICloneableValueObject.cs | 9 +
.../Interfaces/IConvertibleValueObject.cs | 17 +
.../Interfaces/ICreatableValueObject.cs | 17 +
.../Interfaces/IParsableValueObject.cs | 19 +
.../Interfaces/IValueObject.cs | 14 +
.../ValueObjectValidationException.cs | 27 +
...nsions.GeneratedIdentifier.Fixtures.csproj | 28 +
.../Program.cs | 8 +
.../Properties/launchSettings.json | 12 +
...eGuidIdentifier_WithNamespace.verified.txt | 230 ++++++++
...idIdentifier_WithoutNamespace.verified.txt | 228 ++++++++
...teIntIdentifier_WithNamespace.verified.txt | 233 ++++++++
...ntIdentifier_WithoutNamespace.verified.txt | 231 ++++++++
...ShortIdentifier_WithNamespace.verified.txt | 233 ++++++++
...rtIdentifier_WithoutNamespace.verified.txt | 231 ++++++++
...GeneratedIdentifierSourceGeneratorTests.cs | 104 ++++
...xtensions.GeneratedIdentifier.Tests.csproj | 66 +++
.../ModuleInitializer.cs | 16 +
.../TestGuidIdTest.cs | 329 +++++++++++
.../TestIntIdTest.cs | 353 ++++++++++++
.../TestShortIdTest.cs | 353 ++++++++++++
tools/Benchmarks/Benchmarks.csproj | 4 +-
40 files changed, 3791 insertions(+), 7 deletions(-)
create mode 100644 docfx/docs/generatedidentifier.md
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/Declaration.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationExtensions.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationType.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray`1.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/IncrementalValueProviderExtensions.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Common/SymbolExtensions.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/GeneratedIdentifierSourceGenerator.cs
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/LightResults.Extensions.GeneratedIdentifier.csproj
create mode 100644 src/LightResults.Extensions.GeneratedIdentifier/Properties/launchSettings.json
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestGuidId.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestIntId.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestShortId.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICloneableValueObject.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IConvertibleValueObject.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICreatableValueObject.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IParsableValueObject.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IValueObject.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ValueObjectValidationException.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/LightResults.Extensions.GeneratedIdentifier.Fixtures.csproj
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Program.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Properties/launchSettings.json
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithNamespace.verified.txt
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithoutNamespace.verified.txt
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithNamespace.verified.txt
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithoutNamespace.verified.txt
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithNamespace.verified.txt
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithoutNamespace.verified.txt
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/LightResults.Extensions.GeneratedIdentifier.Tests.csproj
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/ModuleInitializer.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestGuidIdTest.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestIntIdTest.cs
create mode 100644 tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestShortIdTest.cs
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 478276d..f8e6e1a 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -60,5 +60,10 @@ jobs:
dotnet build ./src/LightResults.Extensions.Json/LightResults.Extensions.Json.csproj --configuration Release --no-restore
dotnet pack ./src/LightResults.Extensions.Json/LightResults.Extensions.Json.csproj --configuration Release --no-build --output .
+ - name: Pack GeneratedIdentifier
+ run: |
+ dotnet build ./src/LightResults.Extensions.Json/LightResults.Extensions.GeneratedIdentifier.csproj --configuration Release --no-restore
+ dotnet pack ./src/LightResults.Extensions.Json/LightResults.Extensions.GeneratedIdentifier.csproj --configuration Release --no-build --output .
+
- name: Push to NuGet
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/ComparisonBenchmarks/ComparisonBenchmarks.csproj b/ComparisonBenchmarks/ComparisonBenchmarks.csproj
index bf78d27..8ae5d28 100644
--- a/ComparisonBenchmarks/ComparisonBenchmarks.csproj
+++ b/ComparisonBenchmarks/ComparisonBenchmarks.csproj
@@ -11,14 +11,14 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/LightResults.Extensions.sln b/LightResults.Extensions.sln
index dacdd2c..4fb9a40 100644
--- a/LightResults.Extensions.sln
+++ b/LightResults.Extensions.sln
@@ -20,6 +20,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "tools\Benchma
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightResults.Extensions.Operations.Tests", "tests\LightResults.Extensions.Operations.Tests\LightResults.Extensions.Operations.Tests.csproj", "{D4E8F259-596E-412D-A757-769383865764}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightResults.Extensions.GeneratedIdentifier", "src\LightResults.Extensions.GeneratedIdentifier\LightResults.Extensions.GeneratedIdentifier.csproj", "{2D558C96-F052-4959-B996-5B03A66E4FCF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightResults.Extensions.GeneratedIdentifier.Fixtures", "tests\LightResults.Extensions.GeneratedIdentifier.Fixtures\LightResults.Extensions.GeneratedIdentifier.Fixtures.csproj", "{06BDEA4C-9036-44B7-AB44-DBD088556726}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightResults.Extensions.GeneratedIdentifier.Tests", "tests\LightResults.Extensions.GeneratedIdentifier.Tests\LightResults.Extensions.GeneratedIdentifier.Tests.csproj", "{45AF3136-2F1A-48C5-A76F-52635D7F0B90}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -58,6 +64,18 @@ Global
{D4E8F259-596E-412D-A757-769383865764}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4E8F259-596E-412D-A757-769383865764}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4E8F259-596E-412D-A757-769383865764}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D558C96-F052-4959-B996-5B03A66E4FCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D558C96-F052-4959-B996-5B03A66E4FCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D558C96-F052-4959-B996-5B03A66E4FCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D558C96-F052-4959-B996-5B03A66E4FCF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06BDEA4C-9036-44B7-AB44-DBD088556726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06BDEA4C-9036-44B7-AB44-DBD088556726}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06BDEA4C-9036-44B7-AB44-DBD088556726}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06BDEA4C-9036-44B7-AB44-DBD088556726}.Release|Any CPU.Build.0 = Release|Any CPU
+ {45AF3136-2F1A-48C5-A76F-52635D7F0B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {45AF3136-2F1A-48C5-A76F-52635D7F0B90}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {45AF3136-2F1A-48C5-A76F-52635D7F0B90}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45AF3136-2F1A-48C5-A76F-52635D7F0B90}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{93C8BC67-7F34-4935-A671-F0B12AEDB072} = {A6C9FED0-42B6-488C-8961-DE13F291434B}
@@ -65,5 +83,7 @@ Global
{93C8BC67-7F34-4935-A671-F0B12AEDB072} = {A6C9FED0-42B6-488C-8961-DE13F291434B}
{6DC31D8F-B71A-4A08-A892-5593F54EB82C} = {D10E009A-B3B8-4FB3-8B01-F21C383CD513}
{D4E8F259-596E-412D-A757-769383865764} = {A6C9FED0-42B6-488C-8961-DE13F291434B}
+ {06BDEA4C-9036-44B7-AB44-DBD088556726} = {A6C9FED0-42B6-488C-8961-DE13F291434B}
+ {45AF3136-2F1A-48C5-A76F-52635D7F0B90} = {A6C9FED0-42B6-488C-8961-DE13F291434B}
EndGlobalSection
EndGlobal
diff --git a/README.md b/README.md
index 9f88748..38714d0 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,11 @@ Extensions for [LightResults](https://github.com/jscarle/LightResults), an extre
[![nuget](https://img.shields.io/nuget/v/LightResults.Extensions.Json)](https://www.nuget.org/packages/LightResults.Extensions.Json)
[![downloads](https://img.shields.io/nuget/dt/LightResults.Extensions.Json)](https://www.nuget.org/packages/LightResults.Extensions.Json)
+- [Json](https://jscarle.github.io/LightResults.Extensions/docs/generatedidentifier.html) - Provides strongly-typed identifiers.
+
+ [![nuget](https://img.shields.io/nuget/v/LightResults.Extensions.GeneratedIdentifier)](https://www.nuget.org/packages/LightResults.Extensions.GeneratedIdentifier)
+ [![downloads](https://img.shields.io/nuget/dt/LightResults.Extensions.GeneratedIdentifier)](https://www.nuget.org/packages/LightResults.Extensions.GeneratedIdentifier)
+
## Documentation
Make sure to [read the docs](https://jscarle.github.io/LightResults.Extensions/) for the full API.
diff --git a/docfx/docs/generatedidentifier.md b/docfx/docs/generatedidentifier.md
new file mode 100644
index 0000000..01b4f9f
--- /dev/null
+++ b/docfx/docs/generatedidentifier.md
@@ -0,0 +1,7 @@
+# GeneratedIdentifier
+
+A Roslyn source generator that provides strongly-typed identifiers using LightResults.
+
+[![main](https://img.shields.io/github/actions/workflow/status/jscarle/LightResults.Extensions/main.yml?logo=github)](https://github.com/jscarle/LightResults.Extensions)
+[![nuget](https://img.shields.io/nuget/v/LightResults.Extensions.GeneratedIdentifier)](https://www.nuget.org/packages/LightResults.Extensions.GeneratedIdentifier)
+[![downloads](https://img.shields.io/nuget/dt/LightResults.Extensions.GeneratedIdentifier)](https://www.nuget.org/packages/LightResults.Extensions.GeneratedIdentifier)
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/Declaration.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/Declaration.cs
new file mode 100644
index 0000000..e562850
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/Declaration.cs
@@ -0,0 +1,59 @@
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+/// Represents a declaration.
+public sealed record Declaration
+{
+ /// Initializes a new instance of the class with the specified type, name, and generic parameters.
+ /// The type of declaration.
+ /// The name of the declaration.
+ /// A read-only list of generic parameter names, or an empty list if not generic.
+ internal Declaration(DeclarationType type, string name, EquatableImmutableArray genericParameters)
+ {
+ Type = type;
+ Name = name;
+ GenericParameters = genericParameters;
+ }
+
+ /// Gets the type of declaration.
+ public DeclarationType Type { get; }
+
+ /// Gets the name of the declaration.
+ public string Name { get; }
+
+ /// Gets a read-only list of generic parameter names for generic declarations, or an empty list otherwise.
+ public EquatableImmutableArray GenericParameters { get; }
+
+ /// Returns a string representation of the declaration in the appropriate format for its type.
+ /// A string representation of the declaration.
+ public override string ToString()
+ {
+ switch (Type)
+ {
+ case DeclarationType.Namespace:
+ return $"namespace {Name}";
+ case DeclarationType.Interface:
+ case DeclarationType.Class:
+ case DeclarationType.Record:
+ case DeclarationType.Struct:
+ case DeclarationType.RecordStruct:
+ var keyword = ToKeyword(Type);
+ return GenericParameters.Count == 0 ? $"{keyword} {Name}" : $"{keyword} {Name}<{string.Join(", ", GenericParameters)}>";
+ default:
+ return base.ToString();
+ }
+ }
+
+ private static string ToKeyword(DeclarationType declarationType)
+ {
+ return declarationType switch
+ {
+ DeclarationType.Namespace => "namespace",
+ DeclarationType.Interface => "interface",
+ DeclarationType.Class => "class",
+ DeclarationType.Record => "record",
+ DeclarationType.Struct => "struct",
+ DeclarationType.RecordStruct => "record struct",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationExtensions.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationExtensions.cs
new file mode 100644
index 0000000..16db73b
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationExtensions.cs
@@ -0,0 +1,53 @@
+using System.Text;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+/// Provides extension methods for working with declarations.
+internal static class DeclarationExtensions
+{
+ /// Converts a list of declarations to their corresponding namespace.
+ /// The list of declarations to convert.
+ /// The namespace represented by the declarations.
+ public static string ToNamespace(this EquatableImmutableArray declarations)
+ {
+ var builder = new StringBuilder();
+
+ for (var index = 0; index < declarations.Count; index++)
+ {
+ var declaration = declarations[index];
+ if (declaration.Type != DeclarationType.Namespace)
+ continue;
+
+ if (builder.Length > 0)
+ builder.Append('.');
+ builder.Append(declaration.Name);
+ }
+
+ return builder.ToString();
+ }
+
+ /// Converts a list of declarations to their fully qualified name.
+ /// The list of declarations to convert.
+ /// The fully qualified name represented by the declarations.
+ public static string ToFullyQualifiedName(this EquatableImmutableArray declarations)
+ {
+ var builder = new StringBuilder();
+
+ for (var index = 0; index < declarations.Count; index++)
+ {
+ var declaration = declarations[index];
+
+ if (builder.Length > 0)
+ builder.Append('.');
+ builder.Append(declaration.Name);
+
+ if (declaration.GenericParameters.Count == 0)
+ continue;
+
+ builder.Append('`');
+ builder.Append(declaration.GenericParameters.Count);
+ }
+
+ return builder.ToString();
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationType.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationType.cs
new file mode 100644
index 0000000..7d971d1
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/DeclarationType.cs
@@ -0,0 +1,23 @@
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+/// Specifies the kind of declaration.
+public enum DeclarationType
+{
+ /// Represents a namespace declaration.
+ Namespace = 0,
+
+ /// Represents an interface declaration.
+ Interface = 1,
+
+ /// Represents a class declaration.
+ Class = 2,
+
+ /// Represents a record declaration.
+ Record = 3,
+
+ /// Represents a struct declaration.
+ Struct = 4,
+
+ /// Represents a record struct declaration.
+ RecordStruct = 5,
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray.cs
new file mode 100644
index 0000000..6664e9d
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray.cs
@@ -0,0 +1,15 @@
+using System.Collections.Immutable;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+/// Provides extension methods to convert various collections to an .
+internal static class EquatableImmutableArray
+{
+ /// Converts an to an .
+ /// The to convert.
+ /// An containing the same elements as the original enumerable.
+ public static EquatableImmutableArray ToEquatableImmutableArray(this IEnumerable enumerable)
+ {
+ return new EquatableImmutableArray(enumerable.ToImmutableArray());
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray`1.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray`1.cs
new file mode 100644
index 0000000..ccd0315
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/EquatableImmutableArray`1.cs
@@ -0,0 +1,87 @@
+using System.Collections;
+using System.Collections.Immutable;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+/// Represents an immutable array that implements .
+/// The type of elements in the array.
+public readonly struct EquatableImmutableArray : IEquatable>, IReadOnlyList
+{
+ /// Gets an empty .
+ internal static EquatableImmutableArray Empty { get; } = new(ImmutableArray.Empty);
+
+ /// Gets the number of elements in the array.
+ public int Count => Array.Length;
+
+ /// Gets the element at the specified index.
+ /// The zero-based index of the element to get.
+ /// The element at the specified index.
+ public T this[int index] => Array[index];
+
+ private ImmutableArray Array => _array ?? ImmutableArray.Empty;
+ private readonly ImmutableArray? _array;
+
+ internal EquatableImmutableArray(ImmutableArray? array)
+ {
+ _array = array;
+ }
+
+ ///
+ public bool Equals(EquatableImmutableArray other)
+ {
+ return this.SequenceEqual(other);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is EquatableImmutableArray other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+
+ foreach (var item in Array)
+ hashCode.Add(item);
+
+ return hashCode.ToHashCode();
+ }
+
+ /// Determines whether two instances are equal.
+ /// The first to compare.
+ /// The second to compare.
+ /// if the two instances are equal; otherwise, .
+ public static bool operator ==(EquatableImmutableArray left, EquatableImmutableArray right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances are not equal.
+ /// The first to compare.
+ /// The second to compare.
+ /// if the two instances are not equal; otherwise, .
+ public static bool operator !=(EquatableImmutableArray left, EquatableImmutableArray right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ // ReSharper disable once ForCanBeConvertedToForeach
+ // ReSharper disable once LoopCanBeConvertedToQuery
+ for (var index = 0; index < Array.Length; index++)
+ {
+ var item = Array[index];
+ yield return item;
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/IncrementalValueProviderExtensions.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/IncrementalValueProviderExtensions.cs
new file mode 100644
index 0000000..2f18bd3
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/IncrementalValueProviderExtensions.cs
@@ -0,0 +1,13 @@
+using Microsoft.CodeAnalysis;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+internal static class IncrementalValueProviderExtensions
+{
+ public static IncrementalValuesProvider WhereNotNull(this IncrementalValuesProvider source)
+ where TSource : struct
+ {
+ return source.Where(x => x is not null)
+ .Select((x, _) => x!.Value);
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Common/SymbolExtensions.cs b/src/LightResults.Extensions.GeneratedIdentifier/Common/SymbolExtensions.cs
new file mode 100644
index 0000000..7460ab5
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Common/SymbolExtensions.cs
@@ -0,0 +1,100 @@
+using Microsoft.CodeAnalysis;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Common;
+
+/// Provides extension methods for working with symbols.
+internal static class SymbolExtensions
+{
+ /// Gets a list of declarations representing the hierarchy containing the given symbol.
+ /// The to get the containing declarations for.
+ /// The cancellation token.
+ /// An of objects representing the hierarchy.
+ public static EquatableImmutableArray GetContainingDeclarations(this ISymbol symbol, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var declarations = new Stack();
+
+ BuildContainingSymbolHierarchy(symbol, in declarations, cancellationToken);
+
+ return declarations.ToEquatableImmutableArray();
+ }
+
+ private static void BuildContainingSymbolHierarchy(ISymbol symbol, in Stack declarations, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ switch (symbol.ContainingSymbol)
+ {
+ case INamespaceSymbol namespaceSymbol:
+ BuildNamespaceHierarchy(namespaceSymbol, declarations, cancellationToken);
+ break;
+ case INamedTypeSymbol namedTypeSymbol:
+ BuildTypeHierarchy(namedTypeSymbol, declarations, cancellationToken);
+ break;
+ }
+ }
+
+ private static void BuildNamespaceHierarchy(INamespaceSymbol symbol, in Stack declarations, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (!symbol.IsGlobalNamespace)
+ {
+ var namespaceDeclaration = new Declaration(DeclarationType.Namespace, symbol.Name, EquatableImmutableArray.Empty);
+ declarations.Push(namespaceDeclaration);
+ }
+
+ if (symbol.ContainingNamespace is not null && !symbol.ContainingNamespace.IsGlobalNamespace)
+ BuildNamespaceHierarchy(symbol.ContainingNamespace, declarations, cancellationToken);
+ }
+
+ private static void BuildTypeHierarchy(INamedTypeSymbol symbol, in Stack declarations, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var declarationType = symbol.GetDeclarationType(cancellationToken);
+ if (declarationType is null)
+ return;
+
+ var genericTypeParameters = symbol.GetGenericTypeParameters(cancellationToken);
+
+ var typeDeclaration = new Declaration(declarationType.Value, symbol.Name, genericTypeParameters);
+ declarations.Push(typeDeclaration);
+
+ BuildContainingSymbolHierarchy(symbol, declarations, cancellationToken);
+ }
+
+ private static EquatableImmutableArray GetGenericTypeParameters(this INamedTypeSymbol symbol, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (!symbol.IsGenericType)
+ return EquatableImmutableArray.Empty;
+
+ var genericTypeParameters = new List();
+
+ for (var index = 0; index < symbol.TypeParameters.Length; index++)
+ {
+ var typeParameter = symbol.TypeParameters[index];
+ genericTypeParameters.Add(typeParameter.Name);
+ }
+
+ return genericTypeParameters.ToEquatableImmutableArray();
+ }
+
+ private static DeclarationType? GetDeclarationType(this ITypeSymbol symbol, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ return symbol switch
+ {
+ { IsReferenceType: true, TypeKind: TypeKind.Interface } => DeclarationType.Interface,
+ { IsReferenceType: true, IsRecord: true } => DeclarationType.Record,
+ { IsReferenceType: true } => DeclarationType.Class,
+ { IsValueType: true, IsRecord: true } => DeclarationType.RecordStruct,
+ { IsValueType: true } => DeclarationType.Struct,
+ _ => null,
+ };
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/GeneratedIdentifierSourceGenerator.cs b/src/LightResults.Extensions.GeneratedIdentifier/GeneratedIdentifierSourceGenerator.cs
new file mode 100644
index 0000000..59025a1
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/GeneratedIdentifierSourceGenerator.cs
@@ -0,0 +1,527 @@
+using System.Collections.Immutable;
+using System.Text;
+using LightResults.Extensions.GeneratedIdentifier.Common;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+
+namespace LightResults.Extensions.GeneratedIdentifier;
+
+[Generator]
+public sealed class GeneratedIdentifierSourceGenerator : IIncrementalGenerator
+{
+ private const string AttributesNamespace = "LightResults.Extensions.GeneratedIdentifier";
+ private const string GeneratedIdentifierAttributeName = "GeneratedIdentifierAttribute";
+ private const string GeneratedIdentifierAttributeFullyQualifiedName = $"{AttributesNamespace}.{GeneratedIdentifierAttributeName}`1";
+ private const string GeneratedIdentifierAttributeHint = $"{GeneratedIdentifierAttributeFullyQualifiedName}.g.cs";
+
+ private static readonly string FileHeader = $"""
+ //-----------------------------------------------------------------------------
+ //
+ // This code was generated by {nameof(GeneratedIdentifierSourceGenerator)} which
+ // can be found in the {typeof(GeneratedIdentifierSourceGenerator).Namespace} namespace.
+ //
+ // Changes to this file may cause incorrect behavior
+ // and will be lost if the code is regenerated.
+ //
+ //-----------------------------------------------------------------------------
+ #nullable enable
+ """;
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput(RegisterAttributes);
+
+ var generatedIdentifiers = context.SyntaxProvider
+ .ForAttributeWithMetadataName(GeneratedIdentifierAttributeFullyQualifiedName, Filter, Transform)
+ .WhereNotNull()
+ .Collect();
+
+ context.RegisterSourceOutput(generatedIdentifiers, GenerateIdentifier);
+ }
+
+ private static void RegisterAttributes(IncrementalGeneratorPostInitializationContext context)
+ {
+ var source = $"""
+ {FileHeader}
+
+ using System;
+
+ namespace {AttributesNamespace};
+
+ [AttributeUsage(AttributeTargets.Struct)]
+ public sealed class {GeneratedIdentifierAttributeName} : Attribute;
+ """;
+ context.AddSource(GeneratedIdentifierAttributeHint, SourceText.From(source, Encoding.UTF8));
+ }
+
+ private static bool Filter(SyntaxNode syntaxNode, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ return syntaxNode.IsKind(SyntaxKind.StructDeclaration);
+ }
+
+ private static Identifier? Transform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (context.TargetSymbol is not INamedTypeSymbol namedTypeSymbol)
+ return null;
+
+ var containingDeclarations = namedTypeSymbol.GetContainingDeclarations(cancellationToken);
+ var symbolName = namedTypeSymbol.Name;
+
+ var attribute = context.Attributes[0].AttributeClass!;
+ if (attribute.TypeArguments.Length != 1)
+ return null;
+
+ var typeArgument = attribute.TypeArguments[0];
+
+ string? declaredValueType;
+ string? fullValueType;
+ switch (typeArgument.SpecialType)
+ {
+ case SpecialType.System_Int16:
+ declaredValueType = "short";
+ fullValueType = "Int16";
+ break;
+ case SpecialType.System_Int32:
+ declaredValueType = "int";
+ fullValueType = "Int32";
+ break;
+ case SpecialType.System_Int64:
+ declaredValueType = "long";
+ fullValueType = "Int64";
+ break;
+ default:
+ if (typeArgument is not { Name: "Guid", ContainingNamespace: { Name: "System", ContainingNamespace.IsGlobalNamespace: true } })
+ return null;
+ declaredValueType = "Guid";
+ fullValueType = "Guid";
+ break;
+ }
+
+ var symbol = new Identifier(containingDeclarations, symbolName, declaredValueType, fullValueType);
+
+ return symbol;
+ }
+
+ private static void GenerateIdentifier(SourceProductionContext context, ImmutableArray generatedIdentifiers)
+ {
+ foreach (var symbol in generatedIdentifiers)
+ {
+ var structNamespace = symbol.ContainingDeclarations.ToNamespace();
+ var structName = symbol.Name;
+ var declaredValueType = symbol.DeclaredValueType;
+ var fullValueType = symbol.FullValueType;
+
+ var source = new StringBuilder();
+
+ source.AppendLine($"""
+ //-----------------------------------------------------------------------------
+ //
+ // This code was generated by {nameof(GeneratedIdentifierSourceGenerator)} which
+ // can be found in the {typeof(GeneratedIdentifierSourceGenerator).Namespace} namespace.
+ //
+ // Changes to this file may cause incorrect behavior
+ // and will be lost if the code is regenerated.
+ //
+ //-----------------------------------------------------------------------------
+
+ #nullable enable
+
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Text.Json;
+ using System.Text.Json.Serialization;
+ using LightResults;
+ using GeneratedIdentifier.Common.ValueObjects;
+
+ """
+ );
+
+ if (structNamespace.Length > 0)
+ source.AppendLine($"""
+ namespace {structNamespace};
+
+ """
+ );
+
+ source.AppendLine($$"""
+ [TypeConverter(typeof({{structName}}TypeConverter))]
+ [JsonConverter(typeof({{structName}}JsonConverter))]
+ readonly partial struct {{structName}} :
+ ICreatableValueObject<{{declaredValueType}}, {{structName}}>,
+ IParsableValueObject<{{structName}}>,
+ IValueObject<{{declaredValueType}}, {{structName}}>,
+ IComparable<{{structName}}>,
+ IComparable
+ {
+ """
+ );
+
+ source.AppendLine("""
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ """
+ );
+
+ source.AppendLine($"""
+ {declaredValueType} IValueObject<{declaredValueType}, {structName}>.Value => _value;
+
+ private readonly {declaredValueType} _value;
+
+ """
+ );
+
+ source.AppendLine($$"""
+ private {{structName}}({{declaredValueType}} value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public static {{structName}} Create({{declaredValueType}} value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public static Result<{{structName}}> TryCreate({{declaredValueType}} value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail<{{structName}}>(error);
+
+ return Result.Ok<{{structName}}>(new {{structName}}(value, true));
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public static {{structName}} Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public static Result<{{structName}}> TryParse(string s)
+ {
+ if ({{declaredValueType}}.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail<{{structName}}>("The string is not a valid identifier.");
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public static bool TryParse(string s, out {{structName}} identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out {{structName}} identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public bool Equals({{structName}} other)
+ {
+ return _value == other._value;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is {{structName}} other && Equals(other);
+ }
+
+ """
+ );
+
+ if (declaredValueType == "Guid")
+ source.AppendLine("""
+ ///
+ public override int GetHashCode()
+ {
+ return _value.GetHashCode();
+ }
+
+ """
+ );
+ else
+ source.AppendLine("""
+ ///
+ public override int GetHashCode()
+ {
+ return _value;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==({{structName}} left, {{structName}} right)
+ {
+ return left.Equals(right);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=({{structName}} left, {{structName}} right)
+ {
+ return !left.Equals(right);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public int CompareTo({{structName}} other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is {{structName}} other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof({{structName}})}");
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <({{structName}} left, {{structName}} right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >({{structName}} left, {{structName}} right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=({{structName}} left, {{structName}} right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=({{structName}} left, {{structName}} right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public {{declaredValueType}} To{{fullValueType}}()
+ {
+ return _value;
+ }
+
+ """
+ );
+
+ if (declaredValueType == "Guid")
+ source.AppendLine("""
+ ///
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+
+ """
+ );
+ else
+ source.AppendLine("""
+ ///
+ public override string ToString()
+ {
+ return _value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ """
+ );
+
+ if (declaredValueType == "Guid")
+ source.AppendLine($$"""
+ private static Result Validate({{declaredValueType}} value)
+ {
+ return Result.Ok();
+ }
+ """
+ );
+ else
+ source.AppendLine($$"""
+ private static Result Validate({{declaredValueType}} value)
+ {
+ if (value < 0)
+ return Result.Fail("The value must be equal to or greater than zero.");
+
+ return Result.Ok();
+ }
+ """
+ );
+
+ source.AppendLine("""
+ }
+
+ """
+ );
+
+ source.AppendLine($$"""
+ public class {{structName}}TypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof({{declaredValueType}}) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is {{declaredValueType}} identifierValue)
+ return {{structName}}.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+
+ """
+ );
+ source.AppendLine($$"""
+ public class {{structName}}JsonConverter : JsonConverter<{{structName}}>
+ {
+ public override void Write(Utf8JsonWriter writer, {{structName}} identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject<{{declaredValueType}}, {{structName}}>)identifier).Value;
+ """
+ );
+
+ if (declaredValueType == "Guid")
+ source.AppendLine(""" writer.WriteStringValue(value.ToString());""");
+ else
+ source.AppendLine(""" writer.WriteNumberValue(value);""");
+
+ source.Append($$"""
+ }
+
+ public override {{structName}} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.Get{{fullValueType}}();
+ return {{structName}}.Create(value);
+ }
+ }
+
+ """
+ );
+
+ var hint = $"{symbol.ContainingDeclarations.ToFullyQualifiedName()}.{symbol.Name}.g.cs";
+ context.AddSource(hint, source.ToString());
+ }
+ }
+
+ private readonly record struct Identifier(
+ EquatableImmutableArray ContainingDeclarations,
+ string Name,
+ string DeclaredValueType,
+ string FullValueType
+ )
+ {
+ public EquatableImmutableArray ContainingDeclarations { get; } = ContainingDeclarations;
+ public string Name { get; } = Name;
+ public string DeclaredValueType { get; } = DeclaredValueType;
+ public string FullValueType { get; } = FullValueType;
+ }
+}
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/LightResults.Extensions.GeneratedIdentifier.csproj b/src/LightResults.Extensions.GeneratedIdentifier/LightResults.Extensions.GeneratedIdentifier.csproj
new file mode 100644
index 0000000..f2bcf04
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/LightResults.Extensions.GeneratedIdentifier.csproj
@@ -0,0 +1,85 @@
+
+
+
+
+ LightResults.Extensions.GeneratedIdentifier
+ netstandard2.0
+ enable
+ enable
+ latest
+
+
+
+
+ latest-all
+ true
+ true
+ true
+ $(NoWarn);NU5128
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ LightResults.Extensions.GeneratedIdentifier
+ 9.0.0-preview.1
+ 9.0.0.0
+ 9.0.0.0
+ en-US
+ false
+
+
+
+
+ true
+ LightResults.Extensions.GeneratedIdentifier
+ LightResults.Extensions.GeneratedIdentifier
+ A Roslyn source generator that automatically creates strongly-typed identifier structs.
+ README.md
+ https://github.com/jscarle/LightResults.Extensions
+ result results pattern lightresults source-generator strongly-typed identifie
+ https://github.com/jscarle/LightResults.Extensions
+ git
+ Jean-Sebastien Carle
+ Copyright © Jean-Sebastien Carle 2024
+ LICENSE.md
+ Icon.png
+ true
+ snupkg
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(GetTargetPathDependsOn);GetDependencyTargetPaths
+
+
+
+
+
+
+
+
+
diff --git a/src/LightResults.Extensions.GeneratedIdentifier/Properties/launchSettings.json b/src/LightResults.Extensions.GeneratedIdentifier/Properties/launchSettings.json
new file mode 100644
index 0000000..29c3748
--- /dev/null
+++ b/src/LightResults.Extensions.GeneratedIdentifier/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "DebugRoslynSourceGenerator": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "../GeneratedIdentifier.Sample/GeneratedIdentifier.Sample.csproj"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestGuidId.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestGuidId.cs
new file mode 100644
index 0000000..ecfebad
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestGuidId.cs
@@ -0,0 +1,6 @@
+using GeneratedIdentifier;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Fixtures.Identifiers;
+
+[GeneratedIdentifier]
+public partial struct TestGuidId;
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestIntId.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestIntId.cs
new file mode 100644
index 0000000..e6c515b
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestIntId.cs
@@ -0,0 +1,6 @@
+using GeneratedIdentifier;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Fixtures.Identifiers;
+
+[GeneratedIdentifier]
+public partial struct TestIntId;
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestShortId.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestShortId.cs
new file mode 100644
index 0000000..baee8e1
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Identifiers/TestShortId.cs
@@ -0,0 +1,6 @@
+using GeneratedIdentifier;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Fixtures.Identifiers;
+
+[GeneratedIdentifier]
+public partial struct TestShortId;
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICloneableValueObject.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICloneableValueObject.cs
new file mode 100644
index 0000000..7651e14
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICloneableValueObject.cs
@@ -0,0 +1,9 @@
+// Resharper disable CheckNamespace
+
+namespace GeneratedIdentifier.Common.ValueObjects;
+
+public interface ICloneableValueObject : IValueObject
+ where TSelf : notnull
+{
+ TSelf Clone();
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IConvertibleValueObject.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IConvertibleValueObject.cs
new file mode 100644
index 0000000..0e1e792
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IConvertibleValueObject.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+using LightResults;
+
+// Resharper disable CheckNamespace
+namespace GeneratedIdentifier.Common.ValueObjects;
+
+[SuppressMessage(
+ "Design",
+ "CA1000: Do not declare static members on generic types",
+ Justification = "This is required for handling value objects generically."
+)]
+public interface IConvertibleValueObject : IValueObject
+ where TSelf : notnull
+{
+ static abstract TSelf Convert(TSource source);
+ static abstract Result TryConvert(TSource source);
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICreatableValueObject.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICreatableValueObject.cs
new file mode 100644
index 0000000..a203424
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ICreatableValueObject.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+using LightResults;
+
+// Resharper disable CheckNamespace
+namespace GeneratedIdentifier.Common.ValueObjects;
+
+[SuppressMessage(
+ "Design",
+ "CA1000: Do not declare static members on generic types",
+ Justification = "This is required for handling value objects generically."
+)]
+public interface ICreatableValueObject : IValueObject
+ where TSelf : notnull
+{
+ static abstract TSelf Create(TValue value);
+ static abstract Result TryCreate(TValue value);
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IParsableValueObject.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IParsableValueObject.cs
new file mode 100644
index 0000000..9979b84
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IParsableValueObject.cs
@@ -0,0 +1,19 @@
+using System.Diagnostics.CodeAnalysis;
+using LightResults;
+
+// Resharper disable CheckNamespace
+namespace GeneratedIdentifier.Common.ValueObjects;
+
+[SuppressMessage(
+ "Design",
+ "CA1000: Do not declare static members on generic types",
+ Justification = "This is required for handling value objects generically."
+)]
+public interface IParsableValueObject : IValueObject
+ where TSelf : notnull
+{
+ static abstract TSelf Parse(string s);
+ static abstract Result TryParse(string s);
+ static abstract bool TryParse(string s, out TSelf result);
+ static abstract bool TryParse(string s, IFormatProvider provider, out TSelf result);
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IValueObject.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IValueObject.cs
new file mode 100644
index 0000000..a9591f5
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/IValueObject.cs
@@ -0,0 +1,14 @@
+// Resharper disable CheckNamespace
+
+namespace GeneratedIdentifier.Common.ValueObjects;
+
+public interface IValueObject : IValueObject
+ where TSelf : notnull
+{
+ TValue Value { get; }
+}
+
+public interface IValueObject : IValueObject, IEquatable, IComparable, IComparable
+ where TSelf : notnull;
+
+public interface IValueObject;
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ValueObjectValidationException.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ValueObjectValidationException.cs
new file mode 100644
index 0000000..23fd614
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Interfaces/ValueObjectValidationException.cs
@@ -0,0 +1,27 @@
+using IResult = LightResults.IResult;
+
+// Resharper disable CheckNamespace
+namespace GeneratedIdentifier.Common.ValueObjects;
+
+public sealed class ValueObjectException : Exception
+{
+ public ValueObjectException()
+ {
+ }
+
+ public ValueObjectException(string message)
+ : base(message)
+ {
+ }
+
+ public ValueObjectException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public static void ThrowIfFailed(IResult result)
+ {
+ if (result.IsFailed(out var error))
+ throw new ValueObjectException(error.Message);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/LightResults.Extensions.GeneratedIdentifier.Fixtures.csproj b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/LightResults.Extensions.GeneratedIdentifier.Fixtures.csproj
new file mode 100644
index 0000000..931a9f0
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/LightResults.Extensions.GeneratedIdentifier.Fixtures.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+ enable
+ enable
+ latest
+ LightResults.Extensions.GeneratedIdentifier.Fixtures
+ LightResults.Extensions.GeneratedIdentifier.Fixtures
+ latest-Default
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Program.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Program.cs
new file mode 100644
index 0000000..5d25b47
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Program.cs
@@ -0,0 +1,8 @@
+namespace LightResults.Extensions.GeneratedIdentifier.Fixtures;
+
+public static class Program
+{
+ public static void Main()
+ {
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Properties/launchSettings.json b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Properties/launchSettings.json
new file mode 100644
index 0000000..b7cdb7f
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Fixtures/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "Api.SourceGenerators.Fixtures": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:59000;http://localhost:59001"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithNamespace.verified.txt b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithNamespace.verified.txt
new file mode 100644
index 0000000..0436efc
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithNamespace.verified.txt
@@ -0,0 +1,230 @@
+//-----------------------------------------------------------------------------
+//
+// This code was generated by GeneratedIdentifierSourceGenerator which
+// can be found in the LightResults.Extensions.GeneratedIdentifier namespace.
+//
+// Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated.
+//
+//-----------------------------------------------------------------------------
+
+#nullable enable
+
+using System.ComponentModel;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using LightResults;
+using GeneratedIdentifier.Common.ValueObjects;
+
+namespace MyProject.Identifiers;
+
+[TypeConverter(typeof(TestGuidIdTypeConverter))]
+[JsonConverter(typeof(TestGuidIdJsonConverter))]
+readonly partial struct TestGuidId :
+ ICreatableValueObject,
+ IParsableValueObject,
+ IValueObject,
+ IComparable,
+ IComparable
+{
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ Guid IValueObject.Value => _value;
+
+ private readonly Guid _value;
+
+ private TestGuidId(Guid value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ ///
+ public static TestGuidId Create(Guid value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryCreate(Guid value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail(error);
+
+ return Result.Ok(new TestGuidId(value, true));
+ }
+
+ ///
+ public static TestGuidId Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryParse(string s)
+ {
+ if (Guid.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail("The string is not a valid identifier.");
+ }
+
+ ///
+ public static bool TryParse(string s, out TestGuidId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out TestGuidId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public bool Equals(TestGuidId other)
+ {
+ return _value == other._value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is TestGuidId other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _value.GetHashCode();
+ }
+
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==(TestGuidId left, TestGuidId right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=(TestGuidId left, TestGuidId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public int CompareTo(TestGuidId other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is TestGuidId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TestGuidId)}");
+ }
+
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public Guid ToGuid()
+ {
+ return _value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+
+ private static Result Validate(Guid value)
+ {
+ return Result.Ok();
+ }
+}
+
+public class TestGuidIdTypeConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof(Guid) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is Guid identifierValue)
+ return TestGuidId.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+}
+
+public class TestGuidIdJsonConverter : JsonConverter
+{
+ public override void Write(Utf8JsonWriter writer, TestGuidId identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject)identifier).Value;
+ writer.WriteStringValue(value.ToString());
+ }
+
+ public override TestGuidId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetGuid();
+ return TestGuidId.Create(value);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithoutNamespace.verified.txt b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithoutNamespace.verified.txt
new file mode 100644
index 0000000..98ca6ee
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateGuidIdentifier_WithoutNamespace.verified.txt
@@ -0,0 +1,228 @@
+//-----------------------------------------------------------------------------
+//
+// This code was generated by GeneratedIdentifierSourceGenerator which
+// can be found in the LightResults.Extensions.GeneratedIdentifier namespace.
+//
+// Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated.
+//
+//-----------------------------------------------------------------------------
+
+#nullable enable
+
+using System.ComponentModel;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using LightResults;
+using GeneratedIdentifier.Common.ValueObjects;
+
+[TypeConverter(typeof(TestGuidIdTypeConverter))]
+[JsonConverter(typeof(TestGuidIdJsonConverter))]
+readonly partial struct TestGuidId :
+ ICreatableValueObject,
+ IParsableValueObject,
+ IValueObject,
+ IComparable,
+ IComparable
+{
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ Guid IValueObject.Value => _value;
+
+ private readonly Guid _value;
+
+ private TestGuidId(Guid value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ ///
+ public static TestGuidId Create(Guid value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryCreate(Guid value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail(error);
+
+ return Result.Ok(new TestGuidId(value, true));
+ }
+
+ ///
+ public static TestGuidId Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryParse(string s)
+ {
+ if (Guid.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail("The string is not a valid identifier.");
+ }
+
+ ///
+ public static bool TryParse(string s, out TestGuidId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out TestGuidId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public bool Equals(TestGuidId other)
+ {
+ return _value == other._value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is TestGuidId other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _value.GetHashCode();
+ }
+
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==(TestGuidId left, TestGuidId right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=(TestGuidId left, TestGuidId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public int CompareTo(TestGuidId other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is TestGuidId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TestGuidId)}");
+ }
+
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=(TestGuidId left, TestGuidId right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public Guid ToGuid()
+ {
+ return _value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+
+ private static Result Validate(Guid value)
+ {
+ return Result.Ok();
+ }
+}
+
+public class TestGuidIdTypeConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof(Guid) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is Guid identifierValue)
+ return TestGuidId.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+}
+
+public class TestGuidIdJsonConverter : JsonConverter
+{
+ public override void Write(Utf8JsonWriter writer, TestGuidId identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject)identifier).Value;
+ writer.WriteStringValue(value.ToString());
+ }
+
+ public override TestGuidId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetGuid();
+ return TestGuidId.Create(value);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithNamespace.verified.txt b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithNamespace.verified.txt
new file mode 100644
index 0000000..8d23b64
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithNamespace.verified.txt
@@ -0,0 +1,233 @@
+//-----------------------------------------------------------------------------
+//
+// This code was generated by GeneratedIdentifierSourceGenerator which
+// can be found in the LightResults.Extensions.GeneratedIdentifier namespace.
+//
+// Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated.
+//
+//-----------------------------------------------------------------------------
+
+#nullable enable
+
+using System.ComponentModel;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using LightResults;
+using GeneratedIdentifier.Common.ValueObjects;
+
+namespace MyProject.Identifiers;
+
+[TypeConverter(typeof(TestIntIdTypeConverter))]
+[JsonConverter(typeof(TestIntIdJsonConverter))]
+readonly partial struct TestIntId :
+ ICreatableValueObject,
+ IParsableValueObject,
+ IValueObject,
+ IComparable,
+ IComparable
+{
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ int IValueObject.Value => _value;
+
+ private readonly int _value;
+
+ private TestIntId(int value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ ///
+ public static TestIntId Create(int value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryCreate(int value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail(error);
+
+ return Result.Ok(new TestIntId(value, true));
+ }
+
+ ///
+ public static TestIntId Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryParse(string s)
+ {
+ if (int.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail("The string is not a valid identifier.");
+ }
+
+ ///
+ public static bool TryParse(string s, out TestIntId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out TestIntId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public bool Equals(TestIntId other)
+ {
+ return _value == other._value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is TestIntId other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _value;
+ }
+
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==(TestIntId left, TestIntId right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=(TestIntId left, TestIntId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public int CompareTo(TestIntId other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is TestIntId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TestIntId)}");
+ }
+
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public int ToInt32()
+ {
+ return _value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ private static Result Validate(int value)
+ {
+ if (value < 0)
+ return Result.Fail("The value must be equal to or greater than zero.");
+
+ return Result.Ok();
+ }
+}
+
+public class TestIntIdTypeConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof(int) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is int identifierValue)
+ return TestIntId.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+}
+
+public class TestIntIdJsonConverter : JsonConverter
+{
+ public override void Write(Utf8JsonWriter writer, TestIntId identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject)identifier).Value;
+ writer.WriteNumberValue(value);
+ }
+
+ public override TestIntId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetInt32();
+ return TestIntId.Create(value);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithoutNamespace.verified.txt b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithoutNamespace.verified.txt
new file mode 100644
index 0000000..a90adba
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateIntIdentifier_WithoutNamespace.verified.txt
@@ -0,0 +1,231 @@
+//-----------------------------------------------------------------------------
+//
+// This code was generated by GeneratedIdentifierSourceGenerator which
+// can be found in the LightResults.Extensions.GeneratedIdentifier namespace.
+//
+// Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated.
+//
+//-----------------------------------------------------------------------------
+
+#nullable enable
+
+using System.ComponentModel;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using LightResults;
+using GeneratedIdentifier.Common.ValueObjects;
+
+[TypeConverter(typeof(TestIntIdTypeConverter))]
+[JsonConverter(typeof(TestIntIdJsonConverter))]
+readonly partial struct TestIntId :
+ ICreatableValueObject,
+ IParsableValueObject,
+ IValueObject,
+ IComparable,
+ IComparable
+{
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ int IValueObject.Value => _value;
+
+ private readonly int _value;
+
+ private TestIntId(int value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ ///
+ public static TestIntId Create(int value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryCreate(int value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail(error);
+
+ return Result.Ok(new TestIntId(value, true));
+ }
+
+ ///
+ public static TestIntId Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryParse(string s)
+ {
+ if (int.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail("The string is not a valid identifier.");
+ }
+
+ ///
+ public static bool TryParse(string s, out TestIntId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out TestIntId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public bool Equals(TestIntId other)
+ {
+ return _value == other._value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is TestIntId other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _value;
+ }
+
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==(TestIntId left, TestIntId right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=(TestIntId left, TestIntId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public int CompareTo(TestIntId other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is TestIntId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TestIntId)}");
+ }
+
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=(TestIntId left, TestIntId right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public int ToInt32()
+ {
+ return _value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ private static Result Validate(int value)
+ {
+ if (value < 0)
+ return Result.Fail("The value must be equal to or greater than zero.");
+
+ return Result.Ok();
+ }
+}
+
+public class TestIntIdTypeConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof(int) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is int identifierValue)
+ return TestIntId.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+}
+
+public class TestIntIdJsonConverter : JsonConverter
+{
+ public override void Write(Utf8JsonWriter writer, TestIntId identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject)identifier).Value;
+ writer.WriteNumberValue(value);
+ }
+
+ public override TestIntId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetInt32();
+ return TestIntId.Create(value);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithNamespace.verified.txt b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithNamespace.verified.txt
new file mode 100644
index 0000000..c676b2c
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithNamespace.verified.txt
@@ -0,0 +1,233 @@
+//-----------------------------------------------------------------------------
+//
+// This code was generated by GeneratedIdentifierSourceGenerator which
+// can be found in the LightResults.Extensions.GeneratedIdentifier namespace.
+//
+// Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated.
+//
+//-----------------------------------------------------------------------------
+
+#nullable enable
+
+using System.ComponentModel;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using LightResults;
+using GeneratedIdentifier.Common.ValueObjects;
+
+namespace MyProject.Identifiers;
+
+[TypeConverter(typeof(TestShortIdTypeConverter))]
+[JsonConverter(typeof(TestShortIdJsonConverter))]
+readonly partial struct TestShortId :
+ ICreatableValueObject,
+ IParsableValueObject,
+ IValueObject,
+ IComparable,
+ IComparable
+{
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ short IValueObject.Value => _value;
+
+ private readonly short _value;
+
+ private TestShortId(short value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ ///
+ public static TestShortId Create(short value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryCreate(short value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail(error);
+
+ return Result.Ok(new TestShortId(value, true));
+ }
+
+ ///
+ public static TestShortId Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryParse(string s)
+ {
+ if (short.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail("The string is not a valid identifier.");
+ }
+
+ ///
+ public static bool TryParse(string s, out TestShortId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out TestShortId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public bool Equals(TestShortId other)
+ {
+ return _value == other._value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is TestShortId other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _value;
+ }
+
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==(TestShortId left, TestShortId right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=(TestShortId left, TestShortId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public int CompareTo(TestShortId other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is TestShortId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TestShortId)}");
+ }
+
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public short ToInt16()
+ {
+ return _value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ private static Result Validate(short value)
+ {
+ if (value < 0)
+ return Result.Fail("The value must be equal to or greater than zero.");
+
+ return Result.Ok();
+ }
+}
+
+public class TestShortIdTypeConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof(short) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is short identifierValue)
+ return TestShortId.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+}
+
+public class TestShortIdJsonConverter : JsonConverter
+{
+ public override void Write(Utf8JsonWriter writer, TestShortId identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject)identifier).Value;
+ writer.WriteNumberValue(value);
+ }
+
+ public override TestShortId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetInt16();
+ return TestShortId.Create(value);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithoutNamespace.verified.txt b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithoutNamespace.verified.txt
new file mode 100644
index 0000000..21dacba
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.GenerateShortIdentifier_WithoutNamespace.verified.txt
@@ -0,0 +1,231 @@
+//-----------------------------------------------------------------------------
+//
+// This code was generated by GeneratedIdentifierSourceGenerator which
+// can be found in the LightResults.Extensions.GeneratedIdentifier namespace.
+//
+// Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated.
+//
+//-----------------------------------------------------------------------------
+
+#nullable enable
+
+using System.ComponentModel;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using LightResults;
+using GeneratedIdentifier.Common.ValueObjects;
+
+[TypeConverter(typeof(TestShortIdTypeConverter))]
+[JsonConverter(typeof(TestShortIdJsonConverter))]
+readonly partial struct TestShortId :
+ ICreatableValueObject,
+ IParsableValueObject,
+ IValueObject,
+ IComparable,
+ IComparable
+{
+ /// Gets whether this identifier is the default value.
+ public bool IsDefault => _value == default;
+
+ short IValueObject.Value => _value;
+
+ private readonly short _value;
+
+ private TestShortId(short value, bool skipValidation = false)
+ {
+ if (!skipValidation)
+ ValueObjectException.ThrowIfFailed(Validate(value));
+
+ _value = value;
+ }
+
+ ///
+ public static TestShortId Create(short value)
+ {
+ var result = TryCreate(value);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryCreate(short value)
+ {
+ var validation = Validate(value);
+ if (validation.IsFailed(out var error))
+ return Result.Fail(error);
+
+ return Result.Ok(new TestShortId(value, true));
+ }
+
+ ///
+ public static TestShortId Parse(string s)
+ {
+ var result = TryParse(s);
+ if (result.IsSuccess(out var identifier, out var error))
+ return identifier;
+
+ throw new ValueObjectException(error.Message);
+ }
+
+ ///
+ public static Result TryParse(string s)
+ {
+ if (short.TryParse(s, out var value))
+ return TryCreate(value);
+
+ return Result.Fail("The string is not a valid identifier.");
+ }
+
+ ///
+ public static bool TryParse(string s, out TestShortId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public static bool TryParse(string s, IFormatProvider provider, out TestShortId identifier)
+ {
+ return TryParse(s).IsSuccess(out identifier);
+ }
+
+ ///
+ public bool Equals(TestShortId other)
+ {
+ return _value == other._value;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is TestShortId other && Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return _value;
+ }
+
+ /// Determines whether two instances of are equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are equal; otherwise, false.
+ public static bool operator ==(TestShortId left, TestShortId right)
+ {
+ return left.Equals(right);
+ }
+
+ /// Determines whether two instances of are not equal.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the instances are not equal; otherwise, false.
+ public static bool operator !=(TestShortId left, TestShortId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public int CompareTo(TestShortId other)
+ {
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return 1;
+ return obj is TestShortId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TestShortId)}");
+ }
+
+ /// Determines whether the first instance of is less than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than the second instance; otherwise, false.
+ public static bool operator <(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) < 0;
+ }
+
+ /// Determines whether the first instance of is greater than the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than the second instance; otherwise, false.
+ public static bool operator >(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) > 0;
+ }
+
+ /// Determines whether the first instance of is less than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is less than or equal to the second instance; otherwise, false.
+ public static bool operator <=(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) <= 0;
+ }
+
+ /// Determines whether the first instance of is greater than or equal to the second instance.
+ /// The first instance to compare.
+ /// The second instance to compare.
+ /// true if the first instance is greater than or equal to the second instance; otherwise, false.
+ public static bool operator >=(TestShortId left, TestShortId right)
+ {
+ return left.CompareTo(right) >= 0;
+ }
+
+ /// Gets the underlying value of the .
+ /// The underlying value of the .
+ public short ToInt16()
+ {
+ return _value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ private static Result Validate(short value)
+ {
+ if (value < 0)
+ return Result.Fail("The value must be equal to or greater than zero.");
+
+ return Result.Ok();
+ }
+}
+
+public class TestShortIdTypeConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ return sourceType == typeof(short) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is short identifierValue)
+ return TestShortId.Create(identifierValue);
+
+ return base.ConvertFrom(context, culture, value);
+ }
+}
+
+public class TestShortIdJsonConverter : JsonConverter
+{
+ public override void Write(Utf8JsonWriter writer, TestShortId identifier, JsonSerializerOptions options)
+ {
+ var value = ((IValueObject)identifier).Value;
+ writer.WriteNumberValue(value);
+ }
+
+ public override TestShortId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var value = reader.GetInt16();
+ return TestShortId.Create(value);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.cs
new file mode 100644
index 0000000..ba8db70
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/GeneratedIdentifierSourceGeneratorTests.cs
@@ -0,0 +1,104 @@
+using System.Collections.Immutable;
+using Basic.Reference.Assemblies;
+using LightResults.Extensions.GeneratedIdentifier;
+using LightResults.Extensions.GeneratedIdentifier.Tests;
+using Microsoft.CodeAnalysis;
+using SourceGeneratorTestHelpers;
+using SourceGeneratorTestHelpers.XUnit;
+
+namespace GeneratedIdentifier.Tests;
+
+public sealed class GeneratedIdentifierSourceGeneratorTests
+{
+ static GeneratedIdentifierSourceGeneratorTests()
+ {
+ ModuleInitializer.Initialize();
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task GenerateGuidIdentifier(bool withNamespace)
+ {
+ var sources = GetSources("""
+ /// Represents an identifier.
+ [GeneratedIdentifier]
+ public partial struct TestGuidId;
+ """, withNamespace
+ );
+
+ var result = RunGenerator(sources);
+ await result.VerifyAsync("TestGuidId.g.cs")
+ .UseMethodName($"{nameof(GenerateGuidIdentifier)}_With{(withNamespace ? "" : "out")}Namespace");
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task GenerateIntIdentifier(bool withNamespace)
+ {
+ var sources = GetSources("""
+ /// Represents an identifier.
+ [GeneratedIdentifier]
+ public partial struct TestIntId;
+ """, withNamespace
+ );
+
+ var result = RunGenerator(sources);
+ await result.VerifyAsync("TestIntId.g.cs")
+ .UseMethodName($"{nameof(GenerateIntIdentifier)}_With{(withNamespace ? "" : "out")}Namespace");
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task GenerateShortIdentifier(bool withNamespace)
+ {
+ var sources = GetSources("""
+ /// Represents an identifier.
+ [GeneratedIdentifier]
+ public partial struct TestShortId;
+ """, withNamespace
+ );
+
+ var result = RunGenerator(sources);
+ await result.VerifyAsync("TestShortId.g.cs")
+ .UseMethodName($"{nameof(GenerateShortIdentifier)}_With{(withNamespace ? "" : "out")}Namespace");
+ }
+
+ private static IEnumerable GetSources(string source, bool withNamespace = true)
+ {
+ const string usingStatements = """
+ using System;
+ using LightResults.Extensions.GeneratedIdentifier;
+ """;
+
+ if (withNamespace)
+ yield return $"""
+ {usingStatements}
+
+ namespace MyProject.Identifiers;
+
+ {source}
+ """;
+ else
+ yield return $"""
+ {usingStatements}
+
+ {source}
+ """;
+ yield return """
+ using System;
+
+ namespace LightResults.Extensions.GeneratedIdentifier;
+
+ [AttributeUsage(AttributeTargets.Struct)]
+ public sealed class GeneratedIdentifierAttribute : Attribute;
+ """;
+ }
+
+ private static (ImmutableArray Diagnostics, GeneratorDriverRunResult Result) RunGenerator(IEnumerable sources)
+ {
+ return IncrementalGenerator.RunWithDiagnostics(sources, metadataReferences: ReferenceAssemblies.Net80);
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/LightResults.Extensions.GeneratedIdentifier.Tests.csproj b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/LightResults.Extensions.GeneratedIdentifier.Tests.csproj
new file mode 100644
index 0000000..402a32f
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/LightResults.Extensions.GeneratedIdentifier.Tests.csproj
@@ -0,0 +1,66 @@
+
+
+
+ net8.0
+ enable
+ enable
+ latest
+ LightResults.Extensions.GeneratedIdentifier.Tests
+ LightResults.Extensions.GeneratedIdentifier.Tests
+ latest-Default
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+ IdentifierSourceGeneratorTests.cs
+
+
+ IdentifierSourceGeneratorTests.cs
+
+
+ IdentifierSourceGeneratorTests.cs
+
+
+ IdentifierSourceGeneratorTests.cs
+
+
+ IdentifierSourceGeneratorTests.cs
+
+
+ IdentifierSourceGeneratorTests.cs
+
+
+ GeneratedIdentifierSourceGeneratorTests.cs
+
+
+ GeneratedIdentifierSourceGeneratorTests.cs
+
+
+
+
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/ModuleInitializer.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/ModuleInitializer.cs
new file mode 100644
index 0000000..6addfa9
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/ModuleInitializer.cs
@@ -0,0 +1,16 @@
+using DiffEngine;
+using VerifyTests.DiffPlex;
+
+namespace LightResults.Extensions.GeneratedIdentifier.Tests;
+
+public static class ModuleInitializer
+{
+ public static void Initialize()
+ {
+ DiffRunner.Disabled = true;
+ VerifyDiffPlex.Initialize(OutputType.Compact);
+ VerifierSettings.InitializePlugins();
+ VerifierSettings.ScrubLinesContaining("DiffEngineTray");
+ VerifierSettings.IgnoreStackTrace();
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestGuidIdTest.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestGuidIdTest.cs
new file mode 100644
index 0000000..a4a1c0b
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestGuidIdTest.cs
@@ -0,0 +1,329 @@
+using FluentAssertions;
+using GeneratedIdentifier.Common.ValueObjects;
+using LightResults.Extensions.GeneratedIdentifier.Fixtures.Identifiers;
+
+// ReSharper disable SuspiciousTypeConversion.Global
+// ReSharper disable EqualExpressionComparison
+#pragma warning disable CS1718 // Comparison made to same variable
+
+namespace LightResults.Extensions.GeneratedIdentifier.Tests;
+
+public sealed class TestGuidIdTest
+{
+ private static readonly Guid Guid1 = Guid.Parse("bcbac1e2-de00-47e4-9891-58774a68668f");
+ private static readonly Guid Guid2 = Guid.Parse("e8d81b8f-127f-4e7d-b7fb-c9ab6f34be72");
+
+ [Fact]
+ public void Create_ValidValue_ShouldSucceed()
+ {
+ // Arrange
+ var validValue = Guid1;
+
+ // Act
+ var id = TestGuidId.Create(validValue);
+
+ // Assert
+ id.Should().NotBeNull();
+ id.ToGuid().Should().Be(validValue);
+ }
+
+ [Fact]
+ public void TryCreate_ValidValue_ShouldSucceed()
+ {
+ // Arrange
+ var validValue = Guid1;
+
+ // Act
+ var result = TestGuidId.TryCreate(validValue);
+
+ // Assert
+ result.IsSuccess(out var id).Should().BeTrue();
+ id.Should().NotBeNull();
+ id.ToGuid().Should().Be(validValue);
+ }
+
+ [Fact]
+ public void Parse_ValidString_ShouldSucceed()
+ {
+ // Arrange
+ const string validString = "5528cc73-cba9-4960-85c1-ed96dc4c7f95";
+
+ // Act
+ var result = TestGuidId.Parse(validString);
+
+ // Assert
+
+ result.ToGuid().Should().Be(Guid.Parse(validString));
+ }
+
+ [Theory]
+ [InlineData("invalid")]
+ [InlineData("g528cc73-cba9-4960-85c1-ed96dc4c7f95")]
+ public void Parse_InvalidString_ShouldThrowException(string invalidString)
+ {
+ // Act
+ var parse = () => TestGuidId.Parse(invalidString);
+
+ // Assert
+ parse.Should().Throw();
+ }
+
+ [Fact]
+ public void TryParse_ValidString_ShouldSucceed()
+ {
+ // Arrange
+ const string validString = "5528cc73-cba9-4960-85c1-ed96dc4c7f95";
+
+ // Act
+ var result = TestGuidId.TryParse(validString);
+
+ // Assert
+ result.IsSuccess(out var id).Should().BeTrue();
+ id.Should().NotBeNull();
+ id.ToGuid().Should().Be(Guid.Parse(validString));
+ }
+
+ [Theory]
+ [InlineData("invalid")]
+ [InlineData("-1")]
+ public void TryParse_InvalidString_ShouldFail(string invalidString)
+ {
+ // Act
+ var result = TestGuidId.TryParse(invalidString);
+
+ // Assert
+ result.IsFailed().Should().BeTrue();
+ result.Errors.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Equals_SameValues_ShouldBeEqual()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid1);
+
+ // Assert
+ id1.Should().Be(id2);
+ (id1 == id2).Should().BeTrue();
+ (id1 != id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equals_DifferentValues_ShouldNotBeEqual()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid2);
+
+ // Assert
+ id1.Should().NotBe(id2);
+ (id1 == id2).Should().BeFalse();
+ (id1 != id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Equals_ObjectIsNull_ShouldReturnFalse()
+ {
+ // Arrange
+ var id = TestGuidId.Create(Guid1);
+
+ // Act
+ var result = id.Equals(null);
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equals_ObjectIsNotTestGuidId_ShouldReturnFalse()
+ {
+ // Arrange
+ var id = TestGuidId.Create(Guid1);
+
+ // Act
+ var result = id.Equals("not an TestGuidId");
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void GetHashCode_ShouldReturnCorrectValue()
+ {
+ // Arrange
+ var underlyingValue = Guid1;
+ var id = TestGuidId.Create(underlyingValue);
+
+ // Act
+ var hashCode1 = id.GetHashCode();
+ var hashCode2 = underlyingValue.GetHashCode();
+
+ // Assert
+ hashCode1.Should().Be(hashCode2);
+ }
+
+ [Fact]
+ public void Operators_Equality_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid1);
+
+ // Assert
+ (id1 == id2).Should().BeTrue();
+ (id1 != id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Operators_Inequality_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid2);
+
+ // Assert
+ (id1 != id2).Should().BeTrue();
+ (id1 == id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void CompareTo_SameValue_ShouldReturnZero()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid1);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().Be(0);
+ }
+
+ [Fact]
+ public void CompareTo_LesserValue_ShouldReturnNegative()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid2);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().BeNegative();
+ }
+
+ [Fact]
+ public void CompareTo_GreaterValue_ShouldReturnPositive()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid2);
+ var id2 = TestGuidId.Create(Guid1);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().BePositive();
+ }
+
+ [Fact]
+ public void Operators_LessThan_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid2);
+
+ // Assert
+ (id1 < id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_GreaterThan_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid2);
+ var id2 = TestGuidId.Create(Guid1);
+
+ // Assert
+ (id1 > id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_LessThanOrEqual_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid1);
+ var id2 = TestGuidId.Create(Guid2);
+
+ // Assert
+ (id1 <= id2).Should().BeTrue();
+ (id1 <= id1).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_GreaterThanOrEqual_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestGuidId.Create(Guid2);
+ var id2 = TestGuidId.Create(Guid1);
+
+ // Assert
+ (id1 >= id2).Should().BeTrue();
+ (id1 >= id1).Should().BeTrue();
+ }
+
+ [Fact]
+ public void CompareTo_ObjectIsNull_ShouldReturnPositive()
+ {
+ // Arrange
+ var id = TestGuidId.Create(Guid1);
+
+ // Act
+ var result = id.CompareTo(null);
+
+ // Assert
+ result.Should().BePositive();
+ }
+
+ [Fact]
+ public void CompareTo_ObjectIsNotTestGuidId_ShouldThrowException()
+ {
+ // Arrange
+ var id = TestGuidId.Create(Guid1);
+
+ // Act
+ var compareTo = () => id.CompareTo("not an TestGuidId");
+
+ // Assert
+ compareTo.Should().Throw();
+ }
+
+ [Fact]
+ public void ToInt_ShouldReturnCorrectValue()
+ {
+ // Arrange
+ var id = TestGuidId.Create(Guid1);
+
+ // Act
+ var integerValue = id.ToGuid();
+
+ // Assert
+ integerValue.Should().Be(Guid1);
+ }
+
+ [Fact]
+ public void ToString_ShouldReturnStringValue()
+ {
+ // Arrange
+ var id = TestGuidId.Create(Guid1);
+
+ // Act
+ var stringValue = id.ToString();
+
+ // Assert
+ stringValue.Should().Be(Guid1.ToString());
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestIntIdTest.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestIntIdTest.cs
new file mode 100644
index 0000000..3f0a47e
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestIntIdTest.cs
@@ -0,0 +1,353 @@
+using FluentAssertions;
+using GeneratedIdentifier.Common.ValueObjects;
+using LightResults.Extensions.GeneratedIdentifier.Fixtures.Identifiers;
+
+// ReSharper disable SuspiciousTypeConversion.Global
+// ReSharper disable EqualExpressionComparison
+#pragma warning disable CS1718 // Comparison made to same variable
+
+namespace LightResults.Extensions.GeneratedIdentifier.Tests;
+
+public sealed class TestIntIdTest
+{
+ [Fact]
+ public void Create_ValidValue_ShouldSucceed()
+ {
+ // Arrange
+ const int validValue = 42;
+
+ // Act
+ var id = TestIntId.Create(validValue);
+
+ // Assert
+ id.Should().NotBeNull();
+ id.ToInt32().Should().Be(validValue);
+ }
+
+ [Fact]
+ public void Create_InvalidValue_ShouldThrowException()
+ {
+ // Arrange
+ const int invalidValue = -1;
+
+ // Act
+ var create = () => TestIntId.Create(invalidValue);
+
+ // Assert
+ create.Should().Throw();
+ }
+
+ [Fact]
+ public void TryCreate_ValidValue_ShouldSucceed()
+ {
+ // Arrange
+ const int validValue = 42;
+
+ // Act
+ var result = TestIntId.TryCreate(validValue);
+
+ // Assert
+ result.IsSuccess(out var id).Should().BeTrue();
+ id.Should().NotBeNull();
+ id.ToInt32().Should().Be(validValue);
+ }
+
+ [Fact]
+ public void TryCreate_InvalidValue_ShouldFail()
+ {
+ // Arrange
+ const int invalidValue = -1;
+
+ // Act
+ var result = TestIntId.TryCreate(invalidValue);
+
+ // Assert
+ result.IsFailed().Should().BeTrue();
+ result.Errors.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Parse_ValidString_ShouldSucceed()
+ {
+ // Arrange
+ const string validString = "42";
+
+ // Act
+ var result = TestIntId.Parse(validString);
+
+ // Assert
+
+ result.ToInt32().Should().Be(int.Parse(validString));
+ }
+
+ [Theory]
+ [InlineData("invalid")]
+ [InlineData("-1")]
+ public void Parse_InvalidString_ShouldThrowException(string invalidString)
+ {
+ // Act
+ var parse = () => TestIntId.Parse(invalidString);
+
+ // Assert
+ parse.Should().Throw();
+ }
+
+ [Fact]
+ public void TryParse_ValidString_ShouldSucceed()
+ {
+ // Arrange
+ const string validString = "42";
+
+ // Act
+ var result = TestIntId.TryParse(validString);
+
+ // Assert
+ result.IsSuccess(out var id).Should().BeTrue();
+ id.Should().NotBeNull();
+ id.ToInt32().Should().Be(int.Parse(validString));
+ }
+
+ [Theory]
+ [InlineData("invalid")]
+ [InlineData("-1")]
+ public void TryParse_InvalidString_ShouldFail(string invalidString)
+ {
+ // Act
+ var result = TestIntId.TryParse(invalidString);
+
+ // Assert
+ result.IsFailed().Should().BeTrue();
+ result.Errors.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Equals_SameValues_ShouldBeEqual()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(42);
+
+ // Assert
+ id1.Should().Be(id2);
+ (id1 == id2).Should().BeTrue();
+ (id1 != id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equals_DifferentValues_ShouldNotBeEqual()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(99);
+
+ // Assert
+ id1.Should().NotBe(id2);
+ (id1 == id2).Should().BeFalse();
+ (id1 != id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Equals_ObjectIsNull_ShouldReturnFalse()
+ {
+ // Arrange
+ var id = TestIntId.Create(42);
+
+ // Act
+ var result = id.Equals(null);
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equals_ObjectIsNotTestIntId_ShouldReturnFalse()
+ {
+ // Arrange
+ var id = TestIntId.Create(42);
+
+ // Act
+ var result = id.Equals("not an TestIntId");
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void GetHashCode_ShouldReturnCorrectValue()
+ {
+ // Arrange
+ const int underlyingValue = 42;
+ var id = TestIntId.Create(underlyingValue);
+
+ // Act
+ var hashCode1 = id.GetHashCode();
+ var hashCode2 = underlyingValue.GetHashCode();
+
+ // Assert
+ hashCode1.Should().Be(hashCode2);
+ }
+
+ [Fact]
+ public void Operators_Equality_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(42);
+
+ // Assert
+ (id1 == id2).Should().BeTrue();
+ (id1 != id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Operators_Inequality_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(99);
+
+ // Assert
+ (id1 != id2).Should().BeTrue();
+ (id1 == id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void CompareTo_SameValue_ShouldReturnZero()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(42);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().Be(0);
+ }
+
+ [Fact]
+ public void CompareTo_LesserValue_ShouldReturnNegative()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(99);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().BeNegative();
+ }
+
+ [Fact]
+ public void CompareTo_GreaterValue_ShouldReturnPositive()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(99);
+ var id2 = TestIntId.Create(42);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().BePositive();
+ }
+
+ [Fact]
+ public void Operators_LessThan_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(99);
+
+ // Assert
+ (id1 < id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_GreaterThan_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(99);
+ var id2 = TestIntId.Create(42);
+
+ // Assert
+ (id1 > id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_LessThanOrEqual_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(42);
+ var id2 = TestIntId.Create(99);
+
+ // Assert
+ (id1 <= id2).Should().BeTrue();
+ (id1 <= id1).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_GreaterThanOrEqual_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestIntId.Create(99);
+ var id2 = TestIntId.Create(42);
+
+ // Assert
+ (id1 >= id2).Should().BeTrue();
+ (id1 >= id1).Should().BeTrue();
+ }
+
+ [Fact]
+ public void CompareTo_ObjectIsNull_ShouldReturnPositive()
+ {
+ // Arrange
+ var id = TestIntId.Create(42);
+
+ // Act
+ var result = id.CompareTo(null);
+
+ // Assert
+ result.Should().BePositive();
+ }
+
+ [Fact]
+ public void CompareTo_ObjectIsNotTestIntId_ShouldThrowException()
+ {
+ // Arrange
+ var id = TestIntId.Create(42);
+
+ // Act
+ var compareTo = () => id.CompareTo("not an TestIntId");
+
+ // Assert
+ compareTo.Should().Throw();
+ }
+
+ [Fact]
+ public void ToInt_ShouldReturnCorrectValue()
+ {
+ // Arrange
+ var id = TestIntId.Create(42);
+
+ // Act
+ var integerValue = id.ToInt32();
+
+ // Assert
+ integerValue.Should().Be(42);
+ }
+
+ [Fact]
+ public void ToString_ShouldReturnStringValue()
+ {
+ // Arrange
+ var id = TestIntId.Create(42);
+
+ // Act
+ var stringValue = id.ToString();
+
+ // Assert
+ stringValue.Should().Be("42");
+ }
+}
diff --git a/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestShortIdTest.cs b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestShortIdTest.cs
new file mode 100644
index 0000000..ecb96ae
--- /dev/null
+++ b/tests/LightResults.Extensions.GeneratedIdentifier.Tests/TestShortIdTest.cs
@@ -0,0 +1,353 @@
+using FluentAssertions;
+using GeneratedIdentifier.Common.ValueObjects;
+using LightResults.Extensions.GeneratedIdentifier.Fixtures.Identifiers;
+
+// ReSharper disable SuspiciousTypeConversion.Global
+// ReSharper disable EqualExpressionComparison
+#pragma warning disable CS1718 // Comparison made to same variable
+
+namespace LightResults.Extensions.GeneratedIdentifier.Tests;
+
+public sealed class TestShortIdTest
+{
+ [Fact]
+ public void Create_ValidValue_ShouldSucceed()
+ {
+ // Arrange
+ const int validValue = 42;
+
+ // Act
+ var id = TestShortId.Create(validValue);
+
+ // Assert
+ id.Should().NotBeNull();
+ id.ToInt16().Should().Be(validValue);
+ }
+
+ [Fact]
+ public void Create_InvalidValue_ShouldThrowException()
+ {
+ // Arrange
+ const int invalidValue = -1;
+
+ // Act
+ var create = () => TestShortId.Create(invalidValue);
+
+ // Assert
+ create.Should().Throw();
+ }
+
+ [Fact]
+ public void TryCreate_ValidValue_ShouldSucceed()
+ {
+ // Arrange
+ const int validValue = 42;
+
+ // Act
+ var result = TestShortId.TryCreate(validValue);
+
+ // Assert
+ result.IsSuccess(out var id).Should().BeTrue();
+ id.Should().NotBeNull();
+ id.ToInt16().Should().Be(validValue);
+ }
+
+ [Fact]
+ public void TryCreate_InvalidValue_ShouldFail()
+ {
+ // Arrange
+ const int invalidValue = -1;
+
+ // Act
+ var result = TestShortId.TryCreate(invalidValue);
+
+ // Assert
+ result.IsFailed().Should().BeTrue();
+ result.Errors.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Parse_ValidString_ShouldSucceed()
+ {
+ // Arrange
+ const string validString = "42";
+
+ // Act
+ var result = TestShortId.Parse(validString);
+
+ // Assert
+
+ result.ToInt16().Should().Be(short.Parse(validString));
+ }
+
+ [Theory]
+ [InlineData("invalid")]
+ [InlineData("-1")]
+ public void Parse_InvalidString_ShouldThrowException(string invalidString)
+ {
+ // Act
+ var parse = () => TestShortId.Parse(invalidString);
+
+ // Assert
+ parse.Should().Throw();
+ }
+
+ [Fact]
+ public void TryParse_ValidString_ShouldSucceed()
+ {
+ // Arrange
+ const string validString = "42";
+
+ // Act
+ var result = TestShortId.TryParse(validString);
+
+ // Assert
+ result.IsSuccess(out var id).Should().BeTrue();
+ id.Should().NotBeNull();
+ id.ToInt16().Should().Be(short.Parse(validString));
+ }
+
+ [Theory]
+ [InlineData("invalid")]
+ [InlineData("-1")]
+ public void TryParse_InvalidString_ShouldFail(string invalidString)
+ {
+ // Act
+ var result = TestShortId.TryParse(invalidString);
+
+ // Assert
+ result.IsFailed().Should().BeTrue();
+ result.Errors.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Equals_SameValues_ShouldBeEqual()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(42);
+
+ // Assert
+ id1.Should().Be(id2);
+ (id1 == id2).Should().BeTrue();
+ (id1 != id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equals_DifferentValues_ShouldNotBeEqual()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(99);
+
+ // Assert
+ id1.Should().NotBe(id2);
+ (id1 == id2).Should().BeFalse();
+ (id1 != id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Equals_ObjectIsNull_ShouldReturnFalse()
+ {
+ // Arrange
+ var id = TestShortId.Create(42);
+
+ // Act
+ var result = id.Equals(null);
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Equals_ObjectIsNotTestShortId_ShouldReturnFalse()
+ {
+ // Arrange
+ var id = TestShortId.Create(42);
+
+ // Act
+ var result = id.Equals("not an TestShortId");
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void GetHashCode_ShouldReturnCorrectValue()
+ {
+ // Arrange
+ const int underlyingValue = 42;
+ var id = TestShortId.Create(underlyingValue);
+
+ // Act
+ var hashCode1 = id.GetHashCode();
+ var hashCode2 = underlyingValue.GetHashCode();
+
+ // Assert
+ hashCode1.Should().Be(hashCode2);
+ }
+
+ [Fact]
+ public void Operators_Equality_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(42);
+
+ // Assert
+ (id1 == id2).Should().BeTrue();
+ (id1 != id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void Operators_Inequality_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(99);
+
+ // Assert
+ (id1 != id2).Should().BeTrue();
+ (id1 == id2).Should().BeFalse();
+ }
+
+ [Fact]
+ public void CompareTo_SameValue_ShouldReturnZero()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(42);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().Be(0);
+ }
+
+ [Fact]
+ public void CompareTo_LesserValue_ShouldReturnNegative()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(99);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().BeNegative();
+ }
+
+ [Fact]
+ public void CompareTo_GreaterValue_ShouldReturnPositive()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(99);
+ var id2 = TestShortId.Create(42);
+
+ // Act
+ var result = id1.CompareTo(id2);
+
+ // Assert
+ result.Should().BePositive();
+ }
+
+ [Fact]
+ public void Operators_LessThan_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(99);
+
+ // Assert
+ (id1 < id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_GreaterThan_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(99);
+ var id2 = TestShortId.Create(42);
+
+ // Assert
+ (id1 > id2).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_LessThanOrEqual_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(42);
+ var id2 = TestShortId.Create(99);
+
+ // Assert
+ (id1 <= id2).Should().BeTrue();
+ (id1 <= id1).Should().BeTrue();
+ }
+
+ [Fact]
+ public void Operators_GreaterThanOrEqual_ShouldReturnTrue()
+ {
+ // Arrange
+ var id1 = TestShortId.Create(99);
+ var id2 = TestShortId.Create(42);
+
+ // Assert
+ (id1 >= id2).Should().BeTrue();
+ (id1 >= id1).Should().BeTrue();
+ }
+
+ [Fact]
+ public void CompareTo_ObjectIsNull_ShouldReturnPositive()
+ {
+ // Arrange
+ var id = TestShortId.Create(42);
+
+ // Act
+ var result = id.CompareTo(null);
+
+ // Assert
+ result.Should().BePositive();
+ }
+
+ [Fact]
+ public void CompareTo_ObjectIsNotTestShortId_ShouldThrowException()
+ {
+ // Arrange
+ var id = TestShortId.Create(42);
+
+ // Act
+ var compareTo = () => id.CompareTo("not an TestShortId");
+
+ // Assert
+ compareTo.Should().Throw();
+ }
+
+ [Fact]
+ public void ToInt_ShouldReturnCorrectValue()
+ {
+ // Arrange
+ var id = TestShortId.Create(42);
+
+ // Act
+ var integerValue = id.ToInt16();
+
+ // Assert
+ integerValue.Should().Be(42);
+ }
+
+ [Fact]
+ public void ToString_ShouldReturnStringValue()
+ {
+ // Arrange
+ var id = TestShortId.Create(42);
+
+ // Act
+ var stringValue = id.ToString();
+
+ // Assert
+ stringValue.Should().Be("42");
+ }
+}
diff --git a/tools/Benchmarks/Benchmarks.csproj b/tools/Benchmarks/Benchmarks.csproj
index 5a511ea..04ed832 100644
--- a/tools/Benchmarks/Benchmarks.csproj
+++ b/tools/Benchmarks/Benchmarks.csproj
@@ -11,8 +11,8 @@
-
-
+
+