PureXML/Tests/SchemaIdentityFieldTypeTests.swift
Mihaela Mihaljevic 74b9332c48 fix(schema): reject identity field whose declared target is a non-simple complex type
cvc-identity-constraint.3 / c-fields-xpaths require an identity-constraint field to
identify a node with a simple type. The prior fix rejected a field whose selected
instance node carries element children (structural). This adds the case where the
field's DECLARED type is a complex type with non-simple content (empty or
element-only/mixed): an attributes-only element resolves to an empty-content complex
type, so its instance node is childless yet not simple-typed.

The field's target type is resolved through the schema type model (the selector
target's declared type, then the field's same-named child element in its content
model) and classified. A simple or simpleContent target, a type reached only
through a named reference, a complex field path, or an unresolvable target is left
alone, so no valid field is rejected. The resolution helpers live in a separate
extension file to keep the validator within the type-body limit.

This catches W3C XSTS idG006. XSTS invalid-instances-accepted 23 -> 22,
valid-instances-rejected held at 0.
2026-06-22 13:10:30 +02:00

76 lines
3.9 KiB
Swift

import Testing
@testable import PureXML
/// cvc-identity-constraint.3 / c-fields-xpaths: an identity-constraint field must
/// identify a node with a simple type (a simple-typed element, an element with simple
/// content, or an attribute). A field whose target is a complex type with non-simple
/// content is invalid, whether the instance node has element children (idK012) or is
/// empty/attributes-only (idG006). A simple or simpleContent target stays valid.
@Suite("XSD identity-constraint field simple-content")
struct SchemaIdentityFieldTypeTests {
private func validate(_ xsd: String, _ xml: String) throws -> [PureXML.Validation.ValidationError] {
try PureXML.Schema.Document(xsd).validate(xml)
}
private let head = "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
@Test("a field selecting a complex-content element is rejected (idK012)")
func test_fieldWithElementChildrenRejected() throws {
let complexField = """
\(head)
<xs:element name="root">
<xs:complexType><xs:sequence>
<xs:element name="uid" maxOccurs="unbounded"><xs:complexType><xs:sequence>
<xs:element name="pid"><xs:complexType><xs:sequence><xs:element name="gid"/></xs:sequence></xs:complexType></xs:element>
</xs:sequence></xs:complexType></xs:element>
</xs:sequence></xs:complexType>
<xs:key name="k"><xs:selector xpath=".//uid"/><xs:field xpath="pid"/></xs:key>
</xs:element>
</xs:schema>
"""
#expect(try !validate(complexField, "<root><uid><pid><gid>x</gid></pid></uid></root>").isEmpty)
let simpleField = """
\(head)
<xs:element name="root">
<xs:complexType><xs:sequence>
<xs:element name="uid" maxOccurs="unbounded"><xs:complexType><xs:sequence>
<xs:element name="pid" type="xs:string"/>
</xs:sequence></xs:complexType></xs:element>
</xs:sequence></xs:complexType>
<xs:key name="k"><xs:selector xpath=".//uid"/><xs:field xpath="pid"/></xs:key>
</xs:element>
</xs:schema>
"""
#expect(try validate(simpleField, "<root><uid><pid>a</pid></uid><uid><pid>b</pid></uid></root>").isEmpty)
}
@Test("a field whose declared type is an empty/attributes-only complex type is rejected (idG006)")
func test_emptyComplexFieldRejected() throws {
let emptyComplex = """
\(head)
<xs:element name="root">
<xs:complexType><xs:sequence><xs:element ref="uid" maxOccurs="unbounded"/></xs:sequence></xs:complexType>
<xs:key name="uuid"><xs:selector xpath=".//uid"/><xs:field xpath="pid"/></xs:key>
</xs:element>
<xs:element name="uid"><xs:complexType><xs:sequence>
<xs:element name="pid"><xs:complexType><xs:attribute name="p" type="xs:string"/></xs:complexType></xs:element>
</xs:sequence><xs:attribute name="val" type="xs:string"/></xs:complexType></xs:element>
</xs:schema>
"""
#expect(try !validate(emptyComplex, "<root><uid val=\"1\"><pid p=\"11\"/></uid></root>").isEmpty)
let simpleContent = """
\(head)
<xs:element name="root">
<xs:complexType><xs:sequence><xs:element ref="uid" maxOccurs="unbounded"/></xs:sequence></xs:complexType>
<xs:key name="uuid"><xs:selector xpath=".//uid"/><xs:field xpath="pid"/></xs:key>
</xs:element>
<xs:element name="uid"><xs:complexType><xs:sequence>
<xs:element name="pid"><xs:complexType><xs:simpleContent><xs:extension base="xs:string">
<xs:attribute name="p" type="xs:string"/></xs:extension></xs:simpleContent></xs:complexType></xs:element>
</xs:sequence></xs:complexType></xs:element>
</xs:schema>
"""
#expect(try validate(simpleContent, "<root><uid><pid p=\"1\">a</pid></uid></root>").isEmpty)
}
}