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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)val actual: DataCheck 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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)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: DataRequires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)val result: Result<unit,DataMismatch list>result 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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)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: DataRequires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)Choose 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.
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)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: DataRequires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)Use a predicate for a local rule
let positiveNumber =
satisfying "a positive number token" (function
| Data.Number token ->
match System.Decimal.TryParse(token, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture) with
| true, value -> value > 0m
| false, _ -> false
| _ -> 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: stringSystemTryParse: string * System.Globalization.NumberStyles * System.IFormatProvider * byref<decimal> -> boolConverts the string representation of a number to its equivalent using the specified style and culture-specific format. A return value indicates whether the conversion succeeded or failed. The string representation of the number to convert. A bitwise combination of enumeration values that indicates the permitted format of . A typical value to specify is . An object that supplies culture-specific parsing information about . When this method returns, contains the number that is equivalent to the numeric value contained in , if the conversion succeeded, or zero if the conversion failed. The conversion fails if the parameter is or , is not a number in a format compliant with , or represents a number less than Decimal.MinValue or greater than Decimal.MaxValue. This parameter is passed uininitialized; any value originally supplied in is overwritten. is not a value. -or- is the value. if was converted successfully; otherwise, .
System.DecimalRepresents a decimal floating-point number.
Float: System.Globalization.NumberStylesIndicates that the , , , , and styles are used. This is a composite number style.
GlobalizationSystem.Globalization.NumberStylesDetermines the styles permitted in numeric string arguments that are passed to the and methods of the integral and floating-point numeric types.
InvariantCulture: System.Globalization.CultureInfoGets the object that is culture-independent (invariant). The object that is culture-independent (invariant).
System.Globalization.CultureInfoProvides information about a specific culture (called a locale for unmanaged code development). The information includes the names for the culture, the writing system, the calendar used, the sort order of strings, and formatting for dates and numbers.
value: decimal(>): '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.
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)val positiveNumber: DataPatternUse System.Decimal.TryParse rather than F#'s decimal conversion here: a syntactically valid JSON number token such
as 1e999999 is outside decimal's range, and the conversion would throw instead of reporting a mismatch.
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.
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)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: DataRequires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)Why 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.