use crate::{Cd, Element, Ii};
object_wrapper! {
Message wraps hl7_3::Message,
expecting "a Message object with \"id\", \"creationTime\", \"interactionId\", \"sender\", \"receiver\", and \"controlAct\"",
derive [Default],
fields {
id: optw Ii => "id",
creation_time: opt String => "creationTime",
interaction_id: optw Ii => "interactionId",
sender: optw Element => "sender",
receiver: optw Element => "receiver",
control_act: optw ControlAct => "controlAct",
}
}
impl Message {
pub fn parse(xml: &str) -> Result<Message, hl7_3::Error> {
hl7_3::message::parse(xml).map(Message)
}
}
object_wrapper! {
ControlAct wraps hl7_3::ControlAct,
expecting "a ControlAct object with \"classCode\", \"moodCode\", \"code\", and \"domain\"",
derive [Default],
fields {
class_code: req String => "classCode",
mood_code: req String => "moodCode",
code: optw Cd => "code",
domain: optw Element => "domain",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Strict;
const SAMPLE: &str = r#"
<QUQI_IN000001UV01 xmlns="urn:hl7-org:v3">
<id root="2.16.840.1.113883.19.5" extension="MSG00001"/>
<creationTime value="20260101120000"/>
<interactionId root="2.16.840.1.113883.1.6" extension="QUQI_IN000001UV01"/>
<sender typeCode="SND"><device classCode="DEV" determinerCode="INSTANCE"/></sender>
<receiver typeCode="RCV"><device classCode="DEV" determinerCode="INSTANCE"/></receiver>
<controlActProcess classCode="CACT" moodCode="EVN">
<code code="QUQI_TE000001UV01"/>
<subject>
<observation classCode="OBS" moodCode="EVN">
<id root="2.16.840.1.113883.19.5" extension="1"/>
</observation>
</subject>
</controlActProcess>
</QUQI_IN000001UV01>
"#;
#[test]
fn round_trips_a_full_message() {
let message = Message::parse(SAMPLE).unwrap();
let json = serde_json::to_string(&message).unwrap();
let back: Message = serde_json::from_str(&json).unwrap();
assert_eq!(back, message);
let domain = back.0.control_act.unwrap().domain.unwrap();
assert_eq!(domain.local_name(), "observation");
assert_eq!(
domain.child("id").unwrap().attribute("extension"),
Some("1")
);
}
#[test]
fn keys_are_the_camel_case_field_names() {
let json = serde_json::to_value(Message::parse(SAMPLE).unwrap()).unwrap();
let keys: Vec<&str> = json
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
let mut keys = keys;
keys.sort_unstable();
assert_eq!(
keys,
[
"controlAct",
"creationTime",
"id",
"interactionId",
"receiver",
"sender"
]
);
assert_eq!(json["creationTime"], "20260101120000");
assert_eq!(json["sender"]["name"], "device");
assert_eq!(json["controlAct"]["classCode"], "CACT");
}
#[test]
fn an_empty_message_is_all_nulls_and_reads_back() {
let message = Message::parse("<EMPTY/>").unwrap();
let json = serde_json::to_string(&message).unwrap();
assert_eq!(
json,
r#"{"id":null,"creationTime":null,"interactionId":null,"sender":null,"receiver":null,"controlAct":null}"#
);
let back: Message = serde_json::from_str(&json).unwrap();
assert_eq!(back, message);
let bare: Message = serde_json::from_str("{}").unwrap();
assert_eq!(bare, message);
}
#[test]
fn control_act_requires_its_codes() {
let err = serde_json::from_str::<ControlAct>(r#"{"classCode":"CACT"}"#).unwrap_err();
assert!(err.to_string().contains("moodCode"), "{err}");
}
#[test]
fn rejects_a_duplicate_key() {
let err = serde_json::from_str::<Message>(r#"{"creationTime":"1","creationTime":"2"}"#)
.unwrap_err();
assert!(err.to_string().contains("duplicate"), "{err}");
}
#[test]
fn strict_reaches_every_nested_level() {
let err = serde_json::from_str::<Strict<Message>>(r#"{"idd":null}"#).unwrap_err();
assert!(err.to_string().contains("idd"), "{err}");
let err = serde_json::from_str::<Strict<Message>>(r#"{"id":{"root":"1","ext":"x"}}"#)
.unwrap_err();
assert!(err.to_string().contains("ext"), "{err}");
let json = r#"{"controlAct":{"classCode":"CACT","moodCode":"EVN","code":{"code":"X","codeSystm":"y"}}}"#;
let err = serde_json::from_str::<Strict<Message>>(json).unwrap_err();
assert!(err.to_string().contains("codeSystm"), "{err}");
let json = r#"{"controlAct":{"classCode":"CACT","moodCode":"EVN","domain":{"name":"observation","children":[{"name":"id","attrs":{}}]}}}"#;
assert!(
serde_json::from_str::<Message>(json).is_ok(),
"plain stays tolerant"
);
let err = serde_json::from_str::<Strict<Message>>(json).unwrap_err();
assert!(err.to_string().contains("attrs"), "{err}");
}
#[test]
fn strict_accepts_a_real_message_with_no_typos() {
let message = Message::parse(SAMPLE).unwrap();
let json = serde_json::to_string(&message).unwrap();
let back = serde_json::from_str::<Strict<Message>>(&json).unwrap();
assert_eq!(back.0, message);
}
#[test]
fn deref_and_from_work_both_ways() {
let message = Message::parse(SAMPLE).unwrap();
assert_eq!(message.creation_time.as_deref(), Some("20260101120000"));
let inner: hl7_3::Message = message.clone().into();
let outer: Message = inner.into();
assert_eq!(outer, message);
}
}