use super::coerce::type_name;
use super::error::{ErrorKind, TransformError};
use serde_json::{Map, Value};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Seg {
Key(String),
Index(usize),
}
#[derive(Debug, Clone)]
pub(super) struct CompiledPath {
pub(super) segs: Vec<Seg>,
pub(super) spec: String,
}
impl CompiledPath {
pub(super) fn parse(spec: &str) -> anyhow::Result<Self> {
let trimmed = spec.trim();
let body = trimmed.strip_prefix('$').unwrap_or(trimmed);
let body = body.strip_prefix('.').unwrap_or(body);
let mut segs = Vec::new();
if !body.is_empty() {
for part in body.split('.') {
if part.is_empty() {
anyhow::bail!("empty segment in path '{spec}'");
}
let (name, mut brackets) = match part.find('[') {
Some(i) => (&part[..i], &part[i..]),
None => (part, ""),
};
if !name.is_empty() {
segs.push(Seg::Key(name.to_string()));
}
while !brackets.is_empty() {
let close = brackets
.find(']')
.ok_or_else(|| anyhow::anyhow!("unclosed '[' in path '{spec}'"))?;
let raw = &brackets[1..close];
let idx: usize = raw.parse().map_err(|_| {
anyhow::anyhow!("invalid array index '{raw}' in path '{spec}'")
})?;
segs.push(Seg::Index(idx));
brackets = &brackets[close + 1..];
}
}
}
Ok(Self {
segs,
spec: trimmed.to_string(),
})
}
pub(super) fn get<'a>(&self, root: &'a Value) -> Option<&'a Value> {
let mut cur = root;
for seg in &self.segs {
cur = match seg {
Seg::Key(k) => cur.get(k.as_str())?,
Seg::Index(i) => cur.get(*i)?,
};
}
Some(cur)
}
}
#[derive(Debug)]
pub(super) struct CompiledRule {
pub(super) out: Vec<String>,
pub(super) from: CompiledPath,
pub(super) default: Option<Value>,
pub(super) required: bool,
}
pub(super) fn insert_at(
root: &mut Value,
path: &[String],
value: Value,
) -> Result<(), TransformError> {
let (last, parents) = match path.split_last() {
Some(split) => split,
None => return Ok(()),
};
let mut cur = root;
for key in parents {
let obj = match cur {
Value::Object(map) => map,
other => {
return Err(TransformError::new(
format!("$.{}", path.join(".")),
ErrorKind::TypeMismatch,
format!(
"cannot nest under '{key}': it is already a {}",
type_name(other)
),
))
}
};
cur = obj
.entry(key.as_str())
.or_insert_with(|| Value::Object(Map::new()));
}
match cur {
Value::Object(map) => {
map.insert(last.clone(), value);
Ok(())
}
other => Err(TransformError::new(
format!("$.{}", path.join(".")),
ErrorKind::TypeMismatch,
format!("cannot set '{last}': parent is a {}", type_name(other)),
)),
}
}