1use secrecy::{ExposeSecret, SecretString};
2
3#[derive(Clone)]
10pub struct ApiToken(SecretString);
11
12impl ApiToken {
13 pub fn new(s: impl Into<String>) -> Self {
14 Self(SecretString::from(s.into()))
15 }
16
17 pub fn expose_for_auth(&self) -> &str {
19 self.0.expose_secret()
20 }
21}
22
23impl std::fmt::Debug for ApiToken {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.write_str("ApiToken([REDACTED])")
26 }
27}
28
29impl From<String> for ApiToken {
30 fn from(s: String) -> Self {
31 Self::new(s)
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38
39 #[test]
40 fn debug_does_not_expose_secret() {
41 let token = ApiToken::new("super-secret");
42 assert_eq!(format!("{token:?}"), "ApiToken([REDACTED])");
43 assert!(!format!("{token:?}").contains("super-secret"));
44 }
45
46 #[test]
47 fn expose_for_auth_returns_raw_value() {
48 let token = ApiToken::new("super-secret");
49 assert_eq!(token.expose_for_auth(), "super-secret");
50 }
51
52 #[test]
53 fn clone_preserves_value() {
54 let token = ApiToken::new("secret");
55 assert_eq!(token.clone().expose_for_auth(), "secret");
56 }
57}