Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultUsing Data with the rest of Reified
Every schema parse takes a Data value, so any source that can become Data can feed a schema.
Feeding a schema
open Reified
open Reified.DataDSL
open Reified.SchemaDSL
open Reified.ConstraintDSL
type Signup = { Email: string; Age: int }
let signupSchema =
schema<Signup> {
field _.Email { constraints [ email ] }
field _.Age
construct (fun email age -> { Email = email; Age = age })
}
let input =
data [
"email" => "ada@example.com"
"age" => 42
]
let parsed = Schema.parse signupSchema input
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
Reified.SchemaDSLThe concise schema-definition vocabulary: the record computation expression, its field and constructor forms, and the collection-schema operations. Optional and opt-in, in the same shape as Reified.DataDSL and Reified.ConstraintDSL: open Reified.Schema for Schema, then open Reified.SchemaDSL for this vocabulary. There is no constraint catalogue here. One Constraint vocabulary serves direct checking, refinement, and Schema, so a field block reaches for Constraint.email or an opened Reified.ConstraintDSL exactly as standalone code does. Boundary supply is Schema-owned and stays here as mustSupply and mayOmit.
Reified.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
FsLiveDocsGeneratedPage43_6BB268B4E740.SignupEmail: stringstringAn abbreviation for the CLI type . Basic Types
Age: intintAn abbreviation for the CLI type . Basic Types
signupSchema: Schema<Signup>schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(Signup -> string)> -> field<Signup,string>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
``.ctor``: Quotations.Expr<('model -> 'target)> -> field<'model,'target>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
_arg1: SignupConstraints: SchemaFieldSteps.FieldInitial<Signup,string> * Constraint<string> list -> SchemaFieldSteps.FieldConfigured<Signup,string>Adds portable constraints to the field's inferred schema in declaration order.
email: Constraint<string>Alias for .
``.ctor``: Quotations.Expr<(Signup -> int)> -> field<Signup,int>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
_arg2: Signupconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
email: stringage: intinput: Datadata: DataField list -> DataBuilds an object from ordered field instructions. data [ "name" => "Ada"; "active" => true ] // Data.Object [ "name", Data.Text "Ada"; "active", Data.Bool true ]
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
parsed: Result<Signup,SchemaErrors>Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
parse: Schema<'a> -> Data -> Result<'a,SchemaErrors>Parses source-neutral structured data, runs constraints and refinements, and invokes record constructors.
The same schema accepts input from Data.ofJsonDocument for a request body, Data.ofNameValues for a form post,
Data.ofCliArgs for a command line, and Data.ofConfiguration for settings. The schema does not depend on the source.
Adapters produce different trees. JSON carries typed
Booleans and numbers, while form, CLI, and configuration leaves are all text that the schema still has
to parse. Repeated names build a list in ofNameValues but overwrite in ofConfiguration, where lists
come from indexed key segments instead. CLI flags arrive as the text "true" or "false". The same schema accepts
input from any of them, but sources must agree on the resulting tree, not just the logical content. See
the adapter rules for what each one produces.
A fixture written with data [ ... ] exercises the same parse path as production JSON without a serializer.
Redisplay and error reporting
Schema.parseRetainingInput keeps the original Data alongside the parse result, so a failed form
round-trip can re-show exactly what the user typed next to each field error. The paths in schema
diagnostics (address.city, lines[2].quantity) address the input tree.
Data.redisplayPath (string form) or Data.redisplayAt (DataPath form) recovers the raw fragment
at any of them.
See redisplay and field errors for the full pattern.
Boundary adapters
An adapter builds Data from a source such as route values, query strings, headers, or a JSON body before any schema
is involved. Supporting another source means producing Data; every schema and interpreter downstream stays
unchanged.