1use std::fmt;
9
10#[derive(Clone)]
12pub struct Token(String);
13
14impl fmt::Debug for Token {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 f.write_str("Token(<redacted>)")
17 }
18}
19
20impl Token {
21 #[must_use]
27 pub fn new<V: Into<String>>(value: V) -> Self {
28 Self(value.into())
29 }
30
31 #[must_use]
33 pub fn expose(&self) -> &str {
34 &self.0
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::Token;
41
42 #[test]
43 fn debug_never_shows_the_value() {
44 let token = Token::new("s3cret");
45 let rendered = format!("{token:?}");
46 assert_eq!(rendered, "Token(<redacted>)");
47 assert!(!rendered.contains("s3cret"));
48 }
49
50 #[test]
51 fn surrounding_debug_output_stays_redacted() {
52 #[derive(Debug)]
53 struct Client {
54 #[expect(dead_code, reason = "the point of the test is the derived Debug")]
55 token: Token,
56 }
57
58 let client = Client {
59 token: Token::new("s3cret"),
60 };
61 assert!(!format!("{client:?}").contains("s3cret"));
62 }
63}