A complete reference of the built-in validation attributes shipped with the library, plus how validation strategies and custom rules fit together.
Validation runs at construction time. A failed validation throws
ArgumentException(notFormatException) — see CLAUDE.md.
For the architecture (attribute → strategy → rule → factory pipeline), see architecture.md. For practical patterns including custom rules, see advanced-usage.md.
| Category | Where | Count |
|---|---|---|
| Text | Semantics.Strings/Validation/Attributes/Text/ |
7 |
| Format | Semantics.Strings/Validation/Attributes/Format/ |
7 |
| Casing | Semantics.Strings/Validation/Attributes/Casing/ |
9 |
| First-class .NET types | Semantics.Strings/Validation/Attributes/FirstClassTypes/ |
10 |
| Path | Semantics.Paths/Validation/Attributes/Path/ |
10 |
| Strategies | Semantics.Strings/Validation/Strategies/ |
2 |
There is no quantity validation in this list — semantic quantities enforce their own invariants at the type level (see strategy-unified-vector-quantities.md).
Validates that the value parses as an email address.
[IsEmailAddress]
public sealed record EmailAddress : SemanticString<EmailAddress> { }
EmailAddress.Create("user@example.com"); // ✅
EmailAddress.Create("not-an-email"); // ❌ ArgumentExceptionValidates that the value is well-formed Base64.
[IsBase64]
public sealed record ApiToken : SemanticString<ApiToken> { }Self-explanatory substring constraints.
[StartsWith("https://"), Contains(".example.com")]
public sealed record SecureApiUrl : SemanticString<SecureApiUrl> { }Convenience for "must start with X and end with Y".
[PrefixAndSuffix("Bearer ", "==")]
public sealed record BearerToken : SemanticString<BearerToken> { }Arbitrary regex constraint.
[RegexMatch(@"^[a-z0-9]+(-[a-z0-9]+)*$")]
public sealed record BlogSlug : SemanticString<BlogSlug> { }Mutually exclusive — pick one.
Constrain whether the string contains line breaks.
Constrain the line count.
[HasMaximumLines(10), HasNonWhitespaceContent]
public sealed record CommitMessageHeader : SemanticString<CommitMessageHeader> { }| Attribute | Style | Example |
|---|---|---|
[IsCamelCase] |
myVariable |
httpRequest |
[IsPascalCase] |
MyClass |
HttpRequest |
[IsKebabCase] |
lower-with-dashes |
http-request |
[IsSnakeCase] |
lower_with_underscores |
http_request |
[IsMacroCase] |
UPPER_WITH_UNDERSCORES |
HTTP_REQUEST |
[IsLowerCase] |
all lowercase | httprequest |
[IsUpperCase] |
all uppercase | HTTPREQUEST |
[IsSentenceCase] |
first letter upper, rest lower | Http request |
[IsTitleCase] |
first letter of each word upper | Http Request |
These attributes assert that the string parses to a particular .NET type.
| Attribute | Parses as |
|---|---|
[IsBoolean] |
bool |
[IsDateTime] |
DateTime |
[IsDecimal] |
decimal |
[IsDouble] |
double |
[IsGuid] |
Guid |
[IsInt32] |
int |
[IsIpAddress] |
IPAddress |
[IsTimeSpan] |
TimeSpan |
[IsUri] |
Uri |
[IsVersion] |
Version |
[IsGuid]
public sealed record TransactionId : SemanticString<TransactionId> { }
[IsUri]
public sealed record WebsiteUrl : SemanticString<WebsiteUrl> { }When the value will be used as the parsed type rather than as a string, prefer wrapping the .NET type directly (e.g.
record TransactionId(Guid Value)). Use these attributes when the value lives inside a wider string-validation pipeline.
These live in Semantics.Paths and require using ktsu.Semantics.Paths;.
| Attribute | Validates |
|---|---|
[IsPath] |
Legal path characters and length. |
[IsValidPath] |
Stricter: also rejects reserved names. |
[IsAbsolutePath] |
Fully qualified path. |
[IsRelativePath] |
Not absolute. |
[IsFilePath] |
Refers to a file (not a directory). |
[IsDirectoryPath] |
Refers to a directory. |
[IsFileName] |
Filename without separators. |
[IsValidFileName] |
Stricter filename validation. |
[IsExtension] |
File extension including the leading dot. |
[DoesExist] |
The path exists at validation time. Use sparingly — couples the type to the file system. |
[IsAbsolutePath, DoesExist]
public sealed record ConfigFilePath : SemanticString<ConfigFilePath> { }For most use cases, prefer the dedicated path types (AbsoluteFilePath, RelativeDirectoryPath, etc.) from Semantics.Paths — they bundle these attributes and provide rich path operations.
By default, all attributes on a type must pass (ValidateAll semantics). Strategies override that behaviour.
Every attribute must pass. Equivalent to leaving the strategy attribute off.
At least one attribute must pass.
[ValidateAny]
[IsEmailAddress, IsUri]
public sealed record ContactMethod : SemanticString<ContactMethod> { }Need richer logic (e.g. "all critical attributes must pass and at least one secondary attribute must pass")? Implement IValidationStrategy and register it via ValidationStrategyFactory. See advanced-usage.md for the worked example.
Subclass SemanticStringValidationAttribute:
public sealed class IsProductCodeAttribute : SemanticStringValidationAttribute
{
private static readonly Regex Pattern = new(@"^[A-Z][0-9]{5}$", RegexOptions.Compiled);
public override bool Validate(ISemanticString semanticString) =>
Pattern.IsMatch(semanticString.ToString());
}
[IsProductCode]
public sealed record ProductCode : SemanticString<ProductCode> { }Validation runs through the attribute → strategy → rule pipeline regardless of whether the attribute is built-in or custom.
[IsEmailAddress]
public sealed record UserEmail : SemanticString<UserEmail> { }
[RegexMatch(@"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")]
public sealed record ThemeColor : SemanticString<ThemeColor> { }
[RegexMatch(@"^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$")]
public sealed record JwtToken : SemanticString<JwtToken> { }// Default ValidateAll
[StartsWith("https://"), Contains(".example.com"), HasNonWhitespaceContent]
public sealed record SecureApiUrl : SemanticString<SecureApiUrl> { }
// Either-or
[ValidateAny]
[EndsWith(".com"), EndsWith(".org")]
public sealed record TrustedDomain : SemanticString<TrustedDomain> { }For values whose consumer cares about the parsed object (Guid, IPAddress, Uri, …), wrap the .NET type directly instead of validating the string:
public sealed record TransactionId(Guid Value)
{
public static TransactionId New() => new(Guid.NewGuid());
}Use the [Is*] attributes when the value belongs in a string-shaped pipeline (logs, configs, serialised payloads) and the parsed object is incidental.