05 — Code systems
Defines the type-safe code-system enums in each release's codes module, and
how a coded field is typed.
Applies to every modelled release.
| Release | Code-system enums |
|---|---|
| R6 | 459 |
| R5 | 442 |
| R4 | 486 |
| R3 | 386 |
| R2 | 265 |
The counts do not track release age: they follow how many complete code
systems each release's valuesets.json happens to publish. R5 moved several to
external terminologies that no FHIR element binds to with required strength.
Background#
FHIR code values are drawn from CodeSystems. Many are small, closed
enumerations (administrative-gender → male | female | other | unknown).
Representing those as Rust enums gives compile-time safety and exhaustive
matching.
Generating the enums#
- R5.1 For every
CodeSystemin the release'svaluesets.jsonwhosecontentiscompleteand which has at least one concept,codesMUST provide a Rustenumwhose variants are its codes. A system that is notcompletedoes not list all its codes, so a closed Rust enum would reject valid data; those staytypes::Code. - R5.2 Each enum MUST derive
Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default; the first concept is the#[default]variant, so thatCoded<E>and every struct holding one can deriveDefault. - R5.3 Each variant MUST serialize to its exact FHIR code string via
#[serde(rename = "<code>")], soto_value/from_valueuse the canonical codes, not the Rust identifiers. - R5.4 Variant identifiers MUST be sanitized deterministically:
- PascalCase of the code, with non-alphanumeric characters treated as word separators.
- A leading digit is prefixed with
N. Self, which is reserved even in type position, becomesSelfCode.- Collisions within an enum are de-duplicated with a numeric suffix, in specification order.
- R5.5 An enum's name MUST be derived from the last segment of the code
system's URL, sanitized as in R5.4 — not from its
name— because that is what a value-set binding refers to. A URL need not be tidy (urn:iso-astm:E1762-95:2013is real), so the same sanitizing applies. The first system to claim a name keeps it. - R5.6
codesMUST be generated deterministically (same input → identical file) and MUST carry doc comments: the system's description, its URL wrapped in<…>so rustdoc treats it as a link, and each concept's display as the variant's doc.
Typing coded fields#
R5.7 An element with a
requiredbinding whose value set maps to a generated enum MUST be typed as that enum rather than the opaquetypes::Code, so the compiler enforces the value set. The enum is resolved from the value set URL's last segment (ignoring any|versionsuffix) by the same rule as R5.5, so binding and enum always agree. The releases spell the bound value set differently —valueSetin R4/R5,valueSetReferenceorvalueSetUriin R3 — and all three MUST resolve identically (spec 12, R12.17).R5.8 A coded field MUST NOT be a bare enum. A closed enum would reject any code outside the value set, which is unacceptable for a data-exchange library: real data carries newer codes, local extensions, and simply invalid values, and reading MUST NOT fail. The field is wrapped instead:
pub enum Coded<E> { // fhir::coded Known(E), // a recognized code from the value set Unknown(String), // any other code, preserved verbatim }Coded<E>is#[serde(untagged)]: deserialization triesEfirst and falls back toUnknown; serialization emits the code string either way. So a required-binding field isOption<Coded<AdministrativeGender>>, orCoded<…>for1..1.R5.9
Coded<E>is generic over the enum and therefore release-independent. It MUST be defined once (fhir::coded) and re-exported by each release, not duplicated.R5.10 A
Coded::Unknownvalue is by definition outside its required value set, soValidateMUST report it (spec 07).
Why the wrapper rather than an Other(String) variant#
Adding Other(String) to all 900+ generated enums would put the fallback in
every enum's match arms, defeat exhaustive matching, and have to be re-derived
on every regeneration. One wrapper localizes the policy, leaves codes a pure
translation of the specification, and makes the retype round-trip-safe even
where an enum mapping turns out to be wrong: the value simply lands in
Unknown.
Future work#
- Expose value-set membership so
Validatecan checkextensibleandpreferredbindings, not justrequiredones. - Provide
FromStr/Displayconveniences alongside serde.
Acceptance criteria#
codescontains an enum for every completeCodeSystemits release publishes with at least one concept.AdministrativeGender::Femaleserializes to"female";"male"deserializes toAdministrativeGender::Male.- A code outside a required value set round-trips unchanged through
Coded::Unknownand is reported byValidate. - Generation is deterministic and the module compiles with zero clippy warnings.