Skip to main content

Policy

Struct Policy 

Source
pub struct Policy {
    pub rules: Vec<Rule>,
    pub posture: Posture,
    pub unrecognised: Unrecognised,
}
Expand description

An ordered list of rules, plus what to do with everything they do not name.

Rules apply in order, each to the message as it stands (D7, spec §2.4). The Posture then runs over every leaf that no rule named (D9, spec §2.6), and Unrecognised covers a payload that is not ER7 at all (D21, spec §2.8).

§A reject beats an accept (D19)

A rule whose action is Action::Keep accepts the position it names; any other action rejects it. Where a leaf is named by both, the rejecting rule wins — whichever order the two rules are in, and at whatever depth, so a reject naming a whole segment beats an accept naming one field inside it.

A leaf named by both is a policy somebody got wrong, and redacting it is the direction that fails safely (spec §1.5, priority 1): a value redacted by mistake costs a policy edit, and a value left behind by mistake cannot be recalled.

The mirror of that rule: an accept naming a whole segment is not narrowed by the posture. MSH keep exempts every leaf of the header, including ones the policy’s author never saw. Only a reject rule reaches back into it.

Example:

use er7_redact::{Action, Policy, Redactor};

// Redact what is listed...
let listed = Policy::accept_all()
    .with("PID-5", Action::redacted())?
    .with("PID-7", Action::First(4))?;

// ...or redact everything that is not.
let everything_else = Policy::reject_all().with("MSH", Action::Keep)?;

let mut message = er7::parse("MSH|^~\\&|LAB\rPID|1||9||SMITH^JOHN||19610615")?;
Redactor::new(listed).redact(&mut message);
assert_eq!(message.to_er7(), "MSH|^~\\&|LAB\rPID|1||9||REDACTED^REDACTED||1961");

Fields§

§rules: Vec<Rule>

The rules, in the order they apply.

§posture: Posture

What every leaf no rule named gets.

§unrecognised: Unrecognised

What a payload that is not ER7 gets.

Implementations§

Source§

impl Policy

Source

pub fn accept_all() -> Policy

Accept everything: no rules, nothing redacted, and a payload that is not ER7 passed through unchanged (spec §5.6).

This is the starting point for building a policy rule by rule. On its own it does nothing at all, and it says so: a policy named “accept all” that quietly replaced an unparseable payload with *** would be the one surprise it has no excuse for.

A policy file that states no defaults is not quite this: it accepts by default too, but it refuses an unrecognised payload, because it was written by somebody who did not think about one (spec §6.1). Ask for Unrecognised::Pass in the file to get it.

Example:

use er7_redact::{Action, Policy, Posture, Redactor, Unrecognised};

let policy = Policy::accept_all();
assert_eq!(policy.posture, Posture::Accept);
assert_eq!(policy.unrecognised, Unrecognised::Pass);
assert!(policy.is_empty());

// It changes nothing, and reports nothing.
let mut message = er7::parse("MSH|^~\\&|LAB\rPID|1||9||SMITH")?;
let report = Redactor::new(policy).redact(&mut message);
assert_eq!(message.to_er7(), "MSH|^~\\&|LAB\rPID|1||9||SMITH");
assert!(report.is_empty());
Examples found in repository?
examples/redact_absent_empty_null.rs (line 16)
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}
More examples
Hide additional examples
examples/write_a_policy.rs (line 14)
8fn main() -> Result<(), er7_redact::Error> {
9    // 1. Built in Rust, rule by rule. Order matters: rules apply in the
10    //    order they are listed. `accept_all` is the starting point that
11    //    redacts nothing until a rule says so — the other one is
12    //    `reject_all`, which redacts everything until a `keep` rule says
13    //    otherwise.
14    let built = Policy::accept_all()
15        .with("PID-3.1", Action::Pseudonym)?
16        .with("PID-5", Action::redacted())?
17        .with("PID-7", Action::First(4))?
18        .with("PID-19", Action::Clear)?;
19
20    // 2. Read from a policy file — the same thing, in the form a team
21    //    reviews in a pull request.
22    let read = Policy::parse(
23        "
24        PID-3.1  pseudonym    # keep linkage, lose the record number
25        PID-5    replace REDACTED
26        PID-7    first 4      # the birth year is enough for most tests
27        PID-19   clear
28        ",
29    )?;
30    assert_eq!(built.rules, read.rules);
31
32    // Both accept by default: a position no rule names is left alone.
33    assert_eq!(built.posture, Posture::Accept);
34    assert_eq!(read.posture, Posture::Accept);
35
36    // They differ on one thing, and it is worth knowing about. A payload
37    // that is not ER7 at all has no positions in it, so no rule can speak
38    // to it. `accept_all` passes one through, because it is a policy that
39    // redacts nothing and says so. A policy *file* that mentions no
40    // disposition refuses one instead: it was written by somebody who may
41    // simply not have considered the case, and refusing loses no value
42    // quietly.
43    assert_eq!(built.unrecognised, Unrecognised::Pass);
44    assert_eq!(read.unrecognised, Unrecognised::Refuse);
45
46    // Either way, say it outright and the two agree.
47    let built = built.on_unrecognised(Unrecognised::Refuse);
48    assert_eq!(built, read);
49
50    // 3. Start from a built-in and add to it. `--show-policy` on the
51    //    command line writes the built-in out as a file to edit.
52    let extended = Policy::patient_identifiers()
53        .with("NTE-3", Action::Clear)? // free text: nothing positional finds what is in here
54        .with("OBX-5", Action::Clear)?;
55    assert_eq!(
56        extended.rules.len(),
57        Policy::patient_identifiers().rules.len() + 2
58    );
59
60    // A policy writes itself back out in the file format, so the one that
61    // ran can be recorded beside the message it redacted.
62    println!("{built}");
63    assert_eq!(Policy::parse(&built.to_string())?, built);
64
65    let text = "MSH|^~\\&|LAB\rPID|1||PATID1234||EVERYWOMAN^EVE||19610615|F";
66    let mut message = er7::parse(text)?;
67    Redactor::new(built).redact(&mut message);
68    assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
69
70    // A malformed policy is rejected at load time, with the line number:
71    // a typo here means a value that silently was not redacted.
72    let error = Policy::parse("PID-5 obfuscate").unwrap_err();
73    println!("{error}");
74    assert!(error.to_string().contains("policy line 1"));
75
76    Ok(())
77}
Source

