PureXML/Sources/Schema/IdentityFieldType.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

47 lines
2.5 KiB
Swift

extension PureXML.Schema.IdentityValidator {
/// Whether the field's DECLARED target is a complex type with non-simple content
/// (empty or element-only/mixed), which is not a valid identity field even when its
/// instance node has no element children (an attributes-only element, XSTS idG006).
/// Resolved through the schema type model: the selector target's type is looked up,
/// the field's same-named child element found in its content model, and its declared
/// type classified. A simple/simpleContent type, a type reached only through a
/// reference, a complex field path, or an unresolvable target is left alone, so no
/// valid field is rejected (cvc-identity-constraint.3 / c-fields-xpaths).
func fieldTargetIsComplexNonSimple(_ field: String, at target: PureXML.Model.TreeNode) -> Bool {
guard !field.contains(where: { "@/[(.".contains($0) }), let targetName = target.name,
case let .complex(targetComplex)? = declaredType(of: targetName),
let fieldType = childElementType(named: field, in: targetComplex.content),
case let .complex(fieldComplex) = fieldType
else { return false }
if case .simpleContent = fieldComplex.content {
return false
}
return true
}
/// The declared `ElementType` of a global element, by namespaced or local key.
private func declaredType(of name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
types[PureXML.Schema.XSDParser.elementDeclarationKey(name)] ?? types[PureXML.Schema.XSDParser.elementKey(name.localName)]
}
/// The declared type of the first element named `local` directly within a content
/// model (descending through model groups), or nil when none matches.
private func childElementType(named local: String, in content: PureXML.Schema.ContentType) -> PureXML.Schema.ElementType? {
switch content {
case let .elementOnly(particle), let .mixed(particle): childElementType(named: local, in: particle)
default: nil
}
}
private func childElementType(named local: String, in particle: PureXML.Schema.Particle) -> PureXML.Schema.ElementType? {
switch particle.term {
case let .element(name, type, _, _, _, _):
name.localName == local ? type : nil
case let .group(group):
group.particles.lazy.compactMap { childElementType(named: local, in: $0) }.first
case .wildcard:
nil
}
}
}