Declare value and model invariants once. Derive validation, parsing, diagnostics, codecs, contracts, and test data from the same declarations.
Warning
Reified is pre-1.0 and has not been published to NuGet yet. APIs change without deprecation cycles.
Most validation stacks keep the rule and its message in separate places. Reified makes a constraint inspectable data, so checking, diagnostics, export, and generation read the same declaration.
open Reified
let retryCount : Constraint<int> =
Constraint.between 0 10
3 |> Constraint.check retryCount
// Ok ()
42
|> Constraint.check retryCount
|> Result.mapError Violation.render
// Error "expected a value between 0 and 10, but was 42"Nobody wrote the failure sentence separately. Change the bounds and every interpreter observes the new rule.
A schema describes how structured input becomes a model. It returns the typed value only after every field and constructor invariant succeeds.
open Reified
open Reified.ConstraintDSL
open Reified.SchemaDSL
type Signup =
{ Email: string
Age: int }
let signupSchema =
schema<Signup> {
field _.Email {
constraints [ present; email ]
}
field _.Age {
constrain (atLeast 13)
}
construct (fun email age -> { Email = email; Age = age })
}
match Schema.parse signupSchema input with
| Ok signup -> register signup
| Error errors -> display errorsThe same signupSchema can drive a compiled JSON codec, JSON Schema, form metadata, versioned migrations, and matching test data.
You do not write a second description of the wire shape. Json.compile turns the schema you already have into a codec that both encodes and decodes.
open Reified.Schema.Json
let codec = Json.compile signupSchema // compile once, typically at startup
Json.serialize codec { Email = "ada@example.com"; Age = 36 }
// {"email":"ada@example.com","age":36}
Json.deserialize codec """{"email":"ada@example.com","age":36}"""
// { Email = "ada@example.com"; Age = 36 }
match Json.tryDeserialize codec """{"email":"ada@example.com","age":"thirty"}""" with
| Ok signup -> Some signup
| Error message -> None // JSON decode failed at $.age: expected digitThe codec is compiled from the schema's typed field plan, so there is no runtime reflection and it stays AOT-, trimming-, and Fable-safe. It is the trusted-path counterpart to Schema.parse: it enforces the wire shape but skips constraint checking, because payloads from producers you trust already passed those checks. Untrusted input still goes through Schema.parse, which accumulates every violation with its path.
For wire records — DTOs whose whole job is to cross a boundary — declaring the schema by hand is duplication. Mark the record instead:
open Reified.DerivedSchema
[<DeriveSchema>]
type Signup =
{ [<Present; Email>]
Email: string
[<AtLeast 13>]
Age: int }
Signup.schema // Schema<Signup>
Signup.parse // Data -> Result<Signup, SchemaErrors>
Signup.validate // Signup -> Result<Signup, SchemaErrors>This is the same schema as the handwritten one above — not an equivalent one. Reified.Schema.Contracts.Build reads the attributes from F# source at build time and generates ordinary constructor-last Schema DSL, which then compiles normally. The attributes are inert metadata; nothing is reflected over at runtime, and everything downstream — parsing, JSON codecs, JSON Schema, test data — works exactly as it does for a schema you wrote by hand.
Derivation is the preferred approach for DTOs. Keep it to public, permissive boundary records, and map the parsed result through a domain constructor so real invariants live in refined values and domain types rather than on the wire record.
Install Reified to get the complete library, or install an individual package when you need only one capability.
Reified— umbrella package that references all runtime packagesReified.Constraint— reusable, inspectable value rules and structured violationsReified.Refinements— types that carry an invariant after constructionReified.Parse— serialized primitive decodingReified.Result— composition over the standard F#ResulttypeReified.Data— portable structured input and test dataReified.Schema— structured model admission, diagnostics, inspection, and JSON SchemaReified.Schema.Json— compiled JSON codecsReified.Schema.Contracts.Build— MSBuild integration for derived record and wire contracts
The contract compiler and schema-derived testing adapter are repository tooling, not runtime packages.
Reified.Schema.Contracts.Build is not in the umbrella. MSBuild targets do not travel through a transitive package reference, so a project that derives schemas at build time references it directly.
Reified packages have not been published yet.
Start with Getting started — one complete transaction from untrusted input to a
typed model and a derived JSON codec. Its code is compiled and executed on every CI run from
examples/Reified.GettingStarted.
- Constraint
- Values quickstart
- Refined domain values
- Schema quickstart
- JSON codecs
- Versioned contracts
- Runnable Schema examples
Axial describes asynchronous workflows with explicit failures and dependencies. Reified began as a library inside Axial and was forked out to stand alone; neither core depends on the other.