Skip to content
Purview Dev logo

Validation Rules Reference

Stable ZodSharp Reviewed 2026-10-08 purview-dev/zodsharp Star on GitHub 1 aspnetcore c-sharp csharp dotnet json-schema newtonsoft-json schema source-generator system-text-json validation zero-allocation zod

Every built-in validation rule lives in the ZodSharp.Rules namespace and is a readonly record struct implementing ZodSharp.Core.IValidationRule<T>. Rules are the smallest unit of validation: a schema is a type/structural check followed by an ordered list of rules, and each failing rule adds a ValidationError to the result.

This page is the catalogue of the rules that ship with the core package. For authoring your own, see Custom Rules.

namespace ZodSharp.Core;
public interface IValidationRule<T>
{
bool IsValid(in T value);
string GetErrorMessage(in T value);
string Code => "validation_failed";
}
  • String rules additionally implement ZodSharp.Core.IStringValidationRule (bool IsValid(ReadOnlySpan<char>) / string GetErrorMessage(ReadOnlySpan<char>)) so ZodString.ValidateSpan/IsValidSpan can validate without materialising the input.
  • Every rule exposes its error identity as public constants: public const string ErrorCode and public const string MessageFormat (a {0}-style string.Format template). Code returns the rule’s effective code — a per-usage code override when supplied, otherwise ErrorCode — and GetErrorMessage formats MessageFormat with RuleMessage.Format (which caches the parsed CompositeFormat).
  • A constructor message parameter, when supplied, overrides the formatted MessageFormat. MessageFormat is the fallback, not the only message.
  • The analyzer reports ZODSGEN042 when a source-declared rule omits ErrorCode/MessageFormat. See Source Generator Diagnostics.

Rules can be used three ways:

using ZodSharp;
using ZodSharp.Rules;
// 1. Standalone
var rule = new EmailRule();
if (!rule.IsValid("not-an-email"))
Console.WriteLine(rule.GetErrorMessage("not-an-email"));
// 2. Attached to a schema (AddRule or the generic Rule helper)
var schema = Z.String().AddRule(new EmailRule());
var same = Z.String().Rule(new EmailRule());
// 3. Surfaced as a DataAnnotations attribute the [ZodSchema] generator honours
// (built-in mappings, or a custom rule via [ZodRule]). See Source Generator DataAnnotations.

Tests can assert against the rule’s constants instead of duplicating literals:

var result = Z.String().Email().Validate("not-an-email");
await Assert.That(result.Errors[0].Code).IsEqualTo(EmailRule.ErrorCode); // "invalid_string"
await Assert.That(result.Errors[0].Message).IsEqualTo(
string.Format(CultureInfo.CurrentCulture, EmailRule.MessageFormat, "not-an-email"));

The built-in rules report one of the following Zod-compatible codes:

CodeMeaningEmitted by
invalid_stringA string did not match the required formatall string format rules
invalid_typeA value was not the expected type/shape (e.g. not a whole number)IntRule<T>
too_smallA length or value fell below a lower boundMinLengthRule, MinValueRule<T>, GreaterThanRule<T>
too_bigA length or value exceeded an upper boundMaxLengthRule, MaxValueRule<T>, LessThanRule<T>, SafeIntegerRule
not_multiple_ofA number was not a multiple of the divisorMultipleOfRule<T>
not_finiteA number was NaN or infiniteFiniteRule<T>
invalid_valueA value was a rejected sentinel or was not in the allowed setNonSentinelRule<T>, AllowedValuesRule<T>, DeniedValuesRule<T>
invalid_enum_valueA value was not a defined member of the enum type, or was an excluded memberEnumRule<TEnum>
missing_fieldA required value was absent (or empty/whitespace-only)RequiredRule<T>
invalid_lengthA string length fell outside the required rangeLengthRule, StringLengthRule
invalid_rangeA comparable value fell outside the required rangeRangeRule<T>
mismatchA value did not equal the expected valueCompareRule
validation_failedFallback for rules that do not declare an identityany rule without ErrorCode/IZodRule

Fluent methods live on ZodString (ZodSharp.Schemas). The Span column marks rules that implement IStringValidationRule and therefore stay on the zero-allocation span path.

RuleConstructorFluent methodCodeMessage formatSpan
MinLengthRule(int minLength)Min(n), Length(n)too_smallString must be at least {0} characters long, but got {1}yes
MaxLengthRule(int maxLength)Max(n), Length(n)too_bigString must be at most {0} characters long, but got {1}yes

