Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultError taxonomy and recipes
Reified has eight failure shapes. They are deliberately distinct: a constraint failure is not a parse failure, and a
trusted-codec failure is not an accumulated SchemaErrors. Know which one you are holding before mapping it.
The types
| Type | Package | Means | Carries |
|---|---|---|---|
Violation |
Reified.Constraint |
A value failed a constraint | A tree of failed rules (Atomic, All, Any) |
ParseError |
Reified.Parse |
Serialized text could not become a primitive | Missing, malformed, or out-of-range input, with the target type |
SchemaError |
Reified.Schema |
One boundary failure at one path | Missing or blank input, wrong shape, a parse failure, a Violation, or a constructor or custom error |
SchemaIssue |
Reified.Schema |
A SchemaError plus its location |
Path and Error |
SchemaErrors |
Reified.Schema |
Accumulated independent failures, in path order | The issues, via SchemaErrors.toList |
JsonCodecException |
Reified.Schema |
Trusted codec decode failed on structure or wire shape | A schema-relative path in Message |
MigrationError |
Reified.Schema |
A contract migration failed | The migration's message, or the SchemaErrors from revalidation |
ContractError |
Reified.Schema |
A versioned contract could not select, parse, or migrate | A missing, unknown, or too-new version; a parse failure; or a MigrationError |
Violation and ParseError are leaf errors; SchemaError lowers both of them (SchemaError.Violation,
SchemaError.Blank/InvalidFormat/ParseOutOfRange) and adds path structure around them.
Conversions and rendering
open Reified
SchemaError.ofParseError (ParseError.MissingValue "int") // SchemaError.Blank
SchemaError.render (SchemaError.InvalidFormat "int") // "Expected int format."
Constraint.check Constraint.email "ada"
|> Result.mapError Violation.render // Error "expected an email address, but was ada"
ReifiedReified.SchemaErrorModuleFunctions for lowering and rendering boundary schema failures.
ofParseError: ParseError -> SchemaErrorLowers a parse failure. Parsing failures never become constraint violations. ParseError.MissingValue "int" |> SchemaError.ofParseError
Reified.ParseErrorPrimitive parse failures returned by Parse helpers.
MissingValueThe input was missing for the target primitive type.
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.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.
InvalidFormatThe input could not be read as the named target type.
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
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
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
(|>): '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.Result.ResultModuleFail-fast helpers over the standard F# Result type.
mapError: ('a -> 'b) -> Result<'c,'a> -> Result<'c,'b>Maps the error value of a result.
Reified.ViolationModuleInspects, traverses, and renders violations.
render: Violation -> stringRenders a violation as an English sentence fragment with no trailing punctuation, keeping conjunction and alternative groups distinct. Violation.render (Atomic (Expected (PresenceAtom Present, None))) // "value must be present"
Requires & types
Requires: open Reified; open Reified.Result (packages: Reified.Constraint, Reified.Parse, Reified.Result, Reified.Schema)The remaining conversions are signature references; each input is named to show its type:
| Operation | Result |
|---|---|
SchemaErrors.toList errors |
SchemaIssue list, deterministic path order |
SchemaErrors.toString errors |
string list, one rendered line per failure |
Json.deserialize codec badJson |
raises JsonCodecException |
Json.tryDeserialize codec badJson |
Result<'model, string>, first structural failure |
Json.parseData badJson |
raises JsonCodecException when the text is not one JSON value |
Json.tryParseData badJson |
Result<Data, string> |
RetainedParseResult.renderErrors parsed |
string list, one line per failure |
Localized rendering of a SchemaError.Violation goes through Violation.message with a Renderer; the other
SchemaError cases render through the neutral English SchemaError.render. There is no localized SchemaError
renderer, so a boundary that localizes writes one app-owned function:
open Reified
let renderIssue (renderer: Renderer) (issue: SchemaIssue) =
let message =
match issue.Error with
| SchemaError.Violation violation -> Violation.message renderer violation
| other -> SchemaError.render other
let path = SchemaPath.format issue.Path
if path = "" then message else $"{path}: {message}"
ReifiedrenderIssue: Renderer -> SchemaIssue -> stringrenderer: RendererReified.RendererRenders localized messages for one document context and attribute. Immutable: build one at the composition root and derive scoped copies with context and attribute. A renderer holds no violation and a violation holds no renderer. Context arrives here, at the rendering edge, which is what keeps Violation path-free, closure-free comparable data. context appends a document, model, form, or component segment. attribute replaces the whole attribute with one segment, so a form-scoped renderer is safe to reuse for sibling fields without leaking the previous field's noun.
issue: SchemaIssueReified.SchemaIssueOne schema failure and its complete structural location.
message: stringError: 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."
path: stringReified.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.
(=): 'T -> 'T -> boolStructural equality The first parameter. The second parameter. The result of the comparison. 5 = 5 // Evaluates to true 5 = 6 // Evaluates to false [1; 2] = [1; 2] // Evaluates to true (1, 5) = (1, 6) // Evaluates to false
Requires & types
Requires: open Reified (packages: Reified.Constraint, Reified.Schema)val renderIssue: Renderer -> SchemaIssue -> stringMapping a whole parse into your own union
open Reified
open Reified.SchemaDSL
open Reified.ConstraintDSL
type Signup = { Email: string; Age: int }
type SignupError =
| Field of path: string * message: string
let ofParse (parsed: RetainedParseResult<Signup>) : Result<Signup, SignupError list> =
match parsed.Result with
| Ok signup -> Ok signup
| Error errors ->
errors
|> SchemaErrors.toList
|> List.map (fun issue ->
Field(SchemaPath.format issue.Path, SchemaError.render issue.Error))
|> 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
FsLiveDocsGeneratedPage22_9A5DDEA924C8.SignupEmail: stringstringAn abbreviation for the CLI type . Basic Types
Age: intintAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage22_9A5DDEA924C8.SignupErrorFieldpath: stringmessage: stringofParse: RetainedParseResult<Signup> -> Result<Signup,SignupError list>parsed: RetainedParseResult<Signup>Reified.RetainedParseResult`1A schema parse result that retains its original structured input.
Microsoft.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.
Result: Result<'value,SchemaErrors>The parsed model or accumulated schema failures.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
signup: SignupErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
errors: 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 ]
issue: SchemaIssueReified.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.
Reified.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."
Error: SchemaErrorThe schema failure.
Requires & types
Requires: open Reified (packages: Reified.Constraint, Reified.Schema)val ofParse: RetainedParseResult<Signup> -> Result<Signup,SignupError list>Symptom → cause
| Symptom | Likely cause | Fix |
|---|---|---|
| Constraints are not enforced on JSON decode | You used Json.deserialize/tryDeserialize on a schema whose rules matter |
Route untrusted input through Json.parseData + Schema.parse; the trusted codec skips constraints |
missing required field from a codec |
Wire shape mismatch, not a rule | The payload is missing a field the codec declares required; check the producer |
A value passed Schema.parse but later fails a rule |
The public record was constructed directly, outside the boundary | Make the aggregate private, or re-check with Schema.check |
MissingValue from Parse.optional (Some "") |
Blank text is a present-but-missing input, not an absent one | Treat Some "" as a parse failure; the fallback in optionalOr applies only to None |
| Errors are English even though I set up a renderer | Only Violation localizes; SchemaError.render is neutral English |
Use Violation.message for violation cases and SchemaError.render for the rest, as above |
A contract fails with VersionTooNew |
The payload's version is ahead of the contract's current version | Register the newer version as current, or reject the payload |
A contract fails with Migration (RevalidationFailed …) |
A migrated value no longer satisfies the current schema | The migration produced a value the current schema rejects; fix the migration or the current rules |
matrix throws ArgumentException |
More than 256 combinations | Reduce the dimensions; the product is checked before cases are materialized |
A rendered Data.Number is not valid JSON |
The number was built with the Number union case, not Data.num |
Use Data.num (or a parser) so the token is validated before it is stored |
See Redisplay and field errors for path formatting and retained input, and Versioned contracts for the migration failure cases.