qubit_sanitize/core/sensitive_fields.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::collections::BTreeMap;
11
12use super::{
13 SensitiveFieldPreset,
14 SensitivityLevel,
15 canonicalize_field_name,
16 default_sensitive_fields::DEFAULT_EXTRA_FIELDS,
17};
18
19/// Set of sensitive field names and their sensitivity levels.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct SensitiveFields {
22 /// Canonical field names mapped to sensitivity levels.
23 fields: BTreeMap<String, SensitivityLevel>,
24}
25
26impl SensitiveFields {
27 /// Creates an empty sensitive field set.
28 ///
29 /// # Returns
30 ///
31 /// Empty field set without built-in names.
32 pub fn new() -> Self {
33 Self {
34 fields: BTreeMap::new(),
35 }
36 }
37
38 /// Inserts one sensitive field name.
39 ///
40 /// # Parameters
41 ///
42 /// * `field` - Field name to mark sensitive.
43 /// * `level` - Sensitivity level assigned to the field.
44 pub fn insert(&mut self, field: &str, level: SensitivityLevel) {
45 let field = canonicalize_field_name(field);
46 if !field.is_empty() {
47 self.fields.insert(field, level);
48 }
49 }
50
51 /// Inserts each field with the same sensitivity level.
52 ///
53 /// # Parameters
54 ///
55 /// * `fields` - Field names to add.
56 /// * `level` - Sensitivity level assigned to every field.
57 pub fn extend<I, S>(&mut self, fields: I, level: SensitivityLevel)
58 where
59 I: IntoIterator<Item = S>,
60 S: AsRef<str>,
61 {
62 for field in fields {
63 self.insert(field.as_ref(), level);
64 }
65 }
66
67 /// Extends this set with one predefined field group.
68 ///
69 /// # Parameters
70 ///
71 /// * `preset` - Predefined group to insert.
72 pub fn extend_preset(&mut self, preset: SensitiveFieldPreset) {
73 for (field, level) in preset.fields() {
74 self.insert(field, *level);
75 }
76 }
77
78 /// Returns whether a field is configured as sensitive.
79 ///
80 /// # Parameters
81 ///
82 /// * `field` - Field name to test.
83 ///
84 /// # Returns
85 ///
86 /// `true` when `field` has a configured sensitivity level.
87 pub fn contains(&self, field: &str) -> bool {
88 self.level_for(field).is_some()
89 }
90
91 /// Returns the sensitivity level for a field.
92 ///
93 /// # Parameters
94 ///
95 /// * `field` - Field name to resolve.
96 ///
97 /// # Returns
98 ///
99 /// `Some(level)` when the field is sensitive, otherwise `None`.
100 pub fn level_for(&self, field: &str) -> Option<SensitivityLevel> {
101 self.fields.get(&canonicalize_field_name(field)).copied()
102 }
103
104 /// Returns the number of configured sensitive fields.
105 ///
106 /// # Returns
107 ///
108 /// Field count.
109 pub fn len(&self) -> usize {
110 self.fields.len()
111 }
112
113 /// Returns whether no fields are configured.
114 ///
115 /// # Returns
116 ///
117 /// `true` when the set is empty.
118 pub fn is_empty(&self) -> bool {
119 self.fields.is_empty()
120 }
121
122 /// Iterates canonical field names and sensitivity levels.
123 ///
124 /// # Returns
125 ///
126 /// Iterator over canonical field names and their levels.
127 pub fn iter(&self) -> impl Iterator<Item = (&str, SensitivityLevel)> {
128 self.fields
129 .iter()
130 .map(|(field, level)| (field.as_str(), *level))
131 }
132}
133
134impl Default for SensitiveFields {
135 /// Creates a set containing built-in sensitive fields.
136 fn default() -> Self {
137 let mut fields = Self::new();
138 for preset in [
139 SensitiveFieldPreset::Credentials,
140 SensitiveFieldPreset::AuthTokens,
141 SensitiveFieldPreset::Http,
142 SensitiveFieldPreset::Session,
143 ] {
144 fields.extend_preset(preset);
145 }
146 for (field, level) in DEFAULT_EXTRA_FIELDS {
147 fields.insert(field, level);
148 }
149 fields
150 }
151}