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}