refactor(xsd): express the streaming content check as a Validation value
Rule-canon audit (validation-rules.md / the OpenAPIKit idiom): the streaming XSD validator was the one surface applying a bare method (validateShallow) rather than a Validation<> value. Wrap it: ComplexValidator.shallowValidity is a Validation<ResolvedElement, ComplexValidator> whose subject carries the element with its already-resolved effective type (the contextual typing a node-tree walk cannot express), and the streaming driver applies it via apply(to:at:in:). Now every validation surface, DTD, XSD, RELAX NG, Schematron, HTML, structural, conformance, and both streaming validators, routes through a Validation value. Adds a direct unit test of the rule.
This commit is contained in:
parent
067aa91861
commit
4c93555d1f
@ -99,7 +99,7 @@ flowchart TB
|
||||
|
||||
PureXML is a working, dependency-free XML library today: parse, emit, validate,
|
||||
query, and stream documents on macOS, Linux, Windows, and WASM. The test suite
|
||||
currently runs **815 tests in 125 suites** (`swift test`).
|
||||
currently runs **816 tests in 125 suites** (`swift test`).
|
||||
|
||||
### Shipped (libxml2-aligned surface)
|
||||
|
||||
|
||||
@ -1,10 +1,36 @@
|
||||
extension PureXML.Schema.ComplexValidator {
|
||||
public extension PureXML.Schema {
|
||||
/// An element paired with its already-resolved effective type: the subject of
|
||||
/// the streaming XSD content rule, so the contextual type resolution that a
|
||||
/// node-tree walk cannot express is carried on the subject instead.
|
||||
struct ResolvedElement: PureXML.Validation.Validatable {
|
||||
public let element: PureXML.Model.Element
|
||||
public let type: ElementType
|
||||
|
||||
public init(element: PureXML.Model.Element, type: ElementType) {
|
||||
self.element = element
|
||||
self.type = type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension PureXML.Schema.ComplexValidator {
|
||||
// MARK: Streaming support
|
||||
|
||||
/// The streaming XSD content check as a composable ``PureXML/Validation`` value
|
||||
/// (the OpenAPIKit idiom): an element is valid against its resolved type. The
|
||||
/// validator is the document, the cross-cutting context that holds the type
|
||||
/// tables, so the streaming path is rule-driven like every other validator
|
||||
/// rather than a bare method call.
|
||||
static var shallowValidity: PureXML.Validation.Validation<PureXML.Schema.ResolvedElement, PureXML.Schema.ComplexValidator> {
|
||||
.init(description: "Each streamed element is valid against its declared XSD type") { context in
|
||||
context.document.validateShallow(context.subject.element, as: context.subject.type, at: context.codingPath)
|
||||
}
|
||||
}
|
||||
|
||||
/// The effective element type after an `xsi:type` override and following any
|
||||
/// `typeReference` chain, so the streaming driver resolves a type once per
|
||||
/// element rather than re-entering the tree walk.
|
||||
public func effectiveType(_ declared: PureXML.Schema.ElementType, of element: PureXML.Model.Element) -> PureXML.Schema.ElementType {
|
||||
func effectiveType(_ declared: PureXML.Schema.ElementType, of element: PureXML.Model.Element) -> PureXML.Schema.ElementType {
|
||||
var type = declared
|
||||
if let overriding = Self.xsiTypeName(element), let resolved = types[overriding] {
|
||||
type = resolved
|
||||
@ -20,7 +46,7 @@ extension PureXML.Schema.ComplexValidator {
|
||||
/// The declared element type of a child `name` in `parent`'s content model, or
|
||||
/// nil when the parent has no element content or no such child (an undeclared
|
||||
/// child is flagged by the parent's own structure check).
|
||||
public func childType(of parent: PureXML.Schema.ElementType, child name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
|
||||
func childType(of parent: PureXML.Schema.ElementType, child name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
|
||||
guard case let .complex(complex) = parent else { return nil }
|
||||
switch complex.content {
|
||||
case let .elementOnly(particle), let .mixed(particle):
|
||||
@ -34,7 +60,7 @@ extension PureXML.Schema.ComplexValidator {
|
||||
/// names, order, occurrence) and simple content, but not its children's own
|
||||
/// validity, which the streaming driver checks as each child closes. `type`
|
||||
/// must already be effective (see ``effectiveType(_:of:)``).
|
||||
public func validateShallow(
|
||||
func validateShallow(
|
||||
_ element: PureXML.Model.Element,
|
||||
as type: PureXML.Schema.ElementType,
|
||||
at path: [PureXML.Validation.PathKey] = [],
|
||||
|
||||
@ -69,7 +69,10 @@ public extension PureXML.Validation {
|
||||
private mutating func close() {
|
||||
guard let frame = stack.popLast() else { return }
|
||||
if let effective = frame.effective {
|
||||
collected += validator.validateShallow(frame.synthesized(), as: effective, at: frame.path)
|
||||
// Apply the streaming content check as a composable Validation value
|
||||
// (the OpenAPIKit idiom), not a bare method call.
|
||||
let subject = PureXML.Schema.ResolvedElement(element: frame.synthesized(), type: effective)
|
||||
collected += PureXML.Schema.ComplexValidator.shallowValidity.apply(to: subject, at: frame.path, in: validator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -67,4 +67,20 @@ struct StreamingXSDTests {
|
||||
// No items at all (item is required, maxOccurs unbounded but minOccurs 1).
|
||||
try check("<order><id>1</id></order>", expectValid: false)
|
||||
}
|
||||
|
||||
@Test("The streaming content check is a composable Validation value")
|
||||
func test_shallowValidityRule() {
|
||||
// The streaming content check applied directly through the Validation rule.
|
||||
let validator = PureXML.Schema.ComplexValidator()
|
||||
let rule = PureXML.Schema.ComplexValidator.shallowValidity
|
||||
#expect(rule.description == "Each streamed element is valid against its declared XSD type")
|
||||
// An empty-content element carrying a child element is invalid.
|
||||
let bad = PureXML.Schema.ResolvedElement(
|
||||
element: PureXML.Model.Element("v", children: [.element(.init("child"))]),
|
||||
type: .complex(PureXML.Schema.ComplexType(content: .empty)),
|
||||
)
|
||||
let errors = rule.apply(to: bad, at: [.element("v")], in: validator)
|
||||
#expect(errors.contains { $0.reason.contains("must be empty") })
|
||||
#expect(errors.first?.codingPath.map(\.stringValue) == ["v"])
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user