PureXML/Sources/Schema/IDTracker.swift
Mihaela Mihaljevic 380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00

60 lines
2.2 KiB
Swift

extension PureXML.Schema {
/// Accumulates `xs:ID` and `xs:IDREF`/`xs:IDREFS` values across an instance
/// document and reports the document-scoped violations XSD requires: every ID
/// is unique across the document, and every IDREF/IDREFS item matches some ID.
/// These are not simple-type (lexical) checks; they need the whole document,
/// so the tree validator records here during its typed walk and reports after.
final class IDTracker {
private var seenIDs: Set<String> = []
private var duplicates: [PureXML.Validation.ValidationError] = []
private var references: [(value: String, path: [PureXML.Validation.PathKey])] = []
func recordID(_ value: String, at path: [PureXML.Validation.PathKey]) {
if !seenIDs.insert(value).inserted {
duplicates.append(.init(reason: "duplicate ID '\(value)'", at: path))
}
}
func recordReference(_ value: String, at path: [PureXML.Validation.PathKey]) {
references.append((value, path))
}
/// Duplicate-ID errors plus an error for every reference with no matching
/// ID. Resolution runs after the whole document is seen, so a reference may
/// point at an ID declared later.
func errors() -> [PureXML.Validation.ValidationError] {
var result = duplicates
for reference in references where !seenIDs.contains(reference.value) {
result.append(.init(reason: "IDREF '\(reference.value)' has no matching ID", at: reference.path))
}
return result
}
}
}
extension PureXML.Schema.SimpleType {
/// Whether this is `xs:ID` (or an atomic restriction of it).
var isID: Bool {
if case .atomic = variety {
return base == .id
}
return false
}
/// Whether this is `xs:IDREF` (or an atomic restriction of it).
var isIDReference: Bool {
if case .atomic = variety {
return base == .idref
}
return false
}
/// Whether this is `xs:IDREFS` (a list whose item type is `xs:IDREF`).
var isIDReferenceList: Bool {
if case let .list(item) = variety {
return item.base == .idref
}
return false
}
}