Skip to main content
The built-in constraints cover the common shapes of data. Your domain has its own: a SKU, a box size, a code your partners send. There are two ways to write them, depending on whether the rule is used once or everywhere.

Once: constraint

Inside the block of a property, constraint takes a message, an optional code, and a predicate that receives the value and returns true when it is valid:
The value has the type of the block, Int here, so the predicate needs no casts and no null checks. In the message, {path} and {value} are filled in.

Everywhere: an extension on ValidationNode

A constraint you use in several classes is a function. Every built-in constraint is an extension of ValidationNode<V> that calls constraint, and yours can be too:
SkuConstraints.kt
It is then called like any other, and autocomplete offers it only where the value is a String:
Product.kt
Follow the conventions of the built-in constraints and yours will read the same:
  • The code is the name of the function, so a program can tell sku from minLength.
  • The message is a parameter with a default, so one call can change it.
  • The message completes a sentence about the value, in lower case: must be a SKU such as ABC-1234.
  • null arguments skip the rule, as min(null) does, so a rule fed by optional data never fails on its own.
To let users change the default message of your constraints once, for the whole application, give them a messages class of your own. See messages of a module.

A message built from the value

Every built-in constraint that takes an argument has a second form whose message is a function. It receives the value as this and the argument as its parameter, so the message can say what is wrong with this value:
constraint has the same form: constraint(message = { "$it is not in stock" }) { it in stock }.

Rules that suspend

The predicate and the message are suspend lambdas, so a constraint can call a repository or a client directly:
See Coroutines for how that runs, and Fail fast to run a lookup only after the format is right.

Rules about time

A rule about “now” should read the clock of the configuration, not the system clock, so tests can fix it. now { } gives you the current time from that clock: