openehr
openEHR Reference Model types, validation, paths, AQL parsing, and change-control security primitives — in Rust.
openEHR specifies clinical information as a small, stable Reference Model of about ninety classes, plus archetypes that constrain it into clinical content. This crate implements the Reference Model and the machinery around it, so a Rust program can read, build, check, address, and safely disclose openEHR data without inventing its own idea of what a health record is.
[]
= "0.2"
Install
[]
= "0.2"
Requires Rust 1.90+ (edition 2024).
What it does
use Pathable;
use Composition;
use Validate;
// Read a composition another openEHR implementation wrote.
let composition: Composition = from_str?;
// Check the Reference Model invariants. Deserialization never calls a
// constructor, so this is the only gate on data that arrived from elsewhere.
composition.validate_ok?;
// Address a node by openEHR path.
let systolic = composition.item_at_path?;
| Module | openEHR component |
|---|---|
base |
BASE: identifiers, references, intervals, ISO 8601 |
rm::data_types |
RM: Data Types — every DV_* class |
rm::data_structures |
RM: Data Structures — ITEM_*, CLUSTER, ELEMENT, HISTORY |
rm::common |
RM: Common — archetyping, parties, audit, change control |
rm::ehr |
RM: EHR — COMPOSITION, the five entry classes, EHR_STATUS, FOLDER |
rm::demographic |
RM: Demographic — PERSON, ROLE, ORGANISATION, AGENT |
terminology |
TERM: the openEHR support terminology, sixteen groups |
path |
openEHR path parsing and navigation |
aql |
QUERY: AQL lexing, parsing, and static checking |
validation |
Reference Model invariant checking |
security |
EHR_ACCESS, tamper-evident audit chaining, redaction |
Everything serializes to and from openEHR canonical JSON (ITS-JSON).
What it does not do
Stating this plainly is part of the design. A clinical library that implies coverage it does not have is worse than a small one.
| Not implemented | Why |
|---|---|
| Archetypes and templates (AM, ADL, AOM2) | a parser and a constraint engine, each larger than this crate |
| AQL execution | needs a repository; aql parses and checks, and returns no rows |
| Terminology lookup beyond openEHR's own | needs a terminology server; external codes are carried opaquely |
| UCUM unit conversion | a wrong conversion is a thousand-fold dosing error |
| REST service, persistence, EHR Extract | out of scope — see spec/01-scope.md |
HL7 GTS / PIVL timing evaluation |
returns Unsupported rather than a guess |
OpenPGP verification, encryption |
key management belongs to the deployment |
Where openEHR defines an operation this crate does not implement, the operation
returns an explicit Unsupported error naming the specification section that
records the exclusion. It never returns a plausible default.
Three design commitments
Refuse rather than guess. Comparison is partial throughout. A
month-precision date is not ordered against a day inside that month; 5 mg is
not comparable with 5 mL; a path matching three elements fails rather than
returning the first. Each has a plausible wrong answer that no downstream reader
could detect.
let may: Date = "2024-05".parse?;
let may_17: Date = "2024-05-17".parse?;
assert_eq!; // May which day?
let mg = new?;
let ml = new?;
assert_eq!; // not the same dose of anything
Absence is structured. openEHR's four null flavours are four different clinical facts, and this crate will not let them collapse:
| Flavour | Means |
|---|---|
271|no information| |
nobody looked |
253|unknown| |
somebody looked and could not find out |
272|masked| |
the value exists and is withheld |
273|not applicable| |
the question does not arise |
"No allergy history recorded" is the first and "no known allergies" is the fourth. Prescribing software that treats them alike will eventually give a penicillin-allergic patient penicillin.
Nothing prints protected health information. No Display renders an
identifier or a media blob; no error echoes a submitted value; a validation
report names paths and invariants and never content; redaction masks rather than
deletes, and reports how much it withheld rather than what.
Two gates, not one
Constructors enforce invariants on data the program builds. validation
enforces them on data the program receives — serde writes fields directly
and never calls a constructor. A service that deserializes and stores without
validating has no invariant checking at all, whatever its constructors do.
// No constructor in this crate produces this. A sender can still send it.
let element: Element = from_str?;
assert_eq!;
Security
security supplies what a library can supply, and says what it cannot.
EHR_ACCESSwith a default-deny decision, a documented reference scheme, and lossless carriage of schemes it cannot evaluate. Dispatch is by declared scheme name and never by object shape, so a foreign policy is never reinterpreted as an empty local one.- A tamper-evident chain over committed versions, unkeyed or with an
HMAC-SHA-256tag. The documentation states plainly what an unkeyed chain buys — it detects careless modification and supports an external witness, and it does not stop an informed attacker with write access. Only a tag mismatch is a tampering finding; an unheld key is reported as an unheld key. - Redaction that masks as
272|masked|, keeps the document valid, and counts rather than names what it withheld.
What the deployment must still provide: authentication, group membership,
transport security, key storage, consent capture, and log retention. See
spec/11-security.md for the whole boundary.
Examples
Specification-driven
spec/ is normative. Every requirement has a permanent
identifier cited from the code, the tests, and the documentation, so a claim
about this crate is traceable back to a decision.
| Read | For |
|---|---|
spec/index.md |
the map, and what this spec adds to openEHR's |
spec/01-scope.md |
what is excluded and why |
spec/conformance-matrix.md |
what is verified today |
spec/audit.md |
every known gap, with evidence |
Two numbers from those files, because they are the ones worth knowing before
depending on this crate: of 291 requirements, 237 are verified by a named
test and 3 are implemented with no test at all. A further 13 are marked
type — enforced by the compiler, where a runtime test could not fail — rather
than counted as verified, so the first number means what it says.
Status
Version 0.1.0. First release. The Reference Model surface is complete for the
packages listed above and the open findings are in
spec/audit.md — twelve of them, seven already fixed, none a
false claim in the documentation.
Every code fragment above is compiled and run as a test
(tests/readme.rs). A documented example that does not compile is worse than
none, because it costs the reader the time to find out.
Building
MSRV is rust-version in Cargo.toml (currently 1.90), Rust edition 2024.
Licence
Any of these, at your option — MIT, Apache-2.0, BSD-3-Clause, GPL-2.0-only, or
GPL-3.0-only. See LICENSE.md.
openEHR specifications are published by the openEHR Foundation under CC-BY-SA; this crate is an independent implementation and is not endorsed by or affiliated with the openEHR Foundation.