pub fn reject_all() -> Policy

Reject everything: no rules, replace REDACTED over every leaf, and a payload that is not ER7 masked whole (spec §5.6).

The strictest thing in the crate, and it takes the header with it: everything from MSH-3 on reads REDACTED, so the message is no longer routable or identifiable. Policy::all_but_the_header is the same posture with the header kept, and is usually what is wanted.

MSH-1 and MSH-2 survive, as they survive everything: they are the delimiters themselves (D5, spec §4.4).

Example:

use er7_redact::{Action, Policy, Redactor};

let policy = Policy::reject_all().with("OBX-2", Action::Keep)?;
let mut message = er7::parse("MSH|^~\\&|LAB\rOBX|1|NM|2093-3||187")?;
Redactor::new(policy).redact(&mut message);

assert_eq!(
    message.to_er7(),
    "MSH|^~\\&|REDACTED\rOBX|REDACTED|NM|REDACTED||REDACTED",
);
Examples found in repository?
examples/reject_by_default.rs (line 60)
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}
Source

pub fn patient_identifiers() -> Policy

The curated policy: the positions that carry a patient identifier in PID, NK1, PV1, GT1, and IN1.

It accepts by default, so a position the table does not name is left as it is, and it refuses a payload that is not ER7: a list of positions has no opinion about input with no positions in it, and refusing is the fail-closed answer (spec §2.8).

The whole table is written out in spec §5.1, with a reason for each action. It is a starting point, not a compliance certification (D14): it does not touch free text, quasi-identifiers, or local Z segments, and it does not know which positions your senders actually use. Read spec §5.4 and §5.5 before relying on it.

Example:

use er7_redact::{Policy, Redactor};

let mut message = er7::parse(
    "MSH|^~\\&|LAB\rPID|1||PATID1234||EVERYWOMAN^EVE||19610615|F|||12 ELM ST^^BOSTON",
)?;
let report = Redactor::new(Policy::patient_identifiers()).redact(&mut message);

assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
assert_eq!(message.query("PID-7")?.as_deref(), Some("1961"));
assert_eq!(message.query("PID-11.1")?.as_deref(), Some(""));
assert_ne!(message.query("PID-3")?.as_deref(), Some("PATID1234"));

// The sex is not an identifier, so the default policy leaves it.
assert_eq!(message.query("PID-8")?.as_deref(), Some("F"));
assert!(!report.is_empty());
§Panics

Only if the table below is edited to hold something that is not an HL7 path — every entry is a literal, and the_documented_positions_match_the_built_in_policy in tests/integration.rs checks the whole table against spec §5.1.

