Skip to main content

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