PureXML/Sources/Schema/XSDValidations.swift
Mihaela Mihaljevic 4765af0624 refactor(validation): migrate XSD validation onto the framework
Slice 4 of #92. The XSD complex-type and identity-constraint validators move
onto the validation framework, producing located results.

- ComplexValidator threads a coding path through its (inherently recursive)
  descent, so every content-model, attribute, and simple-type violation is now
  a ValidationError located by path ("... at path: order/qty"). Child steps
  carry a sibling index only when a name repeats.
- IdentityValidator returns ValidationError instead of Issue.
- A new XSD rules enum wraps both as Validation<Node, XSDContext> values gated to
  the document root, combined by Validator<XSDContext>. XSDContext carries the
  type table, the identity constraints, and the root declaration.
- Schema.Document.validate builds the context and runs the validator, returning
  [ValidationError].

XSD content-model validation is recursive by nature (a child's type depends on
its position), so the rules delegate to the recursion while contributing to one
error collection. Schematron is the last validator on Issue and migrates next.
2026-06-08 12:43:20 +02:00

60 lines
2.7 KiB
Swift

public extension PureXML.Validation {
/// The compiled-schema context the XSD validations read: the named-type table
/// a content model resolves against, the identity constraints, and the root
/// element's declared type.
struct XSDContext {
public let types: [String: PureXML.Schema.ElementType]
public let constraints: [String: [PureXML.Schema.IdentityConstraint]]
public let rootDeclaration: PureXML.Schema.ElementType?
public init(
types: [String: PureXML.Schema.ElementType],
constraints: [String: [PureXML.Schema.IdentityConstraint]],
rootDeclaration: PureXML.Schema.ElementType?,
) {
self.types = types
self.constraints = constraints
self.rootDeclaration = rootDeclaration
}
}
/// XSD validation expressed as composable ``Validation`` values over an
/// ``XSDContext``. The content-model check is inherently recursive (a child's
/// type depends on its position), so each rule delegates to the recursive
/// validators while contributing located ``ValidationError`` results to one
/// collection.
enum XSD {
/// The document element is valid against its declared XSD type.
static var contentValidity: Validation<PureXML.Model.Node, XSDContext> {
.init(
description: "The document element is valid against its XSD type",
check: { context in
guard case let .element(root) = context.subject, let declaration = context.document.rootDeclaration else {
return []
}
return PureXML.Schema.ComplexValidator(types: context.document.types)
.validate(root, as: declaration, at: [.element(root.name.description)])
},
when: { $0.codingPath.isEmpty },
)
}
/// The document's XSD identity constraints (`unique`, `key`, `keyref`) hold.
static var identityConstraints: Validation<PureXML.Model.Node, XSDContext> {
.init(
description: "XSD identity constraints hold",
check: { context in
PureXML.Schema.IdentityValidator(constraints: context.document.constraints)
.validate(PureXML.Model.TreeNode(context.subject))
},
when: { $0.codingPath.isEmpty },
)
}
/// A validator combining the content-model and identity-constraint rules.
static func validator() -> Validator<XSDContext> {
Validator<XSDContext>.blank.validating(contentValidity, identityConstraints)
}
}
}