Guides
Validating
Parsing never rejects. Checking is a separate call with a separate answer, and its findings are split by whose problem they are — which is the distinction that makes strict mode usable in production.
Parsing is lenient; checking is a separate question
hl7_2::parse accepts unknown segments, unknown data types, and structures that do
not match what the header claimed. None of those is an error, because a parser that rejected
them would reject most real traffic — nearly every production interface carries something the
standard does not describe.
So conformance is asked for, not imposed. Message::validate checks a message
against its dictionary and returns diagnostics. It never fails and never changes the message.
Asking for diagnostics
let message = hl7_2::parse(text)?;
for diagnostic in message.validate() {
println!("{diagnostic}");
// error: MSA[1]-4[1]: "many" is not a valid NM value
}Each diagnostic carries a severity, and the split is the whole design:
use hl7_2::Severity;
let diagnostics = message.validate();
let (errors, warnings): (Vec<_>, Vec<_>) = diagnostics
.into_iter()
.partition(|diagnostic| diagnostic.severity == Severity::Error);
if !errors.is_empty() {
// The sender's problem. Reject, and say why in MSA-3.
}
if !warnings.is_empty() {
// Our problem, or nobody's. Log it; do not reject on it.
}Errors — the message contradicts its dictionary
Severity::Error means the sender said one thing and did another. These are worth
rejecting on.
| Kind | Raised when |
|---|---|
Header | MSH-9.1 or MSH-10 is empty. |
SegmentMissing | A segment or group the structure requires is absent. |
StructureMismatch | The segments do not fit the declared structure. |
ValueFormat | An SI, NM, DT, TM, or DTM value is not one. |
Warnings — the dictionary does not cover the message
Severity::Warning means the message may be perfectly fine and this crate simply
does not model the thing in question. A coverage gap here is not the sender's error, and
treating it as one would reject valid traffic.
| Kind | Raised when |
|---|---|
Header | MSH-12 is empty or names an unmodelled release. |
StructureUnknown | No grammar for this structure id. |
StructureMismatch | The standard segments fit on their own, but local Z-segments do not. |
SegmentUnknown | A segment the dictionary does not define. |
FieldUnknown | A field past the end of the segment's definition. |
ComponentUnknown | A component past the end of the data type's definition. |
Several of those warnings are actionable — by you, not by the sender. A SegmentUnknown or FieldUnknown is exactly the signal that it is time
to write a vendor dictionary.
Z-segments do not fail a message
A segment whose name begins with Z is a local extension the standard says nothing
about — so neither does this crate. No SegmentUnknown is raised for one.
And when a message fails its structure only because of Z-segments — the standard segments fit on their own — the mismatch is a warning rather than an error.
Strict mode
Options::strict runs the same check at parse time and turns any Severity::Error into Error::Invalid, carrying the diagnostics.
Warnings never fail a parse.
let options = hl7_2::Options::new().strict();
match hl7_2::parse_with_options(text, &options) {
Ok(message) => { /* conformant */ }
Err(hl7_2::Error::Invalid(diagnostics)) => { /* every error-level finding */ }
Err(other) => { /* not a message at all */ }
}Note the three arms. “Not a message at all” — empty input, no MSH, a malformed
header — is a different failure from “a message that does not conform”, and an interface that
conflates them will send the wrong acknowledgement code. When you reject, say why:
let mut nack = hl7_2::builder::acknowledge(&message, "AE", "N1", "20260814080100")
.build_valid()?;
nack.set("MSA-3", &reason)?;What is deliberately not checked
- Value formats without a machine-checkable shape.
ST,TX,ID,ISand the rest are constrained by HL7 tables and lengths, which this crate does not model. It says nothing about them rather than guessing. - Empty values and explicit nulls. Neither is checked against a value format — an absent value has no format to violate.
- Table membership. Whether
PID-8holds a code from HL7 table 0001 is not checked; that is a vocabulary question, not a syntax one. - Clinical sense. Nothing here knows that a cholesterol of 1870 mg/dL is implausible. That is your domain layer's job.
The conversion crates check nothing at all — none of the four is a validator, and each says so
in its own spec/index.md. If you need conformance before conversion, run hl7-2 first.
Checking from a shell
$ hl7-v2 --check samples/adt_a01.hl7
ok
$ hl7-v2 --check broken.hl7
error: MSH[1]-10[1]: message control ID is empty
$ echo $?
2Exit status 0 is success, 1 is a usage or parse error, and 2 means --check or --strict found something wrong with the message. Those three being distinct is
what lets a triage script quarantine correctly — see Command line.