1use std::{
11 ops::ControlFlow,
12 sync::Arc,
13};
14
15use super::{
16 AllowRule,
17 FieldClassification,
18 FieldMatchKind,
19 FieldNameMatching,
20 RedactionFloor,
21 ResolvedField,
22 SensitiveFieldRule,
23 Sensitivity,
24 UnknownFieldPolicy,
25 internal::{
26 RedactionPolicyInner,
27 visit_canonical_field_candidates,
28 },
29};
30
31#[must_use]
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct RedactionRules {
35 application: Arc<RedactionPolicyInner>,
36 floor: Option<RedactionFloor>,
37}
38
39impl RedactionRules {
40 pub(crate) fn new(
41 application: RedactionPolicyInner,
42 floor: Option<RedactionFloor>,
43 ) -> Self {
44 Self {
45 application: Arc::new(application),
46 floor,
47 }
48 }
49
50 #[inline]
52 pub fn floor(&self) -> Option<&RedactionFloor> {
53 self.floor.as_ref()
54 }
55
56 pub fn with_floor(mut self, floor: RedactionFloor) -> Self {
58 self.floor = Some(floor);
59 self
60 }
61
62 pub fn disable_floor(mut self) -> Self {
69 self.floor = None;
70 self
71 }
72
73 pub fn classify_field<'a>(
76 &'a self,
77 field: &str,
78 ) -> FieldClassification<'a> {
79 classify_inner(
80 &self.application,
81 field,
82 self.application.matching,
83 true,
84 )
85 }
86
87 #[inline]
89 pub fn sensitivity_for(&self, field: &str) -> Option<Sensitivity> {
90 match self.resolve_field(field) {
91 ResolvedField::Sensitive { sensitivity } => Some(sensitivity),
92 ResolvedField::PassThrough => None,
93 }
94 }
95
96 pub(crate) fn sensitivity_for_exact(
98 &self,
99 field: &str,
100 ) -> Option<Sensitivity> {
101 match self.resolve_field_exact(field) {
102 ResolvedField::Sensitive { sensitivity } => Some(sensitivity),
103 ResolvedField::PassThrough => None,
104 }
105 }
106
107 pub(crate) fn resolve_field_exact(&self, field: &str) -> ResolvedField {
109 let application = sensitivity_inner(
110 &self.application,
111 field,
112 FieldNameMatching::Exact,
113 true,
114 );
115 let floor = self.floor.as_ref().and_then(|floor| {
116 sensitivity_inner(
117 &floor.inner,
118 field,
119 FieldNameMatching::Exact,
120 false,
121 )
122 });
123 match self.floor.as_ref().zip(floor) {
124 Some((_floor, floor_level)) => ResolvedField::Sensitive {
125 sensitivity: application
126 .map_or(floor_level, |level| level.max(floor_level)),
127 },
128 None => match application {
129 Some(sensitivity) => ResolvedField::Sensitive { sensitivity },
130 None => ResolvedField::PassThrough,
131 },
132 }
133 }
134
135 #[inline]
136 pub(crate) fn resolve_field(&self, field: &str) -> ResolvedField {
138 self.resolve_field_with_matching(field, self.application.matching)
139 }
140
141 fn resolve_field_with_matching(
143 &self,
144 field: &str,
145 matching: FieldNameMatching,
146 ) -> ResolvedField {
147 let application =
148 sensitivity_inner(&self.application, field, matching, true);
149 let floor = self.floor.as_ref().and_then(|floor| {
150 sensitivity_inner(&floor.inner, field, floor.inner.matching, false)
151 });
152 match floor {
153 Some(floor_level) => ResolvedField::Sensitive {
154 sensitivity: application
155 .map_or(floor_level, |level| level.max(floor_level)),
156 },
157 None => match application {
158 Some(sensitivity) => ResolvedField::Sensitive { sensitivity },
159 None => ResolvedField::PassThrough,
160 },
161 }
162 }
163
164 #[inline]
166 pub fn matching(&self) -> FieldNameMatching {
167 self.application.matching
168 }
169
170 #[inline]
172 pub fn unknown_field_policy(&self) -> UnknownFieldPolicy {
173 self.application.unknown_field_policy
174 }
175
176 pub fn application_sensitive_rules(
178 &self,
179 ) -> impl Iterator<Item = SensitiveFieldRule<'_>> {
180 self.application
181 .sensitive
182 .iter()
183 .map(|(field, level)| SensitiveFieldRule::new(field, *level))
184 }
185
186 pub fn application_allow_rules(
188 &self,
189 ) -> impl Iterator<Item = AllowRule<'_>> {
190 self.application
191 .allow_exact
192 .iter()
193 .map(|field| AllowRule::new(field, FieldNameMatching::Exact))
194 .chain(self.application.allow_suffix.iter().map(|field| {
195 AllowRule::new(field, FieldNameMatching::ExactOrTokenSuffix)
196 }))
197 }
198
199 pub(crate) fn clone_application(&self) -> RedactionPolicyInner {
201 (*self.application).clone()
202 }
203}
204
205fn classify_inner<'a>(
207 inner: &'a RedactionPolicyInner,
208 field: &str,
209 matching: FieldNameMatching,
210 allow: bool,
211) -> FieldClassification<'a> {
212 match visit_canonical_field_candidates(
213 field,
214 matching,
215 |is_exact, candidate| {
216 let match_kind = if is_exact {
217 FieldMatchKind::Exact
218 } else {
219 FieldMatchKind::TokenSuffix
220 };
221 if allow
222 && is_exact
223 && let Some(field) = inner.allow_exact.get(candidate)
224 {
225 return ControlFlow::Break(FieldClassification::Allowed {
226 rule: AllowRule::new(field, FieldNameMatching::Exact),
227 match_kind,
228 });
229 }
230 if allow && let Some(field) = inner.allow_suffix.get(candidate) {
231 return ControlFlow::Break(FieldClassification::Allowed {
232 rule: AllowRule::new(
233 field,
234 FieldNameMatching::ExactOrTokenSuffix,
235 ),
236 match_kind,
237 });
238 }
239 if let Some((field, sensitivity)) =
240 inner.sensitive.get_key_value(candidate)
241 {
242 return ControlFlow::Break(FieldClassification::Sensitive {
243 rule: SensitiveFieldRule::new(field, *sensitivity),
244 match_kind,
245 });
246 }
247 ControlFlow::Continue(())
248 },
249 ) {
250 ControlFlow::Break(classification) => classification,
251 ControlFlow::Continue(()) => FieldClassification::Unknown,
252 }
253}
254
255fn sensitivity_inner(
256 inner: &RedactionPolicyInner,
257 field: &str,
258 matching: FieldNameMatching,
259 allow: bool,
260) -> Option<Sensitivity> {
261 match classify_inner(inner, field, matching, allow) {
262 FieldClassification::Allowed { .. } => None,
263 FieldClassification::Sensitive { .. }
264 | FieldClassification::Unknown => {
265 strongest_sensitive_match(inner, field, matching)
266 .or_else(|| inner.unknown_field_policy.sensitivity())
267 }
268 }
269}
270
271fn strongest_sensitive_match(
273 inner: &RedactionPolicyInner,
274 field: &str,
275 matching: FieldNameMatching,
276) -> Option<Sensitivity> {
277 let mut strongest: Option<Sensitivity> = None;
278 let _ = visit_canonical_field_candidates(
279 field,
280 matching,
281 |_is_exact, candidate| {
282 if let Some(level) = inner.sensitive.get(candidate) {
283 strongest = Some(
284 strongest.map_or(*level, |current| current.max(*level)),
285 );
286 }
287 ControlFlow::<()>::Continue(())
288 },
289 );
290 strongest
291}