redact_absent_empty_null/redact_absent_empty_null.rs
1//! How redaction treats the three states HL7 keeps apart: a value that was
2//! never sent, one sent blank, and one the sender is clearing.
3//!
4//! Getting this wrong is a patient-safety bug rather than a privacy one,
5//! so it is worth seeing worked through.
6//!
7//! Run with: `cargo run --example redact_absent_empty_null`
8
9use er7_redact::{Action, Policy, Redactor};
10
11fn main() -> Result<(), er7_redact::Error> {
12 // PID-1 has a value, PID-2 was sent blank, PID-3 is the explicit null,
13 // and PID-4 onwards was never sent at all.
14 let text = "MSH|^~\\&|LAB\rPID|1||\"\"";
15
16 let policy = Policy::accept_all()
17 .with("PID-1", Action::redacted())?
18 .with("PID-2", Action::redacted())?
19 .with("PID-3", Action::redacted())?
20 .with("PID-9", Action::redacted())?;
21
22 let mut message = er7::parse(text)?;
23 let report = Redactor::new(policy).redact(&mut message);
24
25 // Only the field that carried a value changed.
26 assert_eq!(message.to_er7(), "MSH|^~\\&|LAB\rPID|REDACTED||\"\"");
27 assert_eq!(report.len(), 1);
28
29 // Why each of the other three was left alone:
30 //
31 // PID-2 was empty. Writing REDACTED into it would invent a value, and
32 // would announce that one used to be there — which is a disclosure.
33 assert!(message.segment("PID").unwrap().field(2).unwrap().is_empty());
34 //
35 // PID-3 is the explicit null: an instruction to the receiver to clear
36 // its stored value, not patient data. Overwriting it would turn
37 // "clear this" into a value, and leave a withdrawn record standing.
38 assert!(message.segment("PID").unwrap().field(3).unwrap().is_null());
39 //
40 // PID-9 was never sent. Redaction does not lengthen a segment to reach
41 // a position that is not there: padding would change what the message
42 // says, and eleven new trailing pipes would announce the redaction.
43 assert!(message.segment("PID").unwrap().field(9).is_none());
44
45 // To *make* a position null — to tell the receiver to clear it — ask
46 // for that, which is the one action that changes the shape of a
47 // message, because an HL7 null is a single `""`.
48 let mut message = er7::parse("MSH|^~\\&|LAB\rPID|1||9||SMITH^JOHN")?;
49 let policy = Policy::accept_all().with("PID-5", Action::Null)?;
50 Redactor::new(policy).redact(&mut message);
51 assert_eq!(message.to_er7(), "MSH|^~\\&|LAB\rPID|1||9||\"\"");
52
53 // Compare with `clear`, which says nothing rather than saying "delete".
54 let mut message = er7::parse("MSH|^~\\&|LAB\rPID|1||9||SMITH^JOHN")?;
55 let policy = Policy::accept_all().with("PID-5", Action::Clear)?;
56 Redactor::new(policy).redact(&mut message);
57 assert_eq!(message.to_er7(), "MSH|^~\\&|LAB\rPID|1||9||^");
58
59 println!("absent, empty, and null all survived redaction unchanged");
60 Ok(())
61}