Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultHTTP JSON boundary recipe
This page shows one complete, untrusted JSON boundary: request text in, a model or localized failures out. It uses
only the published runtime packages (Reified.Schema for parsing and codecs, Reified.Constraint for rendering).
The HTTP host itself — reading the request, writing the response, choosing a status code — is application-owned.
The pipeline
- Turn the request text into
DatawithJson.tryParseData. This applies no schema; it only checks that the text is one JSON value. - Parse that
DatawithSchema.parseRetainingInput, which runs every field rule and keeps the original input for redisplay. - On failure, map each
SchemaIssueto a localized, path-attached message for the client.
open Reified
open Reified.SchemaDSL
open Reified.ConstraintDSL
type Signup = { Email: string; Age: int; Newsletter: bool }
let signupSchema =
schema<Signup> {
field _.Email { constraints [ present; email ] }
field _.Age { constrain (atLeast 13) }
field _.Newsletter
construct (fun email age newsletter ->
{ Email = email; Age = age; Newsletter = newsletter })
}
type SignupFailure =
| MalformedJson of message: string
| Invalid of path: string * message: string
let translations =
Map.ofList
[ "constraint.presence.present", "doit être renseigné"
"constraint.format.email", "doit être une adresse email"
"constraint.relation.atLeast", "doit être au moins {expected}" ]
let renderer = Renderer.ofLookup translations.TryFind
let renderIssue (issue: SchemaIssue) =
let message =
match issue.Error with
| SchemaError.Violation violation -> Violation.message renderer violation
| other -> SchemaError.render other
SchemaPath.format issue.Path, message
let handle (requestText: string) : Result<Signup, SignupFailure list> =
match Json.tryParseData requestText with
| Error message -> Error [ MalformedJson message ]
| Ok data ->
let parsed = Schema.parseRetainingInput signupSchema data
match parsed.Result with
| Ok signup -> Ok signup
| Error errors ->
errors
|> SchemaErrors.toList
|> List.map (fun issue -> Invalid(renderIssue issue))
|> Error
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.
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
FsLiveDocsGeneratedPage26_10EA2B4D0E31.SignupEmail: stringstringAn abbreviation for the CLI type . Basic Types
Age: intintAn abbreviation for the CLI type . Basic Types
Newsletter: boolboolAn 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.
present: Constraint<^value>Alias for .
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: SignupConstrain: SchemaFieldSteps.FieldInitial<Signup,int> * Constraint<int> -> SchemaFieldSteps.FieldConfigured<Signup,int>Adds a portable constraint to the field's current schema value.
atLeast: 'a -> Constraint<'a>Alias for .
``.ctor``: Quotations.Expr<(Signup -> bool)> -> field<Signup,bool>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: Signupconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
email: stringage: intnewsletter: boolFsLiveDocsGeneratedPage26_10EA2B4D0E31.SignupFailureMalformedJsonmessage: stringInvalidpath: stringtranslations: Map<string,string>Microsoft.FSharp.Collections.MapModuleContains operations for working with values of type .
ofList: ('Key * 'T) list -> Map<'Key,'T>Returns a new map made from the given bindings. The input list of key/value pairs. The resulting map. let input = [ (1, "a"); (2, "b") ] input |> Map.ofList // evaluates to map [(1, "a"); (2, "b")]
renderer: RendererReified.RendererModuleBuilds renderers and renders messages through them.
ofLookup: MessageLookup -> RendererA renderer backed by any key-to-template lookup. The portable constructor, and the one Fable applications use. let translations : Map<string, string> = Map.empty let renderer = Renderer.ofLookup translations.TryFind
TryFind: string -> string optionLookup an element in the map, returning a Some value if the element is in the domain of the map and None if not. The input key. The mapped value, or None if the key is not in the map. let sample = Map [ (1, "a"); (2, "b") ] sample.TryFind 1 // evaluates to Some "a" sample.TryFind 3 // evaluates to None
renderIssue: SchemaIssue -> string * stringissue: SchemaIssueReified.SchemaIssueOne schema failure and its complete structural location.
Error: SchemaErrorThe schema failure.
Reified.SchemaErrorSchema input, checking, and contextual rule failures attached to diagnostics paths. The parse/check axis is the organising split. Parsing cases mean the input could not be read as the declared type at all; Violation means it was read and then failed its constraint. Constraint failures are never lowered into a parse-shaped case, because a lowering that discards the atom forces consumers back to reconstructing constraint identity from strings.
ViolationThe value was read successfully and then failed its constraint.
violation: ViolationReified.ViolationModuleInspects, traverses, and renders violations.
message: Renderer -> Violation -> stringRenders a violation as a localized predicate, with no attribute noun. Use this where a label already names the field — a form row, or a Schema result whose returned path identifies it. Conjunctions and alternatives join through the contextual constraint.group.* patterns; an actual value, when the violation carries one, is composed in through constraint.actual. violation |> Violation.message (renderer |> Renderer.context "signup" |> Renderer.attribute "name") // "must be at least 13, but was 11"
other: SchemaErrorReified.SchemaErrorModuleFunctions for lowering and rendering boundary schema failures.
render: SchemaError -> stringRenders a schema error as an English sentence. Constraint violations delegate to Violation.render, so there is one rendering catalogue. SchemaError.render (SchemaError.InvalidFormat "int") // "Expected int format."
Reified.SchemaPathModuleFunctions for building and formatting schema error paths.
format: SchemaPath -> stringFormats a path with dot-separated keys and bracketed indexes.
Path: SchemaPathThe location of the failure.
handle: string -> Result<Signup,SignupFailure list>requestText: stringMicrosoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
Reified.JsonFunctions for compiling and running JSON codecs over built model schemas.
tryParseData: string -> Result<Data,string>Parses a JSON string into Data, returning malformed input as a rendered message. Json.tryParseData "{\"name\":\"Ada\"}" // Ok (Data.Object [ "name", Data.Text "Ada" ]) Thrown when is null.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
data: Dataparsed: RetainedParseResult<Signup>Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
parseRetainingInput: Schema<'a> -> Data -> RetainedParseResult<'a>Parses source-neutral structured data while retaining it for redisplay and error lookup.
Result: Result<'value,SchemaErrors>The parsed model or accumulated schema failures.
signup: Signuperrors: SchemaErrors(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.SchemaErrorsModuleFunctions for inspecting and rendering accumulated schema failures.
toList: SchemaErrors -> SchemaIssue listReturns failures in deterministic path order.
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
map: ('T -> 'U) -> 'T list -> 'U listBuilds a new collection whose elements are the results of applying the given function to each of the elements of the collection. The function to transform elements from the input list. The input list. The list of transformed elements. let inputs = [ "a"; "bbb"; "cc" ] inputs |> List.map (fun x -> x.Length) Evaluates to [ 1; 3; 2 ]
Requires & types
Requires: open Reified; open Reified.ConstraintDSL; open Reified.SchemaDSL (packages: Reified.Constraint, Reified.Schema)val signupSchema: Schema<Signup>val translations: Map<string,string>val renderer: Rendererval renderIssue: SchemaIssue -> string * stringval handle: string -> Result<Signup,SignupFailure list>handle is the whole boundary. The application then owns the response: map Ok signup to a 201 with the model
encoded through a compiled codec, and Error failures to a 400 whose body is whatever error contract the client
expects. Reified never opens a socket or writes a response; it produces the value and the failures.
For a stricter contract, Reified.Schema.Http assembles RFC 9457 problem-details values from these same parse
diagnostics; the host adapters that serve them live in the Axial repository.
Maturity: Incubating
Notes
Json.tryParseDatareturnsResult<Data, string>;Json.parseDatathrowsJsonCodecExceptioninstead. Use thetryform at a boundary so a malformed body is a value, not an exception.Schema.parseRetainingInputkeepsparsed.Input, so a form can redisplay exactly what the user sent next to each field error.- The trusted codec (
Json.compile) is the way out, not the way in for this pipeline:Schema.parseruns the rules the codec deliberately skips. See Choosing an API.