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

rust
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:

rust
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.

KindRaised when
HeaderMSH-9.1 or MSH-10 is empty.
SegmentMissingA segment or group the structure requires is absent.
StructureMismatchThe segments do not fit the declared structure.
ValueFormatAn 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.

KindRaised when
HeaderMSH-12 is empty or names an unmodelled release.
StructureUnknownNo grammar for this structure id.
StructureMismatchThe standard segments fit on their own, but local Z-segments do not.
SegmentUnknownA segment the dictionary does not define.
FieldUnknownA field past the end of the segment's definition.
ComponentUnknownA 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.

rust
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:

rust
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, IS and 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-8 holds 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

sh
$ hl7-v2 --check samples/adt_a01.hl7
ok

$ hl7-v2 --check broken.hl7
error: MSH[1]-10[1]: message control ID is empty
$ echo $?
2

Exit 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.