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 |
| 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 |
Removed in 3.0. The
[IsBoolean],[IsDateTime],[IsDecimal],[IsDouble],[IsGuid],[IsInt32],[IsIpAddress],[IsTimeSpan],[IsUri], and[IsVersion]attributes were deprecated in 2.x and have now been deleted. Wrap the .NET type directly instead — it is more type-safe, faster, and cheaper in memory than validating a string:public sealed record TransactionId(Guid Value); public sealed record WebsiteUrl(Uri Value);If a value genuinely has to stay a string inside a wider validation pipeline, write a custom validation attribute (see Custom validation attributes) wrapping the corresponding
TryParse.
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: a fully qualified path must not name an existing directory. |
[IsDirectoryPath] |
Refers to a directory: a fully qualified path must not name an existing file. |
[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> { }[IsFilePath] and [IsDirectoryPath] consult the file system only for fully qualified paths. A path
that is not fully qualified names no particular location until a caller supplies a base directory, so
probing for it would resolve it against the process's current working directory — making the same
string valid in one process and invalid in another depending on what files happen to sit beside them.
Those paths are validated by shape alone. To ask the existence question about a relative path, resolve
it first with AsAbsolute(baseDirectory) and validate the result.
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, StartsWith("https://")]
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.