Skip to main content

ephemeris_token/
lib.rs

1//! The forge credential.
2//!
3//! The value is read into memory once and is never logged, at any level,
4//! redacted or not. [`Token`] has a hand-written [`fmt::Debug`] so that a
5//! derived `Debug` on a surrounding struct cannot leak it either, and it has no
6//! [`fmt::Display`] at all.
7
8use std::fmt;
9
10/// A forge access token.
11#[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    /// Wrap a credential value supplied by the caller.
22    ///
23    /// Credential discovery belongs to the application boundary. Keeping this
24    /// type unaware of environment variables and files makes it safe to reuse
25    /// in libraries and tests.
26    #[must_use]
27    pub fn new<V: Into<String>>(value: V) -> Self {
28        Self(value.into())
29    }
30
31    /// The raw value, for the one place that builds the request header.
32    #[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}