fnox_core/providers/
secret_ref.rs1use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19use std::borrow::Cow;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum StringOrSecretRef {
28 Literal(String),
30 SecretRef { secret: String },
32}
33
34impl JsonSchema for StringOrSecretRef {
35 fn schema_name() -> Cow<'static, str> {
36 Cow::Borrowed("StringOrSecretRef")
37 }
38
39 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
40 let string_schema = generator.subschema_for::<String>();
42
43 json_schema!({
45 "description": "Either a literal string or a reference to a secret",
46 "oneOf": [
47 string_schema,
48 {
49 "type": "object",
50 "properties": {
51 "secret": { "type": "string" }
52 },
53 "required": ["secret"],
54 "additionalProperties": false
55 }
56 ]
57 })
58 }
59}
60
61impl StringOrSecretRef {
62 #[cfg(test)]
64 pub fn is_secret_ref(&self) -> bool {
65 matches!(self, Self::SecretRef { .. })
66 }
67
68 #[cfg(test)]
70 pub fn secret_name(&self) -> Option<&str> {
71 match self {
72 Self::SecretRef { secret } => Some(secret),
73 Self::Literal(_) => None,
74 }
75 }
76
77 pub fn as_literal(&self) -> Option<&str> {
79 match self {
80 Self::Literal(s) => Some(s),
81 Self::SecretRef { .. } => None,
82 }
83 }
84}
85
86impl<'de> Deserialize<'de> for StringOrSecretRef {
87 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 #[derive(Deserialize)]
92 #[serde(untagged)]
93 enum Helper {
94 Literal(String),
95 SecretRef { secret: String },
96 }
97
98 match Helper::deserialize(deserializer)? {
99 Helper::Literal(s) => Ok(StringOrSecretRef::Literal(s)),
100 Helper::SecretRef { secret } => Ok(StringOrSecretRef::SecretRef { secret }),
101 }
102 }
103}
104
105impl Serialize for StringOrSecretRef {
106 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107 where
108 S: Serializer,
109 {
110 match self {
111 Self::Literal(s) => s.serialize(serializer),
112 Self::SecretRef { secret } => {
113 use serde::ser::SerializeMap;
114 let mut map = serializer.serialize_map(Some(1))?;
115 map.serialize_entry("secret", secret)?;
116 map.end()
117 }
118 }
119 }
120}
121
122impl From<String> for StringOrSecretRef {
123 fn from(s: String) -> Self {
124 Self::Literal(s)
125 }
126}
127
128impl From<&str> for StringOrSecretRef {
129 fn from(s: &str) -> Self {
130 Self::Literal(s.to_string())
131 }
132}
133
134#[derive(Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
141#[schemars(transparent)]
142pub struct OptionStringOrSecretRef(pub Option<StringOrSecretRef>);
143
144impl OptionStringOrSecretRef {
145 pub fn none() -> Self {
147 Self(None)
148 }
149
150 pub fn literal(s: impl Into<String>) -> Self {
152 Self(Some(StringOrSecretRef::Literal(s.into())))
153 }
154
155 pub fn is_none(&self) -> bool {
157 self.0.is_none()
158 }
159
160 #[cfg(test)]
162 pub fn is_some(&self) -> bool {
163 self.0.is_some()
164 }
165
166 pub fn as_ref(&self) -> Option<&StringOrSecretRef> {
168 self.0.as_ref()
169 }
170
171 #[cfg(test)]
173 pub fn has_secret_ref(&self) -> bool {
174 matches!(self.0, Some(StringOrSecretRef::SecretRef { .. }))
175 }
176
177 #[cfg(test)]
179 pub fn secret_name(&self) -> Option<&str> {
180 match &self.0 {
181 Some(StringOrSecretRef::SecretRef { secret }) => Some(secret),
182 _ => None,
183 }
184 }
185
186 #[cfg(test)]
188 pub fn as_literal(&self) -> Option<&str> {
189 self.0.as_ref().and_then(|v| v.as_literal())
190 }
191}
192
193impl<'de> Deserialize<'de> for OptionStringOrSecretRef {
194 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
195 where
196 D: Deserializer<'de>,
197 {
198 let opt: Option<StringOrSecretRef> = Option::deserialize(deserializer)?;
199 Ok(OptionStringOrSecretRef(opt))
200 }
201}
202
203impl Serialize for OptionStringOrSecretRef {
204 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
205 where
206 S: Serializer,
207 {
208 match &self.0 {
209 Some(v) => v.serialize(serializer),
210 None => serializer.serialize_none(),
211 }
212 }
213}
214
215impl From<Option<String>> for OptionStringOrSecretRef {
216 fn from(opt: Option<String>) -> Self {
217 Self(opt.map(StringOrSecretRef::Literal))
218 }
219}
220
221impl From<String> for OptionStringOrSecretRef {
222 fn from(s: String) -> Self {
223 Self(Some(StringOrSecretRef::Literal(s)))
224 }
225}
226
227impl From<&str> for OptionStringOrSecretRef {
228 fn from(s: &str) -> Self {
229 Self(Some(StringOrSecretRef::Literal(s.to_string())))
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_string_or_secret_ref_literal_deser() {
239 let toml_str = r#"field = "literal-value""#;
240 #[derive(Deserialize)]
241 struct Test {
242 field: StringOrSecretRef,
243 }
244 let parsed: Test = toml_edit::de::from_str(toml_str).unwrap();
245 assert_eq!(
246 parsed.field,
247 StringOrSecretRef::Literal("literal-value".to_string())
248 );
249 }
250
251 #[test]
252 fn test_string_or_secret_ref_secret_ref_deser() {
253 let toml_str = r#"field = { secret = "MY_SECRET" }"#;
254 #[derive(Deserialize)]
255 struct Test {
256 field: StringOrSecretRef,
257 }
258 let parsed: Test = toml_edit::de::from_str(toml_str).unwrap();
259 assert_eq!(
260 parsed.field,
261 StringOrSecretRef::SecretRef {
262 secret: "MY_SECRET".to_string()
263 }
264 );
265 }
266
267 #[test]
268 fn test_string_or_secret_ref_literal_ser() {
269 #[derive(Serialize)]
270 struct Test {
271 field: StringOrSecretRef,
272 }
273 let value = Test {
274 field: StringOrSecretRef::Literal("test".to_string()),
275 };
276 let serialized = toml_edit::ser::to_string(&value).unwrap();
277 assert_eq!(serialized.trim(), r#"field = "test""#);
278 }
279
280 #[test]
281 fn test_string_or_secret_ref_secret_ref_ser() {
282 #[derive(Serialize)]
283 struct Test {
284 field: StringOrSecretRef,
285 }
286 let value = Test {
287 field: StringOrSecretRef::SecretRef {
288 secret: "MY_SECRET".to_string(),
289 },
290 };
291 let serialized = toml_edit::ser::to_string(&value).unwrap();
292 assert!(serialized.contains("secret"));
293 assert!(serialized.contains("MY_SECRET"));
294 }
295
296 #[test]
297 fn test_option_string_or_secret_ref_none() {
298 let toml_str = r#""#;
299 #[derive(Deserialize)]
300 struct Test {
301 #[serde(default)]
302 field: OptionStringOrSecretRef,
303 }
304 let parsed: Test = toml_edit::de::from_str(toml_str).unwrap();
305 assert!(parsed.field.is_none());
306 }
307
308 #[test]
309 fn test_option_string_or_secret_ref_literal() {
310 let toml_str = r#"field = "value""#;
311 #[derive(Deserialize)]
312 struct Test {
313 #[serde(default)]
314 field: OptionStringOrSecretRef,
315 }
316 let parsed: Test = toml_edit::de::from_str(toml_str).unwrap();
317 assert!(parsed.field.is_some());
318 assert_eq!(parsed.field.as_literal(), Some("value"));
319 }
320
321 #[test]
322 fn test_option_string_or_secret_ref_secret() {
323 let toml_str = r#"field = { secret = "SECRET_NAME" }"#;
324 #[derive(Deserialize)]
325 struct Test {
326 #[serde(default)]
327 field: OptionStringOrSecretRef,
328 }
329 let parsed: Test = toml_edit::de::from_str(toml_str).unwrap();
330 assert!(parsed.field.is_some());
331 assert!(parsed.field.has_secret_ref());
332 assert_eq!(parsed.field.secret_name(), Some("SECRET_NAME"));
333 }
334
335 #[test]
336 fn test_helpers() {
337 let literal = StringOrSecretRef::Literal("test".to_string());
338 assert!(!literal.is_secret_ref());
339 assert_eq!(literal.as_literal(), Some("test"));
340 assert_eq!(literal.secret_name(), None);
341
342 let secret = StringOrSecretRef::SecretRef {
343 secret: "SECRET".to_string(),
344 };
345 assert!(secret.is_secret_ref());
346 assert_eq!(secret.as_literal(), None);
347 assert_eq!(secret.secret_name(), Some("SECRET"));
348 }
349}