use alloc::format;
use alloc::string::String;
use serde_json::{Map, Value};
pub const PREV: &str = "$prev";
pub const ITEM: &str = "$item";
pub fn fill(input: &Value, prev: Option<&Value>) -> Result<Value, String> {
fill_with(input, prev, None)
}
pub fn fill_with(
input: &Value,
prev: Option<&Value>,
item: Option<&Value>,
) -> Result<Value, String> {
match input {
Value::Object(object) => {
if let Some(pointer) = sole(object, PREV) {
let prev = prev.ok_or_else(|| format!("{PREV} {pointer:?} in the first step"))?;
return prev.pointer(pointer).cloned().ok_or_else(|| {
format!("{PREV} {pointer:?} names nothing in the previous reply")
});
}
if let Some(pointer) = sole(object, ITEM) {
let item = item.ok_or_else(|| format!("{ITEM} {pointer:?} outside a fan-out"))?;
return item
.pointer(pointer)
.cloned()
.ok_or_else(|| format!("{ITEM} {pointer:?} names nothing in the element"));
}
let mut filled = Map::with_capacity(object.len());
for (key, value) in object {
filled.insert(key.clone(), fill_with(value, prev, item)?);
}
Ok(Value::Object(filled))
}
Value::Array(items) => items
.iter()
.map(|value| fill_with(value, prev, item))
.collect::<Result<_, _>>()
.map(Value::Array),
other => Ok(other.clone()),
}
}
#[must_use]
pub fn names_prev(input: &Value) -> bool {
match input {
Value::Object(object) => sole(object, PREV).is_some() || object.values().any(names_prev),
Value::Array(items) => items.iter().any(names_prev),
_ => false,
}
}
fn sole<'a>(object: &'a Map<String, Value>, key: &str) -> Option<&'a str> {
match (object.len(), object.get(key)) {
(1, Some(Value::String(pointer))) => Some(pointer),
_ => None,
}
}
#[cfg(test)]
#[path = "prev_tests.rs"]
mod tests;