Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust crates

FerroEHR’s openEHR specification layer is published on crates.io as eight standalone Rust crates, usable without the CDR. They are the same crates the server itself is built on: the spec types are generated deterministically from the official openEHR machine-readable artifacts (BMM/XSD/OpenAPI), and the engines (ADL, AQL, serialization runtimes) are hand-written against the vendored specification text.

CrateImplementsWhat it gives you
openehr-baseBASE 1.2.0 + 1.3.0Foundation + base types (identification, intervals, ISO-8601 types)
openehr-rmRM 1.1.0 + 1.2.0The Reference Model: COMPOSITION, data structures/types, change control, generated invariant validation, the static RM attribute model
openehr-amAM 1.4.0 + 2.4.0The Archetype Object Model, both majors side by side
openehr-adlADL 2.4.0ADL2/cADL/ODIN parser, AOM2 validation, flattener, OPT2, ADL 1.4→2 conversion
openehr-termTERM 3.1.0Terminology model + the embedded official openEHR terminology (five languages: en, es, ja, pt, zh)
openehr-langLANG 1.0.0 + 1.1.0The BMM meta-model and its P_BMM schema form, plus hand-written ODIN, BEL and Expression-Language readers
openehr-queryQUERY 1.1.0AQL lexer, parser, typed AST, and canonical printer
openehr-itsITS-JSON, ITS-XML, ITS-REST 1.1.0Canonical JSON + XML codecs, the generated ITS-REST contract, OPT 1.4, Simplified Formats (FLAT/STRUCTURED/Web Template)
[dependencies]
openehr-rm = "0.0.64"
openehr-its = "0.0.64"

All eight are edition 2024 with an MSRV of Rust 1.97, and all eight inherit the workspace lint table, including unsafe_code = "forbid", which no attribute anywhere in the crate can relax. There is no unsafe block in the published specification layer.

Generations: reaching more than one specification version

A crate generated from more than one openEHR generation exposes each as a version-named module: openehr_rm::v1_1 and openehr_rm::v1_2, openehr_base::v1_2 / v1_3, openehr_am::v1_4 / v2_4, openehr_lang::v1_0 / v1_1, openehr_term::v3_1. The crate’s prelude re-exports the current generation, so ordinary code needs no module path; an older generation is reached by naming its module in full:

#![allow(unused)]
fn main() {
// The current generation (RM 1.2.0), via the crate prelude:
use openehr_rm::prelude::Composition;

// The released generation (RM 1.1.0), via its own module path. Import
// renaming is not used anywhere in this project; where both generations
// appear in one file, give one of them a type alias:
type Rm110Composition = openehr_rm::v1_1::composition::composition::Composition;
}

Each generation mirrors its specification’s own package structure, so the path after the generation module reads the same in both. Every generation module also carries its own prelude; no name from one generation is ever mixed into another.

openehr-lang is the one crate whose generations also differ in what they contain, because upstream publishes two BMM meta-models side by side. Its v1_1 generation carries them as sibling specification units: the stable, tool-implemented BMM v2.x model (bmm, its persistence form bmm_persistence, and the beom expression model) is on the generation’s prelude, while the paused BMM3 model (bmm3) is reachable only by full module path. They cannot be merged (a set of class names, BmmClass, BmmModel and BmmPackage among them, occurs in both units with materially different shapes) so the prelude carries the stable units and the choice stays explicit at the use site. The older v1_0 generation is what its release actually defines: the BMM model plus an ODIN reader, with no BEL or Expression-Language notation.

Versioning

The package version is the crates’ own independent SemVer line: it tracks this implementation’s code and moves freely with fixes and improvements, never with the vendored openEHR specification versions. While the line is 0.0.x, expect breaking changes between releases, which always ship in lockstep across all eight crates.

The implemented specification version is therefore a separate datum, per generation. Each generated crate emits a Generation enum that is the only authority for it: one variant per generation module, Default marking the current one, each variant carrying its specification version as a const fn, and Display/FromStr round-tripping the module token:

#![allow(unused)]
fn main() {
assert_eq!(openehr_rm::Generation::default().spec_version(), "1.2.0");
assert_eq!(openehr_rm::Generation::V1_1.spec_version(), "1.1.0");
assert_eq!(openehr_rm::Generation::default().as_str(), "v1_2");
}

