Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonConstraints
Reified.Constraint describes which typed values are acceptable. A constraint can be checked directly, reused by a
refined type, attached to a Schema field, inspected for documentation, and rendered in another language.
dotnet add package Reified.Constraint
open Reified
open Reified.ConstraintDSL
let username = Constraint.all [ present; minLength 3; maxLength 30 ]
Constraint.check username "ada"
// Ok ()
Constraint.check username ""
// Error [ Blank; InvalidLength (MinimumLength 3, Some 0) ]
ReifiedReified.ConstraintDSLConstraint constructors usable without the Constraint. prefix inside a module that declares value rules. Optional vocabulary, not another abstraction. Opening it makes a declaration read minLength 3 instead of Constraint.minLength 3; everything here is the same value the qualified name returns. Some constructors are deliberately left out because they shadow names the same validation code is likely to need in scope: contains, distinct, all, any, length, and between shadow core F# operations. Reach for those as Constraint.contains, Constraint.all, and so on, even inside a module that has opened this DSL. Constraint execution (Constraint.satisfies, Constraint.check, Constraint.guard) is likewise always qualified: ConstraintDSL declares constraints, Constraint.* executes and inspects them. orError and mapError are structural adapters matching the corresponding Result operations. They let a constraint pipeline retain its input and finish with the application's error type without adding an Reified.Result dependency. module SignupRules = open Reified.ConstraintDSL let age : Constraint<int> = atLeast 13 let contact : Constraint<string> = Constraint.all [ present; email ] let requireContact value = value |> Constraint.guard contact |> orError EmailRequired
username: Constraint<string>Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
all: Constraint<'value> list -> Constraint<'value>Requires every constraint to hold, evaluating each in declaration order and accumulating failures. The empty list is the satisfied identity. F# visits list elements left to right, so annotate the binding when the first element is a type-directed value: let requiredName : Constraint<string> = Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]. let requiredName : Constraint<string> = Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]
present: Constraint<^value>Alias for .
minLength: int -> Constraint<^value>Alias for .
maxLength: int -> Constraint<^value>Alias for .
check: Constraint<'value> -> 'value -> Result<unit,Violation>Runs a constraint, returning why the value failed. let retryCount = Constraint.between 0 10 42 |> Constraint.check retryCount |> Result.mapError Violation.render
Constraints return the standard F# Result<unit, Violation>. They do not depend on Reified.Result; use that
package, FsToolkit.ErrorHandling, or ordinary pattern matching when you need to compose the result with other work.
Start here
- Quickstart — define and check a constraint.
- Constraint — the core type and its guarantees.
- ConstraintDSL — the authoring vocabulary.
- Reusable constraints — interpreted rules and opaque escape hatches.
- Working with violations — inspect and render failures.
- Localization — contextual messages and language catalogues.
- Tutorial: constraints and Result — map violations into application errors.
- Fable support — use the same rules on JavaScript.
Use Parsing when serialized text must first become a typed value. Use Refined when successful admission should be recorded in the type. Use Schema when fields of a structured input need paths and accumulated diagnostics.