Use with FluentValidation
A command or an inbound message often carries raw text rather than value objects. Its validator should not state the length, the pattern and the check-digit rule of an IBAN a second time: it should defer to the type that owns them, and report the same error codes as the rest of the system.
dotnet add package AdCodicem.ValueObjects.FluentValidation
Text that must become a value object
public sealed class ImportAccountValidator : AbstractValidator<ImportAccountRequest>
{
public ImportAccountValidator()
{
RuleFor(request => request.Iban)
.NotEmpty()
.MustParseAs(typeof(Iban));
}
}
MustParseAs runs the type's own parsing: normalization, the declared rules, the validator hook. A failure
carries the value object's error code — value_object.invalid_format for a wrong check digit — as the
FluentValidation ErrorCode.
The type is passed as a Type rather than a type argument so the rule stays readable: C# cannot infer one type
argument while another is given explicitly.
An underlying value, without building the value object
RuleFor(request => request.Amount).MustSatisfy<TransferRequest, Amount, decimal>();
MustSatisfy checks a raw underlying value against the rules of a value object without constructing one.
An uninitialized value object
RuleFor(command => command.Account).NotDefault<TransferCommand, Iban, string>();
NotDefault catches the one thing a struct value object cannot rule out by itself: an instance that was never
constructed, arriving from a place the VO0010 analyzer cannot see — a deserializer of another library,
reflection, an array element.
Returning the codes
The codes reach an API client if you put them in the response.
ASP.NET Core shows how to return them under the
same errorCodes member that model binding uses.