shuttle_common/
secrets.rs1use serde::{Deserialize, Serialize};
2use std::{
3 collections::{BTreeMap, HashMap},
4 fmt::Debug,
5};
6use zeroize::Zeroize;
7
8#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema), schema(value_type = String, format = "password"))]
19pub struct Secret<T: Zeroize>(T);
20
21impl<T: Zeroize> Debug for Secret<T> {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "[REDACTED {:?}]", std::any::type_name::<T>())
24 }
25}
26
27impl<T: Zeroize> Drop for Secret<T> {
28 fn drop(&mut self) {
29 self.0.zeroize();
30 }
31}
32
33impl<T: Zeroize> From<T> for Secret<T> {
34 fn from(value: T) -> Self {
35 Self::new(value)
36 }
37}
38
39impl<T: Zeroize> Secret<T> {
40 pub fn new(secret: T) -> Self {
41 Self(secret)
42 }
43
44 pub fn expose(&self) -> &T {
46 &self.0
47 }
48
49 pub fn redacted(&self) -> &str {
51 "********"
52 }
53}
54
55#[derive(Deserialize, Serialize, Clone)]
57#[serde(transparent)]
58pub struct SecretStore {
59 pub(crate) secrets: BTreeMap<String, Secret<String>>,
60}
61#[allow(unused)]
63#[typeshare::typeshare]
64type SecretStoreT = HashMap<String, String>;
65
66impl SecretStore {
67 pub fn new(secrets: BTreeMap<String, Secret<String>>) -> Self {
68 Self { secrets }
69 }
70
71 pub fn get(&self, key: &str) -> Option<String> {
72 self.secrets.get(key).map(|s| s.expose().to_owned())
73 }
74}
75
76impl IntoIterator for SecretStore {
77 type Item = (String, String);
78 type IntoIter = <BTreeMap<String, String> as IntoIterator>::IntoIter;
79
80 fn into_iter(self) -> Self::IntoIter {
81 self.secrets
82 .into_iter()
83 .map(|(k, s)| (k, s.expose().to_owned()))
84 .collect::<BTreeMap<_, _>>()
85 .into_iter()
86 }
87}
88
89#[cfg(test)]
90#[allow(dead_code)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn redacted() {
96 let password_string = String::from("VERYSECRET");
97 let secret = Secret::new(password_string);
98 assert_eq!(secret.redacted(), "********");
99 }
100
101 #[test]
102 fn debug() {
103 let password_string = String::from("VERYSECRET");
104 let secret = Secret::new(password_string);
105 let printed = format!("{:?}", secret);
106 assert_eq!(printed, "[REDACTED \"alloc::string::String\"]");
107 }
108
109 #[test]
110 fn expose() {
111 let password_string = String::from("VERYSECRET");
112 let secret = Secret::new(password_string);
113 let printed = secret.expose();
114 assert_eq!(printed, "VERYSECRET");
115 }
116
117 #[test]
118 fn secret_struct() {
119 #[derive(Debug)]
120 struct Wrapper {
121 password: Secret<String>,
122 }
123
124 let password_string = String::from("VERYSECRET");
125 let secret = Secret::new(password_string);
126 let wrapper = Wrapper { password: secret };
127 let printed = format!("{:?}", wrapper);
128 assert_eq!(
129 printed,
130 "Wrapper { password: [REDACTED \"alloc::string::String\"] }"
131 );
132 }
133
134 #[test]
135 fn secretstore_intoiter() {
136 let bt = BTreeMap::from([
137 ("1".to_owned(), "2".to_owned().into()),
138 ("3".to_owned(), "4".to_owned().into()),
139 ]);
140 let ss = SecretStore::new(bt);
141
142 let mut iter = ss.into_iter();
143 assert_eq!(iter.next(), Some(("1".to_owned(), "2".to_owned())));
144 assert_eq!(iter.next(), Some(("3".to_owned(), "4".to_owned())));
145 assert_eq!(iter.next(), None);
146 }
147}