Skip to main content

serde_hl7_v3/
lib.rs

1//! # serde-hl7-v3
2//!
3//! Serde support for [`hl7_3`], the HL7® v3 foundation crate: wrap a parsed
4//! [`hl7_3::Message`] in [`Message`], a RIM class in [`Act`] or [`Entity`],
5//! a data type in [`Ii`] or [`Cd`], or a raw XML element in [`Element`],
6//! and it can flow through `serde_json`, `serde_yaml`, `bincode`, or any
7//! other Serde data format — for storing a decoded interaction in a
8//! document database, logging it as structured JSON, returning it from a
9//! web API, or testing against a literal.
10//!
11//! [`hl7_3`] has one runtime dependency, the XML reader it parses through,
12//! and no Serde support of its own, deliberately: adding `serde` there
13//! would cost every user of `hl7-3` a dependency they may not want. This
14//! crate is the bridge instead — two dependencies, `serde` and `hl7-3`, and
15//! nothing else. Every impl is written by hand against the low-level
16//! `Serializer`/`Deserializer`/`Visitor` traits, following the pattern
17//! [serde's own documentation](https://docs.rs/serde/latest/serde/) walks
18//! through for a manual implementation — no `#[derive(Serialize)]`
19//! anywhere, because the types wrapped here are foreign and the shapes
20//! below are chosen, not derived.
21//!
22//! Most callers get this crate through the `serde-hl7` umbrella, which
23//! re-exports it as `serde_hl7::v3`, the same way `hl7` re-exports
24//! `hl7-3` as `hl7::v3`.
25//!
26//! # Trademarks
27//!
28//! HL7®, and FHIR® are the registered trademarks of Health Level Seven
29//! International and their use of these trademarks does not constitute an
30//! endorsement by HL7.
31//!
32//! # Example
33//!
34//! ```
35//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! use serde_hl7_v3::Message;
37//!
38//! let xml = r#"
39//! <QUQI_IN000001UV01 xmlns="urn:hl7-org:v3">
40//!   <id root="2.16.840.1.113883.19.5" extension="MSG00001"/>
41//!   <interactionId root="2.16.840.1.113883.1.6" extension="QUQI_IN000001UV01"/>
42//!   <controlActProcess classCode="CACT" moodCode="EVN">
43//!     <code code="QUQI_TE000001UV01"/>
44//!     <subject><observation classCode="OBS" moodCode="EVN"/></subject>
45//!   </controlActProcess>
46//! </QUQI_IN000001UV01>"#;
47//!
48//! let message = Message::parse(xml)?;
49//!
50//! // Out to JSON, and back — this crate never mentions JSON itself; any
51//! // Serde format works the same way.
52//! let json = serde_json::to_string(&message)?;
53//! let back: Message = serde_json::from_str(&json)?;
54//!
55//! assert_eq!(back, message);
56//! assert_eq!(back.interaction_id.as_ref().unwrap().extension.as_deref(), Some("QUQI_IN000001UV01"));
57//! # Ok(())
58//! # }
59//! ```
60//!
61//! # The shape each type serializes as
62//!
63//! Every type is an object whose keys are the `hl7-3` field names in
64//! lowerCamelCase — which, for every field that corresponds to an HL7 v3
65//! XML attribute or element, is that attribute's or element's own name:
66//! `classCode`, `moodCode`, `codeSystem`, `effectiveTime`, `interactionId`.
67//! An `Option` field serializes as `null` when absent; a `Vec` field as an
68//! array, `[]` when empty.
69//!
70//! | `hl7-3` type | Wrapper | Keys |
71//! |---|---|---|
72//! | [`hl7_3::Message`] | [`Message`] | `id`, `creationTime`, `interactionId`, `sender`, `receiver`, `controlAct` |
73//! | [`hl7_3::ControlAct`] | [`ControlAct`] | `classCode`, `moodCode`, `code`, `domain` |
74//! | [`hl7_3::xml::Element`] | [`Element`] | `name`, `attributes` (an object), `text`, `children` |
75//! | [`hl7_3::Ii`] | [`Ii`] | `root`, `extension` |
76//! | [`hl7_3::Cd`] | [`Cd`] | `code`, `codeSystem`, `displayName` |
77//! | [`hl7_3::Ivl`] | [`Ivl`] | `value`, `low`, `high` |
78//! | [`hl7_3::Pq`] | [`Pq`] | `value`, `unit` |
79//! | [`hl7_3::Ed`] | [`Ed`] | `mediaType`, `representation`, `text` |
80//! | [`hl7_3::NullFlavor`] | [`NullFlavor`] | a bare string: the code, `"NI"`, `"UNK"`, … |
81//! | [`hl7_3::rim::Act`] | [`Act`] | `classCode`, `moodCode`, `id`, `code`, `statusCode`, `effectiveTime`, `text` |
82//! | [`hl7_3::rim::Entity`] | [`Entity`] | `classCode`, `determinerCode`, `id`, `code`, `name` |
83//! | [`hl7_3::rim::Role`] | [`Role`] | `classCode`, `id`, `code`, `statusCode`, `effectiveTime` |
84//! | [`hl7_3::rim::Participation`] | [`Participation`] | `typeCode`, `time`, `functionCode` |
85//! | [`hl7_3::rim::ActRelationship`] | [`ActRelationship`] | `typeCode`, `inversionInd` |
86//! | [`hl7_3::rim::RoleLink`] | [`RoleLink`] | `typeCode` |
87//!
88//! Deserializing any object ignores a key it does not recognize, by
89//! default. A caller who wants a typo in a hand-written JSON fixture
90//! reported instead deserializes into [`Strict<T>`](Strict), which rejects
91//! an unknown key — at the top level or nested anywhere beneath — rather
92//! than ignoring it.
93//!
94//! # What round-trips
95//!
96//! A value through any Serde format and back is equal to the value that
97//! went in — every field of every type here is public, so nothing is lost
98//! in either direction. What this crate does not promise is XML: `hl7-3`
99//! reads a message into its three-level envelope and keeps the payload as
100//! a raw [`Element`], and it has no XML writer, so there is no
101//! "back to the original document" here to guarantee. The `Element` tree
102//! itself round-trips exactly, attributes and children included.
103//!
104//! # What is deliberately not here
105//!
106//! This crate adds exactly one thing to [`hl7_3`]: Serde support for its
107//! existing public types. It does not add a wire format (`serde_json` is a
108//! dev-dependency only), an XML writer, or Serde support for the
109//! struct-mode trait [`hl7_3::FromElement`] — a caller's own struct is
110//! theirs to derive Serde for.
111//!
112//! # Documentation
113//!
114//! `spec/index.md` in the repository is this crate's specification — what
115//! each wrapper type must serialize as and why. Runnable programs are in
116//! `examples/`.
117
118// No `unsafe` anywhere in this crate, enforced rather than merely true:
119// `forbid` cannot be lifted by an `allow` further down, so this is a
120// property a reviewer can rely on without reading the sources. See
121// SECURITY.md.
122#![forbid(unsafe_code)]
123#![warn(missing_docs, clippy::pedantic)]
124
125#[macro_use]
126mod object;
127
128mod element;
129mod message;
130mod null_flavor;
131mod rim;
132mod strict;
133mod vocabulary;
134
135pub use element::Element;
136pub use message::{ControlAct, Message};
137pub use null_flavor::NullFlavor;
138pub use rim::{Act, ActRelationship, Entity, Participation, Role, RoleLink};
139pub use strict::Strict;
140pub use vocabulary::{Cd, Ed, Ii, Ivl, Pq};
141
142// Re-exported so a caller can name `hl7_3::Error`, `hl7_3::xml::Element`,
143// and the rest without adding their own dependency, the same convenience
144// `hl7-3` itself extends for its XML reader.
145pub use hl7_3;