Examples found in repository?
examples/redact_a_message.rs (line 14)
7fn main() -> Result<(), er7_redact::Error> {
8    let text = "MSH|^~\\&|ADT1|MCM|LABADT|MCM|20260815140000||ADT^A08|MSG00001|P|2.5\r\
9                PID|1||PATID1234^^^ADT1^MR||EVERYWOMAN^EVE^E||19610615|F|||\
10                1200 N ELM STREET^^GREENSBORO^NC^27401-1020||(919)379-1212\r\
11                OBX|1|NM|2093-3^Cholesterol^LN||187|mg/dL|100-199|H|||F";
12
13    let mut message = er7::parse(text)?;
14    let report = Redactor::new(Policy::patient_identifiers()).redact(&mut message);
15
16    println!("{}\n", message.to_er7().replace('\r', "\n"));
17    println!("{} positions changed:\n{report}", report.len());
18
19    // The identifiers are gone.
20    assert_eq!(
21        message.query("PID-5")?.as_deref(),
22        Some("REDACTED^REDACTED^REDACTED")
23    );
24    assert_eq!(message.query("PID-7")?.as_deref(), Some("1961"));
25    assert_eq!(message.query("PID-11")?.as_deref(), Some("^^^^"));
26    assert_eq!(message.query("PID-13")?.as_deref(), Some(""));
27    assert_ne!(message.query("PID-3.1")?.as_deref(), Some("PATID1234"));
28
29    // The message is not.
30    assert_eq!(message.control_id().as_deref(), Some("MSG00001"));
31    assert_eq!(message.query("PID-3.4")?.as_deref(), Some("ADT1")); // assigning authority
32    assert_eq!(message.query("PID-8")?.as_deref(), Some("F")); // not an identifier
33    assert_eq!(message.query("OBX-5")?.as_deref(), Some("187")); // the clinical content
34    assert!(er7::parse(&message.to_er7()).is_ok());
35
36    // And the shape did not move: every position that held a value still
37    // exists, so a test that asserted on PID-11.3 still finds it.
38    assert_eq!(message.query("PID-11.3")?.as_deref(), Some(""));
39
40    Ok(())
41}
More examples
Hide additional examples
examples/read_the_report.rs (line 15)
8fn main() -> Result<(), er7_redact::Error> {
9    let text = "MSH|^~\\&|ADT1|MCM||||||ADT^A08|MSG1|P|2.5\r\
10                PID|1||PATID1234^^^ADT1^MR||EVERYWOMAN^EVE^E||19610615|F|||\
11                12 ELM ST^^BOSTON^MA^02101||555-555-1111~555-555-2222\r\
12                NK1|1|EVERYMAN^ADAM|SPO";
13
14    let mut message = er7::parse(text)?;
15    let report = Redactor::new(Policy::patient_identifiers()).redact(&mut message);
16
17    for change in &report.changes {
18        println!("{:<18} {}", change.path.to_string(), change.action);
19    }
20
21    // One row per leaf that actually changed — so a name in three
22    // components is three rows, and a repeated field is one row per
23    // repetition.
24    let paths: Vec<String> = report.changes.iter().map(|c| c.path.to_string()).collect();
25    assert!(paths.contains(&"PID[1]-5[1].1.1".to_string()));
26    assert!(paths.contains(&"PID[1]-5[1].3.1".to_string()));
27    assert!(paths.contains(&"PID[1]-13[2].1.1".to_string()));
28
29    // Every path is fully qualified, which means it is also a valid
30    // `er7 --query` argument: paste one in to see what is there now.
31    for change in &report.changes {
32        assert!(message.query(&change.path.to_string()).is_ok());
33    }
34
35    // A report carries no values — not the old text, and not the new. A
36    // log line quoting the old value puts the patient's name into the log.
37    let printed = report.to_string();
38    for value in ["EVERYWOMAN", "PATID1234", "19610615", "BOSTON"] {
39        assert!(!printed.contains(value));
40    }
41
42    // And nothing that was not there contributes a row: this message has
43    // no GT1 segment, so the guarantor rules matched nothing, which is not
44    // an error.
45    assert!(!paths.iter().any(|p| p.starts_with("GT1")));
46
47    println!("\n{} positions changed", report.len());
48    Ok(())
49}
examples/pseudonyms_and_linkage.rs (line 21)
14fn main() -> Result<(), er7_redact::Error> {
15    let admit = "MSH|^~\\&|ADT1|MCM||||||ADT^A01|MSG1|P|2.5\r\
16                 PID|1||PATID1234^^^ADT1^MR||EVERYWOMAN^EVE";
17    let result = "MSH|^~\\&|LAB|ACME||||||ORU^R01|MSG2|P|2.5\r\
18                  PID|1||PATID1234^^^ADT1^MR||EVERYWOMAN^EVE\r\
19                  OBX|1|NM|2093-3^Cholesterol^LN||187";
20
21    let redactor = Redactor::new(Policy::patient_identifiers()).with_key(KEY);
22
23    let mut admit = er7::parse(admit)?;
24    let mut result = er7::parse(result)?;
25    redactor.redact(&mut admit);
26    redactor.redact(&mut result);
27
28    // The record number is gone from both...
29    let one = admit.query("PID-3.1")?.expect("a value");
30    let two = result.query("PID-3.1")?.expect("a value");
31    assert_ne!(one, "PATID1234");
32
33    // ...and the two messages still agree that this is the same patient,
34    // which is what makes them useful as a test case.
35    assert_eq!(one, two);
36    println!("both messages now say PID-3.1 = {one}");
37
38    // A different key produces an unrelated mapping, so two data sets
39    // redacted under different keys cannot be joined.
40    let mut other = er7::parse("MSH|^~\\&|ADT1|MCM\rPID|1||PATID1234^^^ADT1^MR")?;
41    Redactor::new(Policy::patient_identifiers())
42        .with_key(1)
43        .redact(&mut other);
44    assert_ne!(other.query("PID-3.1")?.expect("a value"), one);
45
46    // The function is available directly, for building an expected value
47    // in a test.
48    assert_eq!(pseudonym(KEY, "PATID1234"), one);
49
50    // What it costs. A pseudonym preserves equality on purpose, so anyone
51    // holding the redacted data can count how many messages each patient
52    // generated — and anyone holding the key can invert the mapping by
53    // trying every candidate identifier, because record numbers come from
54    // small spaces. Inside your own trust boundary, that is a fair trade;
55    // for data leaving it, clear the value instead.
56    let mut leaving = er7::parse("MSH|^~\\&|ADT1|MCM\rPID|1||PATID1234^^^ADT1^MR")?;
57    let policy = Policy::patient_identifiers().with("PID-3.1", Action::Clear)?;
58    Redactor::new(policy).redact(&mut leaving);
59    assert_eq!(leaving.query("PID-3.1")?.as_deref(), Some(""));
60
61    Ok(())
62}
examples/write_a_policy.rs (line 52)
8fn main() -> Result<(), er7_redact::Error> {
9    // 1. Built in Rust, rule by rule. Order matters: rules apply in the
10    //    order they are listed. `accept_all` is the starting point that
11    //    redacts nothing until a rule says so — the other one is
12    //    `reject_all`, which redacts everything until a `keep` rule says
13    //    otherwise.
14    let built = Policy::accept_all()
15        .with("PID-3.1", Action::Pseudonym)?
16        .with("PID-5", Action::redacted())?
17        .with("PID-7", Action::First(4))?
18        .with("PID-19", Action::Clear)?;
19
20    // 2. Read from a policy file — the same thing, in the form a team
21    //    reviews in a pull request.
22    let read = Policy::parse(
23        "
24        PID-3.1  pseudonym    # keep linkage, lose the record number
25        PID-5    replace REDACTED
26        PID-7    first 4      # the birth year is enough for most tests
27        PID-19   clear
28        ",
29    )?;
30    assert_eq!(built.rules, read.rules);
31
32    // Both accept by default: a position no rule names is left alone.
33    assert_eq!(built.posture, Posture::Accept);
34    assert_eq!(read.posture, Posture::Accept);
35
36    // They differ on one thing, and it is worth knowing about. A payload
37    // that is not ER7 at all has no positions in it, so no rule can speak
38    // to it. `accept_all` passes one through, because it is a policy that
39    // redacts nothing and says so. A policy *file* that mentions no
40    // disposition refuses one instead: it was written by somebody who may
41    // simply not have considered the case, and refusing loses no value
42    // quietly.
43    assert_eq!(built.unrecognised, Unrecognised::Pass);
44    assert_eq!(read.unrecognised, Unrecognised::Refuse);
45
46    // Either way, say it outright and the two agree.
47    let built = built.on_unrecognised(Unrecognised::Refuse);
48    assert_eq!(built, read);
49
50    // 3. Start from a built-in and add to it. `--show-policy` on the
51    //    command line writes the built-in out as a file to edit.
52    let extended = Policy::patient_identifiers()
53        .with("NTE-3", Action::Clear)? // free text: nothing positional finds what is in here
54        .with("OBX-5", Action::Clear)?;
55    assert_eq!(
56        extended.rules.len(),
57        Policy::patient_identifiers().rules.len() + 2
58    );
59
60    // A policy writes itself back out in the file format, so the one that
61    // ran can be recorded beside the message it redacted.
62    println!("{built}");
63    assert_eq!(Policy::parse(&built.to_string())?, built);
64
65    let text = "MSH|^~\\&|LAB\rPID|1||PATID1234||EVERYWOMAN^EVE||19610615|F";
66    let mut message = er7::parse(text)?;
67    Redactor::new(built).redact(&mut message);
68    assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
69
70    // A malformed policy is rejected at load time, with the line number:
71    // a typo here means a value that silently was not redacted.
72    let error = Policy::parse("PID-5 obfuscate").unwrap_err();
73    println!("{error}");
74    assert!(error.to_string().contains("policy line 1"));
75
76    Ok(())
77}
Source