Z.String().Length(n) adds both rules, so the string must be exactly n characters.

RuleConstructorFluent methodCodeMessage formatSpan
EmailRule()Email()invalid_stringInvalid email format: {0}yes
RegexRule(Regex pattern, string? message) or (string pattern, string? message)Regex(...)invalid_stringString does not match the required pattern: {0}yes
UrlRule(string? message)Url()invalid_stringInvalid URL format: {0}no
UriRule(UriKind uriKind, string? message)Uri(), Uri(uriKind)invalid_stringInvalid Uri, kind: {0}, format: {1}no
PhoneRule(string? message)Phone()invalid_stringInvalid phone number format: {0}yes
E164Rule(string? message)E164()invalid_stringInvalid E.164 phone number: {0}yes
CreditCardRule(string? message)CreditCard()invalid_stringInvalid credit card number format: {0}yes
Base64StringRule(string? message)Base64String()invalid_stringInvalid Base64 string format: {0}no
Base64UrlRule(string? message)Base64Url()invalid_stringInvalid base64url string: {0}yes
UUIDRule(string? message) or (UuidVersion version, string? message)UUID() / UUID(version)invalid_stringInvalid UUID format: {0} (or Invalid UUID v{0} format: {1})yes
ULIDRule(string? message)ULID()invalid_stringInvalid ULID: {0}yes
JWTRule(string? message)JWT()invalid_stringInvalid JWT: {0}yes
IPAddressRule(string? message, string? code) or (IPAddressRuleType ruleType, string? message, string? code)IP() / IP(ruleType)invalid_stringInvalid {1} address: {0}yes
HexRule(string? message)Hex()invalid_stringInvalid hexadecimal string: {0}yes
DateStringRule(string? message)Date()invalid_stringInvalid date: {0}yes
TimeStringRule(string? message)Time()invalid_stringInvalid time: {0}yes
DatetimeStringRule(string? message)Datetime()invalid_stringInvalid date-time: {0}yes
NanoidRule(string? message)Nanoid()invalid_stringInvalid nanoid: {0}yes
Cuid2Rule(string? message)Cuid2()invalid_stringInvalid CUID2: {0}yes
StartsWithRule(string prefix, StringComparison comparison = StringComparison.Ordinal, string? message, string? code)StartsWith(prefix, comparison)invalid_stringString must start with '{0}', but got '{1}'yes
EndsWithRule(string suffix, StringComparison comparison = StringComparison.Ordinal, string? message, string? code)EndsWith(suffix, comparison)invalid_stringString must end with '{0}', but got '{1}'yes
IncludesRule(string substring, string? message, string? code)Includes(substring)invalid_stringString must contain '{0}', but got '{1}'yes
EmojiRule(string? message)Emoji()invalid_stringInvalid emoji: {0}yes
XidRule(string? message)Xid()invalid_stringInvalid XID: {0}yes
KsuidRule(string? message)Ksuid()invalid_stringInvalid KSUID: {0}yes
DurationRule(string? message)Duration()invalid_stringInvalid duration: {0}yes
GuidRule(string? message)Guid()invalid_stringInvalid GUID: {0}yes
CidrRule(CidrRuleType ruleType, string? message, string? code)Cidr() / Cidr(ruleType)invalid_stringInvalid {1} CIDR: {0}yes

Every rule constructor also accepts an optional string? code = null after message; when set it overrides the reported error code (otherwise the rule’s own ErrorCode is reported).

