Documentation

Architecture

Fourteen crates in one Cargo workspace, plus er7 outside it. This page is the reasoning: what each seam is for, and why you can stop at any of them.

The map

text
er7                                    the ER7 encoding: delimiters,
                                       escapes, paths, byte-for-byte
                                       rendering, batch splitting
  |
hl7-2                                  the HL7 v2 dictionary: releases
  |                                    2.1-2.9, data types, message
  |                                    structures; three parsing modes;
  |                                    mutation; validation
  |
  +-- hl7-2-mllp                       transport: HL7 v2 over TCP (MLLP)
  +-- hl7-2-soap                       transport: HL7 v2 over HTTP (SOAP)
  +-- hl7-2-from-er7-into-json         format conversions
  +-- hl7-2-from-er7-into-xml
  +-- hl7-2-from-json-into-er7
  +-- hl7-2-from-xml-into-er7
  +-- hl7-2-from-xsd-into-json-dictionary   writes the dictionaries
  |                                          hl7-2 reads, from HL7
  |                                          v2.xml XSDs
  +-- hl7-2-xml-lite-helper            shared minimal XML reader, also
        |                              used directly by:
        +-- hl7-3                      HL7 v3: RIM backbone classes,
              |                        coded values, the three-level
              |                        message envelope — a foundation,
              |                        not a full implementation
              +-- hl7-3-derive         #[derive(FromElement)] for hl7-3's
              |                        struct mode
              +-- hl7-3-soap           transport: HL7 v3 over HTTP (SOAP)

hl7                                    the umbrella crate — hl7::v2 and
                                       hl7::v3 today, room for hl7::fhir

One crate per layer

Read the map top to bottom and each line is a layer that can be taken on its own. That is the organising principle, and it is worth spelling out because it is what makes fourteen crates less alarming than it sounds.

er7 — syntax
Delimiters, escapes, paths, byte-for-byte rendering, batch splitting. No knowledge of what any field means. It lives in its own repository, outside this workspace, because plenty of people want the encoding and not the dictionary.
hl7-2 — meaning
The per-release data-type tables, the message structures, and the three ways to apply them. This is where nearly all the v2 capability lives, and it is the only crate most callers need.
The transports — delivery
hl7-2-mllp and hl7-2-soap know how to get a message across a network and how to answer it. Neither has an opinion about HL7 semantics, and neither owns a socket: IoTransport takes any reader/writer, and the SOAP crate leaves HTTP to whatever stack you already use.
The conversions — representation
Four crates covering ER7's two directions against both target formats. They are the layer you use when what is downstream is not Rust.
The tooling — everything else
The derive macros, the dictionary builder, and the shared XML reader. Each is in a crate of its own for a specific reason, given below.

One module per standard

The umbrella crate is, in its entirety:

rust
// hl7/src/lib.rs, in full, minus the documentation
pub use hl7_2 as v2;
pub use hl7_3 as v3;

Nothing lives at its root, and that is a decision rather than an omission. A “message”, a “segment”, and a “code” mean different things in v2, v3, and FHIR, and flattening them into one namespace would invite mixing them up — in a domain where mixing them up means a clinical record. Depend on hl7-2 or hl7-3 directly if you want one standard without the indirection; the umbrella exists for callers who want the room left for hl7::fhir.

Why the dependency counts are so small

hl7-2 has one dependency. hl7-3 has one. hl7-2-soap has one. hl7-2-xml-lite-helper has none, and intends to keep it that way. hl7-2-mllp with --no-default-features has none.

That is not minimalism for its own sake. Healthcare software gets audited, and a dependency tree is part of what gets audited. The JSON reader that loads dictionaries in hl7-2 is hand-written for exactly this reason — in a domain where trees get read line by line, a two-crate tree is worth a few hundred lines of code.

The same logic is why the derive macros live in separate crates. syn and quote are large, and they are compiled only for callers who ask for the macros; the default build of hl7-2 never sees them.

Forward and reverse are not symmetric

The four conversion crates look like two symmetric pairs. They are not, and the asymmetry is the most interesting thing about them.