pub fn all_but_the_header() -> Policy

The other posture, curated: reject every value, and keep the MSH header so the message stays routable (spec §5.2).

Use it when the message is unfamiliar, or when the answer to “is there anything else in here?” has to be “no” rather than “not that I listed”. The cost is that nothing below MSH is clinically meaningful afterwards; add Keep rules for what a test needs.

Like Policy::patient_identifiers it refuses a payload that is not ER7 rather than guessing (spec §2.8).

The header exception is an ordinary accept rule, so an ordinary reject rule overrides it (D19) — .with("MSH", Action::redacted()) takes the header too.

Example:

use er7_redact::{Action, Policy, Redactor};

let policy = Policy::all_but_the_header().with("OBX-2", Action::Keep)?;
let mut message = er7::parse("MSH|^~\\&|LAB\rOBX|1|NM|2093-3||187")?;
Redactor::new(policy).redact(&mut message);

assert_eq!(
    message.to_er7(),
    "MSH|^~\\&|LAB\rOBX|REDACTED|NM|REDACTED||REDACTED",
);
§Panics

Only if the MSH literal below stops being an HL7 path, which no caller can cause.

Examples found in repository?
examples/reject_by_default.rs (line 21)
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}
Source

pub fn with(self, path: &str, action: Action) -> Result<Policy, Error>

