hl7-rust

HL7, in Rust, one crate per layer

Parse, navigate, validate, modify, and render Health Level Seven messages. HL7 v2 releases 2.1 through 2.9 with a real dictionary behind them; HL7 v3's RIM backbone and message envelope; MLLP and SOAP transports; and lossless conversion between ER7, v2.xml, and JSON. Small dependency trees, permissive licensing, and a normative specification for every crate whose behavior is normative.

cargo add hl7

Start here

What it looks like

HL7 v2 is pipes and carets. The hard part is not the syntax — it is knowing what the pipes and carets mean in the release the sender speaks. That is what the dictionary is for.

A lipid panel result, as it arrives on the wire

er7
MSH|^~\&|LAB|ACME|EHR|CLINIC|20260814080000||ORU^R01|MSG00042|P|2.5
PID|1||444333222^^^ACME&1.2.3.4&ISO^MR||EVERYWOMAN^EVE^E||19620320|F
OBR|1|ORD776655|LAB2233|24331-1^Lipid Panel^LN|||20260813071500
OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|<200|N|||F

Read from it by path or by name

rust
use hl7::v2;

let message = v2::parse(text)?;

// A path addresses a value; the tree names it.
assert_eq!(message.get("PID-5.1")?.as_deref(), Some("EVERYWOMAN"));
assert_eq!(message.tree().find("XPN.1").unwrap().text(), "EVERYWOMAN");

// Every node knows the path that reads it back.
let second = message.tree().find_all("OBX").nth(1);
assert_eq!(message.structure_id(), "ORU_R01");

Follow the quick start →

Convert it, losslessly, both ways

The same ER7 fragment PID|1||241900||TEST^FOUAZ converts to the official v2.xml representation and to a typed JSON mapping designed to preserve everything v2.xml preserves. Piping either back through its reverse crate reproduces the original message.

hl7-2-from-er7-into-xml

xml
<PID>
  <PID.1>1</PID.1>
  <PID.3>
    <CX.1>241900</CX.1>
  </PID.3>
  <PID.5>
    <XPN.1><FN.1>TEST</FN.1></XPN.1>
    <XPN.2>FOUAZ</XPN.2>
  </PID.5>
</PID>

hl7-2-from-er7-into-json

json
{
  "PID": {
    "PID.1": "1",
    "PID.3": { "CX.1": "241900" },
    "PID.5": {
      "XPN.1": { "FN.1": "TEST" },
      "XPN.2": "FOUAZ"
    }
  }
}

The conversion guide →

How the crates fit together

One crate per layer, one module per standard. Each seam is a place you can stop: take the ER7 encoding alone, take v2 without a transport, take a transport without the dictionary. This groups crates by family and layer, not by literal Cargo dependency edges — see what actually depends on what.

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
              +-- hl7-3-derive         #[derive(FromElement)]
              +-- hl7-3-soap           transport: HL7 v3 over HTTP (SOAP)

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

Why the seams fall where they do →

Every crate

Core

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

Transports

Getting messages across a network, and answering them.

Format conversions

ER7 in both directions against both target formats.

Tooling and helpers

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

14 crates in one Cargo workspace, plus er7, which is its own.