use std::fmt;
#[derive(Clone, Eq, PartialEq)]
pub struct ProtectedString {
value: String,
sensitive: bool,
}
impl ProtectedString {
#[must_use]
pub fn plain(value: impl Into<String>) -> Self {
Self {
value: value.into(),
sensitive: false,
}
}
#[must_use]
pub fn sensitive(value: impl Into<String>) -> Self {
Self {
value: value.into(),
sensitive: true,
}
}
#[must_use]
pub const fn is_sensitive(&self) -> bool {
self.sensitive
}
#[must_use]
pub fn expose(&self) -> &str {
&self.value
}
#[must_use]
pub fn redacted(&self) -> &str {
if self.sensitive { "[REDACTED]" } else { &self.value }
}
}
impl fmt::Debug for ProtectedString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("ProtectedString")
.field(&self.redacted())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::ProtectedString;
#[test]
fn sensitive_debug_output_is_redacted() {
let value = ProtectedString::sensitive("never-print-this");
let debug = format!("{value:?}");
assert!(!debug.contains("never-print-this"));
assert!(debug.contains("[REDACTED]"));
assert_eq!(value.expose(), "never-print-this");
}
}