Begin Tier-2 conformance checking, expressed through the OpenAPIKit validation idiom rather than an ad-hoc loop. ConformanceCase is a Validatable named expectation; Conformance.matchesExpected is a Validation<ConformanceCase, Void> that emits one located ValidationError per diverging case, so a suite reports every failure at once with the case name as its coding path. Seeded with a Canonical XML 1.0 corpus (attribute ordering, empty-element expansion, comment removal, CDATA-to-escaped-text, PIs, namespace rendering) and a test proving a divergence is reported as a located failure. Growing the corpora against the official suites is the ongoing Tier-2 work. Part of #105 (Tier 2)
51 lines
2.4 KiB
Swift
51 lines
2.4 KiB
Swift
public extension PureXML.Validation {
|
|
/// One conformance-corpus case: a named expectation that some subsystem's
|
|
/// `actual` output equals the authoritative `expected` output. It conforms to
|
|
/// ``Validatable`` so a corpus is checked by a ``Validation`` rule, the same
|
|
/// composable idiom used to validate documents, rather than an ad-hoc loop.
|
|
struct ConformanceCase: Validatable, Equatable, Sendable {
|
|
public let name: String
|
|
public let actual: String
|
|
public let expected: String
|
|
|
|
public init(name: String, actual: String, expected: String) {
|
|
self.name = name
|
|
self.actual = actual
|
|
self.expected = expected
|
|
}
|
|
}
|
|
|
|
/// Conformance checking, expressed in the validation framework. A corpus is a
|
|
/// set of ``ConformanceCase`` values; the rule states the correct outcome
|
|
/// (positive description) and emits one located ``ValidationError`` per case
|
|
/// whose output diverges, so a suite reports every failure at once.
|
|
enum Conformance {
|
|
/// Each conformance case produces its expected output.
|
|
public static var matchesExpected: Validation<ConformanceCase, Void> {
|
|
.init(description: "Each conformance case produces its expected output") { context in
|
|
let testCase = context.subject
|
|
guard testCase.actual != testCase.expected else { return [] }
|
|
return [
|
|
ValidationError(
|
|
reason: "case '\(testCase.name)': produced \"\(testCase.actual)\", expected \"\(testCase.expected)\"",
|
|
at: [.element(testCase.name)],
|
|
),
|
|
]
|
|
}
|
|
}
|
|
|
|
/// A blank validator carrying the conformance rule, so a caller can compose
|
|
/// further checks onto a corpus the same way other subsystems do.
|
|
public static func validator() -> Validator<Void> {
|
|
Validator<Void>.blank.validating(matchesExpected)
|
|
}
|
|
|
|
/// Every located failure across a corpus (empty means the whole corpus
|
|
/// conforms). Each case is offered to the conformance rules; failures carry
|
|
/// the case name as their coding path.
|
|
public static func failures(in cases: [ConformanceCase]) -> [ValidationError] {
|
|
cases.flatMap { matchesExpected.apply(to: $0, at: [.element($0.name)], in: ()) }
|
|
}
|
|
}
|
|
}
|