diff --git a/Directory.Build.props b/Directory.Build.props
index 81f37f4..6809d8a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -11,7 +11,7 @@
Domain Primitives
ALTA Software llc.
Copyright © 2024 ALTA Software llc.
- 8.0.0
+ 8.0.1
diff --git a/Examples/AltaSoft.DomainPrimitives.Demo/Customer.cs b/Examples/AltaSoft.DomainPrimitives.Demo/Customer.cs
index fffbd93..03c41a2 100644
--- a/Examples/AltaSoft.DomainPrimitives.Demo/Customer.cs
+++ b/Examples/AltaSoft.DomainPrimitives.Demo/Customer.cs
@@ -7,7 +7,8 @@ public sealed record Customer(
[Required] CustomerId A_CustomerId, [Required] Guid A_CustomerIdDotNet,
[Required] BirthDate B_BirthDate, [Required] DateOnly B_BirthDateDotNet,
- [Required] CustomerName C_CustomerName, [Required] string C_CustomerNameDotNet,
+ [Required] CustomerName C_CustomerName,
+ [Required][property: RegularExpression("\\Axxx")] string C_CustomerNameDotNet,
[Required] PositiveAmount D_Amount, [Required] decimal D_AmountDotnet)
{
public CustomerAddress? CustomerAddress { get; set; } //ignore
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Executor.cs b/src/AltaSoft.DomainPrimitives.Generator/Executor.cs
index fd3ab35..5cb8b79 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Executor.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Executor.cs
@@ -9,6 +9,7 @@
using AltaSoft.DomainPrimitives.Generator.Helpers;
using AltaSoft.DomainPrimitives.Generator.Models;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
namespace AltaSoft.DomainPrimitives.Generator;
@@ -389,6 +390,11 @@ private static void Process(GeneratorData data, string ctorCode, DomainPrimitive
usings.Add("System.Globalization");
}
+ if (data.ValidatePattern)
+ {
+ usings.Add("System.Text.RegularExpressions");
+ }
+
var needsMathOperators = data.GenerateAdditionOperators || data.GenerateDivisionOperators ||
data.GenerateMultiplyOperators || data.GenerateSubtractionOperators || data.GenerateModulusOperator;
@@ -780,7 +786,11 @@ private static bool ProcessConstructor(GeneratorData data, SourceCodeBuilder bui
.OpenBracket();
if (data.UnderlyingType == DomainPrimitiveUnderlyingType.String)
+ {
AddStringLengthAttributeValidation(type, data, builder);
+ AddPatternAttribute(type, data, builder);
+
+ }
builder.AppendLine("ValidateOrThrow(value);");
builder.CloseBracket()
@@ -836,4 +846,33 @@ private static void AddStringLengthAttributeValidation(ISymbol domainPrimitiveTy
.AppendLine($"\tthrow InvalidDomainValueException.StringRangeException(typeof({data.ClassName}), value, {minValue.ToString(CultureInfo.InvariantCulture)}, {maxValue.ToString(CultureInfo.InvariantCulture)});")
.NewLine();
}
+
+ ///
+ /// Adds pattern validation to the constructor if the Domain Primitive type is decorated with the PatternAttribute.
+ ///
+ private static void AddPatternAttribute(ISymbol domainPrimitiveType, GeneratorData data, SourceCodeBuilder sb)
+ {
+ var attr = domainPrimitiveType.GetAttributes()
+ .FirstOrDefault(x => string.Equals(x.AttributeClass?.ToDisplayString(), Constants.PatternAttributeFullName, StringComparison.Ordinal));
+
+ if (attr is null)
+ return;
+
+ var pattern = (string)attr.ConstructorArguments[0].Value!;
+ var validate = (bool)attr.ConstructorArguments[1].Value!;
+
+ if (string.IsNullOrEmpty(pattern))
+ return;
+
+ data.Pattern = pattern;
+ data.ValidatePattern = validate;
+ var quotedPattern = SymbolDisplay.FormatLiteral(data.Pattern, quote: true);
+
+ if (validate)
+ {
+ sb.AppendLine($"if (!Regex.IsMatch(value, {quotedPattern}, RegexOptions.Compiled))")
+ .AppendLine($"\tthrow InvalidDomainValueException.InvalidPatternException(typeof({data.ClassName}), value, {quotedPattern});")
+ .NewLine();
+ }
+ }
}
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Helpers/Constants.cs b/src/AltaSoft.DomainPrimitives.Generator/Helpers/Constants.cs
index 3e82212..6de0be4 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Helpers/Constants.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Helpers/Constants.cs
@@ -7,5 +7,6 @@ internal static class Constants
internal const string SupportedOperationsAttribute = "SupportedOperationsAttribute";
internal const string SupportedOperationsAttributeFullName = "AltaSoft.DomainPrimitives.SupportedOperationsAttribute";
internal const string StringLengthAttributeFullName = "AltaSoft.DomainPrimitives.StringLengthAttribute";
+ internal const string PatternAttributeFullName = "AltaSoft.DomainPrimitives.PatternAttribute";
}
}
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs b/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
index 7b4426f..238458a 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Helpers/MethodGeneratorHelper.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using AltaSoft.DomainPrimitives.Generator.Extensions;
using AltaSoft.DomainPrimitives.Generator.Models;
@@ -112,6 +113,18 @@ void AddMapping()
}
}
+ if (data.StringLengthAttributeValidation is { } stringLengthAttribute)
+ {
+ var (min, max) = stringLengthAttribute;
+ builder.Append("MinLength = ").Append(min.ToString(CultureInfo.InvariantCulture)).AppendLine(",");
+ builder.Append("MaxLength = ").Append(max.ToString(CultureInfo.InvariantCulture)).AppendLine(",");
+ }
+
+ if (data.Pattern is not null)
+ {
+ builder.Append("Pattern = ").Append(QuoteAndEscape(data.Pattern)).AppendLine(",");
+ }
+
builder.Length -= SourceCodeBuilder.s_newLineLength + 1;
builder.NewLine();
builder.AppendLine("}");
@@ -424,6 +437,7 @@ internal static void GenerateMandatoryMethods(GeneratorData data, SourceCodeBuil
}
AddStringLengthValidation(data, builder);
+ AddPatternValidation(data, builder);
builder.AppendLine("var validationResult = Validate(value);")
.AppendLine("if (!validationResult.IsValid)")
@@ -477,6 +491,24 @@ static void AddStringLengthValidation(GeneratorData data, SourceCodeBuilder sb)
.CloseBracket()
.NewLine();
}
+
+ static void AddPatternValidation(GeneratorData data, SourceCodeBuilder sb)
+ {
+ if (data.Pattern is null)
+ return;
+
+ if (!data.ValidatePattern)
+ return;
+
+ var quoted = QuoteAndEscape(data.Pattern);
+ sb.AppendLine($"if (!Regex.IsMatch(value, {quoted}, RegexOptions.Compiled))")
+ .OpenBracket()
+ .AppendLine("result = null;")
+ .AppendLine($"errorMessage = \"String does not match the required pattern: \" + {quoted};")
+ .AppendLine("return false;")
+ .CloseBracket()
+ .NewLine();
+ }
}
///
diff --git a/src/AltaSoft.DomainPrimitives.Generator/Models/GeneratorData.cs b/src/AltaSoft.DomainPrimitives.Generator/Models/GeneratorData.cs
index a517be0..95ada09 100644
--- a/src/AltaSoft.DomainPrimitives.Generator/Models/GeneratorData.cs
+++ b/src/AltaSoft.DomainPrimitives.Generator/Models/GeneratorData.cs
@@ -127,4 +127,15 @@ internal sealed class GeneratorData
/// Indicates whether the `Transform` method should be invoked before validation and instantiation.
///
public bool UseTransformMethod { get; set; }
+
+ ///
+ /// Gets or sets pattern for OpenAPI schema generation, or validation.
+ ///
+ public string? Pattern { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to validate the regex pattern at runtime when the `PatternAttribute` is applied to a Domain Primitive type. If set to `true`, the generated code will include logic to validate the pattern during instantiation and throw an exception if the value does not match the specified regex pattern.
+ /// If set to `false`, the pattern will only be used for OpenAPI schema generation and will not be validated at runtime.
+ ///
+ public bool ValidatePattern { get; set; }
}
diff --git a/src/AltaSoft.DomainPrimitives/InvalidDomainValueException.cs b/src/AltaSoft.DomainPrimitives/InvalidDomainValueException.cs
index e8238da..02ff306 100644
--- a/src/AltaSoft.DomainPrimitives/InvalidDomainValueException.cs
+++ b/src/AltaSoft.DomainPrimitives/InvalidDomainValueException.cs
@@ -68,6 +68,19 @@ public static InvalidDomainValueException LimitExceededException(Type type, int
return new InvalidDomainValueException($"The value has exceeded a {underlyingTypeName} limit", type, value);
}
+ ///
+ /// Creates an for string pattern mismatch errors.
+ ///
+ /// The of the domain primitive.
+ /// The string value that failed to match the pattern.
+ /// The expected regex pattern.
+ /// An describing the pattern mismatch.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static InvalidDomainValueException InvalidPatternException(Type type, string value, string pattern)
+ {
+ return new InvalidDomainValueException($"String value does not match the required pattern '{pattern}'", type, value);
+ }
+
///
/// Generates the error message for the including the underlying value.
///
diff --git a/src/AltaSoft.DomainPrimitives/PatternAttribute.cs b/src/AltaSoft.DomainPrimitives/PatternAttribute.cs
new file mode 100644
index 0000000..de6e351
--- /dev/null
+++ b/src/AltaSoft.DomainPrimitives/PatternAttribute.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace AltaSoft.DomainPrimitives;
+
+///
+/// Specifies a regex pattern that is always emitted to the OpenAPI schema and can optionally be used for runtime validation.
+/// By default, the pattern is not validated at runtime; validation occurs only when explicitly requested via .
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class PatternAttribute : Attribute
+{
+ ///
+ /// Gets the regex pattern used in the generated OpenAPI schema and, when enabled, for runtime validation.
+ ///
+ public string Pattern { get; }
+
+ ///
+ /// Gets a value indicating whether the should also be enforced via runtime validation.
+ ///
+ public bool Validate { get; }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ ///
+ /// The regex pattern that will always be included in the OpenAPI schema and may also be used for runtime validation.
+ ///
+ ///
+ /// A value indicating whether runtime validation should be performed using . Defaults to
+ /// to avoid incurring runtime validation overhead unless explicitly requested.
+ ///
+
+ public PatternAttribute([StringSyntax(StringSyntaxAttribute.Regex)] string pattern, bool validate = false)
+ {
+ Pattern = pattern;
+ Validate = validate;
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs
index dbba32a..4f2fc5d 100644
--- a/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/DomainPrimitiveGeneratorTest.cs
@@ -5,6 +5,76 @@ namespace AltaSoft.DomainPrimitives.Generator.Tests;
public class DomainPrimitiveGeneratorTest
{
+ [Fact]
+ public Task StringValue_WithTransStringLengthAndPattern()
+ {
+ const string source = """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+ using AltaSoft.DomainPrimitives;
+
+ namespace AltaSoft.DomainPrimitives;
+
+ ///
+ /// A string domain primitive with both length and pattern validation attributes, as well as a custom validation method.
+ ///
+ [StringLength(1, 100)]
+ [Pattern(@"[A-Z]{100}")]
+ internal partial class StringWithLengthAndPattern : IDomainValue
+ {
+ ///
+ public static PrimitiveValidationResult Validate(string value)
+ {
+ if (value == "Test")
+ return "Invalid Value";
+
+ return PrimitiveValidationResult.Ok;
+ }
+ }
+
+ """;
+
+ return TestHelper.Verify(source, (_, x, _) => Assert.Equal(4, x.Count));
+ }
+
+ [Fact]
+ public Task StringValue_WithTransStringLengthAndPatternWithValidation()
+ {
+ const string source = """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+ using AltaSoft.DomainPrimitives;
+
+ namespace AltaSoft.DomainPrimitives;
+
+ ///
+ /// A string domain primitive with both length and pattern validation attributes, as well as a custom validation method.
+ ///
+ [StringLength(1, 100)]
+ [Pattern(@"[A-Z]{100}",true)]
+ internal partial class StringWithLengthAndPattern : IDomainValue
+ {
+ ///
+ public static PrimitiveValidationResult Validate(string value)
+ {
+ if (value == "Test")
+ return "Invalid Value";
+
+ return PrimitiveValidationResult.Ok;
+ }
+ }
+
+ """;
+
+ return TestHelper.Verify(source, (_, x, _) => Assert.Equal(4, x.Count));
+ }
+
[Fact]
public Task StringValue_WithTransformerGeneratesTransformerCall()
{
@@ -18,7 +88,6 @@ public Task StringValue_WithTransformerGeneratesTransformerCall()
namespace AltaSoft.DomainPrimitives;
- ///
[StringLength(1, 100)]
internal partial class TransformableString : IDomainValue
{
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/PrimitivesWithPatternAttributeTests.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/PrimitivesWithPatternAttributeTests.cs
new file mode 100644
index 0000000..26e50e3
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/PrimitivesWithPatternAttributeTests.cs
@@ -0,0 +1,48 @@
+namespace AltaSoft.DomainPrimitives.Generator.Tests;
+
+public class PrimitivesWithPatternAttributeTests
+{
+ [Theory]
+ [InlineData("[A-Z]{3}")]
+ [InlineData("[A-Z]{10}")]
+ [InlineData("\\d+")]
+ [InlineData("\\w+")]
+ [InlineData("\\s+")]
+ [InlineData("[A-Z]{3}\\d{2}")]
+ [InlineData("[A-Z]{3}-\\d{3}")]
+ [InlineData("^\\d{4}-\\d{2}-\\d{2}$")]
+ [InlineData("^\\w+@\\w+\\.\\w+$")]
+ [InlineData("^\\d{3}-\\d{2}-\\d{4}$")]
+ public void Pattern_Should_Compile(string pattern)
+ {
+ var source = $$"""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+ using AltaSoft.DomainPrimitives;
+
+ namespace AltaSoft.DomainPrimitives;
+
+ ///
+ /// A string domain primitive with both length and pattern validation attributes, as well as a custom validation method.
+ ///
+ [StringLength(1, 100)]
+ [Pattern(@"{{pattern}}, true")]
+ internal partial class StringWithLengthAndPattern : IDomainValue
+ {
+ ///
+ public static PrimitiveValidationResult Validate(string value)
+ {
+ return PrimitiveValidationResult.Ok;
+ }
+ }
+
+ """;
+
+ TestHelper.Compile(source, (_, sources, _) => Assert.Equal(4, sources.Count));
+
+ }
+
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#OpenApiHelper.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#OpenApiHelper.g.verified.cs
new file mode 100644
index 0000000..745d0d1
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#OpenApiHelper.g.verified.cs
@@ -0,0 +1,50 @@
+//HintName: OpenApiHelper.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using Microsoft.OpenApi;
+using System;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+
+[assembly: AltaSoft.DomainPrimitives.DomainPrimitiveAssemblyAttribute]
+namespace generator_Test.Converters.Helpers;
+
+///
+/// Helper class providing methods to configure OpenApiSchema mappings for DomainPrimitive types of generator_Test
+///
+public static class OpenApiHelper
+{
+ ///
+ /// Mapping of DomainPrimitive types to OpenApiSchema definitions.
+ ///
+ ///
+ /// The Dictionary contains mappings for the following types:
+ ///
+ ///
+ ///
+ ///
+ public static FrozenDictionary Schemas = new Dictionary()
+ {
+ {
+ typeof(StringWithLengthAndPattern),
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.String,
+ Title = "StringWithLengthAndPattern",
+ Description = @"A string domain primitive with both length and pattern validation attributes, as well as a custom validation method.",
+ MinLength = 1,
+ MaxLength = 100,
+ Pattern = "[A-Z]{100}"
+ }
+ }
+ }.ToFrozenDictionary();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPattern.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPattern.g.verified.cs
new file mode 100644
index 0000000..56d29f6
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPattern.g.verified.cs
@@ -0,0 +1,328 @@
+//HintName: StringWithLengthAndPattern.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using System;
+using System.Numerics;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using AltaSoft.DomainPrimitives;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using AltaSoft.DomainPrimitives.Converters;
+using System.ComponentModel;
+
+namespace AltaSoft.DomainPrimitives;
+
+[JsonConverter(typeof(StringWithLengthAndPatternJsonConverter))]
+[TypeConverter(typeof(StringWithLengthAndPatternTypeConverter))]
+[UnderlyingPrimitiveType(typeof(string))]
+[DebuggerDisplay("{_value}")]
+internal partial class StringWithLengthAndPattern : IEquatable
+ , IComparable
+ , IComparable
+ , IParsable
+ , IConvertible
+{
+ ///
+ public Type GetUnderlyingPrimitiveType() => typeof(string);
+ ///
+ public object GetUnderlyingPrimitiveValue() => (string)this;
+
+ private string _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(StringWithLengthAndPattern));
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly string _value;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly bool _isInitialized;
+
+ ///
+ /// Initializes a new instance of the class by validating the specified value using static method.
+ ///
+ /// The value to be validated.
+ public StringWithLengthAndPattern(string value) : this(value, true)
+ {
+ }
+
+ private StringWithLengthAndPattern(string value, bool validate)
+ {
+ if (validate)
+ {
+ if (value.Length is < 1 or > 100)
+ throw InvalidDomainValueException.StringRangeException(typeof(StringWithLengthAndPattern), value, 1, 100);
+
+ ValidateOrThrow(value);
+ }
+ _value = value;
+ _isInitialized = true;
+ }
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///
+ [Obsolete("Domain primitive cannot be created using empty Constructor", true)]
+ public StringWithLengthAndPattern()
+ {
+ }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create StringWithLengthAndPattern from
+ /// When this method returns, contains the created StringWithLengthAndPattern if the conversion succeeded, or null if the conversion failed.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(string value, [NotNullWhen(true)] out StringWithLengthAndPattern? result)
+ {
+ return TryCreate(value, out result, out _);
+ }
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create StringWithLengthAndPattern from
+ /// When this method returns, contains the created StringWithLengthAndPattern if the conversion succeeded, or null if the conversion failed.
+ /// When this method returns, contains the error message if the conversion failed; otherwise, null.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(string value, [NotNullWhen(true)] out StringWithLengthAndPattern? result, [NotNullWhen(false)] out string? errorMessage)
+ {
+ if (value.Length is < 1 or > 100)
+ {
+ result = null;
+ errorMessage = "String length is out of range 1..100";
+ return false;
+ }
+
+ var validationResult = Validate(value);
+ if (!validationResult.IsValid)
+ {
+ result = null;
+ errorMessage = validationResult.ErrorMessage;
+ return false;
+ }
+
+ result = new (value, false);
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// Validates the specified value and throws an exception if it is not valid.
+ ///
+ /// The value to validate
+ /// Thrown when the value is not valid.
+ public void ValidateOrThrow(string value)
+ {
+ var result = Validate(value);
+ if (!result.IsValid)
+ throw new InvalidDomainValueException(result.ErrorMessage, typeof(StringWithLengthAndPattern), value);
+ }
+
+
+ ///
+ /// Gets the character at the specified index.
+ ///
+ public char this[int i]
+ {
+ get => _value[i];
+ }
+
+ ///
+ /// Gets the character at the specified index.
+ ///
+ public char this[Index index]
+ {
+ get => _value[index];
+ }
+
+ ///
+ /// Gets the substring by specified range.
+ ///
+ public string this[Range range]
+ {
+ get => _value[range];
+ }
+
+ ///
+ /// Gets the number of characters.
+ ///
+ /// The number of characters in underlying string value.
+ public int Length => _value.Length;
+
+ ///
+ /// Returns a substring of this string.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Substring(int startIndex, int length) => _value.Substring(startIndex, length);
+
+ ///
+ /// Returns a substring of this string.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Substring(int startIndex) => _value.Substring(startIndex);
+
+ ///
+ /// Checks if the specified value is contained within the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Contains(string value) => _value.Contains(value);
+
+ ///
+ /// Determines whether a specified string is a prefix of the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool StartsWith(string value) => _value.StartsWith(value);
+
+ ///
+ /// Determines whether a specified string is a suffix of the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool EndsWith(string value) => _value.EndsWith(value);
+
+ ///
+ /// Returns the entire string as an array of characters.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public char[] ToCharArray() => _value.ToCharArray();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object? obj) => obj is StringWithLengthAndPattern other && Equals(other);
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(StringWithLengthAndPattern? other)
+ {
+ if (other is null || !_isInitialized || !other._isInitialized)
+ return false;
+ return _value.Equals(other._value);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(StringWithLengthAndPattern? left, StringWithLengthAndPattern? right)
+ {
+ if (ReferenceEquals(left, right))
+ return true;
+ if (left is null || right is null)
+ return false;
+ return left.Equals(right);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(StringWithLengthAndPattern? left, StringWithLengthAndPattern? right) => !(left == right);
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (obj is null)
+ return 1;
+
+ if (obj is StringWithLengthAndPattern c)
+ return CompareTo(c);
+
+ throw new ArgumentException("Object is not a StringWithLengthAndPattern", nameof(obj));
+ }
+
+ ///
+ public int CompareTo(StringWithLengthAndPattern? other)
+ {
+ if (other is null || !other._isInitialized)
+ return 1;
+ if (!_isInitialized)
+ return -1;
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator StringWithLengthAndPattern?(string? value) => value is null ? null : new(value);
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator string?(StringWithLengthAndPattern? value) => value is null ? null : (string?)value._valueOrThrow;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static StringWithLengthAndPattern Parse(string s, IFormatProvider? provider) => s;
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out StringWithLengthAndPattern result)
+ {
+ if (s is null)
+ {
+ result = default;
+ return false;
+ }
+
+ return StringWithLengthAndPattern.TryCreate(s, out result);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => _valueOrThrow.GetHashCode();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ TypeCode IConvertible.GetTypeCode() => ((IConvertible)(String)_valueOrThrow).GetTypeCode();
+
+ ///
+ bool IConvertible.ToBoolean(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToBoolean(provider);
+
+ ///
+ byte IConvertible.ToByte(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToByte(provider);
+
+ ///
+ char IConvertible.ToChar(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToChar(provider);
+
+ ///
+ DateTime IConvertible.ToDateTime(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDateTime(provider);
+
+ ///
+ decimal IConvertible.ToDecimal(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDecimal(provider);
+
+ ///
+ double IConvertible.ToDouble(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDouble(provider);
+
+ ///
+ short IConvertible.ToInt16(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt16(provider);
+
+ ///
+ int IConvertible.ToInt32(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt32(provider);
+
+ ///
+ long IConvertible.ToInt64(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt64(provider);
+
+ ///
+ sbyte IConvertible.ToSByte(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToSByte(provider);
+
+ ///
+ float IConvertible.ToSingle(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToSingle(provider);
+
+ ///
+ string IConvertible.ToString(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToString(provider);
+
+ ///
+ object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToType(conversionType, provider);
+
+ ///
+ ushort IConvertible.ToUInt16(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt16(provider);
+
+ ///
+ uint IConvertible.ToUInt32(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt32(provider);
+
+ ///
+ ulong IConvertible.ToUInt64(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt64(provider);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override string ToString() => _valueOrThrow.ToString();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPatternJsonConverter.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPatternJsonConverter.g.verified.cs
new file mode 100644
index 0000000..764a138
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPatternJsonConverter.g.verified.cs
@@ -0,0 +1,62 @@
+//HintName: StringWithLengthAndPatternJsonConverter.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Globalization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace AltaSoft.DomainPrimitives.Converters;
+
+///
+/// JsonConverter for
+///
+internal sealed class StringWithLengthAndPatternJsonConverter : JsonConverter
+{
+ ///
+ public override StringWithLengthAndPattern Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonInternalConverters.StringConverter.Read(ref reader, typeToConvert, options)!;
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new JsonException(ex.Message);
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, StringWithLengthAndPattern value, JsonSerializerOptions options)
+ {
+ JsonInternalConverters.StringConverter.Write(writer, (string)value, options);
+ }
+
+ ///
+ public override StringWithLengthAndPattern ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonInternalConverters.StringConverter.ReadAsPropertyName(ref reader, typeToConvert, options)!;
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new JsonException(ex.Message);
+ }
+ }
+
+ ///
+ public override void WriteAsPropertyName(Utf8JsonWriter writer, StringWithLengthAndPattern value, JsonSerializerOptions options)
+ {
+ JsonInternalConverters.StringConverter.WriteAsPropertyName(writer, (string)value, options);
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPatternTypeConverter.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPatternTypeConverter.g.verified.cs
new file mode 100644
index 0000000..b48242f
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPattern#StringWithLengthAndPatternTypeConverter.g.verified.cs
@@ -0,0 +1,38 @@
+//HintName: StringWithLengthAndPatternTypeConverter.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace AltaSoft.DomainPrimitives.Converters;
+
+///
+/// TypeConverter for
+///
+internal sealed class StringWithLengthAndPatternTypeConverter : StringConverter
+{
+ ///
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ var result = base.ConvertFrom(context, culture, value);
+ if (result is null)
+ return null;
+ try
+ {
+ return new StringWithLengthAndPattern((string)result);
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new FormatException("Cannot parse StringWithLengthAndPattern", ex);
+ }
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#OpenApiHelper.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#OpenApiHelper.g.verified.cs
new file mode 100644
index 0000000..745d0d1
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#OpenApiHelper.g.verified.cs
@@ -0,0 +1,50 @@
+//HintName: OpenApiHelper.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using Microsoft.OpenApi;
+using System;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+
+[assembly: AltaSoft.DomainPrimitives.DomainPrimitiveAssemblyAttribute]
+namespace generator_Test.Converters.Helpers;
+
+///
+/// Helper class providing methods to configure OpenApiSchema mappings for DomainPrimitive types of generator_Test
+///
+public static class OpenApiHelper
+{
+ ///
+ /// Mapping of DomainPrimitive types to OpenApiSchema definitions.
+ ///
+ ///
+ /// The Dictionary contains mappings for the following types:
+ ///
+ ///
+ ///
+ ///
+ public static FrozenDictionary Schemas = new Dictionary()
+ {
+ {
+ typeof(StringWithLengthAndPattern),
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.String,
+ Title = "StringWithLengthAndPattern",
+ Description = @"A string domain primitive with both length and pattern validation attributes, as well as a custom validation method.",
+ MinLength = 1,
+ MaxLength = 100,
+ Pattern = "[A-Z]{100}"
+ }
+ }
+ }.ToFrozenDictionary();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPattern.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPattern.g.verified.cs
new file mode 100644
index 0000000..d5c77a0
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPattern.g.verified.cs
@@ -0,0 +1,339 @@
+//HintName: StringWithLengthAndPattern.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using System;
+using System.Numerics;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using AltaSoft.DomainPrimitives;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using AltaSoft.DomainPrimitives.Converters;
+using System.ComponentModel;
+using System.Text.RegularExpressions;
+
+namespace AltaSoft.DomainPrimitives;
+
+[JsonConverter(typeof(StringWithLengthAndPatternJsonConverter))]
+[TypeConverter(typeof(StringWithLengthAndPatternTypeConverter))]
+[UnderlyingPrimitiveType(typeof(string))]
+[DebuggerDisplay("{_value}")]
+internal partial class StringWithLengthAndPattern : IEquatable
+ , IComparable
+ , IComparable
+ , IParsable
+ , IConvertible
+{
+ ///
+ public Type GetUnderlyingPrimitiveType() => typeof(string);
+ ///
+ public object GetUnderlyingPrimitiveValue() => (string)this;
+
+ private string _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(StringWithLengthAndPattern));
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly string _value;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly bool _isInitialized;
+
+ ///
+ /// Initializes a new instance of the class by validating the specified value using static method.
+ ///
+ /// The value to be validated.
+ public StringWithLengthAndPattern(string value) : this(value, true)
+ {
+ }
+
+ private StringWithLengthAndPattern(string value, bool validate)
+ {
+ if (validate)
+ {
+ if (value.Length is < 1 or > 100)
+ throw InvalidDomainValueException.StringRangeException(typeof(StringWithLengthAndPattern), value, 1, 100);
+
+ if (!Regex.IsMatch(value, "[A-Z]{100}", RegexOptions.Compiled))
+ throw InvalidDomainValueException.InvalidPatternException(typeof(StringWithLengthAndPattern), value, "[A-Z]{100}");
+
+ ValidateOrThrow(value);
+ }
+ _value = value;
+ _isInitialized = true;
+ }
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///
+ [Obsolete("Domain primitive cannot be created using empty Constructor", true)]
+ public StringWithLengthAndPattern()
+ {
+ }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create StringWithLengthAndPattern from
+ /// When this method returns, contains the created StringWithLengthAndPattern if the conversion succeeded, or null if the conversion failed.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(string value, [NotNullWhen(true)] out StringWithLengthAndPattern? result)
+ {
+ return TryCreate(value, out result, out _);
+ }
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create StringWithLengthAndPattern from
+ /// When this method returns, contains the created StringWithLengthAndPattern if the conversion succeeded, or null if the conversion failed.
+ /// When this method returns, contains the error message if the conversion failed; otherwise, null.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(string value, [NotNullWhen(true)] out StringWithLengthAndPattern? result, [NotNullWhen(false)] out string? errorMessage)
+ {
+ if (value.Length is < 1 or > 100)
+ {
+ result = null;
+ errorMessage = "String length is out of range 1..100";
+ return false;
+ }
+
+ if (!Regex.IsMatch(value, "[A-Z]{100}", RegexOptions.Compiled))
+ {
+ result = null;
+ errorMessage = "String does not match the required pattern: " + "[A-Z]{100}";
+ return false;
+ }
+
+ var validationResult = Validate(value);
+ if (!validationResult.IsValid)
+ {
+ result = null;
+ errorMessage = validationResult.ErrorMessage;
+ return false;
+ }
+
+ result = new (value, false);
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// Validates the specified value and throws an exception if it is not valid.
+ ///
+ /// The value to validate
+ /// Thrown when the value is not valid.
+ public void ValidateOrThrow(string value)
+ {
+ var result = Validate(value);
+ if (!result.IsValid)
+ throw new InvalidDomainValueException(result.ErrorMessage, typeof(StringWithLengthAndPattern), value);
+ }
+
+
+ ///
+ /// Gets the character at the specified index.
+ ///
+ public char this[int i]
+ {
+ get => _value[i];
+ }
+
+ ///
+ /// Gets the character at the specified index.
+ ///
+ public char this[Index index]
+ {
+ get => _value[index];
+ }
+
+ ///
+ /// Gets the substring by specified range.
+ ///
+ public string this[Range range]
+ {
+ get => _value[range];
+ }
+
+ ///
+ /// Gets the number of characters.
+ ///
+ /// The number of characters in underlying string value.
+ public int Length => _value.Length;
+
+ ///
+ /// Returns a substring of this string.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Substring(int startIndex, int length) => _value.Substring(startIndex, length);
+
+ ///
+ /// Returns a substring of this string.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Substring(int startIndex) => _value.Substring(startIndex);
+
+ ///
+ /// Checks if the specified value is contained within the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Contains(string value) => _value.Contains(value);
+
+ ///
+ /// Determines whether a specified string is a prefix of the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool StartsWith(string value) => _value.StartsWith(value);
+
+ ///
+ /// Determines whether a specified string is a suffix of the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool EndsWith(string value) => _value.EndsWith(value);
+
+ ///
+ /// Returns the entire string as an array of characters.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public char[] ToCharArray() => _value.ToCharArray();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object? obj) => obj is StringWithLengthAndPattern other && Equals(other);
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(StringWithLengthAndPattern? other)
+ {
+ if (other is null || !_isInitialized || !other._isInitialized)
+ return false;
+ return _value.Equals(other._value);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(StringWithLengthAndPattern? left, StringWithLengthAndPattern? right)
+ {
+ if (ReferenceEquals(left, right))
+ return true;
+ if (left is null || right is null)
+ return false;
+ return left.Equals(right);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(StringWithLengthAndPattern? left, StringWithLengthAndPattern? right) => !(left == right);
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (obj is null)
+ return 1;
+
+ if (obj is StringWithLengthAndPattern c)
+ return CompareTo(c);
+
+ throw new ArgumentException("Object is not a StringWithLengthAndPattern", nameof(obj));
+ }
+
+ ///
+ public int CompareTo(StringWithLengthAndPattern? other)
+ {
+ if (other is null || !other._isInitialized)
+ return 1;
+ if (!_isInitialized)
+ return -1;
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator StringWithLengthAndPattern?(string? value) => value is null ? null : new(value);
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator string?(StringWithLengthAndPattern? value) => value is null ? null : (string?)value._valueOrThrow;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static StringWithLengthAndPattern Parse(string s, IFormatProvider? provider) => s;
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out StringWithLengthAndPattern result)
+ {
+ if (s is null)
+ {
+ result = default;
+ return false;
+ }
+
+ return StringWithLengthAndPattern.TryCreate(s, out result);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => _valueOrThrow.GetHashCode();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ TypeCode IConvertible.GetTypeCode() => ((IConvertible)(String)_valueOrThrow).GetTypeCode();
+
+ ///
+ bool IConvertible.ToBoolean(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToBoolean(provider);
+
+ ///
+ byte IConvertible.ToByte(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToByte(provider);
+
+ ///
+ char IConvertible.ToChar(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToChar(provider);
+
+ ///
+ DateTime IConvertible.ToDateTime(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDateTime(provider);
+
+ ///
+ decimal IConvertible.ToDecimal(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDecimal(provider);
+
+ ///
+ double IConvertible.ToDouble(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDouble(provider);
+
+ ///
+ short IConvertible.ToInt16(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt16(provider);
+
+ ///
+ int IConvertible.ToInt32(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt32(provider);
+
+ ///
+ long IConvertible.ToInt64(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt64(provider);
+
+ ///
+ sbyte IConvertible.ToSByte(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToSByte(provider);
+
+ ///
+ float IConvertible.ToSingle(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToSingle(provider);
+
+ ///
+ string IConvertible.ToString(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToString(provider);
+
+ ///
+ object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToType(conversionType, provider);
+
+ ///
+ ushort IConvertible.ToUInt16(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt16(provider);
+
+ ///
+ uint IConvertible.ToUInt32(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt32(provider);
+
+ ///
+ ulong IConvertible.ToUInt64(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt64(provider);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override string ToString() => _valueOrThrow.ToString();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPatternJsonConverter.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPatternJsonConverter.g.verified.cs
new file mode 100644
index 0000000..764a138
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPatternJsonConverter.g.verified.cs
@@ -0,0 +1,62 @@
+//HintName: StringWithLengthAndPatternJsonConverter.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Globalization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace AltaSoft.DomainPrimitives.Converters;
+
+///
+/// JsonConverter for
+///
+internal sealed class StringWithLengthAndPatternJsonConverter : JsonConverter
+{
+ ///
+ public override StringWithLengthAndPattern Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonInternalConverters.StringConverter.Read(ref reader, typeToConvert, options)!;
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new JsonException(ex.Message);
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, StringWithLengthAndPattern value, JsonSerializerOptions options)
+ {
+ JsonInternalConverters.StringConverter.Write(writer, (string)value, options);
+ }
+
+ ///
+ public override StringWithLengthAndPattern ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonInternalConverters.StringConverter.ReadAsPropertyName(ref reader, typeToConvert, options)!;
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new JsonException(ex.Message);
+ }
+ }
+
+ ///
+ public override void WriteAsPropertyName(Utf8JsonWriter writer, StringWithLengthAndPattern value, JsonSerializerOptions options)
+ {
+ JsonInternalConverters.StringConverter.WriteAsPropertyName(writer, (string)value, options);
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPatternTypeConverter.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPatternTypeConverter.g.verified.cs
new file mode 100644
index 0000000..b48242f
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndPatternWithValidation#StringWithLengthAndPatternTypeConverter.g.verified.cs
@@ -0,0 +1,38 @@
+//HintName: StringWithLengthAndPatternTypeConverter.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace AltaSoft.DomainPrimitives.Converters;
+
+///
+/// TypeConverter for
+///
+internal sealed class StringWithLengthAndPatternTypeConverter : StringConverter
+{
+ ///
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ var result = base.ConvertFrom(context, culture, value);
+ if (result is null)
+ return null;
+ try
+ {
+ return new StringWithLengthAndPattern((string)result);
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new FormatException("Cannot parse StringWithLengthAndPattern", ex);
+ }
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#OpenApiHelper.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#OpenApiHelper.g.verified.cs
new file mode 100644
index 0000000..745d0d1
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#OpenApiHelper.g.verified.cs
@@ -0,0 +1,50 @@
+//HintName: OpenApiHelper.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using Microsoft.OpenApi;
+using System;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Text.Json.Nodes;
+
+[assembly: AltaSoft.DomainPrimitives.DomainPrimitiveAssemblyAttribute]
+namespace generator_Test.Converters.Helpers;
+
+///
+/// Helper class providing methods to configure OpenApiSchema mappings for DomainPrimitive types of generator_Test
+///
+public static class OpenApiHelper
+{
+ ///
+ /// Mapping of DomainPrimitive types to OpenApiSchema definitions.
+ ///
+ ///
+ /// The Dictionary contains mappings for the following types:
+ ///
+ ///
+ ///
+ ///
+ public static FrozenDictionary Schemas = new Dictionary()
+ {
+ {
+ typeof(StringWithLengthAndPattern),
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.String,
+ Title = "StringWithLengthAndPattern",
+ Description = @"A string domain primitive with both length and pattern validation attributes, as well as a custom validation method.",
+ MinLength = 1,
+ MaxLength = 100,
+ Pattern = "[A-Z]{100}"
+ }
+ }
+ }.ToFrozenDictionary();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPattern.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPattern.g.verified.cs
new file mode 100644
index 0000000..56d29f6
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPattern.g.verified.cs
@@ -0,0 +1,328 @@
+//HintName: StringWithLengthAndPattern.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using System;
+using System.Numerics;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using AltaSoft.DomainPrimitives;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using AltaSoft.DomainPrimitives.Converters;
+using System.ComponentModel;
+
+namespace AltaSoft.DomainPrimitives;
+
+[JsonConverter(typeof(StringWithLengthAndPatternJsonConverter))]
+[TypeConverter(typeof(StringWithLengthAndPatternTypeConverter))]
+[UnderlyingPrimitiveType(typeof(string))]
+[DebuggerDisplay("{_value}")]
+internal partial class StringWithLengthAndPattern : IEquatable
+ , IComparable
+ , IComparable
+ , IParsable
+ , IConvertible
+{
+ ///
+ public Type GetUnderlyingPrimitiveType() => typeof(string);
+ ///
+ public object GetUnderlyingPrimitiveValue() => (string)this;
+
+ private string _valueOrThrow => _isInitialized ? _value : throw InvalidDomainValueException.NotInitializedException(typeof(StringWithLengthAndPattern));
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly string _value;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly bool _isInitialized;
+
+ ///
+ /// Initializes a new instance of the class by validating the specified value using static method.
+ ///
+ /// The value to be validated.
+ public StringWithLengthAndPattern(string value) : this(value, true)
+ {
+ }
+
+ private StringWithLengthAndPattern(string value, bool validate)
+ {
+ if (validate)
+ {
+ if (value.Length is < 1 or > 100)
+ throw InvalidDomainValueException.StringRangeException(typeof(StringWithLengthAndPattern), value, 1, 100);
+
+ ValidateOrThrow(value);
+ }
+ _value = value;
+ _isInitialized = true;
+ }
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///
+ [Obsolete("Domain primitive cannot be created using empty Constructor", true)]
+ public StringWithLengthAndPattern()
+ {
+ }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create StringWithLengthAndPattern from
+ /// When this method returns, contains the created StringWithLengthAndPattern if the conversion succeeded, or null if the conversion failed.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(string value, [NotNullWhen(true)] out StringWithLengthAndPattern? result)
+ {
+ return TryCreate(value, out result, out _);
+ }
+
+ ///
+ /// Tries to create an instance of AsciiString from the specified value.
+ ///
+ /// The value to create StringWithLengthAndPattern from
+ /// When this method returns, contains the created StringWithLengthAndPattern if the conversion succeeded, or null if the conversion failed.
+ /// When this method returns, contains the error message if the conversion failed; otherwise, null.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryCreate(string value, [NotNullWhen(true)] out StringWithLengthAndPattern? result, [NotNullWhen(false)] out string? errorMessage)
+ {
+ if (value.Length is < 1 or > 100)
+ {
+ result = null;
+ errorMessage = "String length is out of range 1..100";
+ return false;
+ }
+
+ var validationResult = Validate(value);
+ if (!validationResult.IsValid)
+ {
+ result = null;
+ errorMessage = validationResult.ErrorMessage;
+ return false;
+ }
+
+ result = new (value, false);
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// Validates the specified value and throws an exception if it is not valid.
+ ///
+ /// The value to validate
+ /// Thrown when the value is not valid.
+ public void ValidateOrThrow(string value)
+ {
+ var result = Validate(value);
+ if (!result.IsValid)
+ throw new InvalidDomainValueException(result.ErrorMessage, typeof(StringWithLengthAndPattern), value);
+ }
+
+
+ ///
+ /// Gets the character at the specified index.
+ ///
+ public char this[int i]
+ {
+ get => _value[i];
+ }
+
+ ///
+ /// Gets the character at the specified index.
+ ///
+ public char this[Index index]
+ {
+ get => _value[index];
+ }
+
+ ///
+ /// Gets the substring by specified range.
+ ///
+ public string this[Range range]
+ {
+ get => _value[range];
+ }
+
+ ///
+ /// Gets the number of characters.
+ ///
+ /// The number of characters in underlying string value.
+ public int Length => _value.Length;
+
+ ///
+ /// Returns a substring of this string.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Substring(int startIndex, int length) => _value.Substring(startIndex, length);
+
+ ///
+ /// Returns a substring of this string.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string Substring(int startIndex) => _value.Substring(startIndex);
+
+ ///
+ /// Checks if the specified value is contained within the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Contains(string value) => _value.Contains(value);
+
+ ///
+ /// Determines whether a specified string is a prefix of the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool StartsWith(string value) => _value.StartsWith(value);
+
+ ///
+ /// Determines whether a specified string is a suffix of the current instance.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool EndsWith(string value) => _value.EndsWith(value);
+
+ ///
+ /// Returns the entire string as an array of characters.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public char[] ToCharArray() => _value.ToCharArray();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object? obj) => obj is StringWithLengthAndPattern other && Equals(other);
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(StringWithLengthAndPattern? other)
+ {
+ if (other is null || !_isInitialized || !other._isInitialized)
+ return false;
+ return _value.Equals(other._value);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(StringWithLengthAndPattern? left, StringWithLengthAndPattern? right)
+ {
+ if (ReferenceEquals(left, right))
+ return true;
+ if (left is null || right is null)
+ return false;
+ return left.Equals(right);
+ }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(StringWithLengthAndPattern? left, StringWithLengthAndPattern? right) => !(left == right);
+
+ ///
+ public int CompareTo(object? obj)
+ {
+ if (obj is null)
+ return 1;
+
+ if (obj is StringWithLengthAndPattern c)
+ return CompareTo(c);
+
+ throw new ArgumentException("Object is not a StringWithLengthAndPattern", nameof(obj));
+ }
+
+ ///
+ public int CompareTo(StringWithLengthAndPattern? other)
+ {
+ if (other is null || !other._isInitialized)
+ return 1;
+ if (!_isInitialized)
+ return -1;
+ return _value.CompareTo(other._value);
+ }
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator StringWithLengthAndPattern?(string? value) => value is null ? null : new(value);
+
+ ///
+ /// Implicit conversion from (nullable) to (nullable)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(value))]
+ public static implicit operator string?(StringWithLengthAndPattern? value) => value is null ? null : (string?)value._valueOrThrow;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static StringWithLengthAndPattern Parse(string s, IFormatProvider? provider) => s;
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out StringWithLengthAndPattern result)
+ {
+ if (s is null)
+ {
+ result = default;
+ return false;
+ }
+
+ return StringWithLengthAndPattern.TryCreate(s, out result);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => _valueOrThrow.GetHashCode();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ TypeCode IConvertible.GetTypeCode() => ((IConvertible)(String)_valueOrThrow).GetTypeCode();
+
+ ///
+ bool IConvertible.ToBoolean(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToBoolean(provider);
+
+ ///
+ byte IConvertible.ToByte(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToByte(provider);
+
+ ///
+ char IConvertible.ToChar(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToChar(provider);
+
+ ///
+ DateTime IConvertible.ToDateTime(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDateTime(provider);
+
+ ///
+ decimal IConvertible.ToDecimal(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDecimal(provider);
+
+ ///
+ double IConvertible.ToDouble(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToDouble(provider);
+
+ ///
+ short IConvertible.ToInt16(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt16(provider);
+
+ ///
+ int IConvertible.ToInt32(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt32(provider);
+
+ ///
+ long IConvertible.ToInt64(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToInt64(provider);
+
+ ///
+ sbyte IConvertible.ToSByte(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToSByte(provider);
+
+ ///
+ float IConvertible.ToSingle(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToSingle(provider);
+
+ ///
+ string IConvertible.ToString(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToString(provider);
+
+ ///
+ object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToType(conversionType, provider);
+
+ ///
+ ushort IConvertible.ToUInt16(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt16(provider);
+
+ ///
+ uint IConvertible.ToUInt32(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt32(provider);
+
+ ///
+ ulong IConvertible.ToUInt64(IFormatProvider? provider) => ((IConvertible)(String)_valueOrThrow).ToUInt64(provider);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override string ToString() => _valueOrThrow.ToString();
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPatternJsonConverter.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPatternJsonConverter.g.verified.cs
new file mode 100644
index 0000000..764a138
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPatternJsonConverter.g.verified.cs
@@ -0,0 +1,62 @@
+//HintName: StringWithLengthAndPatternJsonConverter.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Globalization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace AltaSoft.DomainPrimitives.Converters;
+
+///
+/// JsonConverter for
+///
+internal sealed class StringWithLengthAndPatternJsonConverter : JsonConverter
+{
+ ///
+ public override StringWithLengthAndPattern Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonInternalConverters.StringConverter.Read(ref reader, typeToConvert, options)!;
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new JsonException(ex.Message);
+ }
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, StringWithLengthAndPattern value, JsonSerializerOptions options)
+ {
+ JsonInternalConverters.StringConverter.Write(writer, (string)value, options);
+ }
+
+ ///
+ public override StringWithLengthAndPattern ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ try
+ {
+ return JsonInternalConverters.StringConverter.ReadAsPropertyName(ref reader, typeToConvert, options)!;
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new JsonException(ex.Message);
+ }
+ }
+
+ ///
+ public override void WriteAsPropertyName(Utf8JsonWriter writer, StringWithLengthAndPattern value, JsonSerializerOptions options)
+ {
+ JsonInternalConverters.StringConverter.WriteAsPropertyName(writer, (string)value, options);
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPatternTypeConverter.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPatternTypeConverter.g.verified.cs
new file mode 100644
index 0000000..b48242f
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransStringLengthAndXmlSummary#StringWithLengthAndPatternTypeConverter.g.verified.cs
@@ -0,0 +1,38 @@
+//HintName: StringWithLengthAndPatternTypeConverter.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft DomainPrimitives Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using AltaSoft.DomainPrimitives;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace AltaSoft.DomainPrimitives.Converters;
+
+///
+/// TypeConverter for
+///
+internal sealed class StringWithLengthAndPatternTypeConverter : StringConverter
+{
+ ///
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ var result = base.ConvertFrom(context, culture, value);
+ if (result is null)
+ return null;
+ try
+ {
+ return new StringWithLengthAndPattern((string)result);
+ }
+ catch (InvalidDomainValueException ex)
+ {
+ throw new FormatException("Cannot parse StringWithLengthAndPattern", ex);
+ }
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransformerGeneratesTransformerCall#OpenApiHelper.g.verified.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransformerGeneratesTransformerCall#OpenApiHelper.g.verified.cs
index 32ac159..6818420 100644
--- a/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransformerGeneratesTransformerCall#OpenApiHelper.g.verified.cs
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/Snapshots/DomainPrimitiveGeneratorTest.StringValue_WithTransformerGeneratesTransformerCall#OpenApiHelper.g.verified.cs
@@ -39,7 +39,9 @@ public static class OpenApiHelper
new OpenApiSchema
{
Type = JsonSchemaType.String,
- Title = "TransformableString"
+ Title = "TransformableString",
+ MinLength = 1,
+ MaxLength = 100
}
}
}.ToFrozenDictionary();
diff --git a/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelper.cs b/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelper.cs
index 7654d46..79a420c 100644
--- a/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelper.cs
+++ b/tests/AltaSoft.DomainPrimitives.Generator.Tests/TestHelper.cs
@@ -5,19 +5,24 @@
using Microsoft.CodeAnalysis;
using Microsoft.OpenApi;
-namespace AltaSoft.DomainPrimitives.Generator.Tests
+namespace AltaSoft.DomainPrimitives.Generator.Tests;
+
+public static class TestHelper
{
- public static class TestHelper
+ internal static Task Verify(string source, Action, List, GeneratorDriver>? additionalChecks = null, DomainPrimitiveGlobalOptions? options = null)
{
- internal static Task Verify(string source, Action, List, GeneratorDriver>? additionalChecks = null, DomainPrimitiveGlobalOptions? options = null)
- {
- List assemblies = [typeof(JsonSerializer).Assembly, typeof(OpenApiSchema).Assembly];
- var (diagnostics, output, driver) = TestHelpers.GetGeneratedOutput(source, assemblies, options);
+ var driver = Compile(source, additionalChecks, options);
+
+ return Verifier.Verify(driver).UseDirectory("Snapshots");
+ }
- Assert.Empty(diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error));
- additionalChecks?.Invoke(diagnostics, output, driver);
+ internal static GeneratorDriver Compile(string source, Action, List, GeneratorDriver>? additionalChecks, DomainPrimitiveGlobalOptions? options = null)
+ {
+ List assemblies = [typeof(JsonSerializer).Assembly, typeof(OpenApiSchema).Assembly];
+ var (diagnostics, output, driver) = TestHelpers.GetGeneratedOutput(source, assemblies, options);
- return Verifier.Verify(driver).UseDirectory("Snapshots");
- }
+ Assert.Empty(diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error));
+ additionalChecks?.Invoke(diagnostics, output, driver);
+ return driver;
}
}
diff --git a/tests/AltaSoft.DomainPrimitives.UnitTests/TransformableTests/PatternBasedString.cs b/tests/AltaSoft.DomainPrimitives.UnitTests/TransformableTests/PatternBasedString.cs
new file mode 100644
index 0000000..14bf8df
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.UnitTests/TransformableTests/PatternBasedString.cs
@@ -0,0 +1,13 @@
+namespace AltaSoft.DomainPrimitives.UnitTests.TransformableTests;
+
+///
+/// Pattern based string
+///
+[Pattern("^[a-zA-H]{5,10}$", true)]
+public sealed partial class PatternBasedString : IDomainValue
+{
+ public static PrimitiveValidationResult Validate(string value)
+ {
+ return PrimitiveValidationResult.Ok;
+ }
+}
diff --git a/tests/AltaSoft.DomainPrimitives.UnitTests/TransformableTests/PatternBasedStringTests.cs b/tests/AltaSoft.DomainPrimitives.UnitTests/TransformableTests/PatternBasedStringTests.cs
new file mode 100644
index 0000000..8923d9d
--- /dev/null
+++ b/tests/AltaSoft.DomainPrimitives.UnitTests/TransformableTests/PatternBasedStringTests.cs
@@ -0,0 +1,107 @@
+using Xunit;
+
+namespace AltaSoft.DomainPrimitives.UnitTests.TransformableTests;
+
+public class PatternBasedStringTests
+{
+ [Theory]
+ [InlineData("ABCDE")]
+ [InlineData("Hebboo")]
+ [InlineData("ABCDEFGH")]
+ [InlineData("ABCDEFGHAB")]
+ public void Constructor_WithValidValue_CreatesInstance(string value)
+ {
+ var result = new PatternBasedString(value);
+
+ Assert.NotNull(result);
+ Assert.Equal(value, (string)result);
+ }
+
+ [Theory]
+ [InlineData("ABC")]
+ [InlineData("ABCD")]
+ public void Constructor_WithValueTooShort_ThrowsInvalidDomainValueException(string value)
+ {
+ var exception = Assert.Throws(() => new PatternBasedString(value));
+
+ Assert.NotNull(exception);
+ }
+
+ [Theory]
+ [InlineData("ABCDEFGHIJK")]
+ [InlineData("ABCDEFGHIJKLMNOP")]
+ public void Constructor_WithValueTooLong_ThrowsInvalidDomainValueException(string value)
+ {
+ var exception = Assert.Throws(() => new PatternBasedString(value));
+
+ Assert.NotNull(exception);
+ }
+
+ [Theory]
+ [InlineData("ABCDI")]
+ [InlineData("ABCDJ")]
+ [InlineData("ABCDZ")]
+ [InlineData("HELLO1")]
+ [InlineData("ABC@E")]
+ [InlineData("ABC DE")]
+ public void Constructor_WithInvalidCharacters_ThrowsInvalidDomainValueException(string value)
+ {
+ var exception = Assert.Throws(() => new PatternBasedString(value));
+
+ Assert.NotNull(exception);
+ }
+
+ [Theory]
+ [InlineData("ABCDE")]
+ [InlineData("CEBBAx")]
+ [InlineData("ABCDEFGH")]
+ public void TryCreate_WithValidValue_ReturnsTrue(string value)
+ {
+ var result = PatternBasedString.TryCreate(value, out var patternString);
+
+ Assert.True(result);
+ Assert.NotNull(patternString);
+ Assert.Equal(value, (string)patternString);
+ }
+
+ [Theory]
+ [InlineData("ABC")]
+ [InlineData("ABCDEFGHIJK")]
+ [InlineData("1abcde")]
+ [InlineData("ABCDI")]
+ [InlineData("ABC@E")]
+ public void TryCreate_WithInvalidValue_ReturnsFalse(string value)
+ {
+ var result = PatternBasedString.TryCreate(value, out var patternString);
+
+ Assert.False(result);
+ Assert.Null(patternString);
+ }
+
+ [Fact]
+ public void ImplicitCast_FromValidString_CreatesInstance()
+ {
+ PatternBasedString result = "aabccH";
+
+ Assert.NotNull(result);
+ Assert.Equal("aabccH", (string)result);
+ }
+
+ [Fact]
+ public void ImplicitCast_FromInvalidString_ThrowsInvalidDomainValueException()
+ {
+ Assert.Throws(() =>
+ {
+ PatternBasedString result = "1invalid";
+ });
+ }
+
+ [Fact]
+ public void ImplicitCast_FromNull_ReturnsNull()
+ {
+ PatternBasedString? result = (string?)null;
+
+ Assert.Null(result);
+ }
+
+}