In its favour
Messages are tiny, so an interface engine can move millions a day. The format is trivially streamable. And it is embedded in hundreds of thousands of production interfaces, most of which will never be rewritten.
Reference
Background on the format itself, independent of any implementation. This is what the crate reads and writes.
ER7 — “Encoding Rules 7” — is the original text encoding for HL7
version 2 messages, and still the one nearly every production interface speaks. It is defined
in chapter 2 of every v2 release, from 2.1 in 1990 through 2.9. The nickname pipe-hat comes from its two most visible delimiters, | and ^.
An ER7 message is plain text, positional, and small.
Six levels, each with its own delimiter.
| Level | Separated by | Example |
|---|---|---|
| message | — | the whole text |
| segment | carriage return | PID|1||444333222... |
| field | | | EVERYWOMAN^EVE^E |
| repetition | ~ | 555-1111~555-2222 |
| component | ^ | EVERYWOMAN |
| subcomponent | & | 1.2.840.114398.1.100 |
A segment is three characters of name followed by its fields: MSH is the message header, PID patient identification, OBX an observation. Names beginning with Z are local extensions,
defined by whoever is at the two ends and by nobody else.
Everything is positional. PID-5.1 is a family name because it is
fifth and first, not because anything in the message says so. This is why one misplaced | corrupts everything after it, and why a message needs a dictionary — the HL7
standard for that version — before its values mean anything.
Only the segment terminator is fixed: a carriage return, \r, hex 0D. The standard
is explicit that implementers cannot change it. In practice many systems store messages in
files with \n or \r\n instead, so a tolerant reader accepts all
three.
The rest are declared by the message itself, in its first two fields.
MSH|^~\&| ^^^^^ ||||| ||||+- subcomponent separator (MSH-2 position 4) |||+-- escape character (MSH-2 position 3) ||+--- repetition separator (MSH-2 position 2) |+---- component separator (MSH-2 position 1) +----- field separator (MSH-1, 4th character of the message)
MSH-1 is a field whose value is the field separator, and MSH-2 is a field whose value is the encoding characters. This is circular by design, and it is why those two
fields can never be split or escaped like ordinary ones. It also means a reader learns the
delimiters from bytes 4–8 of the message and must not assume |^~\&,
however universal that choice is in practice.
HL7 v2.7 added a fifth encoding character, the truncation character (recommended #), marking a value the sender cut short to fit a length limit. Most
messages omit it.
The batch envelope segments FHS and BHS declare delimiters the same
way, since a batch file may begin with either.
In the crate: the delimiter set is read from every message, never hardcoded, and a set that reuses one character for two roles is rejected — that message could not be read back as the sender meant it.
Three states, easily confused, and the difference is clinical.
| On the wire | Means | A receiver must |
|---|---|---|
the field is absent | no information | leave any stored value alone |
|| | present, but no value | leave any stored value alone |
|""| | the explicit null | clear the stored value |
Trailing fields a sender has nothing for may simply be dropped, so a PID ending
after field 8 is normal and says nothing about fields 9 onward. The two-character "" is the only way to say “delete what you have”.
Getting this wrong is a patient-safety bug. Treating a null as empty leaves a withdrawn allergy on the record. Treating an empty as a null erases a value that was never sent. The crate keeps all three apart, at every level of the tree.
A value that needs to contain a delimiter escapes it. A sequence is the escape character, a
body, and the escape character again: \F\, \X0D\, \.br\.
Five of them stand for the delimiters themselves; the rest are hexadecimal data, character-set switches, display formatting, or locally agreed extensions.
Several messages can share a file, wrapped in an envelope.
FHS file header
BHS batch header
MSH message ...
MSH message ...
BTS batch trailer
FTS file trailer The envelope segments describe the file, not any message in it: who sent the batch,
how many messages it holds, when it was written. A reader that wants the messages drops them
and starts a new message at each MSH.
ER7 messages are usually carried by MLLP (Minimal Lower Layer Protocol): each
message is wrapped in a start byte (0x0B) and an end sequence (0x1C 0x0D) over a TCP
connection, and the receiver answers with an ACK message quoting the
original’s MSH-10 control ID.
The framing is a separate concern from the encoding, which is why this crate handles only the latter. Strip the framing bytes before parsing.
The tradeoffs are stark, and they have kept ER7 in place for thirty-five years.
Messages are tiny, so an interface engine can move millions a day. The format is trivially streamable. And it is embedded in hundreds of thousands of production interfaces, most of which will never be rewritten.
It is positional, so it is brittle and unreadable without tooling. There is no schema in the message itself. And the same field number means different things in different versions.
HL7 published an XML encoding in v2.3.1 and FHIR later, but neither displaced ER7 in the installed base.