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 {
102 &self.urn
103 }
104
105 pub fn value(&self) -> Result<&T, UnboundError> {
107 self.value.as_ref().ok_or_else(|| UnboundError {
108 urn: self.urn.to_string(),
109 })
110 }
111
112 pub fn masked_value(&self) -> String {
114 match &self.value {
115 None => format!("{} [UNBOUND]", self.urn),
116 Some(v) => format!("{} [{}:{}]", self.urn, T::type_name(), v.masked_size()),
117 }
118 }
119
120 pub fn bind(&mut self, registry: &SourceRegistry) -> Result<(), BindError> {
124 let urn_str = self.urn.to_string();
125 let source =
126 registry
127 .get(&self.urn.source_id)
128 .ok_or_else(|| BindError::SourceNotFound {
129 source_id: self.urn.source_id.clone(),
130 })?;
131
132 let bytes = source.get(&self.urn.name).map_err(|e| {
133 use crate::error::SourceError;
134 match e {
135 SourceError::NotFound { name } => BindError::NameNotFound {
136 source_id: self.urn.source_id.clone(),
137 name,
138 },
139 other => BindError::Source {
140 urn: urn_str.clone(),
141 source: other,
142 },
143 }
144 })?;
145
146 self.value = Some(T::from_bytes(bytes, &urn_str)?);
147 Ok(())
148 }
149}
150
151impl<T: SecretValue> fmt::Display for Secret<T> {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 f.write_str(&self.masked_value())
154 }
155}
156
157impl<T: SecretValue> fmt::Debug for Secret<T> {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 write!(f, "Secret({})", self.masked_value())
161 }
162}
163
164impl<T: SecretValue> Serialize for Secret<T> {
165 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
166 serializer.serialize_str(&self.masked_value())
167 }
168}
169
170impl<'de, T: SecretValue> Deserialize<'de> for Secret<T> {
176 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
177 let s = String::deserialize(deserializer)?;
178 let urn = s.parse::<Urn>().map_err(de::Error::custom)?;
179 Ok(Self { urn, value: None })
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::source::SourceRegistry;
187 use crate::sources::env::EnvSource;
188
189 #[test]
190 fn unbound_masked_value() {
191 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
192 assert_eq!(s.masked_value(), "urn:secrets-rs:env:MY_KEY [UNBOUND]");
193 }
194
195 #[test]
196 fn display_shows_masked_value() {
197 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
198 assert_eq!(s.to_string(), "urn:secrets-rs:env:MY_KEY [UNBOUND]");
199 }
200
201 #[test]
202 fn debug_shows_masked_value() {
203 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
204 assert_eq!(
205 format!("{s:?}"),
206 "Secret(urn:secrets-rs:env:MY_KEY [UNBOUND])"
207 );
208 }
209
210 #[test]
211 fn value_before_bind_is_error() {
212 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
213 assert!(s.value().is_err());
214 }
215
216 #[test]
217 fn bound_masked_value_includes_type_and_length() {
218 unsafe { std::env::set_var("SECRET_TEST_MASKED", "hello") };
219 let mut s: Secret<String> = Secret::new("urn:secrets-rs:env:SECRET_TEST_MASKED").unwrap();
220 let mut registry = SourceRegistry::new();
221 registry.register("env", EnvSource);
222 s.bind(®istry).unwrap();
223 assert_eq!(
224 s.masked_value(),
225 "urn:secrets-rs:env:SECRET_TEST_MASKED [string:5]"
226 );
227 unsafe { std::env::remove_var("SECRET_TEST_MASKED") };
228 }
229
230 #[test]
231 fn value_after_bind_returns_correct_value() {
232 unsafe { std::env::set_var("SECRET_TEST_VALUE", "s3cr3t") };
233 let mut s: Secret<String> = Secret::new("urn:secrets-rs:env:SECRET_TEST_VALUE").unwrap();
234 let mut registry = SourceRegistry::new();
235 registry.register("env", EnvSource);
236 s.bind(®istry).unwrap();
237 assert_eq!(s.value().unwrap(), "s3cr3t");
238 unsafe { std::env::remove_var("SECRET_TEST_VALUE") };
239 }
240
241 #[test]
242 fn serialize_produces_masked_string() {
243 let s: Secret<String> = Secret::new("urn:secrets-rs:env:MY_KEY").unwrap();
244 let json = serde_json::to_string(&s).unwrap();
245 assert_eq!(json, r#""urn:secrets-rs:env:MY_KEY [UNBOUND]""#);
246 }
247
248 #[test]
249 fn deserialize_valid_urn_produces_unbound_secret() {
250 let s: Secret<String> = serde_json::from_str(r#""urn:secrets-rs:env:MY_KEY""#).unwrap();
251 assert_eq!(s.urn().to_string(), "urn:secrets-rs:env:MY_KEY");
252 assert!(s.value().is_err());
253 }
254
255 #[test]
256 fn deserialize_non_urn_string_errors() {
257 let result = serde_json::from_str::<Secret<String>>(r#""not-a-urn""#);
258 assert!(result.is_err());
259 }
260}