Skip to main content

qubit_http/options/
sensitive_headers.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9
10use std::collections::BTreeSet;
11
12use crate::constants::DEFAULT_SENSITIVE_HEADER_NAMES;
13
14/// Case-insensitive set of HTTP header names whose values should be masked in logs.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct SensitiveHeaders {
17    /// Normalized lowercase header names.
18    headers: BTreeSet<String>,
19}
20
21impl SensitiveHeaders {
22    /// Creates an empty set (no names marked sensitive).
23    ///
24    /// # Returns
25    /// New [`SensitiveHeaders`] without default names; prefer [`SensitiveHeaders::default`] for built-ins.
26    pub fn new() -> Self {
27        Self {
28            headers: BTreeSet::new(),
29        }
30    }
31
32    /// Returns whether `header_name` is treated as sensitive (compared case-insensitively).
33    ///
34    /// # Parameters
35    /// - `header_name`: Header name to test (any casing).
36    ///
37    /// # Returns
38    /// `true` if masked in logging helpers.
39    pub fn contains(&self, header_name: &str) -> bool {
40        self.headers.contains(&header_name.to_lowercase())
41    }
42
43    /// Inserts one header name after trimming and lowercasing; ignores empty strings.
44    ///
45    /// # Parameters
46    /// - `header_name`: Name to mark sensitive.
47    pub fn insert(&mut self, header_name: &str) {
48        let value = header_name.trim().to_lowercase();
49        if !value.is_empty() {
50            self.headers.insert(value);
51        }
52    }
53
54    /// Inserts each header from the iterator via [`SensitiveHeaders::insert`].
55    ///
56    /// # Parameters
57    /// - `headers`: Iterator of header name-like values.
58    pub fn extend<I, S>(&mut self, headers: I)
59    where
60        I: IntoIterator<Item = S>,
61        S: AsRef<str>,
62    {
63        for header in headers {
64            self.insert(header.as_ref());
65        }
66    }
67
68    /// Clears all stored sensitive header names.
69    pub fn clear(&mut self) {
70        self.headers.clear();
71    }
72
73    /// Returns how many sensitive header names are stored.
74    ///
75    /// # Returns
76    /// Count of entries in the internal set.
77    pub fn len(&self) -> usize {
78        self.headers.len()
79    }
80
81    /// Returns whether no sensitive names are registered.
82    ///
83    /// # Returns
84    /// `true` if [`SensitiveHeaders::len`] is zero.
85    pub fn is_empty(&self) -> bool {
86        self.headers.is_empty()
87    }
88
89    /// Iterates normalized (lowercase) sensitive header names.
90    ///
91    /// # Returns
92    /// Iterator over string slices owned by `self`.
93    pub fn iter(&self) -> impl Iterator<Item = &str> {
94        self.headers.iter().map(String::as_str)
95    }
96}
97
98impl Default for SensitiveHeaders {
99    /// Starts with [`crate::DEFAULT_SENSITIVE_HEADER_NAMES`] pre-registered.
100    ///
101    /// # Returns
102    /// Non-empty [`SensitiveHeaders`].
103    fn default() -> Self {
104        let mut result = SensitiveHeaders::new();
105        result.extend(DEFAULT_SENSITIVE_HEADER_NAMES);
106        result
107    }
108}