import Testing @testable import PureXML @Suite("XSD complex types") struct XSDComplexTypeTests { private typealias Schema = PureXML.Schema private func element(_ name: String, type: Schema.ElementType? = nil, min: Int = 1, max: Int? = 1) -> Schema.Particle { Schema.Particle(minOccurs: min, maxOccurs: max, term: .element(name: .init(name), type: type, typeName: nil)) } private func parse(_ xml: String) throws -> PureXML.Model.Element { guard case let .document(children) = try PureXML.parse(xml), let root = children.compactMap(\.element).first else { throw TestFailure.noRoot } return root } private enum TestFailure: Error { case noRoot } private func validate(_ xml: String, _ type: Schema.ComplexType) throws -> [PureXML.Validation.ValidationError] { try Schema.ComplexValidator().validate(parse(xml), against: type) } // MARK: Sequence and occurrence @Test("A sequence accepts the declared order and occurrence") func test_sequence() throws { let type = Schema.ComplexType(content: .elementOnly( Schema.Particle(term: .group(.init(compositor: .sequence, particles: [ element("title"), element("author", min: 1, max: nil), ]))), )) #expect(try validate("TAB", type).isEmpty) #expect(try !validate("AT", type).isEmpty) #expect(try !validate("T", type).isEmpty) } @Test("A choice accepts exactly one alternative") func test_choice() throws { let type = Schema.ComplexType(content: .elementOnly( Schema.Particle(term: .group(.init(compositor: .choice, particles: [element("a"), element("b")]))), )) #expect(try validate("", type).isEmpty) #expect(try validate("", type).isEmpty) #expect(try !validate("", type).isEmpty) } @Test("An all group accepts any order, each member once") func test_all() throws { let type = Schema.ComplexType(content: .elementOnly( Schema.Particle(term: .group(.init(compositor: .all, particles: [ element("x"), element("y", min: 0, max: 1), ]))), )) #expect(try validate("", type).isEmpty) #expect(try validate("", type).isEmpty) #expect(try !validate("", type).isEmpty) #expect(try !validate("", type).isEmpty) } // MARK: Attributes @Test("Required and typed attributes are enforced") func test_attributes() throws { let type = Schema.ComplexType( attributes: [ .init(name: .init("id"), type: .init(base: .int), required: true), .init(name: .init("lang"), type: .init(base: .language)), ], content: .empty, ) #expect(try validate("", type).isEmpty) #expect(try !validate("", type).isEmpty) // missing required id #expect(try !validate("", type).isEmpty) // id not an int #expect(try !validate("", type).isEmpty) // undeclared attribute } // MARK: Simple and mixed content @Test("Simple content validates the text against a simple type") func test_simpleContent() throws { let type = Schema.ComplexType(content: .simpleContent(.init(base: .int))) #expect(try validate("42", type).isEmpty) #expect(try !validate("x", type).isEmpty) #expect(try !validate("", type).isEmpty) } @Test("Element-only content rejects stray text; mixed allows it") func test_elementOnlyVsMixed() throws { let particle = Schema.Particle(term: .group(.init(compositor: .sequence, particles: [element("a")]))) let elementOnly = Schema.ComplexType(content: .elementOnly(particle)) let mixed = Schema.ComplexType(content: .mixed(particle)) #expect(try !validate("text", elementOnly).isEmpty) #expect(try validate("text", mixed).isEmpty) } // MARK: Recursion @Test("Child elements are validated against their declared types") func test_recursiveTypes() throws { let count = Schema.ElementType.simple(.init(base: .nonNegativeInteger)) let type = Schema.ComplexType(content: .elementOnly( Schema.Particle(term: .group(.init(compositor: .sequence, particles: [ element("count", type: count), ]))), )) #expect(try validate("3", type).isEmpty) #expect(try !validate("-1", type).isEmpty) } }