PureXML/Tests/SchemaValueConstraintValidityTests.swift
Mihaela Mihaljevic 3e3b937e89 feat(schema): validate built-in-typed default/fixed values
An element/attribute default/fixed value must be a valid value of its type
(Attribute/Element Locally Valid, XSD 1.0). A fixed="abc" on an xs:integer or a
default="1 2" on an xs:int was accepted because the value constraint was never
checked at compile time.

The value is now validated against its type when the type resolves to a
built-in datatype (namespace-aware: only a type in the XSD namespace),
normalising whitespace per the type so a default=" 5 " on an integer stays
valid. A named or inline user type is left to its own rules, so no valid value
constraint is rejected.

XSTS: invalid schemas accepted 638 to 621, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:55:55 +02:00

49 lines
2.1 KiB
Swift

@testable import PureXML
import Testing
/// An element/attribute `default`/`fixed` value must be a valid value of its
/// `type` (Attribute/Element Locally Valid, XSD 1.0), checked at compile time.
/// Only a built-in type is checked (a named/inline user type is left to its own
/// rules), covering the attO family (`fixed="abc"` on `xs:integer`).
@Suite("Default/fixed value validity (compile time)")
struct SchemaValueConstraintValidityTests {
private func compile(_ body: String) throws {
_ = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
\(body)
</xs:schema>
""")
}
private func rejects(_ body: String) -> Bool {
do { try compile(body)
return false
} catch { return true }
}
@Test("a built-in-typed default/fixed value outside the type's value space is rejected")
func test_invalidValues() {
#expect(rejects(#"<xs:attribute name="a" type="xs:integer" fixed="abc"/>"#))
#expect(rejects(#"<xs:attribute name="a" type="xs:int" default="abc"/>"#))
#expect(rejects(#"<xs:element name="e" type="xs:int" default="1 2"/>"#))
#expect(rejects(#"<xs:element name="e" type="xs:boolean" fixed="maybe"/>"#))
#expect(rejects(#"<xs:attribute name="a" type="xs:date" default="not-a-date"/>"#))
}
@Test("a valid built-in-typed default/fixed value compiles, with whitespace normalized")
func test_validValues() throws {
try compile(#"<xs:attribute name="a" type="xs:integer" fixed="42"/>"#)
try compile(#"<xs:element name="e" type="xs:int" default=" 5 "/>"#)
try compile(#"<xs:attribute name="a" type="xs:boolean" default="true"/>"#)
try compile(#"<xs:element name="e" type="xs:string" fixed="anything goes"/>"#)
}
@Test("a default/fixed value on a non-built-in type is left alone")
func test_userTypeUnchecked() throws {
try compile(#"""
<xs:simpleType name="t"><xs:restriction base="xs:string"/></xs:simpleType>
<xs:attribute name="a" type="t" fixed="anything"/>
"""#)
}
}