PureXML/Sources/Validation/Conformance.swift
Mihaela Mihaljevic 83507939de feat(validation): conformance-corpus harness via the validation framework
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)
2026-06-09 19:08:17 +02:00

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: ()) }
}
}
}