Skip to main content
Version: Preview (0.3.0-preview.205)

Error codes and exceptions

A rejected value always carries two things: a stable, machine-readable code, and a human-readable message. The code is the contract — it reaches problem details responses and FluentValidation failures, and a client may branch on it. The message is for people, and may change.

Framework codes​

Defined as constants on ValueObjectErrorCodes:

CodeRaised when
value_object.requiredThe value is null, or an empty string on a type without AllowEmpty.
value_object.too_shortA string is shorter than MinLength.
value_object.too_longA string is longer than MaxLength.
value_object.invalid_formatThe value does not match the Pattern of IValueObjectPatternValidator, or of the deprecated option of the same name; also the code of ValidationResult.InvalidFormat.
value_object.out_of_rangeThe value is below the bound of IValueObjectMinimum<T> or above that of IValueObjectMaximum<T>, or of the deprecated options of the same names; also the code of ValidationResult.OutOfRange.
value_object.not_a_known_valueThe value is not one of the known values of a closed set.
value_object.not_parsableThe text does not even have the shape of the underlying type, so no rule of the type ran.

Codes of your own​

A validator hook returns ValidationResult.Failure("delivery_date.sunday", "Nothing is delivered on a Sunday.") for a rule of its own. Prefer a specific code of your own over a framework code that means something else: a client can act on delivery_date.sunday, not on a generic value_object.invalid_format.

ValidationResult​

A readonly struct returned by Validate, by TryCreate and TryParse through their out parameter, and by validator hooks.

Member
static ValidationResult SuccessThe success state, which is default: accepting a value allocates nothing.
bool IsValidtrue on success.
string? ErrorCode, string? ErrorMessageThe code and message of the rule that fired, null on success.
static ValidationResult Failure(string errorCode, string errorMessage)A rejection with a code of your own.
Required, InvalidFormat, OutOfRangeRejections with the framework code and an optional message.

Validation is fail-fast: a result carries one reason, the first rule that failed.

ValueObjectException​

Thrown by Create, by Parse, and by an explicit conversion, when the value is rejected. It carries:

Member
ErrorCodeThe code of the violated rule, the same TryCreate, or for Parse the four-argument TryParse, would have reported. Parse throws value_object.not_parsable only for text that is not of the underlying type at all.
ValueObjectTypeThe value object that refused the value.
AttemptedValueThe value as it was passed in, before normalization.
MessageThe message of the violated rule, after the text and the type for Parse.

The integrations on a boundary report a rejected value in their own terms, so the exception is reserved for code that treats a rejected value as a bug, and for a strict EF Core read:

IntegrationA rejected value
The System.Text.Json convertersJsonException, with the message of the rule.
The Newtonsoft.Json converterJsonSerializationException, with the message of the rule.
ASP.NET Core model bindingA model state error; the problem details carry its code.
FluentValidation, MustParseAs and MustSatisfyA validation failure carrying the code.
DapperDataException, for a value it cannot convert, and for text read into a value object over another type, or a number or a Guid read into one over string, that the value object refuses.
EF Core with strict: trueValueObjectException, from Create: the query fails.

The other reads do not validate. EF Core by default, and Dapper for a value the provider returns as the underlying type or as its date and time counterpart, build the value object with CreateUnchecked: they read what this application validated when it wrote it. EF Core says when to read strictly.

Detecting an uninitialized instance​

IsDefault is true for an instance that was never constructed: one that crossed a boundary the VO0010 analyzer cannot see, such as a default array element or another library's deserializer. FluentValidation's NotDefault rule checks it at the edge.