use serde_json::Value;
use crate::error::RewriteError;
pub fn extract_scalar<'a, I>(doc: &Value, segments: I) -> Result<String, RewriteError>
where
I: IntoIterator<Item = &'a str>,
{
let mut cursor = doc;
let mut walked: Vec<&str> = Vec::new();
for segment in segments {
walked.push(segment);
cursor = cursor
.as_object()
.and_then(|obj| obj.get(segment))
.ok_or_else(|| RewriteError::PathNotScalar {
path: walked.join("."),
})?;
}
scalar_to_string(cursor).ok_or_else(|| RewriteError::PathNotScalar {
path: walked.join("."),
})
}
fn scalar_to_string(value: &Value) -> Option<String> {
match value {
Value::String(s) => Some(s.clone()),
Value::Number(n) => Some(n.to_string()),
Value::Bool(b) => Some(b.to_string()),
Value::Null | Value::Array(_) | Value::Object(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn extracts_nested_string() {
let doc = json!({ "a": { "b": "v" } });
assert_eq!(extract_scalar(&doc, ["a", "b"]).unwrap(), "v");
}
#[test]
fn renders_number_and_bool_deterministically() {
let doc = json!({ "n": 42, "flag": true });
assert_eq!(extract_scalar(&doc, ["n"]).unwrap(), "42");
assert_eq!(extract_scalar(&doc, ["flag"]).unwrap(), "true");
}
#[test]
fn missing_segment_reports_walked_path() {
let doc = json!({ "a": { "b": "v" } });
let err = extract_scalar(&doc, ["a", "c"]).unwrap_err();
assert_eq!(
err,
RewriteError::PathNotScalar {
path: "a.c".to_owned()
}
);
}
#[test]
fn non_scalar_leaf_is_rejected() {
let doc = json!({ "a": { "b": [1, 2] }, "obj": {}, "nil": null });
assert!(extract_scalar(&doc, ["a", "b"]).is_err());
assert!(extract_scalar(&doc, ["obj"]).is_err());
assert!(extract_scalar(&doc, ["nil"]).is_err());
}
}