fhir-types
Per-version FHIR types and terminology operation contracts.
Generated Rust types for the FHIR STU3 (3.0.2), R4 (4.0.1), R4B (4.3.0), R5
(5.0.0), and R6 (6.0.0-ballot5) terminology surface: CodeSystem, ValueSet,
ConceptMap, Parameters, OperationOutcome, CapabilityStatement,
TerminologyCapabilities (R4 and later; STU3 defines none), Bundle, the
datatypes and primitives they reach,
a JSON codec, and typed request and response contracts for $lookup,
$validate-code, $expand, $subsumes, and $translate per version, each
with a runtime descriptor of the declared parameter set. The resources
feature widens the same tree to every concrete resource each package defines,
with the complete closure of the datatypes those resources reference.
Features
One tree holds every type the generator emits, and the features select inside it. The default set is what a dependant gets with no feature list.
| Feature | What it enables | Default |
|---|---|---|
stu3 |
the FHIR STU3 (3.0.2) module | yes |
r4 |
the FHIR R4 (4.0.1) module | yes |
r4b |
the FHIR R4B (4.3.0) module | yes |
r5 |
the FHIR R5 (5.0.0) module | yes |
r6 |
the FHIR R6 (6.0.0-ballot5) module | yes |
terminology |
the terminology root set, the closure of the types it references, and the operation contracts | yes |
resources |
every concrete resource each package defines, with its complete closure; enables terminology |
no |
A version module carries only the types the enabled root set selects, so
default-features = false with r4 and terminology builds the R4
terminology surface alone. Resource holds one variant per resource the
enabled features select, and UnknownResource carries any other resource's
JSON body unchanged.
Every file is generated by fhir-codegen
from the vendored HL7 packages and marked // @generated. To change the
output, change the generator and regenerate; the crate is never hand-edited.
The terminology ecosystem overlay (the HL7 terminology ecosystem IG requirements)
is applied at generation to R4, R4B and R5 and marked per parameter; the STU3
contracts carry exactly the parameters 3.0.2 declares.
The element table
Each version module carries schema::SCHEMAS, the elements of every type it
emits in definition order, over the table types in the crate-level
fhir_types::schema module. Per element it holds the ElementDefinition path,
min, max, the type codes the definition lists, the contentReference
target, the isSummary and isModifier flags, and the kind the XML codec
reads. Schemas::element resolves a dotted
element path through complex types, backbone elements and content references,
and answers on a choice element's base path and on each expanded form the JSON
representation gives it; Schemas::type_of gives the type a path resolves to.
use SCHEMAS;
let = SCHEMAS
.element
.expect;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
A type the enabled features leave out is absent from the table, so a path into
it resolves to None.
The table also carries Element (https://hl7.org/fhir/R4/element.html) with
its id and extension, although no emitted field targets it. FHIR JSON
writes a primitive's id and extension in a sibling member named with a
leading underscore (_birthDate, https://hl7.org/fhir/R4/json.html), and a
consumer walking a path resolves that member's contents against this entry. It
is a table-only entry: the crate emits no Element Rust type, because an
element whose type is Element becomes its own nested struct.
Primitive lexical forms
A primitive value the FHIR JSON representation carries as a string is held to
the lexical form of its type. The form is the regex extension the version's
own package puts on the primitive's value element, anchored to the whole
value and compiled once per primitive
(https://hl7.org/fhir/R5/datatypes.html#primitive). A value outside it is a
DecodeError carrying DecodeErrorKind::BadValue and the element path, so
"date": "yesterday" is refused at CodeSystem.date rather than stored and
read defensively later.
use ;
let document = ?;
let mut path = root;
let object = expect_object?;
let error = from_json
.expect_err;
assert_eq!;
assert_eq!;
# Ok::
Each form is an XML Schema pattern, so \s is the space, the tab, the carriage
return and the line feed alone (https://www.w3.org/TR/xmlschema-2/#regexs) and
a value carrying a non-breaking space, which the HL7 packages themselves
publish inside a display and a code, is not refused for it.
The forms differ between versions and are read per version, never copied:
3.0.2, 4.0.1, 4.3.0 and 6.0.0-ballot5 require a timezone offset once a
dateTime carries a time, and 5.0.0 makes it optional. 3.0.2 states its forms
in the structuredefinition-regex extension and publishes none for
base64Binary, markdown, string or uri, so the STU3 module holds those
to no form. A primitive carried as a JSON number or a boolean (integer, decimal, positiveInt, unsignedInt, boolean) is
checked by the parser of that scalar instead, which is what keeps a decimal's
lexical form intact.
JSON and decimal precision
The codec reads and writes FHIR JSON through its own document model,
fhir_types::codec::Value, which holds a number in the text the document
carried. FHIR says "Do not use an IEEE type floating point type, instead use
something that works like a true decimal"
(https://hl7.org/fhir/R4B/datatypes.html#decimal), so 1.10 keeps its
trailing zero and a decimal past f64 precision keeps its digits, through the
Serialize impls and through Json::to_json alike.
That costs a dependent nothing. The crate turns on no serde_json feature that
changes how numbers deserialize, so your own types keep reading and writing the
JSON they always did. Reading a document uses serde_json's raw value, so the
Deserialize impls read serde_json's format.
Converting to and from serde_json::Value
A decimal is never silently altered, so the conversion out is fallible.
Value::into_serde_json and Value::to_serde_json hand the document to
serde_json only when every number comes back in the text the document
carried; a number serde_json would write differently, or cannot hold at all,
is a ValueConversionError naming the element path
(Observation.valueQuantity.value), the lexical form and what serde_json
writes. With arbitrary_precision on your side every form converts; without
it, whole numbers and the decimals f64 writes back convert and 1.50 is
refused.
Value::from_serde_json is the other direction and cannot fail. It takes the
number in the text serde_json holds, which is the document's own form under
arbitrary_precision and the f64 rendering of it otherwise, so read a
document whose decimals must stay exact through this crate's decoder
(serde_json::from_str::<fhir_types::codec::Value>) rather than through
serde_json::Value.
Where it sits
fhir-types is generated in FerroBRIDGE,
a pure-Rust bridge between openEHR and HL7 FHIR and the OMOP Common Data Model.
The crate is published so other projects can reuse it; the API is pre-1.0 and
moves with the FerroBRIDGE release train. Documentation:
https://docs.rs/fhir-types.
Licence
Apache License, Version 2.0 (LICENSE). This is the one first-party crate the
workspace publishes under Apache 2.0 rather than its Business Source License
1.1, so any Rust project can depend on it without a licence conversation. Clinical terminology content (SNOMED
CT, LOINC, RxNorm, ICD, the Dutch national code systems) is licensed by its
publisher and is never part of this crate.