serde_hl7_v2/lib.rs
1//! # serde-hl7-v2
2//!
3//! Serde support for [`hl7_2`], the HL7® v2 dictionary layer: wrap a parsed
4//! [`hl7_2::Message`] in [`Message`], its generic-mode tree in [`Node`], or a
5//! validation finding in [`Diagnostic`], and it can flow through
6//! `serde_json`, `serde_yaml`, `bincode`, or any other Serde data format —
7//! for storing a message in a document database, logging its
8//! dictionary-named tree as structured JSON, returning validation results
9//! from a web API, or testing against a literal.
10//!
11//! [`hl7_2`] has one runtime dependency, `er7`, and no Serde support of its
12//! own, deliberately: adding `serde` there would cost every user of `hl7-2`
13//! a dependency they may not want. This crate is the bridge instead — two
14//! dependencies, `serde` and `hl7-2`, and nothing else. Every impl is
15//! written by hand against the low-level `Serializer`/`Deserializer`/
16//! `Visitor` traits, following the pattern [serde's own
17//! documentation](https://docs.rs/serde/latest/serde/) walks through for a
18//! manual implementation — no `#[derive(Serialize)]` anywhere, because the
19//! types wrapped here are foreign and the shapes below are chosen, not
20//! derived.
21//!
22//! Most callers get this crate through the `serde-hl7` umbrella, which
23//! re-exports it as `serde_hl7::v2`, the same way `hl7` re-exports
24//! `hl7-2` as `hl7::v2`.
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_v2::Message;
37//!
38//! let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ORU^R01|MSG9|P|2.5\r\
39//! PID|1||12345^^^ACME^MR||SMITH^JOHN^Q||19800101|M\r\
40//! OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|||||F";
41//!
42//! let message = Message::parse(text)?;
43//!
44//! // Out to JSON, and back — this crate never mentions JSON itself; any
45//! // Serde format works the same way.
46//! let json = serde_json::to_string(&message)?;
47//! let back: Message = serde_json::from_str(&json)?;
48//!
49//! assert_eq!(back.to_er7(), text);
50//! assert_eq!(back.version(), message.version());
51//! # Ok(())
52//! # }
53//! ```
54//!
55//! # The shape each type serializes as
56//!
57//! | `hl7-2` type | Wrapper | Serializes as | Deserializes |
58//! |---|---|---|---|
59//! | [`hl7_2::Message`] | [`Message`] | object: `{"version": "2.5", "er7": "MSH\|^~\\&\|…"}` | yes — re-parsed through `hl7_2`, so the dictionary is resolved, not shipped |
60//! | [`hl7_2::Node`] | [`Node`] | object: `{"name", "path", "kind", "text", "null", "children": [...]}` | no — `hl7_2` builds trees only from a parsed message |
61//! | [`hl7_2::generic::Kind`] | [`NodeKind`] | one of the strings `"Group"`, `"Segment"`, `"Field"`, `"Component"`, `"Subcomponent"` | yes |
62//! | [`hl7_2::Diagnostic`] | [`Diagnostic`] | object: `{"severity", "kind", "path", "detail"}` | yes |
63//! | [`hl7_2::Severity`] | [`Severity`] | `"Error"` or `"Warning"` | yes |
64//! | [`hl7_2::validate::Kind`] | [`DiagnosticKind`] | the variant name, e.g. `"SegmentMissing"` | yes |
65//! | [`hl7_2::Version`] | [`Version`] | the release as MSH-12 spells it: `"2.5.1"` | yes |
66//!
67//! The message itself travels as its ER7 text plus the release it was read
68//! as, not as a tree: `hl7-2` is the dictionary layer, not the encoding
69//! layer, and the encoding layer's tree already has a Serde crate of its
70//! own — `serde-er7`, over [`hl7_2::er7`]'s types, reachable from
71//! [`hl7_2::Message::raw`]. What this crate adds on top is the dictionary-aware
72//! view: [`Node`], with `PID.5` and `XPN.1` for names, and [`Diagnostic`],
73//! with what validation found.
74//!
75//! Deserializing an object-shaped type ignores a key it does not recognize,
76//! by default. A caller who wants a typo in a hand-written JSON fixture
77//! reported instead deserializes into [`Strict<Message>`](Strict) or
78//! [`Strict<Diagnostic>`](Strict), which rejects an unknown key rather than
79//! ignoring it.
80//!
81//! A message read through a caller's own dictionary — `hl7-2`'s schema mode
82//! — deserializes through [`Message::seed`], which carries the
83//! [`hl7_2::Options`] the plain `Deserialize` impl has no way to be given.
84//!
85//! # What is deliberately not here
86//!
87//! This crate adds exactly one thing to [`hl7_2`]: Serde support for its
88//! existing public types. It does not add a wire format (`serde_json` is a
89//! dev-dependency only), a tree shape for the ER7 encoding (that is
90//! `serde-er7`'s), or Serde support for [`hl7_2::Dictionary`] — dictionaries
91//! are JSON already, in the shape `hl7_2::Dictionary::from_json` reads, and
92//! a second shape would be a second thing to keep in step.
93//!
94//! # Documentation
95//!
96//! `spec/index.md` in the repository is this crate's specification — what
97//! each wrapper type must serialize as and why. Runnable programs are in
98//! `examples/`.
99
100// No `unsafe` anywhere in this crate, enforced rather than merely true:
101// `forbid` cannot be lifted by an `allow` further down, so this is a
102// property a reviewer can rely on without reading the sources. See
103// SECURITY.md.
104#![forbid(unsafe_code)]
105#![warn(missing_docs, clippy::pedantic)]
106
107mod diagnostic;
108mod kind;
109mod message;
110mod node;
111mod strict;
112mod version;
113
114pub use diagnostic::Diagnostic;
115pub use kind::{DiagnosticKind, NodeKind, Severity};
116pub use message::{Message, MessageSeed};
117pub use node::Node;
118pub use strict::Strict;
119pub use version::Version;
120
121// Re-exported so a caller can name `hl7_2::Options`, `hl7_2::Error`, and
122// the rest — and `hl7_2::er7` beneath them — without adding their own
123// dependency, the same convenience `hl7-2` itself extends for `er7`.
124pub use hl7_2;