Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.Json

Custom rules

Constraint.custom hands Reified prose and nothing else, so prose is all Reified can give back: it renders verbatim in every language. Name a key and the rule becomes translatable:

let isbn =
    Constraint.customLocalized
        "books.isbn.invalid"
        "must be a valid ISBN"
        isValidIsbn

let isbnWithLength =
    Constraint.customLocalizedWith
        "books.isbn.invalid"
        "must be a valid ISBN"
        (Map.ofList [ "expectedLength", ConstraintValue.Integer 13L ])
        isValidIsbn

The prose stays required and becomes the fallback: an untranslated language still says something true. The key takes the ordinary contextual chain, so for context signup and attribute book:

signup.book.books.isbn.invalid
signup.books.isbn.invalid
books.isbn.invalid

A key is segment ("." segment)*. An empty key or empty segment is rejected at construction — a malformed key written in source is a defect, and failing at the call site beats failing at a rendering edge in whichever language nobody tested. %, brackets, whitespace, and non-ASCII characters are exact input; you never pre-encode a key.

Custom constraints declare no plural operand, and Reified does not infer one from an argument's name or value. Guessing would silently change which key a translator has to supply. If you need .one/.other for a custom rule, select it in an advanced resolver.

Reified never invents a key for Constraint.custom prose. A key it made up would name a catalogue entry that does not exist, and the lookup would fail in production, in the language you did not test.