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.
54 lines
2.2 KiB
Swift
54 lines
2.2 KiB
Swift
@testable import PureXML
|
|
import Testing
|
|
|
|
@Suite("Validation")
|
|
struct ValidationTests {
|
|
@Test("Duplicate attribute is one located error")
|
|
func test_duplicateAttributeIsError() {
|
|
let element = PureXML.Model.Element(
|
|
"input",
|
|
attributes: [.init("id", "a"), .init("id", "b")],
|
|
)
|
|
let errors = PureXML.Validation.Validator().errors(for: .element(element))
|
|
#expect(errors.count == 1)
|
|
#expect(errors.first?.reason == "Duplicate attribute 'id' on <input>")
|
|
#expect(errors.first?.codingPath.isEmpty == true)
|
|
}
|
|
|
|
@Test("Unique attributes validate cleanly")
|
|
func test_uniqueAttributesValidate() throws {
|
|
let element = PureXML.Model.Element(
|
|
"input",
|
|
attributes: [.init("id", "a"), .init("name", "b")],
|
|
)
|
|
try PureXML.validate(.element(element))
|
|
}
|
|
|
|
@Test("A located failure renders its coding path")
|
|
func test_errorRendersPath() {
|
|
let bad = PureXML.Model.Element("cell", attributes: [.init("id", "a"), .init("id", "b")])
|
|
let root = PureXML.Model.Element("table", children: [
|
|
.element(PureXML.Model.Element("row", children: [.element(bad)])),
|
|
])
|
|
// Parse output is a document node, so the root element is named in the path.
|
|
let errors = PureXML.Validation.Validator().errors(for: .document([.element(root)]))
|
|
#expect(errors.count == 1)
|
|
#expect(String(describing: errors[0]) == "Duplicate attribute 'id' on <cell> at path: table/row/cell")
|
|
}
|
|
|
|
@Test("Throwing form collects errors into one collection")
|
|
func test_throwsCollection() {
|
|
let element = PureXML.Model.Element("x", attributes: [.init("a", "1"), .init("a", "2")])
|
|
#expect(throws: PureXML.Validation.ValidationErrorCollection.self) {
|
|
try PureXML.validate(.element(element))
|
|
}
|
|
}
|
|
|
|
@Test("A blank validator runs no rules")
|
|
func test_blankValidator() {
|
|
let element = PureXML.Model.Element("x", attributes: [.init("a", "1"), .init("a", "2")])
|
|
let validator = PureXML.Validation.Validator<Void>.blank
|
|
#expect(validator.errors(for: .element(element), in: ()).isEmpty)
|
|
}
|
|
}
|