Skip to main content

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}