1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
4
5use crate::{
6 error::{BindError, UnboundError, UrnParseError},
7 source::SourceRegistry,
8 urn::Urn,
9};
10
11pub trait SecretValue: Sized {
16 fn type_name() -> &'static str;
18
19 fn from_bytes(bytes: Vec<u8>, urn: &str) -> Result<Self, BindError>;
21
22 fn masked_size(&self) -> String;
24}
25
26impl SecretValue for String {
27 fn type_name() -> &'static str {
28 "string"
29 }
30
31 fn from_bytes(bytes: Vec<u8>, urn: &str) -> Result<Self, BindError> {
32 String::from_utf8(bytes).map_err(|e| BindError::TypeConversion {
33 urn: urn.to_owned(),
34 detail: e.to_string(),
35 })
36 }
37
38 fn masked_size(&self) -> String {
39 self.chars().count().to_string()
40 }
41}
42
43impl SecretValue for Vec<u8> {
44 fn type_name() -> &'static str {
45 "bytes"
46 }
47
48 fn from_bytes(bytes: Vec<u8>, _urn: &str) -> Result<Self, BindError> {
49 Ok(bytes)
50 }
51
52 fn masked_size(&self) -> String {
53 self.len().to_string()
54 }
55}
56
57impl SecretValue for serde_json::Value {
58 fn type_name() -> &'static str {
59 "json"
60 }
61
62 fn from_bytes(bytes: Vec<u8>, urn: &str) -> Result<Self, BindError> {
63 serde_json::from_slice(&bytes).map_err(|e| BindError::TypeConversion {
64 urn: urn.to_owned(),
65 detail: e.to_string(),
66 })
67 }
68
69 fn masked_size(&self) -> String {
70 self.to_string().len().to_string()
72 }
73}
74
75pub struct Secret<T: SecretValue> {
87 urn: Urn,
88 value: Option<T>,
89}
90
91impl<T: SecretValue> Secret<T> {
92 pub fn new(urn_str: &str) -> Result<Self, UrnParseError> {
94 Ok(Self {
95 urn: urn_str.parse()?,
96 value: None,
97 })
98 }
99
100 pub fn urn(&self) -> &Urn {
120 &self.urn
121 }
122
123 pub fn value(&self) -> Result<&T, UnboundError> {
125 self.value.as_ref().ok_or_else(|| UnboundError {
126 urn: self.urn.to_string(),
127 })
128 }
129
130 pub fn masked_value(&self) -> String {
132 match &self.value {
133 None => format!("{} [UNBOUND]", self.urn),
134 Some(v) => format!("{} [{}:{}]", self.urn, T::type_name(), v.masked_size()),
135 }
136 }
137
138 pub fn bind(&mut self, registry: &SourceRegistry) -> Result<(), BindError> {
142 let urn_str = self.urn.to_string();
143 let source =
144 registry
145 .get(&self.urn.source_id)
146 .ok_or_else(|| BindError::SourceNotFound {
147 source_id: self.urn.source_id.clone(),
148 })?;
149
150 let bytes = source.get(&self.urn.name).map_err(|e| {
151 use crate::error::SourceError;
152 match e {
153 SourceError::NotFound { name } => BindError::NameNotFound {
154 source_id: self.urn.source_id.clone(),
155 name,
156 },
157 other => BindError::Source {
158 urn: urn_str.clone(),
159 source: other,
160 },
161 }
162 })?;
163
164 self.value = Some(T::from_bytes(bytes, &urn_str)?);
165 Ok(())
166 }
167}
168
169impl<T: SecretValue> fmt::Display for Secret<T> {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.write_str(&self.masked_value())
172 }
173}
174
175impl<T: SecretValue> fmt::Debug for Secret<T> {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(f, "Secret({})", self.masked_value())
179 }
180}
181
182impl<T: SecretValue> Serialize for Secret<T> {
183 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
184 serializer.serialize_str(&self.masked_value())
185 }
186}
187
188impl<'de, T: SecretValue> Deserialize<'de> for Secret<T> {
194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
195 let s = String::deserialize(deserializer)?;
196 let urn = s.parse::<Urn>().map_err(de::Error::custom)?;
197 Ok(Self { urn, value: None })
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::source::SourceRegistry;
205
206 #[test]
207 fn unbound_masked_value() {
208 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
209 assert_eq!(s.masked_value(), "urn:secrets-rs:env:MY_KEY [UNBOUND]");
210 }
211
212 #[test]
213 fn display_shows_masked_value() {
214 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
215 assert_eq!(s.to_string(), "urn:secrets-rs:env:MY_KEY [UNBOUND]");
216 }
217
218 #[test]
219 fn debug_shows_masked_value() {
220 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
221 assert_eq!(
222 format!("{s:?}"),
223 "Secret(urn:secrets-rs:env:MY_KEY [UNBOUND])"
224 );
225 }
226
227 #[test]
228 fn value_before_bind_is_error() {
229 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
230 assert!(s.value().is_err());
231 }
232
233 #[test]
234 fn bound_masked_value_includes_type_and_length() {
235 unsafe { std::env::set_var("SECRET_TEST_MASKED", "hello") };
236 let mut s: Secret<String> = Secret::new("urn:secrets-rs:env:SECRET_TEST_MASKED").unwrap();
237 let registry = SourceRegistry::new();
238 s.bind(®istry).unwrap();
239 assert_eq!(
240 s.masked_value(),
241 "urn:secrets-rs:env:SECRET_TEST_MASKED [string:5]"
242 );
243 unsafe { std::env::remove_var("SECRET_TEST_MASKED") };
244 }
245
246 #[test]
247 fn value_after_bind_returns_correct_value() {
248 unsafe { std::env::set_var("SECRET_TEST_VALUE", "s3cr3t") };
249 let mut s: Secret<String> = Secret::new("urn:secrets-rs:env:SECRET_TEST_VALUE").unwrap();
250 let registry = SourceRegistry::new();
251 s.bind(®istry).unwrap();
252 assert_eq!(s.value().unwrap(), "s3cr3t");
253 unsafe { std::env::remove_var("SECRET_TEST_VALUE") };
254 }
255
256 #[test]
257 fn serialize_produces_masked_string() {
258 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
259 let json = serde_json::to_string(&s).unwrap();
260 assert_eq!(json, r#""urn:secrets-rs:env:MY_KEY [UNBOUND]""#);
261 }
262
263 #[test]
264 fn deserialize_valid_urn_produces_unbound_secret() {
265 let s: Secret<String> = serde_json::from_str(r#""urn:secrets-rs:env:MY_KEY""#).unwrap();
266 assert_eq!(s.urn().to_string(), "urn:secrets-rs:env:MY_KEY");
267 assert!(s.value().is_err());
268 }
269
270 #[test]
271 fn deserialize_non_urn_string_errors() {
272 let result = serde_json::from_str::<Secret<String>>(r#""not-a-urn""#);
273 assert!(result.is_err());
274 }
275
276 #[test]
277 fn urn_returns_source_id_and_name() {
278 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
279 assert_eq!(s.urn().source_id, "env");
280 assert_eq!(s.urn().name, "MY_KEY");
281 }
282
283 #[test]
284 fn urn_is_unchanged_after_bind() {
285 unsafe { std::env::set_var("SECRET_URN_BIND_TEST", "value") };
286 let mut s: Secret<String> = Secret::new("urn:secrets-rs:env:SECRET_URN_BIND_TEST").unwrap();
287 let urn_before = s.urn().to_string();
288 let registry = SourceRegistry::new();
289 s.bind(®istry).unwrap();
290 assert_eq!(s.urn().to_string(), urn_before);
291 unsafe { std::env::remove_var("SECRET_URN_BIND_TEST") };
292 }
293
294 #[test]
295 fn urn_can_be_used_to_construct_second_unbound_secret() {
296 let original: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
297 let copy: Secret<String> = Secret::new(&original.urn().to_string()).unwrap();
298 assert_eq!(original.urn(), copy.urn());
299 assert!(copy.value().is_err());
300 }
301}