There is deliberately no crate-level SPEC_VERSION constant in the generated crates: a single constant would contradict a caller using a non-current generation. Exactly three crates implement one specification each and do expose one: openehr_its::SPEC_VERSION, openehr_query::SPEC_VERSION, openehr_adl::SPEC_VERSION.

Taking only the part of openehr-its you need

openehr-its layers its features so a browser or embedded consumer does not have to compile an HTTP server to read a template. The parsing spine is jsonxmlopt14flat, each pulling in the one below it. Three layers sit beside the spine and are declined independently: cache (the async WebTemplate cache, the crate’s only moka user), schema-validation (validation against the compiled-in ITS-JSON RM schema, its only jsonschema user) and rest-server (the generated ITS-REST contract and its response runtime, which is what brings in axum). The default feature full is all seven, so an existing dependency line keeps the crate it had.

flat and opt14 build for wasm32-unknown-unknown, so a browser consumer can call flat::build_web_template or read an OPT 1.4 template:

[dependencies]
openehr-its = { version = "0.0.64", default-features = false, features = ["flat"] }

With default-features = false and no feature at all, the crate compiles to the SMART App Launch scope grammar alone: std-only, with no dependency of any kind, so a REST client can parse scope strings with the grammar the CDR enforces instead of carrying a second one.

Releases

The eight crates publish as the last leg of the release pipeline, gated on a human: the leg runs in a protected environment with a required reviewer, so the run pauses there and nothing reaches crates.io without an explicit approval. A separate dispatch lane covers a publish between releases and the recovery path when the release leg fails.

Both lanes authenticate with crates.io Trusted Publishing (OIDC, no long-lived token anywhere) and publish the crates one at a time in dependency order, treating “already exists on the index” as done, so a run interrupted halfway can be re-run to finish the set. The lane then reads the registry back and refuses to report success unless all eight resolve at the same version, because while the line is 0.0.x a straggler makes its siblings’ internal requirements unresolvable for every consumer.

Licensing

The eight crates fall into two groups.

The five generated model crates (openehr-base, openehr-rm, openehr-am, openehr-lang, openehr-term) are Apache-2.0, the licence of the openEHR machine-readable artifacts they are generated from, so any Rust project can use them, in proprietary and hosted products included. They embed material derived from those artifacts (generated types carrying specification documentation text), which is Apache-2.0 as well, and their generated files name the openEHR Foundation as a second copyright holder.

The three hand-written engines are under the Business Source License 1.1, the licence of the FerroEHR application (each ships its LICENSE with the parameters): openehr-query and openehr-adl declare BUSL-1.1 and ship only their own Rust sources, the README and that text; openehr-its declares BUSL-1.1 AND Apache-2.0, because its generated codecs and REST contract derive from the Apache-2.0 openEHR XSD, OpenAPI and BMM artifacts and it packages the vendored ITS-JSON schema as bytes, so it ships both texts and its README carries that file’s attribution (upstream repository, the exact vendored commit, and the licence) inside the package, where it travels with any redistribution. Non-production use of these three is free, production use is free for Non-Commercial Purposes, and any other production use, hosting for third parties or distribution for a fee needs a commercial licence, exactly as for the application.

openehr-term carries a third term, because it embeds a different kind of openEHR material: the official terminology XML (the five language bundles, the external-terminology index, and the property/unit data) is CC-BY-SA 3.0, redistributed verbatim with attribution. Its manifest declares Apache-2.0 AND CC-BY-SA-3.0 and the package ships both license texts, so the declaration a consumer reads names every license the crate’s own bytes are under. If you redistribute the crate, the terminology data travels under CC-BY-SA 3.0.

Versions 0.0.56 and earlier were published under the MIT terms in force at the time (MIT AND Apache-2.0 for the crates that embed openEHR material) and keep them; 0.0.57 was never published; 0.0.58 and 0.0.59 are Apache-2.0 for all eight; from 0.0.60 the three engines are BUSL-1.1 as stated above. Published versions keep the licence they were published with. The full picture, including the vendored material that never reaches a published package, is in Licensing.

The openEHR specifications themselves are © the openEHR Foundation.