byteor_redaction/types.rs
1//! Public redaction types and outcomes.
2
3use crate::{BTreeMap, String, Vec};
4
5/// Structured record type used for deterministic redaction.
6///
7/// Notes:
8/// - A `BTreeMap` is used to keep iteration order deterministic.
9/// - This type is intentionally small and does not prescribe a wire format.
10pub type Record = BTreeMap<String, Value>;
11
12/// A minimally-typed field value.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum Value {
15 /// UTF-8 string.
16 String(String),
17 /// Raw bytes.
18 Bytes(Vec<u8>),
19 /// Signed 64-bit integer.
20 I64(i64),
21 /// Unsigned 64-bit integer.
22 U64(u64),
23 /// Boolean.
24 Bool(bool),
25}
26
27/// Enforcement mode for a redaction policy.
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum EnforcementMode {
30 /// Reject records containing any configured sensitive fields.
31 Block,
32 /// Transform records deterministically and accept.
33 Transform,
34}
35
36/// Supported deterministic hash algorithms.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum HashAlgo {
39 /// SHA-256 of the canonical field value bytes, emitted as lowercase hex.
40 Sha256,
41}
42
43/// A redaction action for a matching field.
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub enum Action {
46 /// Drop the field from the record.
47 Drop,
48 /// Replace the field value with a fixed value.
49 ReplaceWith(Value),
50 /// Replace the field value with a deterministic hash.
51 Hash(HashAlgo),
52 /// Partially mask the field value.
53 Mask {
54 /// Number of leading characters to preserve.
55 visible_prefix: usize,
56 /// Number of trailing characters to preserve.
57 visible_suffix: usize,
58 /// Character repeated across the masked middle segment.
59 mask_char: char,
60 },
61 /// Limit the field value to at most `max_bytes` bytes.
62 Truncate {
63 /// Maximum number of bytes to preserve.
64 max_bytes: usize,
65 },
66}
67
68/// One redaction rule.
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct Rule {
71 /// Field name.
72 pub field: String,
73 /// Action to apply.
74 pub action: Action,
75}
76
77/// A deterministic redaction policy.
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct RedactionPolicy {
80 rules: Vec<Rule>,
81}
82
83impl RedactionPolicy {
84 /// Create a policy from rules.
85 ///
86 /// Rules are normalized into a stable order (lexicographic by field name).
87 pub fn new(mut rules: Vec<Rule>) -> Self {
88 rules.sort_by(|a, b| a.field.cmp(&b.field));
89 Self { rules }
90 }
91
92 /// Borrow the normalized rules.
93 pub fn rules(&self) -> &[Rule] {
94 &self.rules
95 }
96}
97
98/// Stable enforcement outcome code.
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub enum OutcomeCode {
101 /// Accepted without transforming.
102 Accepted,
103 /// Accepted after applying one or more transforms.
104 Transformed,
105 /// Rejected due to a sensitive field being present.
106 RejectedBlockedField,
107}
108
109/// Result of enforcing a redaction policy.
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct Outcome {
112 /// Whether the record is accepted.
113 pub accepted: bool,
114 /// Stable outcome code.
115 pub code: OutcomeCode,
116 /// Human-readable message.
117 pub message: String,
118 /// Number of transforms applied.
119 pub transforms_applied: u32,
120}
121
122impl Outcome {
123 pub(crate) fn accept(transforms_applied: u32, msg: impl Into<String>) -> Self {
124 Self {
125 accepted: true,
126 code: if transforms_applied == 0 {
127 OutcomeCode::Accepted
128 } else {
129 OutcomeCode::Transformed
130 },
131 message: msg.into(),
132 transforms_applied,
133 }
134 }
135
136 pub(crate) fn reject(code: OutcomeCode, msg: impl Into<String>) -> Self {
137 Self {
138 accepted: false,
139 code,
140 message: msg.into(),
141 transforms_applied: 0,
142 }
143 }
144}
145
146/// Outcome of applying a deterministic redaction action.
147#[derive(Clone, Debug, PartialEq, Eq)]
148pub enum ActionOutcome {
149 /// Remove the field.
150 Drop,
151 /// Replace the field value.
152 ReplaceWith(Value),
153}