Add a rule, for building a policy in one expression.

§Errors

Error::Er7 when path is not an HL7 path; see Rule::new.

Examples found in repository?
examples/pseudonyms_and_linkage.rs (line 57)
14fn main() -> Result<(), er7_redact::Error> {
15    let admit = "MSH|^~\\&|ADT1|MCM||||||ADT^A01|MSG1|P|2.5\r\
16                 PID|1||PATID1234^^^ADT1^MR||EVERYWOMAN^EVE";
17    let result = "MSH|^~\\&|LAB|ACME||||||ORU^R01|MSG2|P|2.5\r\
18                  PID|1||PATID1234^^^ADT1^MR||EVERYWOMAN^EVE\r\
19                  OBX|1|NM|2093-3^Cholesterol^LN||187";
20
21    let redactor = Redactor::new(Policy::patient_identifiers()).with_key(KEY);
22
23    let mut admit = er7::parse(admit)?;
24    let mut result = er7::parse(result)?;
25    redactor.redact(&mut admit);
26    redactor.redact(&mut result);
27
28    // The record number is gone from both...
29    let one = admit.query("PID-3.1")?.expect("a value");
30    let two = result.query("PID-3.1")?.expect("a value");
31    assert_ne!(one, "PATID1234");
32
33    // ...and the two messages still agree that this is the same patient,
34    // which is what makes them useful as a test case.
35    assert_eq!(one, two);
36    println!("both messages now say PID-3.1 = {one}");
37
38    // A different key produces an unrelated mapping, so two data sets
39    // redacted under different keys cannot be joined.
40    let mut other = er7::parse("MSH|^~\\&|ADT1|MCM\rPID|1||PATID1234^^^ADT1^MR")?;
41    Redactor::new(Policy::patient_identifiers())
42        .with_key(1)
43        .redact(&mut other);
44    assert_ne!(other.query("PID-3.1")?.expect("a value"), one);
45
46    // The function is available directly, for building an expected value
47    // in a test.
48    assert_eq!(pseudonym(KEY, "PATID1234"), one);
49
50    // What it costs. A pseudonym preserves equality on purpose, so anyone
51    // holding the redacted data can count how many messages each patient
52    // generated — and anyone holding the key can invert the mapping by
53    // trying every candidate identifier, because record numbers come from
54    // small spaces. Inside your own trust boundary, that is a fair trade;
55    // for data leaving it, clear the value instead.
56    let mut leaving = er7::parse("MSH|^~\\&|ADT1|MCM\rPID|1||PATID1234^^^ADT1^MR")?;
57    let policy = Policy::patient_identifiers().with("PID-3.1", Action::Clear)?;
58    Redactor::new(policy).redact(&mut leaving);
59    assert_eq!(leaving.query("PID-3.1")?.as_deref(), Some(""));
60
61    Ok(())
62}
More examples
Hide additional examples
examples/redact_absent_empty_null.rs (line 17)
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}
examples/reject_by_default.rs (line 22)
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}
examples/write_a_policy.rs (line 15)
8fn main() -> Result<(), er7_redact::Error> {
9    // 1. Built in Rust, rule by rule. Order matters: rules apply in the
10    //    order they are listed. `accept_all` is the starting point that
11    //    redacts nothing until a rule says so — the other one is
12    //    `reject_all`, which redacts everything until a `keep` rule says
13    //    otherwise.
14    let built = Policy::accept_all()
15        .with("PID-3.1", Action::Pseudonym)?
16        .with("PID-5", Action::redacted())?
17        .with("PID-7", Action::First(4))?
18        .with("PID-19", Action::Clear)?;
19
20    // 2. Read from a policy file — the same thing, in the form a team
21    //    reviews in a pull request.
22    let read = Policy::parse(
23        "
24        PID-3.1  pseudonym    # keep linkage, lose the record number
25        PID-5    replace REDACTED
26        PID-7    first 4      # the birth year is enough for most tests
27        PID-19   clear
28        ",
29    )?;
30    assert_eq!(built.rules, read.rules);
31
32    // Both accept by default: a position no rule names is left alone.
33    assert_eq!(built.posture, Posture::Accept);
34    assert_eq!(read.posture, Posture::Accept);
35
36    // They differ on one thing, and it is worth knowing about. A payload
37    // that is not ER7 at all has no positions in it, so no rule can speak
38    // to it. `accept_all` passes one through, because it is a policy that
39    // redacts nothing and says so. A policy *file* that mentions no
40    // disposition refuses one instead: it was written by somebody who may
41    // simply not have considered the case, and refusing loses no value
42    // quietly.
43    assert_eq!(built.unrecognised, Unrecognised::Pass);
44    assert_eq!(read.unrecognised, Unrecognised::Refuse);
45
46    // Either way, say it outright and the two agree.
47    let built = built.on_unrecognised(Unrecognised::Refuse);
48    assert_eq!(built, read);
49
50    // 3. Start from a built-in and add to it. `--show-policy` on the
51    //    command line writes the built-in out as a file to edit.
52    let extended = Policy::patient_identifiers()
53        .with("NTE-3", Action::Clear)? // free text: nothing positional finds what is in here
54        .with("OBX-5", Action::Clear)?;
55    assert_eq!(
56        extended.rules.len(),
57        Policy::patient_identifiers().rules.len() + 2
58    );
59
60    // A policy writes itself back out in the file format, so the one that
61    // ran can be recorded beside the message it redacted.
62    println!("{built}");
63    assert_eq!(Policy::parse(&built.to_string())?, built);
64
65    let text = "MSH|^~\\&|LAB\rPID|1||PATID1234||EVERYWOMAN^EVE||19610615|F";
66    let mut message = er7::parse(text)?;
67    Redactor::new(built).redact(&mut message);
68    assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
69
70    // A malformed policy is rejected at load time, with the line number:
71    // a typo here means a value that silently was not redacted.
72    let error = Policy::parse("PID-5 obfuscate").unwrap_err();
73    println!("{error}");
74    assert!(error.to_string().contains("policy line 1"));
75
76    Ok(())
77}
Source

