robinpath 0.2.0

RobinPath - A lightweight, fast scripting language interpreter for automation and data processing
Documentation
use super::Value;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum AttributePathSegment {
    #[serde(rename = "property")]
    Property { name: String },
    #[serde(rename = "index")]
    Index { index: i64 },
    #[serde(rename = "dynamicKey")]
    DynamicKey {
        variable: String,
        #[serde(rename = "varPath")]
        var_path: Option<Vec<AttributePathSegment>>,
    },
}

/// Resolve a path against a base value, returning the nested value or Null
pub fn resolve_path(base: &Value, path: &[AttributePathSegment]) -> Value {
    let mut current = base.clone();

    for segment in path {
        match segment {
            AttributePathSegment::Property { name } => {
                current = match &current {
                    Value::Object(obj) => obj.get(name).cloned().unwrap_or(Value::Null),
                    Value::Array(arr) if name == "length" => Value::Number(arr.len() as f64),
                    Value::String(s) if name == "length" => Value::Number(s.chars().count() as f64),
                    _ => return Value::Null,
                };
            }
            AttributePathSegment::Index { index } => {
                current = match &current {
                    Value::Array(arr) => {
                        let idx = *index;
                        if idx < 0 || idx as usize >= arr.len() {
                            return Value::Null;
                        }
                        arr[idx as usize].clone()
                    }
                    _ => return Value::Null,
                };
            }
            AttributePathSegment::DynamicKey { .. } => {
                // Dynamic keys need runtime resolution — handled in executor
                return Value::Null;
            }
        }
    }

    current
}

/// Set a value at a path within a base value, mutating in place
pub fn set_at_path(base: &mut Value, path: &[AttributePathSegment], value: Value) {
    if path.is_empty() {
        return;
    }

    if path.len() == 1 {
        match &path[0] {
            AttributePathSegment::Property { name } => {
                if let Value::Object(obj) = base {
                    obj.insert(name.clone(), value);
                } else {
                    // Auto-create object
                    let mut obj = indexmap::IndexMap::new();
                    obj.insert(name.clone(), value);
                    *base = Value::Object(obj);
                }
            }
            AttributePathSegment::Index { index } => {
                if let Value::Array(arr) = base {
                    let idx = *index as usize;
                    if idx < arr.len() {
                        arr[idx] = value;
                    } else {
                        // Extend array with nulls to reach the target index
                        arr.resize(idx + 1, Value::Null);
                        arr[idx] = value;
                    }
                }
            }
            AttributePathSegment::DynamicKey { .. } => {
                // Handled in executor
            }
        }
        return;
    }

    let (first, rest) = path.split_first().unwrap();
    match first {
        AttributePathSegment::Property { name } => {
            if let Value::Object(obj) = base {
                let entry = obj
                    .entry(name.clone())
                    .or_insert_with(|| Value::Object(indexmap::IndexMap::new()));
                set_at_path(entry, rest, value);
            } else {
                let mut obj = indexmap::IndexMap::new();
                let mut child = Value::Object(indexmap::IndexMap::new());
                set_at_path(&mut child, rest, value);
                obj.insert(name.clone(), child);
                *base = Value::Object(obj);
            }
        }
        AttributePathSegment::Index { index } => {
            if let Value::Array(arr) = base {
                let idx = *index as usize;
                if idx < arr.len() {
                    set_at_path(&mut arr[idx], rest, value);
                }
            }
        }
        AttributePathSegment::DynamicKey { .. } => {
            // Handled in executor
        }
    }
}