A forward crate — ER7 into XML or JSON — needs the full HL7 v2.5 dictionary, because naming <XPN.1> rather than <PID.5.1> requires knowing that PID-5 holds an XPN. It also needs the message-structure grammars, to decide what nests inside what.

A reverse crate needs neither. Every element or key name the forward crate writes already carries its own position, as the number after the name's last dot — whether the name in front of that number is a recognized data type or a positional fallback. So reversal is a purely structural rebuild. Each reverse crate's spec/index.md §1.1 says exactly this, and it is why those two crates are so much smaller than their counterparts.

What is shared, and what is deliberately not

hl7-2-xml-lite-helper exists because three crates in this family had each written their own minimal XML reader, and three copies of a parser is three places for a bug. It now serves four: hl7-2-soap, hl7-3-soap, hl7-2-from-xml-into-er7, and hl7-2-from-xsd-into-json-dictionary — plus hl7-3, which reads v3's native XML through it. Its name says it is scoped to this family; it is not offered as a general-purpose parser.

What is not shared is just as deliberate. The two forward conversion crates keep their own renderers, because the target formats differ in ways no shared abstraction would model honestly — JSON has real arrays and a real null; XML has repeated sibling elements and empty elements. Their specs are kept consistent with each other instead, and each one's §0 states exactly where they are meant to diverge.

hl7-3-soap deliberately does not depend on hl7-3. A SOAP envelope is XML, and reading one requires no HL7 knowledge beyond the names of a few elements — so the transport stays usable by anyone who wants to route v3 traffic without decoding it.

The workspace, and the history behind it

This is one Cargo workspace with one Cargo.lock at its root. Member crates depend on each other by relative path, and the flat one-directory-per-crate layout was kept specifically so those paths did not have to change — because most of these directories used to be standalone repositories, merged in with git subtree so that each crate's commit history is still walkable under its own directory.

Each crate keeps its own edition, feature set, and dependency list; the workspace does not use [workspace.package] inheritance or a shared [workspace.dependencies] table. That is a live decision rather than an oversight — adding either would touch every member's manifest at once.

One historical wrinkle shows up in names. Every hl7-v2* crate was renamed to hl7-2*hl7-v2 itself because that name was already taken on crates.io by an unrelated crate, and the rest for consistency. The archived repositories those directories came from still carry the original names, and the installed CLI binary is still called hl7-v2.

Every crate, by layer

Core

The standards themselves, and the umbrella crate that re-exports them.

CrateDepends onWhat it adds
hl7hl7-2, hl7-3The umbrella crate: one module per HL7 standard
hl7-2er7HL7 v2 itself: parse, navigate, validate, modify, render
hl7-3hl7-2-xml-lite-helperHL7 v3: the RIM backbone, the data types, the message envelope

Transports

Getting messages across a network, and answering them.

CrateDepends onWhat it adds
hl7-2-mllphl7-2 (feature `ack`), chrono (feature `clock`)MLLP: HL7 v2 framed on a TCP stream
hl7-2-soaphl7-2-xml-lite-helperHL7 v2 carried in a SOAP envelope over HTTP
hl7-3-soaphl7-2-xml-lite-helperHL7 v3 carried in a SOAP envelope over HTTP

Format conversions

ER7 in both directions against both target formats.

CrateDepends onWhat it adds
hl7-2-from-er7-into-xmler7, hl7-2ER7 → the official v2.xml XML representation
hl7-2-from-xml-into-er7er7, hl7-2-xml-lite-helperv2.xml XML → ER7
hl7-2-from-er7-into-jsoner7ER7 → typed JSON
hl7-2-from-json-into-er7er7Typed JSON → ER7

Tooling and helpers

The macros, the dictionary builder, and the shared XML reader.

CrateDepends onWhat it adds
hl7-2-derivesyn, quote#[derive(FromHl7)] and #[derive(ToHl7)] for struct mode
hl7-3-derivesyn, quote#[derive(FromElement)] for HL7 v3 struct mode
hl7-2-from-xsd-into-json-dictionaryhl7-2-xml-lite-helperHL7 v2.xml XSDs → the JSON dictionary hl7-2 reads
hl7-2-xml-lite-helpernothingThe small, dependency-free XML reader this family shares

14 crates here, plus er7 in its own repository. See the crate reference for a page on each.