Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultMatch selected parts of data
Matching checks only the values relevant to a test. Unmentioned fields and items can vary without breaking it.
open Reified
open Reified.DataDSL
let actual =
data [
"customer" => [
"id" => "c-123"
"name" => "Ada"
"address" => [ "city" => "Adelaide"; "postcode" => 5000 ]
]
"roles" => [ "author"; "billing"; "admin" ]
"timeline" => [ "created"; "checked"; "activated" ]
"events" => [ [ "id" => "e-1" ]; [ "id" => "e-2" ] ]
"values" => [ 1; 2; 3 ]
"total" => 19.95m
]
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
actual: 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.
Check selected paths
Use at and absent when only a few observations matter:
actual
|> matching [
at "customer.name" "Ada"
at "customer.id" anyText
absent "error"
]
actual: Data(|>): '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
matching: DataExpectation list -> Data -> unitChecks expectations or raises DataMatchException. open Reified.DataDSL let actual = data [ "user" => data [ "name" => "Ada" ] ] matching [ at "user.name" "Ada"; absent "error" ] actual // returns unit when both expectations hold; otherwise raises DataMatchException
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
anyText: DataPatternMatches any text value.
absent: string -> DataExpectationRequires a path to be absent.
Every expectation is checked against the same root. Failures accumulate instead of stopping after the first mismatch.
The match returns unit without comparing unmentioned response fields.
For a result-returning assertion:
let result =
Data.tryMatch [
at "customer.name" "Grace"
absent "customer.id"
] actual
result: Result<unit,DataMismatch list>Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
absent: string -> DataExpectationRequires a path to be absent.
actual: Dataresult is Error containing two DataMismatch values: one at customer.name with the actual text "Ada", and one
at customer.id with the present identifier "c-123" that was expected to be absent.
result
// => Error [
// { ExpectationIndex = 0; Path = DataPath.parse "customer.name"; Actual = Some (Data.Text "Ada"); ... }
// { ExpectationIndex = 1; Path = DataPath.parse "customer.id"; Actual = Some (Data.Text "c-123"); ... }
// ]
result: Result<unit,DataMismatch list>Match a partial object
Use containing to check related fields as one object:
actual
|> matching [
at "customer" (
containing [
"name" => "Ada"
"address" => containing [
"city" => "Adelaide"
]
])
]
actual: Data(|>): '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
matching: DataExpectation list -> Data -> unitChecks expectations or raises DataMatchException. open Reified.DataDSL let actual = data [ "user" => data [ "name" => "Ada" ] ] matching [ at "user.name" "Ada"; absent "error" ] actual // returns unit when both expectations hold; otherwise raises DataMatchException
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
containing: DataField list -> DataPatternCreates a partial object pattern from required fields. Data.tryMatch [ at "" (containing [ "id" => 42 ]) ] (data [ "id" => 42; "extra" => true ]) // Ok ()
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
Extra fields are allowed. Required fields must be present and match their literal or recursive pattern.
This succeeds for an object such as {"name":"Ada","address":{"city":"Adelaide","postcode":5000},"id":"c-123"}.
Neither postcode nor id is rejected because the pattern does not mention them at their respective object levels.
Data.tryMatch [
at "customer" (containing [
"name" => "Ada"
"address" => containing [ "city" => "Adelaide" ]
])
] actual
// => Ok ()
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
containing: DataField list -> DataPatternCreates a partial object pattern from required fields. Data.tryMatch [ at "" (containing [ "id" => 42 ]) ] (data [ "id" => 42; "extra" => true ]) // Ok ()
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
actual: DataChoose list semantics explicitly
actual
|> matching [
at "roles" (containingItems [ "admin"; "author" ])
at "timeline" (inOrder [ "created"; "activated" ])
at "events" (allItems (containing [ "id" => anyText ]))
at "values" (someItem anyNumber)
]
actual: Data(|>): '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
matching: DataExpectation list -> Data -> unitChecks expectations or raises DataMatchException. open Reified.DataDSL let actual = data [ "user" => data [ "name" => "Ada" ] ] matching [ at "user.name" "Ada"; absent "error" ] actual // returns unit when both expectations hold; otherwise raises DataMatchException
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
containingItems: ^a list -> DataPatternMatches expected items as an unordered consumed subset. open Reified.DataDSL let actual = data [ "items" => [ "Ada"; "Grace"; "Mallory" ] ] Data.tryMatch [ at "items" (containingItems [ "Ada"; "Grace" ]) ] actual // Ok () when both values occur, in either order
inOrder: ^a list -> DataPatternMatches expected items as an ordered subsequence.
allItems: ^a -> DataPatternRequires every actual list item to satisfy a pattern.
containing: DataField list -> DataPatternCreates a partial object pattern from required fields. Data.tryMatch [ at "" (containing [ "id" => 42 ]) ] (data [ "id" => 42; "extra" => true ]) // Ok ()
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
anyText: DataPatternMatches any text value.
someItem: ^a -> DataPatternRequires at least one actual list item to satisfy a pattern.
anyNumber: DataPatternMatches any number token.
containingItems consumes actual occurrences, so an actual item cannot satisfy two expected occurrences.
inOrder allows unrelated values between expected items. It does not reorder the actual list.
For roles = ["author","billing","admin"], containingItems [ "admin"; "author" ] succeeds regardless of order.
For timeline = ["created","checked","activated"], inOrder [ "created"; "activated" ] succeeds.
containingItems [ "admin"; "admin" ] fails when admin occurs once. inOrder [ "activated"; "created" ] fails
because those values occur in the opposite order.
Data.tryMatch [ at "roles" (containingItems [ "admin"; "author" ]) ] actual
// => Ok ()
Data.tryMatch [ at "roles" (containingItems [ "admin"; "admin" ]) ] actual
// => Error [ { Path = DataPath.parse "roles[1]"; Expected = "a matching list item"; ... } ]
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
containingItems: ^a list -> DataPatternMatches expected items as an unordered consumed subset. open Reified.DataDSL let actual = data [ "items" => [ "Ada"; "Grace"; "Mallory" ] ] Data.tryMatch [ at "items" (containingItems [ "Ada"; "Grace" ]) ] actual // Ok () when both values occur, in either order
actual: DataUse a predicate for a local rule
let positiveNumber =
satisfying "a positive number token" (function
| Data.Number token -> decimal token > 0m
| _ -> false)
actual
|> matching [
at "total" positiveNumber
]
positiveNumber: DataPatternsatisfying: string -> (Data -> bool) -> DataPatternMatches an ordinary predicate and uses its description in diagnostics.
Reified.DataA portable tree for structured data. Data preserves null, text, number, Boolean, list, and object distinctions without depending on a serializer or input format. Data is a structured-value model, not a source syntax tree. It does not model whitespace, comments, source locations, or other format-specific syntax. Number values currently retain a lexical token so adapters do not narrow arbitrary-size integers, decimal precision, or exponent notation to one runtime numeric type.
NumberA number whose portable lexical token avoids narrowing it to one runtime numeric type.
token: stringdecimal: ^T -> decimalConverts the argument to System.Decimal using a direct conversion for all primitive numeric types. For strings, the input is converted using UInt64.Parse() with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type. The input value. The converted decimal. decimal "42.23" // evaluates to 42.23M decimal 0xff // evaluates to 255M decimal -10 // evaluates to -10M
(>): 'T -> 'T -> boolStructural greater-than The first parameter. The second parameter. The result of the comparison. 5 > 1 // Evaluates to true 5 > 5 // Evaluates to false (1, "a") > (1, "z") // Evaluates to false
actual: Data(|>): '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
matching: DataExpectation list -> Data -> unitChecks expectations or raises DataMatchException. open Reified.DataDSL let actual = data [ "user" => data [ "name" => "Ada" ] ] matching [ at "user.name" "Ada"; absent "error" ] actual // returns unit when both expectations hold; otherwise raises DataMatchException
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
The description appears in mismatch output, so use wording that explains the failed requirement.
Data.Number "19.95" satisfies this predicate. Data.Number "0" produces a mismatch whose expected description is
a positive number token; Data.Text "19.95" also fails because the predicate requires a number shape.
Data.tryMatch [ at "total" positiveNumber ] (data [ "total" => 19.95m ])
// => Ok ()
Data.tryMatch [ at "total" positiveNumber ] (data [ "total" => 0 ])
// => Error [ { Path = DataPath.parse "total"; Expected = "a positive number token"; ... } ]
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
positiveNumber: DataPatterndata: 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.
Complete matching vocabulary
The full pattern inventory is in the DataPattern API reference; a few representative
patterns:
Data.tryMatch [
at "customer.name" (oneOf [ exactly "Ada"; exactly "Grace" ])
at "customer.id" anyText
at "values" (allItems anyNumber)
] actual
// => Ok ()
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
oneOf: DataPattern list -> DataPatternMatches when one supplied alternative matches.
exactly: ^a -> DataPatternCreates an exact recursive pattern.
anyText: DataPatternMatches any text value.
allItems: ^a -> DataPatternRequires every actual list item to satisfy a pattern.
anyNumber: DataPatternMatches any number token.
actual: DataWhy patterns are not constraints
anyText, anyNumber, and satisfying check Data nodes: whether a branch is text, a value is present, or a list
contains an item. Constraint checks extracted typed values, such as whether a string is a valid email or an int
is at least 13. Tests of produced JSON need the former.
Reified.Data has no dependencies, so test projects can use it without a validation library. Accepting a
Constraint<'value> in at would add a dependency on Reified.Constraint for a test convenience.
For typed rules inside a Data tree, parse it with a schema and check the model; the schema reports paths and
accumulated errors. Use satisfying for a local structural rule that no schema owns.