pub fn posture(self, posture: Posture) -> Policy

Set what every leaf no rule named gets (spec §2.6).

Posture::Reject with Action::Keep is normalised to Posture::Accept, so that the policy file’s reject keep and this method agree about what they mean.

This is the only way to make a policy less strict: appending one policy to another never weakens it (D20, Policy::append).

Source

pub fn on_unrecognised(self, unrecognised: Unrecognised) -> Policy

Set what a payload that is not ER7 gets (spec §2.8).

Unrecognised::Apply with an action that writes nothing — Action::Keep or Action::Null — is normalised to Unrecognised::Pass, which is what it does.

Examples found in repository?
examples/write_a_policy.rs (line 47)
8fn main() -> Result<(), er7_redact::Error> {
9    // 1. Built in Rust, rule by rule. Order matters: rules apply in the
10    //    order they are listed. `accept_all` is the starting point that
11    //    redacts nothing until a rule says so — the other one is
12    //    `reject_all`, which redacts everything until a `keep` rule says
13    //    otherwise.
14    let built = Policy::accept_all()
15        .with("PID-3.1", Action::Pseudonym)?
16        .with("PID-5", Action::redacted())?
17        .with("PID-7", Action::First(4))?
18        .with("PID-19", Action::Clear)?;
19
20    // 2. Read from a policy file — the same thing, in the form a team
21    //    reviews in a pull request.
22    let read = Policy::parse(
23        "
24        PID-3.1  pseudonym    # keep linkage, lose the record number
25        PID-5    replace REDACTED
26        PID-7    first 4      # the birth year is enough for most tests
27        PID-19   clear
28        ",
29    )?;
30    assert_eq!(built.rules, read.rules);
31
32    // Both accept by default: a position no rule names is left alone.
33    assert_eq!(built.posture, Posture::Accept);
34    assert_eq!(read.posture, Posture::Accept);
35
36    // They differ on one thing, and it is worth knowing about. A payload
37    // that is not ER7 at all has no positions in it, so no rule can speak
38    // to it. `accept_all` passes one through, because it is a policy that
39    // redacts nothing and says so. A policy *file* that mentions no
40    // disposition refuses one instead: it was written by somebody who may
41    // simply not have considered the case, and refusing loses no value
42    // quietly.
43    assert_eq!(built.unrecognised, Unrecognised::Pass);
44    assert_eq!(read.unrecognised, Unrecognised::Refuse);
45
46    // Either way, say it outright and the two agree.
47    let built = built.on_unrecognised(Unrecognised::Refuse);
48    assert_eq!(built, read);
49
50    // 3. Start from a built-in and add to it. `--show-policy` on the
51    //    command line writes the built-in out as a file to edit.
52    let extended = Policy::patient_identifiers()
53        .with("NTE-3", Action::Clear)? // free text: nothing positional finds what is in here
54        .with("OBX-5", Action::Clear)?;
55    assert_eq!(
56        extended.rules.len(),
57        Policy::patient_identifiers().rules.len() + 2
58    );
59
60    // A policy writes itself back out in the file format, so the one that
61    // ran can be recorded beside the message it redacted.
62    println!("{built}");
63    assert_eq!(Policy::parse(&built.to_string())?, built);
64
65    let text = "MSH|^~\\&|LAB\rPID|1||PATID1234||EVERYWOMAN^EVE||19610615|F";
66    let mut message = er7::parse(text)?;
67    Redactor::new(built).redact(&mut message);
68    assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
69
70    // A malformed policy is rejected at load time, with the line number:
71    // a typo here means a value that silently was not redacted.
72    let error = Policy::parse("PID-5 obfuscate").unwrap_err();
73    println!("{error}");
74    assert!(error.to_string().contains("policy line 1"));
75
76    Ok(())
77}
Source

