reject_by_default/reject_by_default.rs
1//! The other posture: reject every value by default, and name what to
2//! accept.
3//!
4//! Use this when the message is unfamiliar — full of `Z` segments nobody
5//! documented, say — and the answer to "is there anything else in here?"
6//! has to be "no" rather than "not that I listed".
7//!
8//! Run with: `cargo run --example reject_by_default`
9
10use er7_redact::{Action, Policy, Redactor};
11
12fn main() -> Result<(), er7_redact::Error> {
13 let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ORU^R01|MSG9|P|2.5\r\
14 PID|1||PATID1234||EVERYWOMAN^EVE||19610615|F\r\
15 OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|100-199|H|||F\r\
16 ZPD|1|LOCAL^EXTENSION^SEGMENT";
17
18 // Rejecting by default covers every leaf no rule named; a `keep` rule
19 // accepts a position, exempting it from that. A rejecting rule would
20 // beat the `keep` whichever order the two were written in (D19).
21 let policy = Policy::all_but_the_header()
22 .with("OBX-2", Action::Keep)? // value type
23 .with("OBX-3", Action::Keep)? // observation identifier
24 .with("OBX-5", Action::Keep)? // the number the test asserts on
25 .with("OBX-6", Action::Keep)?; // units
26
27 let mut message = er7::parse(text)?;
28 let report = Redactor::new(policy).redact(&mut message);
29 println!("{}\n", message.to_er7().replace('\r', "\n"));
30
31 // The header is untouched, so the message still routes and still says
32 // which version it is.
33 assert_eq!(message.query("MSH-9")?.as_deref(), Some("ORU^R01"));
34 assert_eq!(message.version().as_deref(), Some("2.5"));
35
36 // The result is intact, because four rules said so.
37 assert_eq!(message.query("OBX-3.2")?.as_deref(), Some("Cholesterol"));
38 assert_eq!(message.query("OBX-5")?.as_deref(), Some("187"));
39
40 // Everything else is gone — including the local segment, which no
41 // curated policy could have known about.
42 assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
43 assert_eq!(message.query("ZPD-2.1")?.as_deref(), Some("REDACTED"));
44
45 // The cost: values a positional policy would have kept are gone too.
46 assert_eq!(message.query("PID-8")?.as_deref(), Some("REDACTED"));
47 assert_eq!(message.query("OBX-8")?.as_deref(), Some("REDACTED"));
48
49 println!("{} positions changed", report.len());
50
51 // And what this posture is really for: a payload that is not ER7 at
52 // all has no positions to name, so the policy says outright what
53 // becomes of it. The curated policy above refuses one, which is what
54 // makes the CLI exit non-zero rather than write it out.
55 let redactor = Redactor::new(Policy::all_but_the_header());
56 assert_eq!(redactor.unrecognised("{\"name\": \"EVERYWOMAN\"}"), None);
57
58 // `Policy::reject_all` masks it whole instead — nothing routable
59 // survives, and neither does anything else.
60 let redactor = Redactor::new(Policy::reject_all());
61 assert_eq!(
62 redactor.unrecognised("EVERYWOMAN").as_deref(),
63 Some("**********")
64 );
65 Ok(())
66}