Skip to main content
Some rules depend on who is asking: the credit limit of the logged-in customer, the catalog of a tenant, a repository to look things up in. That data is not part of the object, so the object cannot reach it on its own. Implement ContextualValidatable<C>, and validation(context) receives it:
Shopper.kt
PurchaseLine.kt
Purchase.kt
The caller passes the context to validate:
The type of the context is checked by the compiler: a Purchase cannot be validated without a Shopper, and there is no map of attributes to read and cast.

Passing the context down

Nested objects receive the context explicitly, with valid(context). That keeps the flow visible, and lets a child take only a part of it:
A child that needs no context is a plain Validatable, checked with valid(), even inside a contextual parent.

The four ways to run

ContextualValidatable has the same four functions as Validatable, with the context first:
Each also takes a Validity after the context, for other messages or another clock.
The context can be anything: a data class, a repository, a client. When it is a repository with suspend functions, the rules can call them directly. See Coroutines.