Skip to main content

qubit_redact/policy/redaction_policy_builder/
fields_builder.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Transactional view over application field rules.
9
10use super::MaskingPolicy;
11use super::PolicyError;
12use super::PolicyLocation;
13use super::RedactionFloor;
14use super::RedactionPolicyBuilder;
15use super::Sensitivity;
16use crate::policy::FieldNameMatching;
17use crate::policy::MaskPolicy;
18use crate::policy::SensitiveFieldPreset;
19use crate::policy::UnknownFieldPolicy;
20
21/// Mutable view over the base field policy.
22///
23/// # Examples
24///
25/// ```
26/// use qubit_redact::RedactionPolicy;
27/// use qubit_redact::Sensitivity;
28///
29/// let policy = RedactionPolicy::builder().fields(|fields| {
30///     fields.secret_sensitive("pin").allow_exact("request_id");
31/// })?.build()?;
32/// assert_eq!(policy.sensitivity_for("pin"), Some(Sensitivity::Secret));
33/// # Ok::<(), qubit_redact::PolicyError>(())
34/// ```
35pub struct FieldsBuilder<'a> {
36    /// Root builder receiving validated field changes.
37    pub(super) builder: &'a mut RedactionPolicyBuilder,
38    /// First validation error recorded by the transactional view.
39    pub(super) error: Option<PolicyError>,
40}
41
42impl FieldsBuilder<'_> {
43    /// Marks a field as low sensitivity.
44    #[inline(always)]
45    pub fn low_sensitive(&mut self, field: &str) -> &mut Self {
46        self.set_sensitive(field, Sensitivity::Low)
47    }
48
49    /// Marks a field as medium sensitivity.
50    #[inline(always)]
51    pub fn medium_sensitive(&mut self, field: &str) -> &mut Self {
52        self.set_sensitive(field, Sensitivity::Medium)
53    }
54
55    /// Marks a field as high sensitivity.
56    #[inline(always)]
57    pub fn high_sensitive(&mut self, field: &str) -> &mut Self {
58        self.set_sensitive(field, Sensitivity::High)
59    }
60
61    /// Marks a field as secret sensitivity.
62    #[inline(always)]
63    pub fn secret_sensitive(&mut self, field: &str) -> &mut Self {
64        self.set_sensitive(field, Sensitivity::Secret)
65    }
66
67    /// Raises a field's minimum sensitivity to `level`.
68    #[inline(always)]
69    pub fn sensitive(&mut self, level: Sensitivity, field: &str) -> &mut Self {
70        self.set_sensitive(field, level)
71    }
72
73    /// Sets field-name matching for the base policy.
74    #[inline(always)]
75    pub fn matching(&mut self, matching: FieldNameMatching) -> &mut Self {
76        self.builder.rules.matching(matching);
77        self
78    }
79
80    /// Sets the base fallback for unknown fields.
81    #[inline(always)]
82    pub fn unknown_field_policy(&mut self, policy: UnknownFieldPolicy) -> &mut Self {
83        self.builder.rules.unknown_field_policy(policy);
84        self
85    }
86
87    /// Includes all fields from a built-in sensitive preset.
88    #[inline(always)]
89    pub fn include_preset(&mut self, preset: SensitiveFieldPreset) -> &mut Self {
90        self.builder.rules.include_preset(preset);
91        self
92    }
93
94    /// Raises a base field's minimum sensitivity.
95    pub fn raise(&mut self, field: &str, level: Sensitivity) -> &mut Self {
96        if self.error.is_none()
97            && let Err(error) = self.builder.rules.raise(field, level)
98        {
99            self.error = Some(error);
100        }
101        self
102    }
103
104    /// Replaces one base field rule without weakening floors.
105    pub fn override_level(&mut self, field: &str, level: Sensitivity) -> &mut Self {
106        if self.error.is_none()
107            && let Err(error) = self.builder.rules.override_level(field, level)
108        {
109            self.error = Some(error);
110        }
111        self
112    }
113
114    /// Adds a base exact allow rule.
115    pub fn allow_exact(&mut self, field: &str) -> &mut Self {
116        if self.error.is_none()
117            && let Err(error) = self.builder.rules.allow_canonical_exact(field)
118        {
119            self.error = Some(error);
120        }
121        self
122    }
123
124    /// Adds a base suffix allow rule.
125    pub fn allow_suffix(&mut self, field: &str) -> &mut Self {
126        if self.error.is_none()
127            && let Err(error) = self.builder.rules.allow_suffix(field)
128        {
129            self.error = Some(error);
130        }
131        self
132    }
133
134    /// Removes a base exact allow rule.
135    pub fn remove_allow_exact(&mut self, field: &str) -> &mut Self {
136        if self.error.is_none()
137            && let Err(error) = self.builder.rules.remove_allow_canonical_exact(field)
138        {
139            self.error = Some(error);
140        }
141        self
142    }
143
144    /// Removes a base suffix allow rule.
145    pub fn remove_allow_suffix(&mut self, field: &str) -> &mut Self {
146        if self.error.is_none()
147            && let Err(error) = self.builder.rules.remove_allow_suffix(field)
148        {
149            self.error = Some(error);
150        }
151        self
152    }
153
154    /// Removes all base allow rules.
155    #[inline(always)]
156    pub fn clear_allow_rules(&mut self) -> &mut Self {
157        self.builder.rules.clear_allow_rules();
158        self
159    }
160
161    /// Replaces the base minimum-protection floor.
162    #[inline(always)]
163    pub fn floor(&mut self, floor: RedactionFloor) -> &mut Self {
164        self.builder.floor = Some(floor);
165        self
166    }
167
168    /// Disables the base floor explicitly.
169    #[inline(always)]
170    pub fn disable_floor(&mut self) -> &mut Self {
171        self.builder.floor = None;
172        self
173    }
174
175    /// Replaces one shared masking level.
176    pub fn mask(&mut self, level: Sensitivity, policy: MaskPolicy) -> &mut Self {
177        let mut masking = MaskingPolicy::builder_from(&self.builder.masking);
178        masking.policy(level, policy);
179        let masking = masking.build();
180        if self.error.is_none() {
181            match masking.validate(PolicyLocation::Rules) {
182                Ok(()) => self.builder.masking = masking,
183                Err(error) => self.error = Some(error),
184            }
185        }
186        self
187    }
188
189    /// Raises one field's minimum sensitivity in a transactional draft.
190    #[inline(always)]
191    fn set_sensitive(&mut self, field: &str, level: Sensitivity) -> &mut Self {
192        if self.error.is_none()
193            && let Err(error) = self.builder.rules.raise(field, level)
194        {
195            self.error = Some(error);
196        }
197        self
198    }
199}