Behaviour notes:

  • EmailRule matches a compiled, case-insensitive regex (^[^@\s]+@[^@\s]+\.[^@\s]+$).
  • RegexRule uses the supplied Regex; the Z.String().Regex(string) overload compiles the pattern with a 100 ms timeout. Its message’s {0} is the pattern, not the value.
  • UrlRule accepts the compiled HTTP(S) regex or an absolute http/https URI (Uri.TryCreate), so it may allocate when the fast regex misses.
  • UriRule validates with Uri.TryCreate against the supplied UriKind and therefore allocates. Z.String().Uri() defaults to UriKind.RelativeOrAbsolute; pass an explicit UriKind to require an absolute or relative URI.
  • PhoneRule mirrors [Phone]: digits plus () .+-, with at least one digit.
  • E164Rule requires +, a first digit 1-9, and 7-15 digits in total.
  • CreditCardRule mirrors [CreditCard]: Luhn check, ignoring spaces and hyphens.
  • Base64StringRule mirrors [Base64String]: Convert.FromBase64String must succeed.
  • Base64UrlRule accepts URL-safe base64 (A-Z a-z 0-9 - _) with no padding; a length of 4n+1 is rejected.
  • UUIDRule accepts RFC 9562 versions 1-8 with a variant nibble of 8-9/a-b, plus the nil and max UUIDs. The versioned constructor requires a specific version and rejects nil/max.
  • ULIDRule requires 26 Crockford base32 characters, with the first character 0-7.
  • JWTRule requires three non-empty base64url-encoded segments separated by periods.
  • IPAddressRule accepts IPv4 or IPv6 (IPAddress.TryParse); pass an IPAddressRuleType to require IPv4, IPv6, or Any (the default).
  • HexRule accepts any run of ASCII hex digits; the empty string is valid, matching Zod.
  • DateStringRule parses yyyy-MM-dd (invariant culture); TimeStringRule accepts HH:mm, optionally :ss and fractional seconds; DatetimeStringRule requires yyyy-MM-dd + T + HH:mm:ss[.fff] + Z.
  • NanoidRule requires exactly 21 URL-safe characters; Cuid2Rule requires non-empty lowercase alphanumerics.
  • StartsWithRule and EndsWithRule use the supplied StringComparison (default Ordinal); IncludesRule uses ordinal comparison.
  • EmojiRule mirrors Zod’s emoji(): every rune must be an Extended_Pictographic or Emoji_Component code point, and at least one must be an anchor (a pictograph, a regional indicator, or a keycap). The Unicode tables are generated from Unicode 16.0 emoji-data.txt, because .NET’s regex engine does not expose the Extended_Pictographic property.
  • XidRule requires exactly 20 base32hex characters (0-9, a-v/A-V); KsuidRule requires exactly 27 base62 characters.
  • DurationRule accepts ISO 8601-1 durations; the 8601-2 extensions (negative durations and fractional/negative components) are rejected, matching Zod.
  • GuidRule accepts any 8-4-4-4-12 hexadecimal identifier, including nil and max; UUIDRule additionally checks the RFC 9562 version and variant nibbles.
  • CidrRule accepts an IPv4 or IPv6 block; pass a CidrRuleType to require IPv4 or IPv6 (Any is the default).

Fluent methods live on ZodNumber (ZodSharp.Schemas), which validates double.

RuleConstructorFluent methodCodeMessage format
MinValueRule<T>(T minValue)Min(v), NonNegative()too_smallValue must be at least {0}, but got {1}
MaxValueRule<T>(T maxValue)Max(v), NonPositive()too_bigValue must be at most {0}, but got {1}
GreaterThanRule<T>(T exclusiveMinimum)Gt(v), Positive()too_smallValue must be greater than {0}, but got {1}
LessThanRule<T>(T exclusiveMaximum)Lt(v), Negative()too_bigValue must be less than {0}, but got {1}
GreaterThanOrEqualRule<T>(T minValue, string? message)Gte(v)too_smallValue must be greater than or equal to {0}, but got {1}
LessThanOrEqualRule<T>(T maxValue, string? message)Lte(v)too_bigValue must be less than or equal to {0}, but got {1}
EvenRule<T>(string? message)Even()invalid_valueNumber must be even, but got {0}
OddRule<T>(string? message)Odd()invalid_valueNumber must be odd, but got {0}
MultipleOfRule<T>(T divisor, string? message)MultipleOf(divisor)not_multiple_ofNumber must be a multiple of {0}, but got {1}
FiniteRule<T>(string? message)Finite()not_finiteNumber must be finite, but got {0}
SafeIntegerRule(string? message)Safe()too_bigNumber must be a safe integer, but got {0}
IntRule<T>()Int()invalid_typeExpected integer, but got {0}

