smart_config/source/
json.rs

1use std::sync::Arc;
2
3use super::{ConfigSource, Hierarchical};
4use crate::value::{FileFormat, Map, Pointer, Value, ValueOrigin, WithOrigin};
5
6/// JSON-based configuration source.
7#[derive(Debug, Clone)]
8pub struct Json {
9    origin: Arc<ValueOrigin>,
10    inner: WithOrigin,
11}
12
13impl Json {
14    /// Creates an empty JSON source with the specified name.
15    pub fn empty(filename: &str) -> Self {
16        Self::new(filename, serde_json::Map::default())
17    }
18
19    /// Creates a source with the specified name and contents.
20    pub fn new(filename: &str, object: serde_json::Map<String, serde_json::Value>) -> Self {
21        let origin = Arc::new(ValueOrigin::File {
22            name: filename.to_owned(),
23            format: FileFormat::Json,
24        });
25        let inner = Self::map_value(serde_json::Value::Object(object), &origin, String::new());
26        Self { origin, inner }
27    }
28
29    /// Merges a value at the specified path into JSON.
30    ///
31    /// If any ancestors in `at` are not objects, they are replaced with objects.
32    ///
33    /// # Panics
34    ///
35    /// - Panics if serializing `value` to the JSON object model fails.
36    /// - Panics if `at` is empty and `value` doesn't serialize to an object (which would lead to the root object
37    ///   being replaced with non-object data).
38    pub fn merge(&mut self, at: &str, value: impl serde::Serialize) {
39        let value = serde_json::to_value(value).expect("failed serializing inserted value");
40        assert!(
41            !at.is_empty() || value.is_object(),
42            "Cannot overwrite root object"
43        );
44
45        let value = Self::map_value(value, &self.origin, at.to_owned());
46
47        let merge_point = if let Some((parent, last_segment)) = Pointer(at).split_last() {
48            self.inner.ensure_object(parent, |path| {
49                Arc::new(ValueOrigin::Path {
50                    source: self.origin.clone(),
51                    path: path.0.to_owned(),
52                })
53            });
54
55            // Safe since the object has just been inserted.
56            let parent = self.inner.get_mut(parent).unwrap();
57            let Value::Object(parent_object) = &mut parent.inner else {
58                unreachable!();
59            };
60            if !parent_object.contains_key(last_segment) {
61                parent_object.insert(
62                    last_segment.to_owned(),
63                    WithOrigin {
64                        inner: Value::Null,
65                        origin: Arc::default(), // Doesn't matter; will be overwritten below
66                    },
67                );
68            }
69            parent_object.get_mut(last_segment).unwrap()
70        } else {
71            &mut self.inner
72        };
73
74        merge_point.deep_merge(value);
75        debug_assert!(matches!(&self.inner.inner, Value::Object(_)));
76    }
77
78    pub(crate) fn map_value(
79        value: serde_json::Value,
80        file_origin: &Arc<ValueOrigin>,
81        path: String,
82    ) -> WithOrigin {
83        let inner = match value {
84            serde_json::Value::Bool(value) => value.into(),
85            serde_json::Value::Number(value) => value.into(),
86            serde_json::Value::String(value) => value.into(),
87            serde_json::Value::Null => Value::Null,
88            serde_json::Value::Array(values) => Value::Array(
89                values
90                    .into_iter()
91                    .enumerate()
92                    .map(|(i, value)| {
93                        let child_path = Pointer(&path).join(&i.to_string());
94                        Self::map_value(value, file_origin, child_path)
95                    })
96                    .collect(),
97            ),
98            serde_json::Value::Object(values) => Value::Object(
99                values
100                    .into_iter()
101                    .map(|(key, value)| {
102                        let value = Self::map_value(value, file_origin, Pointer(&path).join(&key));
103                        (key, value)
104                    })
105                    .collect(),
106            ),
107        };
108
109        WithOrigin {
110            inner,
111            origin: if path.is_empty() {
112                file_origin.clone()
113            } else {
114                Arc::new(ValueOrigin::Path {
115                    source: file_origin.clone(),
116                    path,
117                })
118            },
119        }
120    }
121
122    #[cfg(test)]
123    pub(crate) fn inner(&self) -> &WithOrigin {
124        &self.inner
125    }
126}
127
128impl ConfigSource for Json {
129    type Kind = Hierarchical;
130
131    fn into_contents(self) -> WithOrigin<Map> {
132        self.inner.map(|value| match value {
133            Value::Object(map) => map,
134            _ => Map::default(),
135        })
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use assert_matches::assert_matches;
142
143    use super::*;
144    use crate::{testonly::extract_json_name, value::StrValue};
145
146    #[test]
147    fn creating_json_config() {
148        let json = serde_json::json!({
149            "bool_value": true,
150            "nested": {
151                "int_value": 123,
152                "str": "???",
153            },
154        });
155        let serde_json::Value::Object(json) = json else {
156            unreachable!();
157        };
158        let mut json = Json::new("test.json", json);
159
160        let bool_value = json.inner.get(Pointer("bool_value")).unwrap();
161        assert_matches!(bool_value.inner, Value::Bool(true));
162        assert_matches!(
163            bool_value.origin.as_ref(),
164            ValueOrigin::Path { path, source } if path == "bool_value" && extract_json_name(source) == "test.json"
165        );
166
167        let str = json.inner.get(Pointer("nested.str")).unwrap();
168        assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "???");
169        assert_matches!(
170            str.origin.as_ref(),
171            ValueOrigin::Path { path, source } if path == "nested.str" && extract_json_name(source) == "test.json"
172        );
173
174        json.merge("nested.str", "!!!");
175        let str = json.inner.get(Pointer("nested.str")).unwrap();
176        assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "!!!");
177
178        json.merge(
179            "nested",
180            serde_json::json!({
181                "int_value": 5,
182                "array": [1, 2],
183            }),
184        );
185        let str = json.inner.get(Pointer("nested.str")).unwrap();
186        assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "!!!");
187        let int_value = json.inner.get(Pointer("nested.int_value")).unwrap();
188        assert_matches!(&int_value.inner, Value::Number(num) if *num == 5_u64.into());
189        let array = json.inner.get(Pointer("nested.array")).unwrap();
190        assert_matches!(&array.inner, Value::Array(items) if items.len() == 2);
191    }
192
193    #[test]
194    fn creating_config_using_macro() {
195        let json = config! {
196            "bool_value": true,
197            "nested.str": "???",
198            "nested.int_value": 123,
199        };
200
201        let bool_value = json.inner.get(Pointer("bool_value")).unwrap();
202        assert_matches!(bool_value.inner, Value::Bool(true));
203        assert_matches!(
204            bool_value.origin.as_ref(),
205            ValueOrigin::Path { path, source }
206                if path == "bool_value" && extract_json_name(source).contains("inline config")
207        );
208
209        let str = json.inner.get(Pointer("nested.str")).unwrap();
210        assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "???");
211        assert_matches!(
212            str.origin.as_ref(),
213            ValueOrigin::Path { path, .. } if path == "nested.str"
214        );
215    }
216}