1use serde::{de::DeserializeOwned, Deserialize, Serialize};
74
75#[derive(Debug, Clone, PartialEq)]
96pub enum ConfigValue<T>
97where
98 T: Serialize + DeserializeOwned + Clone,
99{
100 Secret { name: String },
102
103 EnvironmentVariable {
105 name: String,
106 default: Option<String>,
107 },
108
109 Static(T),
111}
112
113pub type ConfigValueString = ConfigValue<String>;
115pub type ConfigValueU16 = ConfigValue<u16>;
117pub type ConfigValueU32 = ConfigValue<u32>;
119pub type ConfigValueU64 = ConfigValue<u64>;
121pub type ConfigValueUsize = ConfigValue<usize>;
123pub type ConfigValueBool = ConfigValue<bool>;
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
130#[schema(as = ConfigValueString)]
131pub struct ConfigValueStringSchema(pub ConfigValueString);
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
135#[schema(as = ConfigValueU16)]
136pub struct ConfigValueU16Schema(pub ConfigValueU16);
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
140#[schema(as = ConfigValueU32)]
141pub struct ConfigValueU32Schema(pub ConfigValueU32);
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
145#[schema(as = ConfigValueU64)]
146pub struct ConfigValueU64Schema(pub ConfigValueU64);
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
150#[schema(as = ConfigValueUsize)]
151pub struct ConfigValueUsizeSchema(pub ConfigValueUsize);
152
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
155#[schema(as = ConfigValueBool)]
156pub struct ConfigValueBoolSchema(pub ConfigValueBool);
157
158impl<T> Serialize for ConfigValue<T>
160where
161 T: Serialize + DeserializeOwned + Clone,
162{
163 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164 where
165 S: serde::Serializer,
166 {
167 use serde::ser::SerializeMap;
168
169 match self {
170 ConfigValue::Secret { name } => {
171 let mut map = serializer.serialize_map(Some(2))?;
172 map.serialize_entry("kind", "Secret")?;
173 map.serialize_entry("name", name)?;
174 map.end()
175 }
176 ConfigValue::EnvironmentVariable { name, default } => {
177 let size = if default.is_some() { 3 } else { 2 };
178 let mut map = serializer.serialize_map(Some(size))?;
179 map.serialize_entry("kind", "EnvironmentVariable")?;
180 map.serialize_entry("name", name)?;
181 if let Some(d) = default {
182 map.serialize_entry("default", d)?;
183 }
184 map.end()
185 }
186 ConfigValue::Static(value) => value.serialize(serializer),
187 }
188 }
189}
190
191impl<'de, T> Deserialize<'de> for ConfigValue<T>
193where
194 T: Serialize + DeserializeOwned + Clone + 'static,
195{
196 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197 where
198 D: serde::Deserializer<'de>,
199 {
200 use serde::de::Error;
201 use serde_json::Value;
202
203 let value = Value::deserialize(deserializer)?;
204
205 if let Value::Object(ref map) = value {
207 if let Some(Value::String(kind)) = map.get("kind") {
208 match kind.as_str() {
209 "Secret" => {
210 let name = map
211 .get("name")
212 .and_then(|v| v.as_str())
213 .ok_or_else(|| D::Error::missing_field("name"))?
214 .to_string();
215
216 return Ok(ConfigValue::Secret { name });
217 }
218 "EnvironmentVariable" => {
219 let name = map
220 .get("name")
221 .and_then(|v| v.as_str())
222 .ok_or_else(|| D::Error::missing_field("name"))?
223 .to_string();
224
225 let default = map
226 .get("default")
227 .and_then(|v| v.as_str())
228 .map(|s| s.to_string());
229
230 return Ok(ConfigValue::EnvironmentVariable { name, default });
231 }
232 _ => {
233 return Err(D::Error::custom(format!("Unknown kind: {kind}")));
234 }
235 }
236 }
237 }
238
239 if let Value::String(s) = &value {
241 if let Some(env_ref) = parse_posix_env_var(s) {
242 return Ok(env_ref);
243 }
244 }
245
246 let static_value: T = serde_json::from_value(value)
248 .map_err(|e| D::Error::custom(format!("Failed to deserialize as static value: {e}")))?;
249
250 Ok(ConfigValue::Static(static_value))
251 }
252}
253
254fn parse_posix_env_var<T>(s: &str) -> Option<ConfigValue<T>>
256where
257 T: Clone + Serialize + DeserializeOwned,
258{
259 if !s.starts_with("${") || !s.ends_with('}') {
260 return None;
261 }
262
263 let inner = &s[2..s.len() - 1];
264
265 if let Some(colon_pos) = inner.find(":-") {
266 let name = inner[..colon_pos].to_string();
267 let default = Some(inner[colon_pos + 2..].to_string());
268 Some(ConfigValue::EnvironmentVariable { name, default })
269 } else {
270 let name = inner.to_string();
271 Some(ConfigValue::EnvironmentVariable {
272 name,
273 default: None,
274 })
275 }
276}
277
278impl<T> Default for ConfigValue<T>
279where
280 T: Serialize + DeserializeOwned + Clone + Default,
281{
282 fn default() -> Self {
283 ConfigValue::Static(T::default())
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_deserialize_static_string() {
293 let json = r#""hello""#;
294 let value: ConfigValue<String> = serde_json::from_str(json).expect("deserialize");
295 assert_eq!(value, ConfigValue::Static("hello".to_string()));
296 }
297
298 #[test]
299 fn test_deserialize_static_number() {
300 let json = r#"5432"#;
301 let value: ConfigValue<u16> = serde_json::from_str(json).expect("deserialize");
302 assert_eq!(value, ConfigValue::Static(5432));
303 }
304
305 #[test]
306 fn test_deserialize_posix_with_default() {
307 let json = r#""${DB_PORT:-5432}""#;
308 let value: ConfigValue<String> = serde_json::from_str(json).expect("deserialize");
309 match value {
310 ConfigValue::EnvironmentVariable { name, default } => {
311 assert_eq!(name, "DB_PORT");
312 assert_eq!(default, Some("5432".to_string()));
313 }
314 _ => panic!("Expected EnvironmentVariable"),
315 }
316 }
317
318 #[test]
319 fn test_deserialize_posix_without_default() {
320 let json = r#""${DB_HOST}""#;
321 let value: ConfigValue<String> = serde_json::from_str(json).expect("deserialize");
322 match value {
323 ConfigValue::EnvironmentVariable { name, default } => {
324 assert_eq!(name, "DB_HOST");
325 assert_eq!(default, None);
326 }
327 _ => panic!("Expected EnvironmentVariable"),
328 }
329 }
330
331 #[test]
332 fn test_deserialize_structured_secret() {
333 let json = r#"{"kind": "Secret", "name": "db-password"}"#;
334 let value: ConfigValue<String> = serde_json::from_str(json).expect("deserialize");
335 match value {
336 ConfigValue::Secret { name } => assert_eq!(name, "db-password"),
337 _ => panic!("Expected Secret"),
338 }
339 }
340
341 #[test]
342 fn test_deserialize_structured_env() {
343 let json = r#"{"kind": "EnvironmentVariable", "name": "DB_HOST", "default": "localhost"}"#;
344 let value: ConfigValue<String> = serde_json::from_str(json).expect("deserialize");
345 match value {
346 ConfigValue::EnvironmentVariable { name, default } => {
347 assert_eq!(name, "DB_HOST");
348 assert_eq!(default, Some("localhost".to_string()));
349 }
350 _ => panic!("Expected EnvironmentVariable"),
351 }
352 }
353
354 #[test]
355 fn test_serialize_static() {
356 let value = ConfigValue::Static("hello".to_string());
357 let json = serde_json::to_string(&value).expect("serialize");
358 assert_eq!(json, r#""hello""#);
359 }
360
361 #[test]
362 fn test_serialize_env_var() {
363 let value: ConfigValue<String> = ConfigValue::EnvironmentVariable {
364 name: "DB_HOST".to_string(),
365 default: Some("localhost".to_string()),
366 };
367 let json = serde_json::to_string(&value).expect("serialize");
368 let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse json");
369 assert_eq!(parsed["kind"], "EnvironmentVariable");
370 assert_eq!(parsed["name"], "DB_HOST");
371 assert_eq!(parsed["default"], "localhost");
372 }
373
374 #[test]
375 fn test_serialize_secret() {
376 let value: ConfigValue<String> = ConfigValue::Secret {
377 name: "my-secret".to_string(),
378 };
379 let json = serde_json::to_string(&value).expect("serialize");
380 let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse json");
381 assert_eq!(parsed["kind"], "Secret");
382 assert_eq!(parsed["name"], "my-secret");
383 }
384
385 #[test]
386 fn test_roundtrip_static() {
387 let original = ConfigValue::Static(42u16);
388 let json = serde_json::to_string(&original).expect("serialize");
389 let deserialized: ConfigValue<u16> = serde_json::from_str(&json).expect("deserialize");
390 assert_eq!(original, deserialized);
391 }
392
393 #[test]
394 fn test_default() {
395 let value: ConfigValue<String> = ConfigValue::default();
396 assert_eq!(value, ConfigValue::Static(String::default()));
397 }
398}