Behaviour notes:

  • All six bound rules (MinValueRule<T>, MaxValueRule<T>, GreaterThanRule<T>, LessThanRule<T>, GreaterThanOrEqualRule<T>, LessThanOrEqualRule<T>) are generic over T : IComparable<T>, so they can be reused with any comparable type (the fluent methods close them with double). ZodBigInt and ZodDate close the generic rules with long and DateTime.
  • The arithmetic rules (IntRule<T>, FiniteRule<T>, MultipleOfRule<T>, EvenRule<T>, OddRule<T>) are generic over T : INumber<T>, so they close with int, long, double, decimal, and every other numeric type; the fluent methods close them with double.
  • Positive() is GreaterThanRule<double>(0.0), Negative() is LessThanRule<double>(0.0), NonNegative() is MinValueRule<double>(0.0), and NonPositive() is MaxValueRule<double>(0.0).
  • EvenRule<T> and OddRule<T> treat a value as even when value % 2 == 0.
  • MultipleOfRule<T> throws ArgumentException when the divisor is 0, and compares the distance to the nearest multiple against a relative tolerance of 1e-12 (so 0.3 is accepted for a divisor of 0.1). Integer types cannot represent the tolerance, so their comparison is exact.
  • FiniteRule<T> rejects NaN and infinities via T.IsFinite.
  • SafeIntegerRule is intentionally double-only (“safe integer” is a JavaScript Number concept); it requires a whole number within int.MinValue..int.MaxValue.
  • IntRule<T> requires value % T.One == T.Zero, which also rejects infinities (a truncate comparison would accept them).
RuleConstructorFluent methodCodeMessage format
NonSentinelRule<T>(string? message)NonSentinel()invalid_valueValue is a sentinel value, but got {0}
NullOrNonWhiteSpaceRule(string? message, string? code)—invalid_stringValue must be null or non-whitespace, but got '{0}'

NonSentinelRule<T> rejects the framework default/boundary values an ORM commonly stores to mean “no value”: Guid.Empty; DateTime, DateTimeOffset, DateOnly, and TimeOnly MinValue/MaxValue; and null/empty/whitespace strings. Types without a known sentinel always pass, so the rule never rejects a type it does not understand. It detects sentinels with a typeof(T) dispatch and reinterprets the value in place, so no boxing occurs.

NullOrNonWhiteSpaceRule is the null-tolerant counterpart: it accepts null but rejects an empty or whitespace-only string. It exists for nullable scalar value objects, which round-trip JSON null, so their schema must accept null while still rejecting whitespace — [RequiredZod] always rejects null and so cannot express it. Apply it to a nullable scalar type with [NullOrNonWhiteSpace]; a rule written against the underlying string? value is adapted to the value object automatically.

The fluent NonSentinel() method is declared on ZodType<TOutput, TInput>, so it is available on every schema and closes the rule with the schema’s output type. ZodString and ZodDate override it with a covariant return type so the fluent chain keeps the concrete schema:

var schema = Z.Date().NonSentinel();
var result = schema.Validate(DateTime.MinValue);
// result.Errors[0].Code == NonSentinelRule<DateTime>.ErrorCode ("invalid_value")
// Covariant overrides keep the concrete schema, so later fluent calls still compile.
var chained = Z.String().NonSentinel().Min(3);

The same rule can be attached directly with AddRule/Rule, or through the shipped [NonSentinel] attribute, which closes the open generic with the annotated member/scalar type — see Custom Rules.

These rules mirror System.ComponentModel.DataAnnotations attributes as first-class rules, so the same behaviour is available standalone, through the fluent API, or through a generated attribute:

RuleConstructorFluent methodCodeMessage format
RequiredRule<T>(bool allowEmptyStrings = false, bool trimWhitespace = false, string? message, string? code)ZodString.Required(...)missing_fieldField is required
RangeRule<T>(T minimum, T maximum, bool minimumIsExclusive = false, bool maximumIsExclusive = false, string? message, string? code)ZodNumber.Range(...), ZodBigInt.Range(...), ZodDate.Range(...)invalid_rangeValue must be between {0} and {1}, but got {2}
LengthRule(int minimum, int maximum, string? message, string? code)ZodString.Length(min, max)invalid_lengthString length must be between {0} and {1} characters, but got {2}
StringLengthRule(int maximumLength, int minimumLength = 0, string? message, string? code)ZodString.StringLength(max, min)invalid_lengthString must be at most {0} characters long and at least {1}, but got {2}
CompareRule(string other, StringComparison comparison = Ordinal, string? message, string? code)ZodString.Compare(...)mismatchValue must match '{0}', but got '{1}'
AllowedValuesRule<T>(T[] values, string? message, string? code)AllowedValues(values)invalid_valueValue '{0}' is not one of the allowed values
DeniedValuesRule<T>(T[] values, string? message, string? code)DeniedValues(values)invalid_valueValue '{0}' is one of the denied values