pub fn parse(text: &str) -> Result<Policy, Error>

Read a policy file (spec §6).

Blank lines and # comments are ignored; every other line is either a path, whitespace, and an action, in the order they apply, or one of the three reserved first words — accept, reject, and unrecognised — that set what the policy does by default.

A file that states no defaults accepts by default and refuses a payload that is not ER7: unlike Policy::accept_all, a file was written by somebody who may simply not have considered one, and refusing is the answer that cannot lose a value quietly.

Example:

use er7_redact::{Action, Policy, Posture, Unrecognised};

let policy = Policy::parse("
    MSH    keep      # everything but the header...
    OBX-5  keep      # ...and the numbers the test asserts on

    reject replace REDACTED
    unrecognised mask *
")?;

assert_eq!(policy.rules.len(), 2);
assert_eq!(policy.posture, Posture::Reject(Action::redacted()));
assert_eq!(policy.unrecognised, Unrecognised::Apply(Action::Mask('*')));

// A file that says nothing accepts, and refuses what it cannot read.
let quiet = Policy::parse("PID-5 clear")?;
assert_eq!(quiet.posture, Posture::Accept);
assert_eq!(quiet.unrecognised, Unrecognised::Refuse);

// A malformed line names itself.
let e = Policy::parse("PID-5 obfuscate").unwrap_err();
assert_eq!(e.to_string(), "policy line 1: \"PID-5 obfuscate\": unknown action \"obfuscate\"");
§Errors

Error::BadPolicy naming the line number, the line, and the problem. Reading a policy is the one place this crate is strict, because a typo means a value that silently was not redacted (spec §6.4).

Examples found in repository?
examples/write_a_policy.rs (lines 22-29)
8fn main() -> Result<(), er7_redact::Error> {
9    // 1. Built in Rust, rule by rule. Order matters: rules apply in the
10    //    order they are listed. `accept_all` is the starting point that
11    //    redacts nothing until a rule says so — the other one is
12    //    `reject_all`, which redacts everything until a `keep` rule says
13    //    otherwise.
14    let built = Policy::accept_all()
15        .with("PID-3.1", Action::Pseudonym)?
16        .with("PID-5", Action::redacted())?
17        .with("PID-7", Action::First(4))?
18        .with("PID-19", Action::Clear)?;
19
20    // 2. Read from a policy file — the same thing, in the form a team
21    //    reviews in a pull request.
22    let read = Policy::parse(
23        "
24        PID-3.1  pseudonym    # keep linkage, lose the record number
25        PID-5    replace REDACTED
26        PID-7    first 4      # the birth year is enough for most tests
27        PID-19   clear
28        ",
29    )?;
30    assert_eq!(built.rules, read.rules);
31
32    // Both accept by default: a position no rule names is left alone.
33    assert_eq!(built.posture, Posture::Accept);
34    assert_eq!(read.posture, Posture::Accept);
35
36    // They differ on one thing, and it is worth knowing about. A payload
37    // that is not ER7 at all has no positions in it, so no rule can speak
38    // to it. `accept_all` passes one through, because it is a policy that
39    // redacts nothing and says so. A policy *file* that mentions no
40    // disposition refuses one instead: it was written by somebody who may
41    // simply not have considered the case, and refusing loses no value
42    // quietly.
43    assert_eq!(built.unrecognised, Unrecognised::Pass);
44    assert_eq!(read.unrecognised, Unrecognised::Refuse);
45
46    // Either way, say it outright and the two agree.
47    let built = built.on_unrecognised(Unrecognised::Refuse);
48    assert_eq!(built, read);
49
50    // 3. Start from a built-in and add to it. `--show-policy` on the
51    //    command line writes the built-in out as a file to edit.
52    let extended = Policy::patient_identifiers()
53        .with("NTE-3", Action::Clear)? // free text: nothing positional finds what is in here
54        .with("OBX-5", Action::Clear)?;
55    assert_eq!(
56        extended.rules.len(),
57        Policy::patient_identifiers().rules.len() + 2
58    );
59
60    // A policy writes itself back out in the file format, so the one that
61    // ran can be recorded beside the message it redacted.
62    println!("{built}");
63    assert_eq!(Policy::parse(&built.to_string())?, built);
64
65    let text = "MSH|^~\\&|LAB\rPID|1||PATID1234||EVERYWOMAN^EVE||19610615|F";
66    let mut message = er7::parse(text)?;
67    Redactor::new(built).redact(&mut message);
68    assert_eq!(message.query("PID-5.1")?.as_deref(), Some("REDACTED"));
69
70    // A malformed policy is rejected at load time, with the line number:
71    // a typo here means a value that silently was not redacted.
72    let error = Policy::parse("PID-5 obfuscate").unwrap_err();
73    println!("{error}");
74    assert!(error.to_string().contains("policy line 1"));
75
76    Ok(())
77}
Source

pub fn append(&mut self, other: Policy)

Append another policy’s rules, take the stricter posture, and take the appended policy’s disposition for an unrecognised payload (D20, spec §2.6).

This is how the command line concatenates several --policy files and --rule arguments; order is significant for the rules (D7).

The posture cannot be weakened by appending, and deliberately: a file of extra rules says nothing about its posture, so it accepts by default, and adopting that would switch redaction off for everything the file did not name. Silence is indistinguishable from a decision, so silence is not trusted. To relax a posture, say so with Policy::posture.

The disposition for an unrecognised payload is different, and the appended policy’s wins outright. Nothing is silent there: Policy::parse gives a file that says nothing the strictest disposition there is, Unrecognised::Refuse, so every value one carries is somebody’s decision — and a file that goes to the trouble of writing unrecognised pass should not be quietly overruled by a default it never saw.

Example:

use er7_redact::{Action, Policy, Posture};

let mut policy = Policy::all_but_the_header();
policy.append(Policy::parse("OBX-2 keep")?);

// The appended file accepts by default; the strict policy still rejects.
assert_eq!(policy.posture, Posture::Reject(Action::redacted()));

// And a stricter action in the appended policy does win.
policy.append(Policy::parse("reject clear")?);
assert_eq!(policy.posture, Posture::Reject(Action::Clear));
Source

pub fn is_empty(&self) -> bool

True when the policy would redact nothing at all: no rules, and it accepts by default.

What it does with an unrecognised payload is not part of this: a policy that refuses one still redacts nothing.

Trait Implementations§

Source§

impl Clone for Policy

Source§

fn clone(&self) -> Policy

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Policy

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Policy

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

The canonical policy file (spec §6.5): one rule per line, paths padded to a common width, then the two default lines — always both, whatever they say, so that a reader never has to know which default was the quiet one.

Example:

use er7_redact::{Action, Policy};

let policy = Policy::accept_all()
    .with("PID-5", Action::redacted())?
    .with("PID-7", Action::First(4))?
    .posture(er7_redact::Posture::Reject(Action::Clear));

assert_eq!(policy.to_string(), "\
PID-5  replace REDACTED
PID-7  first 4

reject        clear
unrecognised  pass
");

// And it reads back as the same policy.
assert_eq!(Policy::parse(&policy.to_string())?, policy);
Source§

impl Eq for Policy

Source§

impl PartialEq for Policy

Source§

fn eq(&self, other: &Policy) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Policy

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.