Using the attribute:/wrapped: initializer with both flags left at their defaults produces a value that doesn't survive an encode/decode round trip (seen on main):
let xml = OpenAPI.XML(name: "hello", attribute: false, wrapped: false)
let decoded = try JSONDecoder().decode(OpenAPI.XML.self, from: JSONEncoder().encode(xml))
// decoded != xml
The initializer builds .legacy(attribute: false, wrapped: false). Encoding that writes no keys, and decoding no keys lands on the (false, false, nil) case, which sets structure to nil. Since == compares structure, the decoded value is unequal to the original.
Relatedly, OpenAPI.XML(name: "hello", nodeType: nil) encodes to identical JSON but compares unequal to the value above.
Both fields default to false in the spec, so "omitted" and "present and false" describe the same document. That suggests the legacy initializer should produce nil when neither flag is set. The alternative — having the decoder reconstruct .legacy(false, false) — would just move the asymmetry onto the nodeType: nil spelling instead.
OpenAPIKit30 isn't affected, since it stores attribute/wrapped as plain Bools.
Happy to open a PR for the first approach if that's the direction you'd prefer.
Found with AI assistance (Claude Code), via a generated round-trip property test; I've reviewed the analysis and am happy to discuss it.
Using the
attribute:/wrapped:initializer with both flags left at their defaults produces a value that doesn't survive an encode/decode round trip (seen onmain):The initializer builds
.legacy(attribute: false, wrapped: false). Encoding that writes no keys, and decoding no keys lands on the(false, false, nil)case, which setsstructuretonil. Since==comparesstructure, the decoded value is unequal to the original.Relatedly,
OpenAPI.XML(name: "hello", nodeType: nil)encodes to identical JSON but compares unequal to the value above.Both fields default to
falsein the spec, so "omitted" and "present and false" describe the same document. That suggests the legacy initializer should producenilwhen neither flag is set. The alternative — having the decoder reconstruct.legacy(false, false)— would just move the asymmetry onto thenodeType: nilspelling instead.OpenAPIKit30isn't affected, since it storesattribute/wrappedas plainBools.Happy to open a PR for the first approach if that's the direction you'd prefer.
Found with AI assistance (Claude Code), via a generated round-trip property test; I've reviewed the analysis and am happy to discuss it.