Skip to content

No counterpart for a validation rule whose logic is an arbitrary predicate #47

Description

@woksin

A validation rule whose logic is an arbitrary predicate has no counterpart in the language, so it is left out of the document entirely — the reader is not told that a rule exists at all, only that something was dropped.

In FluentValidation this is Must / MustAsync:

RuleFor(_ => _.OrgNumber)
    .Must(BeAValidOrganizationNumber)
    .WithMessage(_ => CustomersMessages.Approve_OrgNumberRequired);

The declarative rules — NotEmpty, MaximumLength, GreaterThan, Matches, Equal, EmailAddress and friends — all map cleanly. Must does not, because its logic is a method.

Why it is worth a construct

Measured while generating from a real production application: ~248 Must/MustAsync rules, plus ~37 more with other non-declarative shapes (NotEqual, InclusiveBetween, IsInEnum). Together they are the single largest remaining category of dropped content — around 285 rules, comfortably more than everything else the generator cannot express put together.

The consequence is not just missing detail. A command with three declarative rules and four Must rules currently reads as a command with three rules. The document understates how constrained the input is, and a reader has no way to tell the difference between "this field has no further rules" and "this field has rules that could not be written down".

Recovering the message helps a little — Cratis/Arc#2409 now writes $strings. keys — but a message with no rule to attach to still has nowhere to go.

Suggested direction

A way to state that a named rule applies without expressing its logic. Something like:

validate
  orgNumber
    not empty
    rule BeAValidOrganizationNumber message $strings.CustomersMessages.Approve_OrgNumberRequired

The name is statically available — it is the method or lambda the predicate resolves to — and it is meaningful to a reader in a way "a rule was dropped" is not. The document would then be honest about how many constraints a field carries, even where it cannot say what each one computes.

Points worth deciding:

  • Whether an unnamed predicate (an inline lambda with no method to name) should be expressible at all, or should stay a diagnostic.
  • Whether such a rule participates in anything the language does with rules elsewhere, or is purely descriptive.
  • Whether the async variant needs distinguishing.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions