Design¶
The full design document lives in the repository as
DESIGN.md. It
reviews the XSD format and 17 implementations across 8 languages, and lays
out the staged plan this crate follows. What is below is the short version of
the decisions that shaped the API you see.
Why the component model is the product¶
Covered in The component model: the specification defines
every validation rule against schema components, so a library that exposes only
validate() has built the interesting layer and then hidden it. The survey
found this repeatedly — xsd-parser builds a codegen-shaped intermediate that
discards validation semantics; uppsala builds a real model internally and
exposes only a verdict. Neither can answer "what may go inside this element,
and can it repeat?"
Two types, not one¶
SchemaSetBuilder reads; Schemas is the result. They are separate types so
that a Schemas cannot exist in an unresolved state — "did you remember to
call Compile()?" is not a question you can be asked here, because it is not
representable.
Ids, not references¶
The component graph is cyclic, so references would mean Rc<RefCell<…>>
throughout, which would cost Send + Sync and make every access a runtime
borrow check. Components live in arenas addressed by Copy u32 ids, and
Index is implemented for each so lookups read naturally.
The cost is that an unresolved reference is representable during loading. It is
paid for with a placeholder sentinel that every Index implementation asserts
against in debug builds, and a compile-time pass that patches every id-bearing
field before a Schemas is handed out.
Every error, not the first¶
Discussed in Diagnostics. The decision costs a Diagnostics
collection threaded through the loader, and it is worth it.
Glushkov automata for content models¶
Content models compile to position automata. Unique Particle Attribution
checking then falls out of the same structure instead of being a separate
analysis, and possible_children / child_repeats / child_is_optional are
answered from the automaton rather than by interpreting the particle tree at
every call. xs:all is handled with per-member counters rather than expanding
to n! interleavings.
Datatypes implemented, not delegated¶
All 14 non-trivial XSD datatypes are implemented in src/atomic.rs — the
civil-calendar arithmetic, the ±14-hour timezone partial order, duration
comparison against the four reference dateTimes the specification names. The
reasoning is in DESIGN.md §3.12.4.
Facts, not interpretation¶
Schema families encode conventions the standard never defined — in appinfo,
in attribute names, in type-naming schemes. xsdkit exposes the facts and
stops: attribute uses folded down the derivation chain, fixed and default
values, enumeration facets, appinfo kept verbatim, and schema-supplied
values flagged in the PSVI.
Building the interpretation on top is a handful of lines for someone who knows the convention. A built-in heuristic would be right most of the time and silently wrong the rest — and a silently wrong answer is worse than none, because nothing downstream can tell.
What is deliberately out of scope¶
Code generation. That is xsd-parser's
job, and it does it well. Permanently out of scope.
Config and binding generation for a particular downstream reader. That
belongs in a library of its own, so reading a schema never pulls in
dependencies you did not ask for. Three seams in this crate exist so such a
consumer can be written against it — Annotation::appinfo verbatim, the
possible_children / child_repeats / child_is_optional trio, and
ContentMatcher — and are not to be removed.
Roadmap¶
| ✅ | Component model, loading, composition | done |
| ✅ | Content automata, UPA | done |
| ✅ | Python bindings, type stubs, encoding detection | done |
| ✅ | Instance validation, typed reading (PSVI) | done |
| ✅ | redefine / override |
done |
| ✅ | XSD 1.1 open content, default attributes, relaxed UPA | done |
| ✅ | Identity constraints, xs:ID / xs:IDREF |
done |
| → | XSD 1.1 assertions and conditional type assignment | next |