style(schema): satisfy swiftlint on the XSTS conformance additions

The conformance fixes tripped swiftlint --strict (it had been run only as
swiftformat): the test helper binding `s` is too short (now `doc`), the
duration reference dateTimes were a three-member tuple (now (year, month) with
day fixed at 1), and DurationValue.Order nested two levels deep (now the
sibling DurationOrder). No behavior change; full suite and XSTS stay green.
This commit is contained in:
Mihaela Mihaljevic 2026-06-13 17:07:18 +02:00
parent a0ccd017f5
commit f94b2e43e0
5 changed files with 47 additions and 45 deletions

View File

@ -1,4 +1,7 @@
extension PureXML.Schema {
/// The outcome of comparing two durations, which form a partial order.
enum DurationOrder { case lessThan, equal, greaterThan, incomparable }
/// An `xs:duration` as its two independent signed components, months and
/// seconds, with the XSD 1.0 order relation. Because a month is 28 to 31
/// days, the order is partial: P and Q compare by adding each to the four
@ -10,14 +13,13 @@ extension PureXML.Schema {
let months: Int
let seconds: Double
enum Order { case lessThan, equal, greaterThan, incomparable }
/// The four reference dateTimes (year, month, day) at T00:00:00Z.
private static let references: [(year: Int, month: Int, day: Int)] = [
(1696, 9, 1), (1697, 2, 1), (1903, 3, 1), (1903, 7, 1),
/// The four reference points (year, month); each is the first of its
/// month at T00:00:00Z, so the day is always 1 and need not be carried.
private static let references: [(year: Int, month: Int)] = [
(1696, 9), (1697, 2), (1903, 3), (1903, 7),
]
func compare(to other: DurationValue) -> Order {
func compare(to other: DurationValue) -> DurationOrder {
var sawLess = false
var sawGreater = false
var sawEqual = false
@ -40,11 +42,11 @@ extension PureXML.Schema {
return .incomparable
}
/// The instant, in seconds, of `reference` + (months, seconds): add the
/// months with calendar rollover (clamping the day to the resulting
/// month's length, per the spec), then add the seconds component.
/// The instant, in seconds, of `reference` (day 1) + (months, seconds):
/// add the months with calendar rollover (clamping the day to the
/// resulting month's length, per the spec), then add the seconds.
private static func instant(
_ reference: (year: Int, month: Int, day: Int),
_ reference: (year: Int, month: Int),
addingMonths addMonths: Int,
seconds: Double,
) -> Double {
@ -52,7 +54,7 @@ extension PureXML.Schema {
let yearsCarried = Int((Double(total) / 12.0).rounded(.down))
let newYear = reference.year + yearsCarried
let newMonth = total - yearsCarried * 12 + 1
let newDay = Swift.min(reference.day, DateTimeValue.daysInMonth(year: newYear, month: newMonth))
let newDay = Swift.min(1, DateTimeValue.daysInMonth(year: newYear, month: newMonth))
let days = DateTimeValue.daysFromCivil(year: newYear, month: newMonth, day: newDay)
return Double(days) * 86400 + seconds
}

View File

@ -266,7 +266,7 @@ public extension PureXML.Schema {
/// the ordered range), so an incomparable result is a violation.
private func durationRangeError(_ value: String) -> String? {
guard let actual = DurationValue(value) else { return nil }
func order(_ bound: String?) -> DurationValue.Order? {
func order(_ bound: String?) -> DurationOrder? {
bound.flatMap(DurationValue.init).map(actual.compare(to:))
}
if let order = order(facets.minInclusive), order == .lessThan || order == .incomparable {

View File

@ -21,25 +21,25 @@ struct SchemaDurationFacetTests {
@Test("minInclusive enforces the lower bound")
func test_minInclusive() throws {
let s = try schema(#"<xs:minInclusive value="P1Y"/>"#)
#expect(try s.validate("<v>P1Y</v>").isEmpty) // equal
#expect(try s.validate("<v>P2Y</v>").isEmpty) // greater
#expect(try !s.validate("<v>P6M</v>").isEmpty) // less
let doc = try schema(#"<xs:minInclusive value="P1Y"/>"#)
#expect(try doc.validate("<v>P1Y</v>").isEmpty) // equal
#expect(try doc.validate("<v>P2Y</v>").isEmpty) // greater
#expect(try !doc.validate("<v>P6M</v>").isEmpty) // less
}
@Test("minExclusive rejects the bound itself")
func test_minExclusive() throws {
let s = try schema(#"<xs:minExclusive value="P1Y"/>"#)
#expect(try !s.validate("<v>P1Y</v>").isEmpty) // equal: rejected
#expect(try s.validate("<v>P2Y</v>").isEmpty) // greater
let doc = try schema(#"<xs:minExclusive value="P1Y"/>"#)
#expect(try !doc.validate("<v>P1Y</v>").isEmpty) // equal: rejected
#expect(try doc.validate("<v>P2Y</v>").isEmpty) // greater
}
@Test("maxInclusive enforces the upper bound and rejects incomparable values")
func test_maxInclusivePartialOrder() throws {
let s = try schema(#"<xs:maxInclusive value="P30D"/>"#)
#expect(try s.validate("<v>P20D</v>").isEmpty) // less
#expect(try !s.validate("<v>P40D</v>").isEmpty) // greater
let doc = try schema(#"<xs:maxInclusive value="P30D"/>"#)
#expect(try doc.validate("<v>P20D</v>").isEmpty) // less
#expect(try !doc.validate("<v>P40D</v>").isEmpty) // greater
// P1M is incomparable to P30D (28..31 days) so it is not <= P30D.
#expect(try !s.validate("<v>P1M</v>").isEmpty)
#expect(try !doc.validate("<v>P1M</v>").isEmpty)
}
}

View File

@ -21,18 +21,18 @@ struct SchemaListFacetTests {
@Test("length on NMTOKENS counts items, not characters")
func test_nmtokensLength() throws {
let s = try schema("xs:NMTOKENS", #"<xs:length value="3"/>"#)
#expect(try s.validate("<v>alpha beta gamma</v>").isEmpty) // 3 items: valid
#expect(try !s.validate("<v>alpha beta</v>").isEmpty) // 2 items: invalid
#expect(try !s.validate("<v>alpha beta gamma delta</v>").isEmpty) // 4 items: invalid
let doc = try schema("xs:NMTOKENS", #"<xs:length value="3"/>"#)
#expect(try doc.validate("<v>alpha beta gamma</v>").isEmpty) // 3 items: valid
#expect(try !doc.validate("<v>alpha beta</v>").isEmpty) // 2 items: invalid
#expect(try !doc.validate("<v>alpha beta gamma delta</v>").isEmpty) // 4 items: invalid
}
@Test("minLength and maxLength on IDREFS count items")
func test_idrefsMinMax() throws {
let s = try schema("xs:IDREFS", #"<xs:minLength value="2"/><xs:maxLength value="3"/>"#)
#expect(try s.validate("<v>a b</v>").isEmpty) // 2 items: valid
#expect(try s.validate("<v>a b c</v>").isEmpty) // 3 items: valid
#expect(try !s.validate("<v>a</v>").isEmpty) // 1 item: invalid
#expect(try !s.validate("<v>a b c d</v>").isEmpty) // 4 items: invalid
let doc = try schema("xs:IDREFS", #"<xs:minLength value="2"/><xs:maxLength value="3"/>"#)
#expect(try doc.validate("<v>a b</v>").isEmpty) // 2 items: valid
#expect(try doc.validate("<v>a b c</v>").isEmpty) // 3 items: valid
#expect(try !doc.validate("<v>a</v>").isEmpty) // 1 item: invalid
#expect(try !doc.validate("<v>a b c d</v>").isEmpty) // 4 items: invalid
}
}

View File

@ -23,31 +23,31 @@ struct SchemaUnionFacetTests {
@Test("Enumeration on a union restricts to the listed values")
func test_enumeration() throws {
let s = try schema(#"<xs:enumeration value="5"/><xs:enumeration value="foo"/>"#)
#expect(try s.validate("<v>5</v>").isEmpty) // listed
#expect(try s.validate("<v>foo</v>").isEmpty) // listed
#expect(try !s.validate("<v>7</v>").isEmpty) // valid int, not listed
#expect(try !s.validate("<v>bar</v>").isEmpty) // valid string, not listed
let doc = try schema(#"<xs:enumeration value="5"/><xs:enumeration value="foo"/>"#)
#expect(try doc.validate("<v>5</v>").isEmpty) // listed
#expect(try doc.validate("<v>foo</v>").isEmpty) // listed
#expect(try !doc.validate("<v>7</v>").isEmpty) // valid int, not listed
#expect(try !doc.validate("<v>bar</v>").isEmpty) // valid string, not listed
}
@Test("Union enumeration compares in value space, not lexically")
func test_enumerationValueSpace() throws {
let s = try schema(#"<xs:enumeration value="5"/>"#)
let doc = try schema(#"<xs:enumeration value="5"/>"#)
// 05 is a valid xs:int equal to the enumerated 5, so it is in the set.
#expect(try s.validate("<v>05</v>").isEmpty)
#expect(try doc.validate("<v>05</v>").isEmpty)
}
@Test("Pattern on a union constrains the lexical value")
func test_pattern() throws {
let s = try schema(#"<xs:pattern value="[a-z]+"/>"#)
#expect(try s.validate("<v>abc</v>").isEmpty) // valid string, matches
#expect(try !s.validate("<v>5</v>").isEmpty) // valid int, fails pattern
let doc = try schema(#"<xs:pattern value="[a-z]+"/>"#)
#expect(try doc.validate("<v>abc</v>").isEmpty) // valid string, matches
#expect(try !doc.validate("<v>5</v>").isEmpty) // valid int, fails pattern
}
@Test("A union with no facets still admits any member value")
func test_noFacets() throws {
let s = try schema("")
#expect(try s.validate("<v>42</v>").isEmpty)
#expect(try s.validate("<v>anything</v>").isEmpty)
let doc = try schema("")
#expect(try doc.validate("<v>42</v>").isEmpty)
#expect(try doc.validate("<v>anything</v>").isEmpty)
}
}