use std::sync::Arc;
use super::{ConfigSource, Hierarchical};
use crate::value::{FileFormat, Map, Pointer, Value, ValueOrigin, WithOrigin};
#[derive(Debug, Clone)]
pub struct Json {
origin: Arc<ValueOrigin>,
inner: WithOrigin,
}
impl Json {
pub fn empty(filename: &str) -> Self {
Self::new(filename, serde_json::Map::default())
}
pub fn new(filename: &str, object: serde_json::Map<String, serde_json::Value>) -> Self {
let origin = Arc::new(ValueOrigin::File {
name: filename.to_owned(),
format: FileFormat::Json,
});
let inner = Self::map_value(serde_json::Value::Object(object), &origin, String::new());
Self { origin, inner }
}
pub fn merge(&mut self, at: &str, value: impl serde::Serialize) {
let value = serde_json::to_value(value).expect("failed serializing inserted value");
assert!(
!at.is_empty() || value.is_object(),
"Cannot overwrite root object"
);
let value = Self::map_value(value, &self.origin, at.to_owned());
let merge_point = if let Some((parent, last_segment)) = Pointer(at).split_last() {
self.inner.ensure_object(parent, |path| {
Arc::new(ValueOrigin::Path {
source: self.origin.clone(),
path: path.0.to_owned(),
})
});
let parent = self.inner.get_mut(parent).unwrap();
let Value::Object(parent_object) = &mut parent.inner else {
unreachable!();
};
if !parent_object.contains_key(last_segment) {
parent_object.insert(
last_segment.to_owned(),
WithOrigin {
inner: Value::Null,
origin: Arc::default(), },
);
}
parent_object.get_mut(last_segment).unwrap()
} else {
&mut self.inner
};
merge_point.deep_merge(value);
debug_assert!(matches!(&self.inner.inner, Value::Object(_)));
}
pub(crate) fn map_value(
value: serde_json::Value,
file_origin: &Arc<ValueOrigin>,
path: String,
) -> WithOrigin {
let inner = match value {
serde_json::Value::Bool(value) => value.into(),
serde_json::Value::Number(value) => value.into(),
serde_json::Value::String(value) => value.into(),
serde_json::Value::Null => Value::Null,
serde_json::Value::Array(values) => Value::Array(
values
.into_iter()
.enumerate()
.map(|(i, value)| {
let child_path = Pointer(&path).join(&i.to_string());
Self::map_value(value, file_origin, child_path)
})
.collect(),
),
serde_json::Value::Object(values) => Value::Object(
values
.into_iter()
.map(|(key, value)| {
let value = Self::map_value(value, file_origin, Pointer(&path).join(&key));
(key, value)
})
.collect(),
),
};
WithOrigin {
inner,
origin: if path.is_empty() {
file_origin.clone()
} else {
Arc::new(ValueOrigin::Path {
source: file_origin.clone(),
path,
})
},
}
}
#[cfg(test)]
pub(crate) fn inner(&self) -> &WithOrigin {
&self.inner
}
}
impl ConfigSource for Json {
type Kind = Hierarchical;
fn into_contents(self) -> WithOrigin<Map> {
self.inner.map(|value| match value {
Value::Object(map) => map,
_ => Map::default(),
})
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
use crate::{testonly::extract_json_name, value::StrValue};
#[test]
fn creating_json_config() {
let json = serde_json::json!({
"bool_value": true,
"nested": {
"int_value": 123,
"str": "???",
},
});
let serde_json::Value::Object(json) = json else {
unreachable!();
};
let mut json = Json::new("test.json", json);
let bool_value = json.inner.get(Pointer("bool_value")).unwrap();
assert_matches!(bool_value.inner, Value::Bool(true));
assert_matches!(
bool_value.origin.as_ref(),
ValueOrigin::Path { path, source } if path == "bool_value" && extract_json_name(source) == "test.json"
);
let str = json.inner.get(Pointer("nested.str")).unwrap();
assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "???");
assert_matches!(
str.origin.as_ref(),
ValueOrigin::Path { path, source } if path == "nested.str" && extract_json_name(source) == "test.json"
);
json.merge("nested.str", "!!!");
let str = json.inner.get(Pointer("nested.str")).unwrap();
assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "!!!");
json.merge(
"nested",
serde_json::json!({
"int_value": 5,
"array": [1, 2],
}),
);
let str = json.inner.get(Pointer("nested.str")).unwrap();
assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "!!!");
let int_value = json.inner.get(Pointer("nested.int_value")).unwrap();
assert_matches!(&int_value.inner, Value::Number(num) if *num == 5_u64.into());
let array = json.inner.get(Pointer("nested.array")).unwrap();
assert_matches!(&array.inner, Value::Array(items) if items.len() == 2);
}
#[test]
fn creating_config_using_macro() {
let json = config! {
"bool_value": true,
"nested.str": "???",
"nested.int_value": 123,
};
let bool_value = json.inner.get(Pointer("bool_value")).unwrap();
assert_matches!(bool_value.inner, Value::Bool(true));
assert_matches!(
bool_value.origin.as_ref(),
ValueOrigin::Path { path, source }
if path == "bool_value" && extract_json_name(source).contains("inline config")
);
let str = json.inner.get(Pointer("nested.str")).unwrap();
assert_matches!(&str.inner, Value::String(StrValue::Plain(s)) if s == "???");
assert_matches!(
str.origin.as_ref(),
ValueOrigin::Path { path, .. } if path == "nested.str"
);
}
}