Table of Contents

Class ValidationCodeProjection

Namespace
Trellis.FluentValidation
Assembly
Trellis.FluentValidation.dll

Maps a FluentValidation ErrorCode onto the Trellis reason-code vocabulary.

public static class ValidationCodeProjection
Inheritance
ValidationCodeProjection
Inherited Members

Remarks

FluentValidation defaults a failure's ErrorCode to the validator's type name — "NotEmptyValidator", "GreaterThanValidator". Those names are stable and well-known, but they are FluentValidation's vocabulary, not Trellis's: a client would have to key on NotEmptyValidator from a FluentValidation rule and value.not-empty from a generated TryCreate for the same condition. Translating here is what lets one client branch cover both producers.

The table is keyed on the error-code string, never the CLR validator type. The two disagree in practice: AspNetCoreCompatibleEmailValidator reports Name = "EmailValidator", so a type-keyed lookup would miss it while a string-keyed one hits. Keying on the string also means a caller's WithErrorCode("EmailValidator") maps identically to the built-in rule, which is the behaviour a reader expects.

Four rules, in order:

  1. A blank code becomes the neutral sentinel — there is nothing to report.
  2. The legacy placeholder validation.error becomes the sentinel, so an application that adopted the old placeholder is not mistaken for one making a deliberate statement.
  3. A reserved name becomes its mapped Trellis code.
  4. Anything else passes through verbatim. A caller who wrote WithErrorCode("order.too-large") means it, and silently rewriting it would make WithErrorCode useless.

PredicateValidator and AsyncPredicateValidator map to the sentinel rather than to a code of their own. A Must(...) rule can express any condition whatsoever, so its validator name says only "some custom predicate failed" — which is precisely what the sentinel means. Minting a predicate.failed code would look informative while telling a client nothing it could branch on.

Methods

Project(string?)

Translates a FluentValidation error code into a Trellis reason code.

public static string Project(string? errorCode)

Parameters

errorCode string

The failure's ErrorCode; may be null or blank.

Returns

string

The mapped Trellis code for a reserved FluentValidation validator name, the neutral sentinel for a blank or legacy-placeholder code, and errorCode unchanged otherwise.

Project(string?, object?)

Translates a FluentValidation error code into a Trellis reason code, refining NotEmptyValidator against the value that was actually rejected.

public static string Project(string? errorCode, object? attemptedValue)

Parameters

errorCode string

The failure's ErrorCode; may be null or blank.

attemptedValue object

The failure's AttemptedValue.

Returns

string

The mapped Trellis code.

Remarks

FluentValidation's NotEmpty() is one rule covering three failures the vocabulary keeps apart: an absent value is value.not-null, a present-but-blank string or empty collection is value.not-empty, and a value type left at its default — Guid.Empty, 0, default(DateTime) — is value.not-default. Mapping the rule to a single code would make RuleFor(x => x.Id).NotEmpty() report value.not-empty for Guid.Empty while a Trellis primitive reports value.not-default for the same input, which is exactly the producer divergence the vocabulary exists to remove. The code describes the failure, not the rule that caught it.