RequiredRule<T> always rejects null. When allowEmptyStrings is false an empty string is rejected as well; trimWhitespace additionally treats a whitespace-only string as empty (leading/trailing whitespace on a non-empty value is preserved). The ZodString.Required fluent method is a covariant convenience over the generic rule.

AllowedValuesRule<T> and DeniedValuesRule<T> accept both a single value (new AllowedValuesRule<int>(5)) and an array. The generated [AllowedValuesZod] / [DeniedValuesZod] attributes surface the array as a params object[] (plus a single-value overload), so [AllowedValuesZod("a", "b", "c")] works on a string member and [DeniedValuesZod(1, 2, 3)] on an int member; the resolver converts each element to the member type.

RuleConstructorFluent methodCodeMessage format
EnumRule<TEnum>() or (TEnum[]? disallowed, string? message, string? code)Enum()invalid_enum_value'{0}' is not a defined member of {1}

EnumRule<TEnum> is generic over TEnum : struct, Enum and rejects a value that is not a defined member of the enum type, or that resolves to a member the rule excludes. It is equivalent to Zod’s z.nativeEnum(Enum) semantics. The defined members are resolved once per closed generic type, so the validation path performs no reflection.

The [ZodSchema] generator emits this rule automatically for every non-flags enum property — see Source Generator. The rule is closed with the property’s enum type and receives the excluded members as its disallowed set:

  • every enum member marked [ZodIgnore] (a member that is defined but never a valid value, such as Unspecified), and
  • the values the property’s [DeniedValues] attribute lists.
using System.ComponentModel.DataAnnotations;
using ZodSharp;
public enum ExampleEnum
{
[ZodIgnore]
Unspecified,
AValidValue,
AnotherValidValue,
}
[ZodSchema]
public class Model
{
public ExampleEnum Status { get; set; }
[DeniedValues(ExampleEnum.AnotherValidValue)]
public ExampleEnum SecondaryStatus { get; set; }
}

The rule is skipped for a [Flags] enum (a combination is valid without being a defined member), for a property that declares an explicit [AllowedValues] allow-list (the allow-list governs), and when the schema opts out with [ZodSchema(ValidateEnumValues = false)]. A hand-written ZodNativeEnum<TEnum> schema already rejects undefined members; its Enum() method adds this rule to customise the message/code, and the rule can be attached to any enum-output schema with AddRule/Rule.

Each built-in rule that can be expressed as an attribute ships a generated ValidationAttribute in the ZodSharp.Rules namespace (inside the Purview.ZodSharp assembly), so a member or a scalar can be annotated directly:

AttributeRuleNotes
[Email]EmailRule
[E164]E164Rule
[Regex("…")]RegexRulemirrors the (string pattern, string? message) overload
[UUID] / [UUID(UuidVersion.…)]UUIDRulethe version is optional; omit it for the versionless rule
[ULID]ULIDRule
[JWT]JWTRule
[IPAddress] / [IPAddress(IPAddressRuleType.…)]IPAddressRuledefaults to Any; pass a type to require IPv4/IPv6
[Hex]HexRule
[Base64Url]Base64UrlRule
[Nanoid]NanoidRule
[Cuid2]Cuid2Rule
[DateString]DateStringRule
[TimeString]TimeStringRule
[DatetimeString]DatetimeStringRule
[StartsWith("…")]StartsWithRule
[EndsWith("…")]EndsWithRule
[Includes("…")]IncludesRule
[MultipleOf(…)]MultipleOfRule<T>closes the open generic with the member type
[Finite]FiniteRule<T>closes the open generic with the member type
[SafeInteger]SafeIntegerRule
[Int]IntRule<T>closes the open generic with the member type
[Uri(UriKind.…)]UriRulethe UriKind is required
[NonSentinel]NonSentinelRule<T>closes the open generic with the member/scalar type
[NullOrNonWhiteSpace]NullOrNonWhiteSpaceRuleaccepts null but rejects empty/whitespace-only strings (for nullable scalar value objects)
[Enum]EnumRule<TEnum>closes the open generic with the enum member type; excludes only the members the rule receives
[MinValue(…)]MinValueRule<T>the type-parameter bound is a double; the rule closes with the member type
[MaxValue(…)]MaxValueRule<T>as above
[GreaterThan(…)]GreaterThanRule<T>as above
[LessThan(…)]LessThanRule<T>as above
[GreaterThanOrEqual(…)]GreaterThanOrEqualRule<T>as above
[LessThanOrEqual(…)]LessThanOrEqualRule<T>as above
[Even]EvenRule<T>closes the open generic with the member type
[Odd]OddRule<T>closes the open generic with the member type
[MinLengthZod(…)]MinLengthRulesuffixed to avoid the DataAnnotations name; exposes Code/Message
[MaxLengthZod(…)]MaxLengthRuleas above
[UrlZod]UrlRuleas above
[PhoneZod]PhoneRuleas above
[CreditCardZod]CreditCardRuleas above
[Base64StringZod]Base64StringRuleas above
[Emoji]EmojiRule
[Xid]XidRule
[Ksuid]KsuidRule
[Duration]DurationRule
[Guid]GuidRuleaccepts any 8-4-4-4-12 hex identifier
[Cidr] / [Cidr(CidrRuleType.…)]CidrRuledefaults to Any; pass a type to require IPv4/IPv6
[RequiredZod(…)]RequiredRule<T>suffixed; exposes AllowEmptyStrings/TrimWhitespace and rejects an absent value exactly like [Required]
[RangeZod(…)]RangeRule<T>the type-parameter bounds are doubles; the rule closes with the member type
[LengthZod(…)]LengthRulesuffixed to avoid the DataAnnotations name
[StringLengthZod(…)]StringLengthRuleas above
[CompareZod(…)]CompareRuleas above
[AllowedValuesZod(…)]AllowedValuesRule<T>a params object[] array (and a single-value overload); the rule closes with the member type
[DeniedValuesZod(…)]DeniedValuesRule<T>a params object[] array (and a single-value overload); the rule closes with the member type

