er7 0.1.2

Parse, query, edit, and write HL7 v2 messages in the ER7 pipe-hat encoding, with zero dependencies
Documentation
  • Coverage
  • 100%
    127 out of 127 items documented56 out of 76 items with examples
  • Size
  • Source code size: 288.28 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 2.48 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 3s Average build duration of successful builds.
  • all releases: 3s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • er7-rust/er7-rust
    1 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • joelparkerhenderson

er7

websitedocumentationsourcecrateemail

Parse, query, edit, and write HL7 v2 messages in the ER7 pipe-hat encoding — as a Rust library and a command-line tool, with zero dependencies.

MSH|^~\&|LAB|ACME|EHR|CLINIC|20260815081500||ORU^R01^ORU_R01|MSG00042|P|2.5
PID|1||444333222^^^ACME&1.2.840.114398.1.100&ISO^MR||EVERYWOMAN^EVE^E||19620320|F
OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|<200|N|||F

ER7 is compact, ubiquitous, and unforgiving: a value's meaning comes entirely from its position, so one misplaced | silently shifts everything after it. This crate makes that structure explicit and keeps it intact.

Contents

Install

cargo add er7

Or for the command-line tool:

cargo install er7

Command line

# Show every value with the HL7 path that names it
er7 samples/oru_r01.er7
MSH-1       |
MSH-2       ^~\&
MSH-3       LAB
MSH-9.1     ORU
MSH-9.2     R01
MSH-9.3     ORU_R01
MSH-10      MSG00042
MSH-11      P
MSH-12      2.5
PID-3.1     444333222
PID-3.4.1   ACME
PID-3.4.2   1.2.840.114398.1.100
PID-5.1     EVERYWOMAN
PID-5.2     EVE
PID-13[1]   555-555-1111
PID-13[2]   555-555-2222
OBX[1]-3.2  Cholesterol
OBX[1]-5    187
OBX[2]-3.2  Triglycerides

Every label in that outline is a valid query, so you can read a path off the output and paste it straight back in:

# Pull out specific values
er7 --query PID-5.1 --query OBX-5 samples/oru_r01.er7

# Show text exactly as sent, without decoding escape sequences
er7 --raw --query OBX-5 samples/oru_r01.er7

# Rewrite as canonical ER7, with line feeds so a terminal can show it
er7 --normalize --terminator lf samples/oru_r01.er7

# Read a batch file, take its second message
cat samples/batch.er7 | er7 --message 2

er7 --help lists the rest. The full contract is spec §12.

Library

let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815081500||ORU^R01|MSG00042|P|2.5\r\
            PID|1||444333222^^^ACME^MR||EVERYWOMAN^EVE^E||19620320|F\r\
            OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|||||F";

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

assert_eq!(message.control_id().as_deref(), Some("MSG00042"));
assert_eq!(message.query("PID-5.1")?.as_deref(), Some("EVERYWOMAN"));
assert_eq!(message.query("OBX-3.2")?.as_deref(), Some("Cholesterol"));

// What went in comes back out, byte for byte.
assert_eq!(message.to_er7(), text);

query returns the first match and query_all returns every one, so OBX-5 across a result with three observations gives three values.

Editing goes through set, which encodes delimiters on the way in, so a value can never break the structure that holds it:

let separators = message.separators;
message
    .segment_at_mut("PID", 1).unwrap()
    .field_mut(5).unwrap()
    .repetition_mut(1).unwrap()
    .component_mut(1).unwrap()
    .subcomponent_mut(1).unwrap()
    .set("O'BRIEN & SONS", &separators);

assert!(message.to_er7().contains(r"O'BRIEN \T\ SONS"));
assert_eq!(message.query("PID-5.1")?.as_deref(), Some("O'BRIEN & SONS"));

There is a runnable program for each of these in examples/, and a step-by-step walk-through in docs/usage/.

What it does

  • Full hierarchy: message, segment, field, repetition (~), component (^), subcomponent (&).
  • Delimiters from the message: MSH-1 and MSH-2 are read, never assumed, including the truncation character HL7 v2.7 added. A message that uses #*!?@ parses as happily as one that uses |^~\&.
  • Round trip, byte for byte: text is stored exactly as it arrived and decoded only when you ask for a value, so a message survives a trip through this crate unchanged — unusual delimiters, unknown segments, empty positions, escape sequences and all.
  • Escape sequences: the whole vocabulary — \F\ \S\ \T\ \R\ \E\, \Xdd..\, \H\, \N\, \Zdd..\, \Cxxyy\, \Mxxyyzz\, \.br\ — is tokenized and classified. The ones that stand for characters decode; the ones that describe presentation are preserved as written. See docs/escapes/.
  • HL7 paths: PID-5.1, OBX[2]-5, PID-13[2].1, in either the PID-5.1 or PID.5.1 spelling. See docs/paths/.
  • Absent, empty, and null are three different answers, not one — the explicit "" means clear this value, and losing that distinction corrupts patient records.
  • Batch files: FHS/BHS/BTS/FTS envelopes are recognized and messages come out one at a time, as borrowed slices of the input.
  • Nothing fails except a missing header: unknown segments, local Z segments, ragged field counts, and stray positions are data, not errors.

What it deliberately does not do

This crate is an encoding, not a dictionary. It does not know which fields a segment has, what data type each carries, which message structures exist, or what any code table means — all of that is version-specific and belongs in a layer above. It performs no validation, and no transport (MLLP framing is a separate concern).

The one exception is a handful of MSH accessors — message_code, trigger_event, message_structure, control_id, version — because routing a message requires reading them and those positions have never moved in any HL7 v2 release. The reasoning, and what was declined, is in spec §10.

The crate family

er7 is the bottom of a stack; each layer above it is a separate crate, so a caller pays only for what they use.

Crate Adds
er7-redact redaction: remove patient detail without changing the shape of the message
serde-er7 Serde support, so a message tree can travel as JSON, YAML, or any other format
hl7-2-5-to-xml / hl7-2-5-to-json the HL7 v2.5 dictionary: data types, message structures, and a renderer

All four are presented together at https://er7-rust.github.io/ecosystem/, and the boundary between them is spec §1.3.1.

Documentation

Where What
docs/usage/ tutorial: from a string to values, edits, and back
docs/paths/ HL7 path notation, in full
docs/escapes/ escape sequences, with worked examples
docs/api/ the complete public API surface
docs/faq/ frequently asked questions
examples/ runnable programs, one concept each
spec/ the normative specification — source of truth for behaviour
spec/02-er7-encoding.md the ER7 format itself, independent of this crate
AGENTS.md conventions and required checks for anyone, human or agent, changing this code
samples/ example messages: a lab result, an admission update with a Z segment, a batch file

Rendered API docs are at https://docs.rs/er7/, or locally with cargo doc --no-deps --open. The same material, presented for the web, is at https://er7-rust.github.io/.

Development

cargo test                                # unit, integration, and doc tests
cargo clippy --all-targets -- -D warnings # lint-clean
cargo fmt --check                         # formatting
cargo rustdoc --lib -- -W missing-docs    # every public item documented
cargo run -- samples/oru_r01.er7          # try the CLI
cargo run --example parse_a_message       # try an example

Behavioural changes start in spec/, not in the code — see AGENTS/spec-driven-development.md.

License

Multi-licensed, so a downstream project can pick whichever fits: MIT, Apache-2.0, BSD-3-Clause, GPL-2.0-only, or GPL-3.0-only. See LICENSE.md.