Skip to main content

via/
secrets.rs

1use secrecy::{ExposeSecret, SecretString};
2use serde::{Deserialize, Deserializer};
3
4#[derive(Clone)]
5pub struct SecretValue {
6    value: SecretString,
7}
8
9impl SecretValue {
10    pub fn new(value: String) -> Self {
11        Self {
12            value: SecretString::from(value),
13        }
14    }
15
16    pub fn from_utf8_lossy_trimmed(bytes: Vec<u8>) -> Self {
17        let value = match String::from_utf8(bytes) {
18            Ok(value) => value,
19            Err(error) => String::from_utf8_lossy(error.as_bytes()).into_owned(),
20        };
21        Self::new_trimmed(value)
22    }
23
24    pub fn expose(&self) -> &str {
25        self.value.expose_secret()
26    }
27
28    fn new_trimmed(mut value: String) -> Self {
29        while value.ends_with(['\r', '\n']) {
30            value.pop();
31        }
32        Self::new(value)
33    }
34}
35
36impl<'de> Deserialize<'de> for SecretValue {
37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38    where
39        D: Deserializer<'de>,
40    {
41        String::deserialize(deserializer).map(Self::new)
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn exposes_secret_value_when_explicitly_requested() {
51        let secret = SecretValue::new("secret-token".to_owned());
52
53        assert_eq!(secret.expose(), "secret-token");
54    }
55
56    #[test]
57    fn builds_secret_value_from_trimmed_utf8_output() {
58        let secret = SecretValue::from_utf8_lossy_trimmed(b"secret-token\r\n".to_vec());
59
60        assert_eq!(secret.expose(), "secret-token");
61    }
62}