pub struct Message(pub Message);Expand description
A Serde-enabled er7::Message — the crate’s main entry point.
This is the type most callers reach for: parse ER7 with
Message::parse (or wrap an er7::Message you already have), hand
it to any Serializer — serde_json::to_string, a YAML or CBOR writer,
anything Serde-compatible — and get it back the same way on the other
end.
It serializes as an object with two fields, "separators" and
"segments", exactly the shape er7::Message itself has
(er7::Message::separators, er7::Message::segments) and the shape
serde’s own manual-implementation
guide walks through for a struct
with named fields.
§What round-trips and what does not
Every subcomponent serializes as its raw text — escape sequences
intact, not decoded — so Message::parse(text)? through any Serde
format and back out through er7::Message::to_er7 reproduces the
original bytes wherever er7::parse(text)?.to_er7() already would (see
er7::Message::to_er7 for exactly when that is: canonical input
round-trips unchanged; non-canonical terminators and blank lines are
normalized once, at the first parse, same as in plain er7).
Example:
use serde_er7::Message;
let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ORU^R01|MSG9|P|2.5\r\
PID|1||12345^^^ACME^MR||SMITH^JOHN^Q||19800101|M\r\
OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|||||F";
let message = Message::parse(text)?;
// Any Serde format works; this crate never mentions JSON itself.
let json = serde_json::to_string_pretty(&message)?;
assert!(json.contains(r#""name": "PID""#));
// ...and it comes back the same message.
let back: Message = serde_json::from_str(&json)?;
assert_eq!(back.to_er7(), text);Tuple Fields§
§0: MessageImplementations§
Source§impl Message
impl Message
Sourcepub fn parse(text: &str) -> Result<Message, Error>
pub fn parse(text: &str) -> Result<Message, Error>
Parse ER7 text directly into a Serde-enabled Message.
A thin wrapper over er7::parse(), so the crate’s flagship path —
text in, any Serde format out — needs only this one call plus
whichever format’s to_string/to_writer.
Example:
use serde_er7::Message;
let message = Message::parse("MSH|^~\\&|LAB\rPID|1")?;
assert_eq!(message.segments.len(), 2);§Errors
Returns er7::Error exactly as er7::parse() does: the input held
no segments, the first segment is not a header, or the header
declared an unusable delimiter set. Nothing is added here.
Methods from Deref<Target = Message>§
Sourcepub fn segments_named<'a>(
&'a self,
name: &'a str,
) -> impl Iterator<Item = &'a Segment>
pub fn segments_named<'a>( &'a self, name: &'a str, ) -> impl Iterator<Item = &'a Segment>
Every segment with this name, in message order.
Example:
let message = er7::parse("MSH|^~\\&|LAB\rOBX|1\rOBX|2\rNTE|1")?;
assert_eq!(message.segments_named("OBX").count(), 2);
assert_eq!(message.segments_named("ZZZ").count(), 0);Sourcepub fn segment(&self, name: &str) -> Option<&Segment>
pub fn segment(&self, name: &str) -> Option<&Segment>
The first segment with this name.
Example:
let message = er7::parse("MSH|^~\\&|LAB\rPID|1\rOBX|1\rOBX|2")?;
assert_eq!(message.segment("OBX").unwrap().field(1).unwrap()
.to_er7(&message.separators), "1");
assert!(message.segment("ZZZ").is_none());Sourcepub fn segment_at(&self, name: &str, occurrence: usize) -> Option<&Segment>
pub fn segment_at(&self, name: &str, occurrence: usize) -> Option<&Segment>
The 1-based occurrenceth segment with this name, e.g. the second
OBX of a result.
Example:
let message = er7::parse("MSH|^~\\&|LAB\rOBX|1\rOBX|2")?;
let second = message.segment_at("OBX", 2).unwrap();
assert_eq!(second.field(1).unwrap().to_er7(&message.separators), "2");
assert!(message.segment_at("OBX", 3).is_none());Sourcepub fn segment_at_mut(
&mut self,
name: &str,
occurrence: usize,
) -> Option<&mut Segment>
pub fn segment_at_mut( &mut self, name: &str, occurrence: usize, ) -> Option<&mut Segment>
Mutable access to the 1-based occurrenceth segment with this name.
Sourcepub fn header(&self) -> Option<&Segment>
pub fn header(&self) -> Option<&Segment>
The header segment — the first segment, which declared the
delimiters. A parsed message always has one; a message built by hand
might not, hence the Option.
Example:
let message = er7::parse("MSH|^~\\&|LAB\rPID|1")?;
assert_eq!(message.header().unwrap().name, "MSH");
// A batch envelope header works the same way.
let batch = er7::parse("BHS|^~\\&|SENDER")?;
assert_eq!(batch.header().unwrap().name, "BHS");Sourcepub fn query(&self, path: &str) -> Result<Option<String>, Error>
pub fn query(&self, path: &str) -> Result<Option<String>, Error>
The decoded text at path, or None if the message has nothing
there.
A path that names a level above a subcomponent returns that whole
subtree written back as ER7, with only the leaf text decoded — so
PID-5 on SMITH^JOHN gives SMITH^JOHN and PID-5.1 gives
SMITH. Where the path leaves an occurrence open, the first is
taken; use Message::query_all to get them all.
A position the message does not carry gives None rather than an
error or an empty string (R20, spec §8.2). The Err case is only a
malformed path.
Example:
let message = er7::parse("MSH|^~\\&|LAB\rPID|1||9|4|SMITH^JOHN")?;
assert_eq!(message.query("PID-5")?.as_deref(), Some("SMITH^JOHN"));
assert_eq!(message.query("PID-5.2")?.as_deref(), Some("JOHN"));
assert_eq!(message.query("PID-99")?, None);
assert_eq!(message.query("ZZZ-1")?, None);
assert!(message.query("PID-0").is_err());§Errors
Error::BadPath only — and only for a malformed path. A position
the message does not carry is Ok(None), never an error (R20).
Sourcepub fn query_all(&self, path: &str) -> Result<Vec<String>, Error>
pub fn query_all(&self, path: &str) -> Result<Vec<String>, Error>
Every value matching path, in message order: one per matching
segment, and one per repetition where the path does not pin one
down. Repeated OBX-5 across a result is the motivating case.
Example:
let message = er7::parse(
"MSH|^~\\&|LAB\r\
PID|555-1111~555-2222\r\
OBX|1|NM|2093-3^Cholesterol^LN||187\r\
OBX|2|NM|2571-8^Triglycerides^LN||102\r\
OBX|3|ST|X^Note^L",
)?;
// One value per matching segment.
assert_eq!(message.query_all("OBX-3.2")?, ["Cholesterol", "Triglycerides", "Note"]);
// The third OBX carried no fifth field, so it contributes nothing.
assert_eq!(message.query_all("OBX-5")?, ["187", "102"]);
// Stopping at the field keeps the repetition separator...
assert_eq!(message.query_all("PID-1")?, ["555-1111~555-2222"]);
// ...going deeper splits it.
assert_eq!(message.query_all("PID-1.1")?, ["555-1111", "555-2222"]);§Errors
Error::BadPath only; see Message::query. A path that matches
nothing gives an empty Vec.
Sourcepub fn query_path(&self, path: &Path) -> Vec<String>
pub fn query_path(&self, path: &Path) -> Vec<String>
Message::query_all against an already-parsed Path, which
saves re-parsing when the same path is applied to many messages.
Example:
let path: er7::Path = "PID-5.1".parse()?;
let messages = [
er7::parse("MSH|^~\\&|LAB\rPID|1||9|4|SMITH^JOHN")?,
er7::parse("MSH|^~\\&|LAB\rPID|1||9|4|JONES^MARY")?,
];
let names: Vec<String> = messages
.iter()
.flat_map(|message| message.query_path(&path))
.collect();
assert_eq!(names, ["SMITH", "JONES"]);Sourcepub fn query_path_raw(&self, path: &Path) -> Vec<String>
pub fn query_path_raw(&self, path: &Path) -> Vec<String>
Message::query_path without decoding: every value comes back
exactly as the sender wrote it, escape sequences included.
Use this when you are going to put the text back into a message rather than read it, or when you need to tell an explicit null from an empty value without reaching for the node (spec §8.2.1).
Example:
let message = er7::parse("MSH|^~\\&|LAB\rPID|Smith \\T\\ Jones|\"\"|")?;
let name: er7::Path = "PID-1".parse()?;
assert_eq!(message.query_path(&name), ["Smith & Jones"]);
assert_eq!(message.query_path_raw(&name), [r"Smith \T\ Jones"]);
// Decoded, a null and an empty field look the same; raw, they do not.
let null: er7::Path = "PID-2".parse()?;
let empty: er7::Path = "PID-3".parse()?;
assert_eq!(message.query_path(&null), message.query_path(&empty));
assert_eq!(message.query_path_raw(&null), [r#""""#]);
assert_eq!(message.query_path_raw(&empty), [""]);Sourcepub fn message_code(&self) -> Option<String>
pub fn message_code(&self) -> Option<String>
MSH-9.1, the message code, e.g. ADT.
This and the four accessors below are the only HL7 semantics this crate knows, and they are the documented exception to R24. They earn their place on two grounds that both have to hold: every tool that touches a message needs them to route or log it, and these positions have not moved in any v2 release, so reading them requires no version knowledge (spec §10.2).
Each returns None when the position is absent or empty, because
for these five “sent blank” and “not sent” mean the same thing to a
caller (R22).
Example:
let message = er7::parse(
"MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ADT^A08^ADT_A01|MSG9|P|2.5",
)?;
assert_eq!(message.message_code().as_deref(), Some("ADT"));
assert_eq!(message.trigger_event().as_deref(), Some("A08"));
assert_eq!(message.message_structure().as_deref(), Some("ADT_A01"));
assert_eq!(message.control_id().as_deref(), Some("MSG9"));
assert_eq!(message.version().as_deref(), Some("2.5"));
// Absent or empty both read as None.
let sparse = er7::parse("MSH|^~\\&|LAB")?;
assert_eq!(sparse.message_code(), None);
assert_eq!(sparse.control_id(), None);Sourcepub fn trigger_event(&self) -> Option<String>
pub fn trigger_event(&self) -> Option<String>
MSH-9.2, the trigger event, e.g. A08 — what happened that caused
the message. See Message::message_code for an example.
Sourcepub fn message_structure(&self) -> Option<String>
pub fn message_structure(&self) -> Option<String>
MSH-9.3, the message structure, e.g. ADT_A01 — which segments the
message may hold.
Older senders often omit it, in which case the structure has to be derived from the code and trigger event. This crate deliberately does not do that: the mapping differs between HL7 versions, and a wrong answer routes a message to the wrong handler (spec §10.3). Derive it in the dictionary layer that knows the version.
See Message::message_code for an example.
Sourcepub fn control_id(&self) -> Option<String>
pub fn control_id(&self) -> Option<String>
MSH-10, the message control ID: the sender’s unique identifier for this message, and what an acknowledgement quotes back in MSA-2.
See Message::message_code for an example.
Sourcepub fn version(&self) -> Option<String>
pub fn version(&self) -> Option<String>
MSH-12.1, the HL7 version ID, e.g. 2.5.
This reads the first component rather than the whole field, because
a v2.5.1-and-later sender may write 2.5.1^AUS^2.5.1 and only the
first component is the version ID (spec §10.1).
See Message::message_code for an example.
Sourcepub fn to_er7(&self) -> String
pub fn to_er7(&self) -> String
Write the message as ER7 with the default RenderOptions:
carriage-return terminators and no trailing terminator.
Parsing and writing round-trip: for any message this crate parsed, the output differs from the input only where the input was not already canonical — that is, where it had blank lines or a different terminator, the two things parsing normalizes (R16, spec §7.2).
Example:
// Canonical input comes back byte for byte.
let text = "MSH|^~\\&|LAB\rPID|1||9|4|SMITH^JOHN";
assert_eq!(er7::parse(text)?.to_er7(), text);
// Including unusual delimiters, empty positions, and escapes the
// crate does not decode.
for text in [
"MSH#*!?@#LAB#*A*B#C!D",
"MSH|^~\\&|LAB\rPID||A~~B|^^C|||D",
"MSH|^~\\&|LAB\rNTE|1||line\\.br\\next\rZPD|1|LOCAL",
] {
assert_eq!(er7::parse(text)?.to_er7(), text);
}
// Non-canonical input is normalized, and then round-trips.
let messy = "MSH|^~\\&|LAB\r\n\r\nPID|1\n";
assert_eq!(er7::parse(messy)?.to_er7(), "MSH|^~\\&|LAB\rPID|1");Sourcepub fn to_er7_with(&self, options: RenderOptions) -> String
pub fn to_er7_with(&self, options: RenderOptions) -> String
Write the message as ER7, choosing the segment terminator and whether the last segment gets one.
Example:
use er7::{RenderOptions, Terminator};
let message = er7::parse("MSH|^~\\&|LAB\rPID|1")?;
// Readable in a terminal.
let readable = RenderOptions { terminator: Terminator::Lf, ..Default::default() };
assert_eq!(message.to_er7_with(readable), "MSH|^~\\&|LAB\nPID|1");
// Strict wire output terminates every segment, the last included.
let wire = RenderOptions { terminator: Terminator::Cr, trailing_terminator: true };
assert_eq!(message.to_er7_with(wire), "MSH|^~\\&|LAB\rPID|1\r");