1pub mod datetime;
30pub mod parser;
31pub mod spec;
32pub mod validate;
33
34mod render;
36mod text;
37mod tui;
38mod view;
39
40#[doc(hidden)]
41pub mod cli;
42
43pub use parser::{Component, Field, Message, ParseError, Repetition, Segment, Separators};
44pub use validate::{Category, Finding, Report, Severity};
45
46pub fn parse(text: &str) -> Result<Message<'_>, ParseError> {
57 let (raws, _notes) = parser::split_messages(text);
58 let Some(first) = raws.first() else {
59 return Err(ParseError::no_message());
60 };
61 parser::parse_message(first)
62}
63
64#[must_use]
66pub fn validate(message: &Message<'_>) -> Report {
67 validate::validate(message)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::{parse, validate, ParseError, Severity};
73
74 const ADT: &str = "MSH|^~\\&|HIS|MERCY|LIS|LAB|20240115143200||ADT^A01|MSG1|P|2.5.1\r\
75 EVN|A01|20240115143200\r\
76 PID|1||123456^^^MERCY^MR||Smith^John||19850312|M\r\
77 PV1|1|I|ER^101^A\r";
78
79 #[test]
80 fn parse_reads_the_first_message() {
81 let message = parse(ADT).expect("the fixture parses");
82 assert_eq!(message.version(), "2.5.1");
83 assert_eq!(message.type_label(), "ADT^A01");
84 assert_eq!(message.control_id(), "MSG1");
85 assert_eq!(message.segments.len(), 4);
86 assert_eq!(
87 message.first("PID").expect("PID is present").comp(5, 1),
88 "Smith"
89 );
90 }
91
92 #[test]
93 fn parse_takes_the_first_of_several() {
94 let batch = format!("{ADT}{}", ADT.replace("MSG1", "MSG2"));
95 assert_eq!(parse(&batch).expect("parses").control_id(), "MSG1");
96 }
97
98 #[test]
99 fn parse_rejects_text_that_holds_no_message() {
100 for text in ["", "not hl7 at all", "PID|1||x\r"] {
101 let error = parse(text).expect_err("there is no message here");
102 assert!(error.to_string().contains("MSH"), "{error}");
103 }
104 }
105
106 #[test]
107 fn parse_rejects_a_message_whose_delimiters_are_unusable() {
108 let error = parse("MSHzzzz\rPID|1\r").expect_err("z cannot be a separator");
109 assert!(error.to_string().contains("separator"), "{error}");
110 }
111
112 #[test]
114 fn parse_errors_are_std_errors() {
115 fn read(text: &str) -> Result<String, Box<dyn std::error::Error>> {
116 Ok(parse(text)?.type_label())
117 }
118 assert_eq!(read(ADT).expect("ok"), "ADT^A01");
119 assert!(read("nothing").is_err());
120
121 let error: ParseError = parse("MSHzzzz\r").expect_err("unusable");
122 assert_eq!(error.line, 1);
123 assert!(!error.message.is_empty());
124 }
125
126 #[test]
127 fn validate_reports_on_a_parsed_message() {
128 let clean = validate(&parse(ADT).expect("parses"));
129 assert_eq!(clean.errors(), 0, "{:?}", clean.findings);
130
131 let broken = ADT
133 .replace("19850312", "19850332")
134 .replace("PV1|1|I|ER^101^A\r", "");
135 let report = validate(&parse(&broken).expect("still parses"));
136 assert!(report.errors() > 0);
137 assert_eq!(report.worst(), Some(Severity::Error));
138 assert!(
139 report
140 .findings
141 .iter()
142 .any(|f| f.location == "PID-7" && f.severity == Severity::Error),
143 "{:?}",
144 report.findings
145 );
146 }
147}