qubit_http/sanitize/sensitive_body_fields.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10
11use std::collections::BTreeSet;
12
13use super::default_sensitive_names::{
14 canonicalize_structured_sensitive_name,
15 DEFAULT_SENSITIVE_BODY_FIELD_NAMES,
16};
17
18/// Set of structured body field names whose values should be masked.
19///
20/// Names are matched case-insensitively and common `_` / `-` separators are
21/// ignored, so `client_secret`, `client-secret`, and `clientSecret` are equivalent.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SensitiveBodyFields {
24 /// Canonical body field names.
25 names: BTreeSet<String>,
26}
27
28impl SensitiveBodyFields {
29 /// Creates an empty set.
30 ///
31 /// # Returns
32 /// Empty [`SensitiveBodyFields`].
33 pub fn new() -> Self {
34 Self {
35 names: BTreeSet::new(),
36 }
37 }
38
39 /// Returns whether `name` is sensitive.
40 ///
41 /// # Parameters
42 /// - `name`: Structured body field name.
43 ///
44 /// # Returns
45 /// `true` if the field value should be masked.
46 pub fn contains(&self, name: &str) -> bool {
47 self.names
48 .contains(&canonicalize_structured_sensitive_name(name))
49 }
50
51 /// Inserts one field name.
52 ///
53 /// # Parameters
54 /// - `name`: Field name to mark sensitive.
55 pub fn insert(&mut self, name: &str) {
56 let value = canonicalize_structured_sensitive_name(name);
57 if !value.is_empty() {
58 self.names.insert(value);
59 }
60 }
61
62 /// Inserts many field names.
63 ///
64 /// # Parameters
65 /// - `names`: Field names to mark sensitive.
66 pub fn extend<I, S>(&mut self, names: I)
67 where
68 I: IntoIterator<Item = S>,
69 S: AsRef<str>,
70 {
71 for name in names {
72 self.insert(name.as_ref());
73 }
74 }
75
76 /// Clears all field names.
77 pub fn clear(&mut self) {
78 self.names.clear();
79 }
80
81 /// Returns the number of stored body field names.
82 ///
83 /// # Returns
84 /// Stored body field count.
85 pub fn len(&self) -> usize {
86 self.names.len()
87 }
88
89 /// Returns whether no body field names are stored.
90 ///
91 /// # Returns
92 /// `true` when empty.
93 pub fn is_empty(&self) -> bool {
94 self.names.is_empty()
95 }
96
97 /// Iterates canonical body field names.
98 ///
99 /// # Returns
100 /// Iterator over stored canonical body field names.
101 pub fn iter(&self) -> impl Iterator<Item = &str> {
102 self.names.iter().map(String::as_str)
103 }
104}
105
106impl Default for SensitiveBodyFields {
107 /// Creates a set containing common credential and token field names.
108 fn default() -> Self {
109 let mut result = Self::new();
110 result.extend(DEFAULT_SENSITIVE_BODY_FIELD_NAMES);
111 result
112 }
113}