A generated attribute’s constructor mirrors the rule’s value parameters: a parameter the rule declares without a default becomes a required constructor argument, and a parameter with a default keeps that default (so an optional value such as [StartsWith("…", StringComparison.OrdinalIgnoreCase)] still has a usable default). The value parameters are also exposed as settable properties. The rule’s message and identity (code/origin) parameters stay properties only — Message and, where present, Code — and default to the rule’s own MessageFormat/ErrorCode, so the attribute is self-describing.

Each generated attribute reports the rule’s own ErrorCode/Origin because every built-in rule implements IZodRule.

Rules whose derived attribute name is already taken by System.ComponentModel.DataAnnotations — MinLengthRule, MaxLengthRule, UrlRule, PhoneRule, CreditCardRule, Base64StringRule, RequiredRule, RangeRule, LengthRule, StringLengthRule, CompareRule, AllowedValuesRule, DeniedValuesRule — are emitted under a Zod suffix instead (for example [MinLengthZod], [RequiredZod], [RangeZod]), so the rule’s own identity and message stay usable. The generic bound rules surface their type-parameter bound as a double, so [MinValue(3)] works on an int or a double member (the value is converted to the member type).

The [ZodSchema] generator maps several built-in rules to their System.ComponentModel.DataAnnotations attributes, and any custom rule can be mapped with [ZodRule(typeof(...))]:

AttributeRuleFailure code
[EmailAddress]EmailRuleinvalid_string
[Url]UrlRuleinvalid_string
[Phone]PhoneRuleinvalid_string
[CreditCard]CreditCardRuleinvalid_string
[Base64String]Base64StringRuleinvalid_string
[RegularExpression]compiled Regex, not RegexRuleinvalid_string

Size and range attributes ([Length], [StringLength], [MinLength], [MaxLength], [Range]) are emitted as direct, typed codegen; the equivalent rule structs (LengthRule, StringLengthRule, MinLengthRule, MaxLengthRule, RangeRule<T>) are also available directly and through their generated attributes. The rule-identity variants of the colliding names are the suffixed generated attributes ([MinLengthZod], [MaxLengthZod], [UrlZod], [PhoneZod], [CreditCardZod], [Base64StringZod], [RequiredZod], [RangeZod], [LengthZod], [StringLengthZod], [CompareZod], [AllowedValuesZod], [DeniedValuesZod]), which report the rule’s own Code and accept a Message. The remaining built-in rules are covered by their own generated attributes — for example [Regex("…")] validates through RegexRule, whereas [RegularExpression] compiles the pattern inline. See Source Generator DataAnnotations for the full attribute table and the generated metadata, and Custom Rules for mapping your own rules to attributes.