Skip to main content

secrets_core/
spec_schema.rs

1use crate::SecretSpec;
2use serde_json::{Map, Value, json};
3use std::collections::BTreeMap;
4
5/// Convert an iterator of secret specs into a JSON Schema value.
6///
7/// The resulting schema describes an object where each spec becomes a string
8/// property. Specs are emitted in lexicographic order to keep the schema stable
9/// for downstream tooling and snapshots.
10pub fn specs_to_json_schema<'a>(specs: impl Iterator<Item = &'a SecretSpec>) -> Value {
11    let mut ordered: BTreeMap<&'a str, &'a SecretSpec> = BTreeMap::new();
12    for spec in specs {
13        ordered.insert(spec.name, spec);
14    }
15
16    let mut properties = Map::new();
17    for spec in ordered.values() {
18        properties.insert(
19            spec.name.to_string(),
20            json!({
21                "type": "string",
22                "description": spec.description.unwrap_or(""),
23            }),
24        );
25    }
26
27    json!({
28        "type": "object",
29        "properties": properties,
30    })
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::{SecretDescribable, SecretSpec};
37
38    struct DemoA;
39    struct DemoB;
40
41    impl SecretDescribable for DemoA {
42        fn secret_specs() -> &'static [SecretSpec] {
43            &[
44                SecretSpec {
45                    name: "ALPHA",
46                    description: Some("alpha description"),
47                },
48                SecretSpec {
49                    name: "OMEGA",
50                    description: None,
51                },
52            ]
53        }
54    }
55
56    impl SecretDescribable for DemoB {
57        fn secret_specs() -> &'static [SecretSpec] {
58            &[SecretSpec {
59                name: "BETA",
60                description: Some("beta description"),
61            }]
62        }
63    }
64
65    #[test]
66    fn schema_properties_are_stably_ordered() {
67        let schema = specs_to_json_schema(
68            DemoA::secret_specs()
69                .iter()
70                .chain(DemoB::secret_specs().iter()),
71        );
72
73        let object = schema.as_object().expect("schema object");
74        assert_eq!(object.get("type").and_then(Value::as_str), Some("object"));
75
76        let properties = object
77            .get("properties")
78            .and_then(Value::as_object)
79            .expect("properties map");
80
81        let ordered_keys: Vec<_> = properties.keys().cloned().collect();
82        assert_eq!(ordered_keys, vec!["ALPHA", "BETA", "OMEGA"]);
83
84        let alpha = properties
85            .get("ALPHA")
86            .and_then(Value::as_object)
87            .expect("alpha schema");
88        assert_eq!(alpha.get("type").and_then(Value::as_str), Some("string"));
89        assert_eq!(
90            alpha.get("description").and_then(Value::as_str),
91            Some("alpha description")
92        );
93    }
94}