Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultRefined fields in Schema
A refined type moves a field-local rule into the model, where it holds no matter how the value was built. Schema resolves a refined field type the same way it resolves primitives: every refined type has exactly one canonical schema. This page shows the result; Refined Schemas is the deeper reference for defining your own domain types and contributing their canonical schemas.
Signup.Email as a plain string leaves nothing stopping { Email = ""; Age = 36; Newsletter = true }.
Reified.Refinements ships the common refined types. Use them as field types directly:
open Reified
open Reified.SchemaDSL
open Reified.Refinements
type Registration =
{ Owner: NonBlankString
Seats: int
Aliases: NonEmptyList<NonBlankString> }
let registrationSchema =
schema<Registration> {
field _.Owner
field _.Seats
field _.Aliases
construct (fun owner seats aliases ->
{ Owner = owner; Seats = seats; Aliases = aliases })
}
ReifiedReified.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.
RefinementsFsLiveDocsGeneratedPage6_10EA2B4D0E31.RegistrationOwner: NonBlankStringReified.Refinements.NonBlankStringA string that is not null, empty, or whitespace.
Seats: intintAn abbreviation for the CLI type . Basic Types
Aliases: NonEmptyList<NonBlankString>Reified.Refinements.NonEmptyList`1A list that contains at least one item. The case is public: non-emptiness is carried by the representation rather than by a checked constructor, so head, last, reduce, min, and max are total and pattern matching is available to callers.
registrationSchema: Schema<Registration>schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(Registration -> NonBlankString)> -> field<Registration,NonBlankString>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: Registration``.ctor``: Quotations.Expr<(Registration -> int)> -> field<Registration,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: Registration``.ctor``: Quotations.Expr<(Registration -> NonEmptyList<NonBlankString>)> -> field<Registration,NonEmptyList<NonBlankString>>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"
_arg3: Registrationconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
owner: NonBlankStringseats: intaliases: NonEmptyList<NonBlankString>Requires & types
Requires: open Reified.Refinements; open Reified.SchemaDSL (packages: Reified.Refinements, Reified.Schema)val registrationSchema: Schema<Registration>The fields stay bare. Every refined type has exactly one schema, so the field resolves it from the type the same way
string resolved Schema.text. Rules that need a parameter, a length range, a pattern, are constraints rather
than types, and go on the field as their own line: field _.Name { constrain (Constraint.lengthBetween 2 80) }.
NonEmptyList<NonBlankString> composes: the outer refinement resolves, and so does the item.
It flows through
A refinement carries its constraints, so the interpreters see them:
JsonSchema.generate registrationSchema
// "owner": {"type":"string","pattern":"\\S"}
// "seats": {"type":"integer"}
// "aliases": {"type":"array","items":{"type":"string"},"minItems":1}
Reified.JsonSchemaGenerates JSON Schema documents from built model schemas. The generator is a pure interpreter over descriptions: it lowers shapes, declared formats, and portable constraint metadata to JSON Schema keywords without parsing input, running checks, or constructing models. One schema declaration therefore drives parsing, validation, and the published contract. Lowering rules: primitives map to type (with format for dates, date-times, and uuids), refined values lower to their underlying primitive representation, nested models to object with properties and required, collections to array with items, maps to object with additionalProperties, and tagged unions to oneOf with a const-constrained discriminator property per case. An option lowers to its payload in an omittable record field, where the codec leaves None out, and to anyOf of its payload and null everywhere the codec writes None as null. Constraint metadata lowers to minLength, maxLength, pattern, enum, minimum/maximum (and exclusive variants), multipleOf, minItems/maxItems, and uniqueItems; constraints without a JSON Schema equivalent, such as trimmed, are skipped. Default-value metadata attached with Schema.withDefault lowers to default.
generate: Schema<'model> -> stringGenerates a compact JSON Schema document from any completed schema declaration. The record, primitive, collection, union, or other completed schema to lower. Thrown when is null. open Reified.SchemaDSL open Reified.ConstraintDSL type Customer = { Name: string; Email: string } let customerSchema = schema<Customer> { field _.Name { constrain present } field _.Email { constraints [ present; email ] } construct (fun name email -> { Name = name; Email = email }) } let document = JsonSchema.generate customerSchema // {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{...},"required":[...]}
registrationSchema: Schema<Registration>Requires & types
Requires: open Reified (packages: Reified.Schema)Inspect.model registrationSchema
// owner -> [ "present" ]
// seats -> [ ]
// aliases -> [ "minLength" ]
Reified.InspectThe inspection API over built schemas and value schemas. Inspect is the entry point for non-validation interpreters: JSON Schema generation, documentation, UI metadata, and codec planning all start from the same descriptions. The returned trees are plain immutable data — inspecting them never parses input, runs checks, or constructs models.
model: Schema<'model> -> ModelDescriptionDescribes a built model schema as inspectable field metadata. The built model schema to describe. Thrown when is null. Thrown when is not a completed model schema. let description = Inspect.model customerSchema let names = description.Fields |> List.map _.Name
registrationSchema: Schema<Registration>Requires & types
Requires: open Reified (packages: Reified.Schema)Failures arrive on the right paths:
owner: This value must be present.
aliases: Length must be at least 1; got 0.
The generated JSON Schema, inspection metadata, and parse diagnostics all read the refinement definition. Each check runs once, at the layer that owns it.
Your own domain types participate the same way. See Refined Schemas for defining Email
or WorkspaceName and contributing a canonical schema.