Skip to main content

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}