Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/AltaSoft.DomainPrimitives.Generator/Executor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
Expand Down Expand Up @@ -422,7 +423,7 @@ private static void Process(GeneratorData data, string ctorCode, DomainPrimitive
{
// Add int constructor
builder.AppendComment("// Private constructor with 'int' value");
builder.AppendLine($"private {data.ClassName}(int value) : this(value is >= {data.PrimitiveTypeFriendlyName}.MinValue and <= {data.PrimitiveTypeFriendlyName}.MaxValue ? ({data.PrimitiveTypeFriendlyName})value : throw new InvalidDomainValueException(\"The value has exceeded a {data.PrimitiveTypeFriendlyName} limit\"))")
builder.AppendLine($"private {data.ClassName}(int value) : this(value is >= {data.PrimitiveTypeFriendlyName}.MinValue and <= {data.PrimitiveTypeFriendlyName}.MaxValue ? ({data.PrimitiveTypeFriendlyName})value : throw InvalidDomainValueException.LimitExceededException(typeof({data.ClassName}), value, \"{data.PrimitiveTypeFriendlyName}\"))")
.OpenBracket()
.CloseBracket()
.NewLine();
Expand Down Expand Up @@ -718,7 +719,7 @@ private static bool ProcessConstructor(GeneratorData data, SourceCodeBuilder bui

var underlyingTypeName = interfaceGenericType.GetFriendlyName();

builder.AppendLine($"private {underlyingTypeName} _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException(\"The domain value has not been initialized\", this);");
builder.AppendLine($"private {underlyingTypeName} _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof({type.Name}));");

builder.AppendLine("[DebuggerBrowsable(DebuggerBrowsableState.Never)]");
builder.AppendLine($"private readonly {underlyingTypeName} _value;");
Expand Down Expand Up @@ -796,7 +797,7 @@ private static void AddStringLengthAttributeValidation(ISymbol domainPrimitiveTy
.AppendIf(hasMinValue, $"< {minValue}")
.AppendIf(hasMinValue && hasMaxValue, " or ")
.AppendIf(hasMaxValue, $"> {maxValue}").AppendLine(")")
.AppendLine($"\tthrow new InvalidDomainValueException(\"String length is out of range {minValue}..{maxValue}\", this);")
.AppendLine($"\tthrow InvalidDomainValueException.StringRangeException(typeof({data.ClassName}), value, {minValue.ToString(CultureInfo.InvariantCulture)}, {maxValue.ToString(CultureInfo.InvariantCulture)});")
.NewLine();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -432,15 +432,15 @@ internal static void GenerateMandatoryMethods(GeneratorData data, SourceCodeBuil
.CloseBracket()
.NewLine();

builder.AppendSummary(" Validates the specified value and throws an exception if it is not valid.")
builder.AppendSummary("Validates the specified value and throws an exception if it is not valid.")
.AppendParamDescription("value", "The value to validate")
.AppendExceptionDescription("InvalidDomainValueException", "Thrown when the value is not valid.");

builder.AppendLine($"public void ValidateOrThrow({primitiveType} value)")
.OpenBracket()
.AppendLine("var result = Validate(value);")
.AppendLine("if (!result.IsValid)")
.AppendLine("\tthrow new InvalidDomainValueException(result.ErrorMessage, this);")
.AppendLine($"\tthrow new InvalidDomainValueException(result.ErrorMessage, typeof({data.ClassName}), value);")
.CloseBracket()
.NewLine();

Expand All @@ -464,7 +464,7 @@ static void AddStringLengthValidation(GeneratorData data, SourceCodeBuilder sb)
.AppendIf(hasMaxValue, $"> {maxValue}").AppendLine(")")
.OpenBracket()
.AppendLine("result = null;")
.AppendLine($"errorMessage =\" String length is out of range {minValue}..{maxValue}\";")
.AppendLine($"errorMessage = \"String length is out of range {minValue}..{maxValue}\";")
.AppendLine("return false;")
.CloseBracket()
.NewLine();
Expand Down
83 changes: 75 additions & 8 deletions src/AltaSoft.DomainPrimitives/InvalidDomainValueException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,89 @@ public class InvalidDomainValueException : Exception
/// Initializes a new instance of the <see cref="InvalidDomainValueException"/> class with a specific error message.
/// </summary>
/// <param name="message">The error message that describes the reason for the exception.</param>
/// <param name="instance">actual instance of domain primitive</param>
/// <param name="type">The <see cref="Type"/> of the domain primitive that failed validation.</param>
/// <param name="value">The underlying value that caused the failure, or <c>null</c> if not applicable.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public InvalidDomainValueException(string message, IDomainValue instance) : base(GenerateErrorMessage(message, instance))
public InvalidDomainValueException(string message, Type type, object? value) : base(GenerateErrorMessage(message, type, value))
{
}

/// <summary>
/// Generates the error message for the <see cref="InvalidDomainValueException"/>.
/// Initializes a new instance of the <see cref="InvalidDomainValueException"/> class with a specific error message.
/// </summary>
/// <param name="message">The error message that describes the reason for the exception.</param>
/// <param name="value">The actual value of the domain primitive.</param>
/// <returns>The generated error message.</returns>
private static string GenerateErrorMessage(string message, IDomainValue value)
/// <param name="type">The <see cref="Type"/> of the domain primitive that failed validation.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public InvalidDomainValueException(string message, Type type) : base(GenerateErrorMessage(message, type))
{
}
Comment on lines 17 to +31

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking API change. The original constructor InvalidDomainValueException(string message, IDomainValue instance) has been completely removed and replaced with constructors that accept Type and object parameters. Any external code that manually constructs this exception using the old signature will break. Consider maintaining the old constructor as obsolete with a forwarding implementation, or clearly document this as a breaking change in release notes.

Copilot uses AI. Check for mistakes.

/// <summary>
/// Creates an <see cref="InvalidDomainValueException"/> that indicates the domain value has not been initialized.
/// </summary>
/// <param name="type">The <see cref="Type"/> of the domain primitive that is not initialized.</param>
/// <returns>An <see cref="InvalidDomainValueException"/> describing the not-initialized error.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public static InvalidDomainValueException NotInitializedException(Type type)
{
return new InvalidDomainValueException("The domain value has not been initialized", type);
}

/// <summary>
/// Creates an <see cref="InvalidDomainValueException"/> for string length range violations.
/// </summary>
/// <param name="type">The <see cref="Type"/> of the domain primitive.</param>
/// <param name="value">The string value that caused the violation.</param>
/// <param name="min">The minimum allowed length.</param>
/// <param name="max">The maximum allowed length.</param>
/// <returns>An <see cref="InvalidDomainValueException"/> describing the range violation.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public static InvalidDomainValueException StringRangeException(Type type, string value, int min, int max)
{
return new InvalidDomainValueException($"String length is out of range {min}..{max}", type, value);
}

/// <summary>
/// Creates an <see cref="InvalidDomainValueException"/> for numeric limit exceeded errors.
/// </summary>
/// <param name="type">The <see cref="Type"/> of the domain primitive.</param>
/// <param name="value">The numeric value that exceeded the limit.</param>
/// <param name="underlyingTypeName">The name of the underlying primitive type.</param>
/// <returns>An <see cref="InvalidDomainValueException"/> describing the limit exceeded error.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public static InvalidDomainValueException LimitExceededException(Type type, int value, string underlyingTypeName)
{
return new InvalidDomainValueException($"The value has exceeded a {underlyingTypeName} limit", type, value);
}

/// <summary>
/// Generates the error message for the <see cref="InvalidDomainValueException"/> including the underlying value.
/// </summary>
/// <param name="message">The main error message.</param>
/// <param name="type">The <see cref="Type"/> of the domain primitive.</param>
/// <param name="value">The underlying value that caused the error, or <c>null</c>.</param>
/// <returns>A formatted error message string.</returns>
private static string GenerateErrorMessage(string message, Type type, object? value)
{
var typeName = type.FullName ?? type.Name;
var strValue = value switch
{
null => "(null)",
string s => $"\"{s}\"",
_ => value.ToString()
};
return $"Cannot create instance of '{typeName}'. {message}. Value: {strValue}";
}

/// <summary>
/// Generates the error message for the <see cref="InvalidDomainValueException"/> without the underlying value.
/// </summary>
/// <param name="message">The main error message.</param>
/// <param name="type">The <see cref="Type"/> of the domain primitive.</param>
/// <returns>A formatted error message string.</returns>
private static string GenerateErrorMessage(string message, Type type)
{
var type = value.GetType();
var typeName = type.FullName ?? type.Name;
return $"Cannot create instance of '{typeName}'. {message}";
return $"Cannot create instance of '{typeName}'. {message}.";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
namespace AltaSoft.DomainPrimitives;

/// <inheritdoc/>
[StringLength(1, 100)]
internal partial class TransformableString : IDomainValue<string>
{
/// <inheritdoc/>
Expand Down Expand Up @@ -857,7 +858,7 @@

""";

return TestHelper.Verify(source, (_, x, _) => Assert.Equal(1, x.Count), new DomainPrimitiveGlobalOptions()

Check warning on line 861 in tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs

View workflow job for this annotation

GitHub Actions / build

Do not use Assert.Equal() to check for collection size. Use Assert.Single instead. (https://xunit.net/xunit.analyzers/rules/xUnit2013)

Check warning on line 861 in tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs

View workflow job for this annotation

GitHub Actions / build

Do not use Assert.Equal() to check for collection size. Use Assert.Single instead. (https://xunit.net/xunit.analyzers/rules/xUnit2013)
{
GenerateEntityFrameworkCoreValueConverters = false,
GenerateJsonConverters = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (bool)this;

private bool _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private bool _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(BoolValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly bool _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -99,15 +99,15 @@ public static bool TryCreate(bool value, [NotNullWhen(true)] out BoolValue? res
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(bool value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(BoolValue), value);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (byte)this;

private byte _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private byte _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(ByteValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly byte _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -103,15 +103,15 @@ public static bool TryCreate(byte value, [NotNullWhen(true)] out ByteValue? res
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(byte value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(ByteValue), value);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (char)this;

private char _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private char _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(CharValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly char _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -103,15 +103,15 @@ public static bool TryCreate(char value, [NotNullWhen(true)] out CharValue? res
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(char value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(CharValue), value);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (DateOnly)this;

private DateOnly _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private DateOnly _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(DateOnlyValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly DateOnly _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -104,15 +104,15 @@ public static bool TryCreate(DateOnly value, [NotNullWhen(true)] out DateOnlyVa
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(DateOnly value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(DateOnlyValue), value);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (DateOnly)this;

private DateOnly _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private DateOnly _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(DateOnlyXmlValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly DateOnly _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -103,15 +103,15 @@ public static bool TryCreate(DateOnly value, [NotNullWhen(true)] out DateOnlyXm
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(DateOnly value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(DateOnlyXmlValue), value);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (DateTimeOffset)this;

private DateTimeOffset _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private DateTimeOffset _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(DateTimeOffsetValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly DateTimeOffset _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -103,15 +103,15 @@ public static bool TryCreate(DateTimeOffset value, [NotNullWhen(true)] out Date
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(DateTimeOffset value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(DateTimeOffsetValue), value);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace AltaSoft.DomainPrimitives;
/// <inheritdoc/>
public object GetUnderlyingPrimitiveValue() => (DateTime)this;

private DateTime _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
private DateTime _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(DateTimeValue));
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly DateTime _value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -104,15 +104,15 @@ public static bool TryCreate(DateTime value, [NotNullWhen(true)] out DateTimeVa
}

/// <summary>
/// Validates the specified value and throws an exception if it is not valid.
/// Validates the specified value and throws an exception if it is not valid.
/// </summary>
/// <param name="value">The value to validate</param>
/// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
public void ValidateOrThrow(DateTime value)
{
var result = Validate(value);
if (!result.IsValid)
throw new InvalidDomainValueException(result.ErrorMessage, this);
throw new InvalidDomainValueException(result.ErrorMessage, typeof(DateTimeValue), value);
}


Expand Down
Loading