#![allow(clippy::missing_const_for_fn)]
use std::fmt;
#[derive(Clone, Eq, PartialEq)]
pub struct SecretToken(String);
impl SecretToken {
#[must_use]
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
#[must_use]
pub fn from_env(var: &str) -> Option<Self> {
let raw = std::env::var(var).ok()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
None
} else {
Some(Self(trimmed.to_owned()))
}
}
#[must_use]
pub fn reveal(&self) -> &str {
&self.0
}
#[must_use]
pub fn expose(&self) -> &str {
&self.0
}
#[must_use]
pub fn byte_len(&self) -> usize {
self.0.len()
}
}
impl fmt::Debug for SecretToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SecretToken(***)")
}
}
impl fmt::Display for SecretToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("***")
}
}
impl Drop for SecretToken {
fn drop(&mut self) {
self.0.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_is_redacted() {
let tok = SecretToken::new("abc");
assert_eq!(tok.to_string(), "***");
}
#[test]
fn debug_is_redacted() {
let tok = SecretToken::new("abc");
assert_eq!(format!("{tok:?}"), "SecretToken(***)");
}
#[test]
fn debug_does_not_leak_token_bytes() {
let tok = SecretToken::new("super-secret-1234");
let dbg = format!("{tok:?}");
assert!(!dbg.contains("super-secret-1234"));
}
#[test]
fn from_env_returns_none_when_unset() {
assert!(SecretToken::from_env("MNEM_NONEXISTENT_VAR_12345_ZZZZ").is_none());
}
#[test]
fn reveal_and_expose_agree() {
let tok = SecretToken::new("xyz");
assert_eq!(tok.reveal(), "xyz");
assert_eq!(tok.expose(), "xyz");
}
}