Skip to main content

serde_er7/
message.rs

1//! [`Message`]: the whole tree, serialized as an object.
2
3use std::fmt;
4use std::ops::{Deref, DerefMut};
5
6use serde::de::{self, MapAccess, Visitor};
7use serde::ser::SerializeStruct;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use crate::{Segment, Separators};
11
12/// A Serde-enabled [`er7::Message`] — the crate's main entry point.
13///
14/// This is the type most callers reach for: parse ER7 with
15/// [`Message::parse`] (or wrap an [`er7::Message`] you already have), hand
16/// it to any `Serializer` — `serde_json::to_string`, a YAML or CBOR writer,
17/// anything Serde-compatible — and get it back the same way on the other
18/// end.
19///
20/// It serializes as an object with two fields, `"separators"` and
21/// `"segments"`, exactly the shape [`er7::Message`] itself has
22/// ([`er7::Message::separators`], [`er7::Message::segments`]) and the shape
23/// [serde's own manual-implementation
24/// guide](https://docs.rs/serde/latest/serde/) walks through for a struct
25/// with named fields.
26///
27/// # What round-trips and what does not
28///
29/// Every subcomponent serializes as its `raw` text — escape sequences
30/// intact, not decoded — so `Message::parse(text)?` through any Serde
31/// format and back out through [`er7::Message::to_er7`] reproduces the
32/// original bytes wherever `er7::parse(text)?.to_er7()` already would (see
33/// [`er7::Message::to_er7`] for exactly when that is: canonical input
34/// round-trips unchanged; non-canonical terminators and blank lines are
35/// normalized once, at the first parse, same as in plain `er7`).
36///
37/// Example:
38///
39/// ```
40/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
41/// use serde_er7::Message;
42///
43/// let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ORU^R01|MSG9|P|2.5\r\
44///             PID|1||12345^^^ACME^MR||SMITH^JOHN^Q||19800101|M\r\
45///             OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|||||F";
46///
47/// let message = Message::parse(text)?;
48///
49/// // Any Serde format works; this crate never mentions JSON itself.
50/// let json = serde_json::to_string_pretty(&message)?;
51/// assert!(json.contains(r#""name": "PID""#));
52///
53/// // ...and it comes back the same message.
54/// let back: Message = serde_json::from_str(&json)?;
55/// assert_eq!(back.to_er7(), text);
56/// # Ok(())
57/// # }
58/// ```
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Message(pub er7::Message);
61
62impl Message {
63    /// Parse ER7 text directly into a Serde-enabled [`Message`].
64    ///
65    /// A thin wrapper over [`er7::parse()`], so the crate's flagship path —
66    /// text in, any Serde format out — needs only this one call plus
67    /// whichever format's `to_string`/`to_writer`.
68    ///
69    /// Example:
70    ///
71    /// ```
72    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
73    /// use serde_er7::Message;
74    ///
75    /// let message = Message::parse("MSH|^~\\&|LAB\rPID|1")?;
76    /// assert_eq!(message.segments.len(), 2);
77    /// # Ok(())
78    /// # }
79    /// ```
80    ///
81    /// # Errors
82    ///
83    /// Returns `er7::Error` exactly as [`er7::parse()`] does: the input held
84    /// no segments, the first segment is not a header, or the header
85    /// declared an unusable delimiter set. Nothing is added here.
86    pub fn parse(text: &str) -> Result<Message, er7::Error> {
87        er7::parse(text).map(Message)
88    }
89}
90
91impl From<er7::Message> for Message {
92    fn from(inner: er7::Message) -> Message {
93        Message(inner)
94    }
95}
96
97impl From<Message> for er7::Message {
98    fn from(outer: Message) -> er7::Message {
99        outer.0
100    }
101}
102
103impl Deref for Message {
104    type Target = er7::Message;
105
106    fn deref(&self) -> &er7::Message {
107        &self.0
108    }
109}
110
111impl DerefMut for Message {
112    fn deref_mut(&mut self) -> &mut er7::Message {
113        &mut self.0
114    }
115}
116
117impl fmt::Display for Message {
118    /// The message as ER7; see [`er7::Message::to_er7`].
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_str(&self.0.to_er7())
121    }
122}
123
124impl Serialize for Message {
125    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126    where
127        S: Serializer,
128    {
129        let segments: Vec<Segment> = self.0.segments.iter().map(|s| Segment(s.clone())).collect();
130        let mut state = serializer.serialize_struct("Message", 2)?;
131        state.serialize_field("separators", &Separators(self.0.separators))?;
132        state.serialize_field("segments", &segments)?;
133        state.end()
134    }
135}
136
137const FIELDS: &[&str] = &["separators", "segments"];
138
139struct MessageVisitor;
140
141impl<'de> Visitor<'de> for MessageVisitor {
142    type Value = Message;
143
144    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
145        formatter.write_str("a Message object with \"separators\" and \"segments\"")
146    }
147
148    fn visit_map<V>(self, mut map: V) -> Result<Message, V::Error>
149    where
150        V: MapAccess<'de>,
151    {
152        let mut separators: Option<Separators> = None;
153        let mut segments: Option<Vec<Segment>> = None;
154
155        while let Some(key) = map.next_key::<String>()? {
156            match key.as_str() {
157                "separators" => {
158                    if separators.is_some() {
159                        return Err(de::Error::duplicate_field("separators"));
160                    }
161                    separators = Some(map.next_value()?);
162                }
163                "segments" => {
164                    if segments.is_some() {
165                        return Err(de::Error::duplicate_field("segments"));
166                    }
167                    segments = Some(map.next_value()?);
168                }
169                _ => {
170                    let _ = map.next_value::<de::IgnoredAny>()?;
171                }
172            }
173        }
174
175        let separators = separators.ok_or_else(|| de::Error::missing_field("separators"))?;
176        let segments = segments.ok_or_else(|| de::Error::missing_field("segments"))?;
177        Ok(Message(er7::Message {
178            separators: separators.0,
179            segments: segments.into_iter().map(|s| s.0).collect(),
180        }))
181    }
182}
183
184impl<'de> Deserialize<'de> for Message {
185    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
186    where
187        D: Deserializer<'de>,
188    {
189        deserializer.deserialize_struct("Message", FIELDS, MessageVisitor)
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    const ADT: &str = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ADT^A08^ADT_A01|MSG9|P|2.5\r\
198                       PID|1||12345^^^ACME&1.2.3&ISO^MR||SMITH^JOHN^Q||19800101|M|||||\
199                       555-1111~555-2222\r\
200                       OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL\r\
201                       OBX|2|ST|X^Note^L||\"\"";
202
203    #[test]
204    fn round_trips_a_full_message_through_json() {
205        let message = Message::parse(ADT).unwrap();
206        let json = serde_json::to_string(&message).unwrap();
207        let back: Message = serde_json::from_str(&json).unwrap();
208        assert_eq!(back.to_er7(), ADT);
209        assert_eq!(back, message);
210    }
211
212    #[test]
213    fn round_trips_custom_delimiters() {
214        let text = "MSH#*!?@#LAB#*A*B#C!D";
215        let message = Message::parse(text).unwrap();
216        let json = serde_json::to_string(&message).unwrap();
217        let back: Message = serde_json::from_str(&json).unwrap();
218        assert_eq!(back.to_er7(), text);
219    }
220
221    #[test]
222    fn round_trips_a_batch() {
223        let text = "FHS|^~\\&|SENDER\rBHS|^~\\&|SENDER\r\
224                    MSH|^~\\&|SENDER||RECEIVER||20260815090000||ACK^A08^ACK|B1|P|2.5\r\
225                    MSA|AA|MSG00001\rBTS|1\rFTS|1";
226        let message = Message::parse(text).unwrap();
227        let json = serde_json::to_string(&message).unwrap();
228        let back: Message = serde_json::from_str(&json).unwrap();
229        assert_eq!(back.to_er7(), text);
230    }
231
232    #[test]
233    fn deref_reaches_query() {
234        let message = Message::parse(ADT).unwrap();
235        assert_eq!(message.query("PID-5.1").unwrap().as_deref(), Some("SMITH"));
236    }
237
238    #[test]
239    fn rejects_a_message_missing_segments() {
240        let err = serde_json::from_str::<Message>(r#"{"separators":{"field":"|","component":"^","repetition":"~","escape":"\\","subcomponent":"&"}}"#)
241            .unwrap_err();
242        assert!(err.to_string().contains("segments"));
243    }
244
245    #[test]
246    fn pretty_json_keeps_the_tree_shape() {
247        let message = Message::parse("MSH|^~\\&|LAB\rPID|1||9|4|SMITH^JOHN").unwrap();
248        let json = serde_json::to_value(&message).unwrap();
249        let pid = &json["segments"][1];
250        assert_eq!(pid["name"], "PID");
251        // PID-5 (index 4) is field 5 = ["SMITH", "JOHN"] one repetition deep.
252        assert_eq!(
253            pid["fields"][4][0],
254            serde_json::json!([["SMITH"], ["JOHN"]])
255        );
256    }
257}