Skip to main content

boxferry_model/
value.rs

1//! Values that retain whether their contents are sensitive.
2
3use std::fmt;
4
5/// Plain or sensitive text with redacting debug output.
6#[derive(Clone, Eq, PartialEq)]
7pub struct ProtectedString {
8    value: String,
9    sensitive: bool,
10}
11
12impl ProtectedString {
13    /// Creates ordinary non-sensitive text.
14    #[must_use]
15    pub fn plain(value: impl Into<String>) -> Self {
16        Self {
17            value: value.into(),
18            sensitive: false,
19        }
20    }
21
22    /// Creates sensitive text whose debug output is redacted.
23    #[must_use]
24    pub fn sensitive(value: impl Into<String>) -> Self {
25        Self {
26            value: value.into(),
27            sensitive: true,
28        }
29    }
30
31    /// Returns whether the value is sensitive.
32    #[must_use]
33    pub const fn is_sensitive(&self) -> bool {
34        self.sensitive
35    }
36
37    /// Explicitly exposes the contained text to an authorized caller.
38    #[must_use]
39    pub fn expose(&self) -> &str {
40        &self.value
41    }
42
43    /// Returns the value or the standard redaction marker.
44    #[must_use]
45    pub fn redacted(&self) -> &str {
46        if self.sensitive { "[REDACTED]" } else { &self.value }
47    }
48}
49
50impl fmt::Debug for ProtectedString {
51    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52        formatter
53            .debug_tuple("ProtectedString")
54            .field(&self.redacted())
55            .finish()
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::ProtectedString;
62
63    #[test]
64    fn sensitive_debug_output_is_redacted() {
65        let value = ProtectedString::sensitive("never-print-this");
66        let debug = format!("{value:?}");
67        assert!(!debug.contains("never-print-this"));
68        assert!(debug.contains("[REDACTED]"));
69        assert_eq!(value.expose(), "never-print-this");
70    }
71}