Skip to main content

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