The error-array forms of && and || were unit-tested; the Bool predicate
forms were not. Add a pure-function test so every combinator overload in
the algebra is exercised, completing the framework's combinator coverage.
Bring the validation-framework unit tests up to the OpenAPIKit idiom's
bar: each combinator is exercised as a pure function against a hand-built
context.
- The comparison operators (>, >=, <, <=, !=) lifting a KeyPath and a
literal into a predicate.
- lift running child validations against a lifted value.
- unwrap erroring on nil and otherwise running child validations.
- The erased wrapper yielding two errors when the same value of the same
type is applied twice.
- ValidationError rendering: at-path, at-root-of-document, and the
stripped trailing period.
Slice 1 of #92. Introduce the OpenAPIKit-idiom validation framework, replacing
the imperative if-tree structural validator:
- ValidationError (reason + coding path) renders "<reason> at path: root/row/cell";
ValidationErrorCollection gathers every failure and is thrown once.
- Validation<Subject, Document> is the atomic, composable rule value, with the
multi-error and single-error Bool inits (the latter auto-forms "Failed to
satisfy: <description>"). Predicate-gated, type-directed via an erased
AnyValidation that guards an optional from matching a non-optional rule.
- The framework is parameterized over both Subject and Document so the structural,
DTD, XSD, and Schematron layers share one machinery (Document = Void for rules
that need only their subject).
- Validator<Document> walks the node tree, building the coding path, with blank /
default factories and fluent validating / withoutValidating.
- The combinator algebra (&&, ||, comparison lifts, take, lift, unwrap, all) as
free functions returning closures.
The structural duplicate-attribute rule is reborn as Validation<Element, Void>.
Combinators and the erased wrapper are unit-tested as pure functions per the rule.
The legacy Validation.Issue stays only for the not-yet-migrated schema validators
and is retired in a later slice.