1use crate::json::{self, Value};
20use std::collections::BTreeMap;
21use std::fmt;
22
23pub const VARIABLE: &str = "VAR";
28
29const UNSTATED: &str = "";
34
35#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum Item {
40 Segment {
42 name: String,
44 required: bool,
46 repeats: bool,
48 },
49 Group {
51 name: String,
53 required: bool,
55 repeats: bool,
57 items: Vec<Item>,
59 },
60}
61
62impl Item {
63 #[must_use]
65 pub fn name(&self) -> &str {
66 match self {
67 Item::Segment { name, .. } | Item::Group { name, .. } => name,
68 }
69 }
70
71 #[must_use]
73 pub fn required(&self) -> bool {
74 match self {
75 Item::Segment { required, .. } | Item::Group { required, .. } => *required,
76 }
77 }
78
79 #[must_use]
81 pub fn repeats(&self) -> bool {
82 match self {
83 Item::Segment { repeats, .. } | Item::Group { repeats, .. } => *repeats,
84 }
85 }
86
87 #[must_use]
93 pub fn can_start(&self, segment: &str) -> bool {
94 match self {
95 Item::Segment { name, .. } => name == segment,
96 Item::Group { items, .. } => {
97 for item in items {
98 if item.can_start(segment) {
99 return true;
100 }
101 if item.required() {
102 return false;
103 }
104 }
105 false
106 }
107 }
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Default)]
117pub struct Dictionary {
118 name: String,
119 version: Option<String>,
120 types: BTreeMap<String, Vec<String>>,
121 segments: BTreeMap<String, Vec<String>>,
122 cardinality: BTreeMap<String, Vec<Cardinality>>,
123 structures: BTreeMap<String, Vec<Item>>,
124 aliases: BTreeMap<String, String>,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
137pub struct Cardinality {
138 pub required: bool,
140 pub repeats: bool,
142}
143
144impl Dictionary {
145 pub fn empty(name: impl Into<String>) -> Dictionary {
149 Dictionary {
150 name: name.into(),
151 ..Dictionary::default()
152 }
153 }
154
155 #[must_use]
158 pub fn name(&self) -> &str {
159 &self.name
160 }
161
162 #[must_use]
165 pub fn version(&self) -> Option<&str> {
166 self.version.as_deref()
167 }
168
169 pub fn composite_components(&self, data_type: &str) -> Option<&[String]> {
174 self.types.get(data_type).map(Vec::as_slice)
175 }
176
177 #[must_use]
179 pub fn is_composite(&self, data_type: &str) -> bool {
180 self.types.contains_key(data_type)
181 }
182
183 pub fn segment_fields(&self, segment: &str) -> Option<&[String]> {
186 self.segments.get(segment).map(Vec::as_slice)
187 }
188
189 pub fn field_type(&self, segment: &str, field: usize) -> Option<&str> {
195 let types = self.segment_fields(segment)?;
196 match types.get(field.checked_sub(1)?).map(String::as_str) {
197 Some(UNSTATED) | None => None,
198 found => found,
199 }
200 }
201
202 #[must_use]
209 pub fn field_cardinality(&self, segment: &str, field: usize) -> Cardinality {
210 field
211 .checked_sub(1)
212 .and_then(|index| self.cardinality.get(segment)?.get(index).copied())
213 .unwrap_or_default()
214 }
215
216 #[must_use]
224 pub fn variable_type(&self, segment: &er7::Segment) -> Option<&str> {
225 let named = segment
226 .component(2, 1)?
227 .subcomponent(1)?
228 .raw
229 .trim()
230 .to_string();
231 self.types
232 .get_key_value(&named)
233 .map(|(key, _)| key.as_str())
234 }
235
236 pub fn structure(&self, id: &str) -> Option<&[Item]> {
238 self.structures.get(id).map(Vec::as_slice)
239 }
240
241 #[must_use]
252 pub fn structure_id(&self, code: &str, trigger: &str) -> String {
253 if code.is_empty() {
254 return "HL7Message".to_string();
255 }
256 let joined = if trigger.is_empty() {
257 code.to_string()
258 } else {
259 format!("{code}_{trigger}")
260 };
261 if let Some(target) = self.aliases.get(&joined) {
262 return target.clone();
263 }
264 if self.structures.contains_key(&joined) {
265 return joined;
266 }
267 if self.structures.contains_key(code) {
268 return code.to_string();
269 }
270 joined
271 }
272
273 pub fn structure_ids(&self) -> impl Iterator<Item = &str> {
275 self.structures.keys().map(String::as_str)
276 }
277
278 pub fn segment_names(&self) -> impl Iterator<Item = &str> {
280 self.segments.keys().map(String::as_str)
281 }
282
283 pub fn type_names(&self) -> impl Iterator<Item = &str> {
285 self.types.keys().map(String::as_str)
286 }
287
288 pub fn from_json(text: &str, name: impl Into<String>) -> Result<Dictionary, Error> {
310 Dictionary::from_json_resolving(text, name, |version| {
311 crate::Version::parse(version).map(crate::Version::dictionary)
312 })
313 }
314
315 pub fn from_json_over(
324 text: &str,
325 name: impl Into<String>,
326 base: &Dictionary,
327 ) -> Result<Dictionary, Error> {
328 let name = name.into();
329 let value = json::parse(text).map_err(Error::Json)?;
330 let mut dictionary = base.clone();
331 dictionary.name = name;
332 dictionary.version = None;
333 dictionary.apply(&value)?;
334 Ok(dictionary)
335 }
336
337 pub fn from_json_resolving(
348 text: &str,
349 name: impl Into<String>,
350 resolve: impl Fn(&str) -> Option<std::sync::Arc<Dictionary>>,
351 ) -> Result<Dictionary, Error> {
352 let name = name.into();
353 let value = json::parse(text).map_err(Error::Json)?;
354 let mut dictionary = match value.get("inherits") {
355 None => Dictionary::empty(name.clone()),
356 Some(Value::String(base)) => match resolve(base) {
357 Some(base) => Dictionary {
358 name: name.clone(),
359 ..(*base).clone()
360 },
361 None => return Err(Error::UnknownBase(base.clone())),
362 },
363 Some(other) => {
364 return Err(Error::field("inherits", "a version string", other));
365 }
366 };
367 dictionary.apply(&value)?;
368 Ok(dictionary)
369 }
370
371 fn apply(&mut self, value: &Value) -> Result<(), Error> {
376 if value.as_object().is_none() {
377 return Err(Error::field("<document>", "an object", value));
378 }
379 if let Some(version) = value.get("version") {
380 match version.as_str() {
381 Some(text) => self.version = Some(text.to_string()),
382 None => return Err(Error::field("version", "a version string", version)),
383 }
384 }
385 for section in ["types", "segments"] {
386 let Some(members) = value.get(section) else {
387 continue;
388 };
389 let members = members
390 .as_object()
391 .ok_or_else(|| Error::field(section, "an object", members))?;
392 for (key, entry) in members {
393 let is_segments = section == "segments";
394 let table = if is_segments {
395 &mut self.segments
396 } else {
397 &mut self.types
398 };
399 if entry.is_null() {
400 table.remove(key);
401 if is_segments {
402 self.cardinality.remove(key);
403 }
404 continue;
405 }
406 let inherited = table.get(key).cloned().unwrap_or_default();
407 let inherited_cardinality = if is_segments {
408 self.cardinality.get(key).cloned().unwrap_or_default()
409 } else {
410 Vec::new()
411 };
412 let (names, cardinality) = positions(
413 entry,
414 inherited,
415 inherited_cardinality,
416 &format!("{section}.{key}"),
417 )?;
418 table.insert(key.clone(), names);
419 if is_segments {
423 self.cardinality.insert(key.clone(), cardinality);
424 }
425 }
426 }
427 if let Some(aliases) = value.get("aliases") {
428 let members = aliases
429 .as_object()
430 .ok_or_else(|| Error::field("aliases", "an object", aliases))?;
431 for (key, entry) in members {
432 if entry.is_null() {
433 self.aliases.remove(key);
434 continue;
435 }
436 let target = entry.as_str().ok_or_else(|| {
437 Error::field(&format!("aliases.{key}"), "a structure ID", entry)
438 })?;
439 self.aliases.insert(key.clone(), target.to_string());
440 }
441 }
442 if let Some(structures) = value.get("structures") {
443 let members = structures
444 .as_object()
445 .ok_or_else(|| Error::field("structures", "an object", structures))?;
446 for (key, entry) in members {
447 if entry.is_null() {
448 self.structures.remove(key);
449 continue;
450 }
451 let items = parse_items(entry, &format!("structures.{key}"))?;
452 self.structures.insert(key.clone(), items);
453 }
454 }
455 Ok(())
456 }
457}
458
459fn positions(
471 value: &Value,
472 inherited: Vec<String>,
473 inherited_cardinality: Vec<Cardinality>,
474 path: &str,
475) -> Result<(Vec<String>, Vec<Cardinality>), Error> {
476 if let Some(list) = value.as_array() {
477 let mut names = Vec::with_capacity(list.len());
478 let mut cardinality = Vec::with_capacity(list.len());
479 for (index, item) in list.iter().enumerate() {
480 let (name, card) = entry_of(item, &format!("{path}[{index}]"))?;
481 names.push(name);
482 cardinality.push(card);
483 }
484 return Ok((names, cardinality));
485 }
486 let members = value
487 .as_object()
488 .ok_or_else(|| Error::field(path, "an array, or an object of position overrides", value))?;
489 let mut names = inherited;
490 let mut cardinality = inherited_cardinality;
491 for (key, entry) in members {
492 let path = format!("{path}.{key}");
493 let position: usize = key
494 .parse()
495 .ok()
496 .filter(|position| *position > 0)
497 .ok_or_else(|| Error::Field {
498 path: path.clone(),
499 expected: "a 1-based position number".to_string(),
500 found: format!("{key:?}"),
501 })?;
502 let (name, card) = entry_of(entry, &path)?;
503 if names.len() < position {
504 names.resize(position, UNSTATED.to_string());
505 }
506 if cardinality.len() < position {
507 cardinality.resize(position, Cardinality::default());
508 }
509 names[position - 1] = name;
510 cardinality[position - 1] = card;
511 }
512 cardinality.resize(names.len(), Cardinality::default());
515 Ok((names, cardinality))
516}
517
518fn entry_of(value: &Value, path: &str) -> Result<(String, Cardinality), Error> {
525 if let Some(name) = value.as_str() {
526 return Ok((name.to_string(), Cardinality::default()));
527 }
528 if value.as_object().is_none() {
531 return Err(Error::field(path, "a data type name", value));
532 }
533 let name = value
534 .get("type")
535 .ok_or_else(|| Error::missing(&format!("{path}.type")))?
536 .as_str()
537 .ok_or_else(|| {
538 Error::field(
539 &format!("{path}.type"),
540 "a data type name",
541 value.get("type").unwrap_or(value),
542 )
543 })?;
544 Ok((
545 name.to_string(),
546 Cardinality {
547 required: flag(value, "required", path)?,
548 repeats: flag(value, "repeats", path)?,
549 },
550 ))
551}
552
553fn parse_items(value: &Value, path: &str) -> Result<Vec<Item>, Error> {
556 let list = value
557 .as_array()
558 .ok_or_else(|| Error::field(path, "an array of structure items", value))?;
559 let mut items = Vec::with_capacity(list.len());
560 for (index, entry) in list.iter().enumerate() {
561 let path = format!("{path}[{index}]");
562 if let Some(name) = entry.as_str() {
565 items.push(Item::Segment {
566 name: name.to_string(),
567 required: false,
568 repeats: false,
569 });
570 continue;
571 }
572 let required = flag(entry, "required", &path)?;
573 let repeats = flag(entry, "repeats", &path)?;
574 if let Some(name) = entry.get("segment") {
575 let name = name
576 .as_str()
577 .ok_or_else(|| Error::field(&format!("{path}.segment"), "a segment name", name))?;
578 items.push(Item::Segment {
579 name: name.to_string(),
580 required,
581 repeats,
582 });
583 } else if let Some(name) = entry.get("group") {
584 let name = name
585 .as_str()
586 .ok_or_else(|| Error::field(&format!("{path}.group"), "a group name", name))?;
587 let children = entry
588 .get("items")
589 .ok_or_else(|| Error::missing(&format!("{path}.items")))?;
590 items.push(Item::Group {
591 name: name.to_string(),
592 required,
593 repeats,
594 items: parse_items(children, &format!("{path}.items"))?,
595 });
596 } else {
597 return Err(Error::field(
598 &path,
599 "an item with a `segment` or `group` member",
600 entry,
601 ));
602 }
603 }
604 Ok(items)
605}
606
607fn flag(entry: &Value, name: &str, path: &str) -> Result<bool, Error> {
609 match entry.get(name) {
610 None => Ok(false),
611 Some(value) => value
612 .as_bool()
613 .ok_or_else(|| Error::field(&format!("{path}.{name}"), "true or false", value)),
614 }
615}
616
617#[derive(Debug, Clone, PartialEq, Eq)]
619pub enum Error {
620 Json(json::Error),
622 Field {
624 path: String,
626 expected: String,
628 found: String,
630 },
631 Missing {
633 path: String,
635 },
636 UnknownBase(String),
638}
639
640impl Error {
641 fn field(path: &str, expected: &str, found: &Value) -> Error {
642 Error::Field {
643 path: path.to_string(),
644 expected: expected.to_string(),
645 found: found.kind().to_string(),
646 }
647 }
648
649 fn missing(path: &str) -> Error {
650 Error::Missing {
651 path: path.to_string(),
652 }
653 }
654}
655
656impl fmt::Display for Error {
657 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658 match self {
659 Error::Json(error) => write!(f, "{error}"),
660 Error::Field {
661 path,
662 expected,
663 found,
664 } => write!(f, "{path}: expected {expected}, found {found}"),
665 Error::Missing { path } => write!(f, "{path}: required member is missing"),
666 Error::UnknownBase(base) => {
667 write!(f, "`inherits`: {base:?} is not a known HL7 version")
668 }
669 }
670 }
671}
672
673impl std::error::Error for Error {}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678 use crate::Version;
679
680 #[test]
681 fn reads_the_base_release() {
682 let dictionary = Version::V2_5.dictionary();
683 assert_eq!(dictionary.field_type("PID", 5), Some("XPN"));
684 assert_eq!(dictionary.field_type("MSH", 9), Some("MSG"));
685 assert_eq!(dictionary.field_type("OBX", 5), Some(VARIABLE));
686 assert_eq!(dictionary.field_type("PID", 999), None);
687 assert_eq!(dictionary.field_type("ZZZ", 1), None);
688 assert_eq!(
689 dictionary
690 .composite_components("XPN")
691 .map(|c| c[0].as_str()),
692 Some("FN")
693 );
694 assert!(!dictionary.is_composite("ST"));
695 assert!(dictionary.structure("ORU_R01").is_some());
696 }
697
698 #[test]
699 fn a_delta_adds_removes_and_inherits() {
700 let dictionary = Dictionary::from_json(
701 r#"{
702 "inherits": "2.5",
703 "types": { "TS": ["ST"], "XPN": null },
704 "segments": { "ZPD": ["ST", "CX"] },
705 "structures": { "ORU_R01": null }
706 }"#,
707 "test",
708 )
709 .unwrap();
710 assert_eq!(dictionary.composite_components("TS").unwrap(), ["ST"]); assert_eq!(dictionary.composite_components("XPN"), None); assert_eq!(dictionary.field_type("ZPD", 2), Some("CX")); assert_eq!(dictionary.field_type("PID", 5), Some("XPN")); assert_eq!(dictionary.structure("ORU_R01"), None); assert!(dictionary.structure("ACK").is_some()); assert_eq!(dictionary.name(), "test");
717 }
718
719 #[test]
720 fn a_sparse_delta_restates_one_position_and_keeps_the_rest() {
721 let dictionary = Dictionary::from_json(
722 r#"{"inherits": "2.5", "segments": {"MSH": {"12": "ID"}}}"#,
723 "test",
724 )
725 .unwrap();
726 assert_eq!(dictionary.field_type("MSH", 12), Some("ID")); assert_eq!(dictionary.field_type("MSH", 9), Some("MSG")); assert_eq!(dictionary.field_type("MSH", 21), Some("EI")); let dictionary =
732 Dictionary::from_json(r#"{"segments": {"ZZZ": {"3": "CX"}}}"#, "test").unwrap();
733 assert_eq!(dictionary.field_type("ZZZ", 3), Some("CX"));
734 assert_eq!(dictionary.field_type("ZZZ", 1), None);
735 let error =
736 Dictionary::from_json(r#"{"segments": {"ZZZ": {"0": "CX"}}}"#, "test").unwrap_err();
737 assert!(error.to_string().contains("1-based position"), "{error}");
738 }
739
740 #[test]
741 fn reads_structures_including_the_string_shorthand() {
742 let dictionary = Dictionary::from_json(
743 r#"{"structures": {"ZZZ_Z01": [
744 {"segment": "MSH", "required": true},
745 "NTE",
746 {"group": "ORDER", "repeats": true, "items": [{"segment": "ORC", "required": true}]}
747 ]}}"#,
748 "test",
749 )
750 .unwrap();
751 let items = dictionary.structure("ZZZ_Z01").unwrap();
752 assert!(matches!(&items[0], Item::Segment { name, required: true, .. } if name == "MSH"));
753 assert!(matches!(
754 &items[1],
755 Item::Segment {
756 required: false,
757 repeats: false,
758 ..
759 }
760 ));
761 assert!(items[2].repeats() && !items[2].required());
762 assert!(items[2].can_start("ORC"));
763 assert!(!items[2].can_start("OBX"));
764 }
765
766 #[test]
767 fn a_group_can_start_at_any_leading_optional_segment() {
768 let dictionary = Version::V2_5.dictionary();
771 let items = dictionary.structure("ORU_R01").unwrap();
772 let patient_result = &items[2];
773 assert_eq!(patient_result.name(), "PATIENT_RESULT");
774 assert!(patient_result.can_start("PID"));
775 assert!(patient_result.can_start("OBR"));
776 assert!(!patient_result.can_start("MSA"));
777 }
778
779 #[test]
780 fn resolves_obx_5_through_obx_2() {
781 let dictionary = Version::V2_5.dictionary();
782 let message = er7::parse("MSH|^~\\&|A||||1||ORU^R01|1|P|2.5\rOBX|1|CE|X||a^b").unwrap();
783 let obx = message.segment("OBX").unwrap();
784 assert_eq!(dictionary.variable_type(obx), Some("CE"));
785 let message = er7::parse("MSH|^~\\&|A||||1||ORU^R01|1|P|2.5\rOBX|1|NM|X||7").unwrap();
786 assert_eq!(
787 dictionary.variable_type(message.segment("OBX").unwrap()),
788 None
789 );
790 }
791
792 #[test]
793 fn a_field_may_state_its_cardinality_as_well_as_its_type() {
794 let dictionary = Dictionary::from_json(
795 r#"{"segments": {"PID": [
796 "SI",
797 {"type": "CX", "required": true},
798 {"type": "XTN", "repeats": true},
799 {"type": "ST", "required": true, "repeats": true}
800 ]}}"#,
801 "x",
802 )
803 .unwrap();
804 assert_eq!(dictionary.field_type("PID", 1), Some("SI"));
806 assert_eq!(dictionary.field_type("PID", 2), Some("CX"));
807 assert_eq!(
808 dictionary.field_cardinality("PID", 1),
809 Cardinality::default()
810 );
811 assert_eq!(
812 dictionary.field_cardinality("PID", 2),
813 Cardinality {
814 required: true,
815 repeats: false
816 }
817 );
818 assert_eq!(
819 dictionary.field_cardinality("PID", 3),
820 Cardinality {
821 required: false,
822 repeats: true
823 }
824 );
825 assert_eq!(
826 dictionary.field_cardinality("PID", 4),
827 Cardinality {
828 required: true,
829 repeats: true
830 }
831 );
832 assert_eq!(
834 dictionary.field_cardinality("PID", 99),
835 Cardinality::default()
836 );
837 assert_eq!(
838 dictionary.field_cardinality("ZZZ", 1),
839 Cardinality::default()
840 );
841 assert_eq!(
842 dictionary.field_cardinality("PID", 0),
843 Cardinality::default()
844 );
845 }
846
847 #[test]
848 fn cardinality_layers_and_is_removed_like_everything_else() {
849 let dictionary = Dictionary::from_json(
851 r#"{"inherits": "2.5", "segments": {"PID": {"13": {"type": "XTN", "repeats": true}}}}"#,
852 "x",
853 )
854 .unwrap();
855 assert!(dictionary.field_cardinality("PID", 13).repeats);
856 assert!(!dictionary.field_cardinality("PID", 5).repeats);
857 assert_eq!(dictionary.field_type("PID", 5), Some("XPN")); let dictionary =
861 Dictionary::from_json(r#"{"inherits": "2.5", "segments": {"PID": null}}"#, "x")
862 .unwrap();
863 assert_eq!(
864 dictionary.field_cardinality("PID", 13),
865 Cardinality::default()
866 );
867 }
868
869 #[test]
870 fn reports_where_a_malformed_dictionary_is_wrong() {
871 let error = Dictionary::from_json(r#"{"segments": {"PID": [1]}}"#, "x").unwrap_err();
872 assert_eq!(
873 error.to_string(),
874 "segments.PID[0]: expected a data type name, found number"
875 );
876 let error = Dictionary::from_json(r#"{"inherits": "9.9"}"#, "x").unwrap_err();
877 assert!(matches!(error, Error::UnknownBase(_)), "{error}");
878 let error =
879 Dictionary::from_json(r#"{"structures": {"A": [{"group": "G"}]}}"#, "x").unwrap_err();
880 assert_eq!(
881 error.to_string(),
882 "structures.A[0].items: required member is missing"
883 );
884 assert!(matches!(
885 Dictionary::from_json("not json", "x"),
886 Err(Error::Json(_))
887 ));
888 }
889
890 #[test]
891 fn layering_over_an_explicit_base_ignores_inherits() {
892 let base = Dictionary::from_json(r#"{"segments": {"AAA": ["ST"]}}"#, "base").unwrap();
893 let over = Dictionary::from_json_over(
894 r#"{"inherits": "2.5", "segments": {"BBB": ["NM"]}}"#,
895 "over",
896 &base,
897 )
898 .unwrap();
899 assert_eq!(over.field_type("AAA", 1), Some("ST"));
900 assert_eq!(over.field_type("BBB", 1), Some("NM"));
901 assert_eq!(
902 over.field_type("PID", 5),
903 None,
904 "2.5 must not have been pulled in"
905 );
906 }
907}