Skip to main content

robinpath_modules/modules/
transform_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    // transform.pick object keys → filtered object
5    rp.register_builtin("transform.pick", |args, _| {
6        let data = args.first().cloned().unwrap_or(Value::Null);
7        let keys = args.get(1).cloned().unwrap_or(Value::Null);
8        if let (Value::Object(obj), Value::Array(key_list)) = (&data, &keys) {
9            let mut result = indexmap::IndexMap::new();
10            for key_val in key_list {
11                let key = key_val.to_display_string();
12                if key.contains('.') {
13                    // Nested dot notation
14                    let val = get_nested(&data, &key);
15                    if !matches!(val, Value::Null) {
16                        result.insert(key, val);
17                    }
18                } else if let Some(v) = obj.get(&key) {
19                    result.insert(key, v.clone());
20                }
21            }
22            Ok(Value::Object(result))
23        } else {
24            Ok(data)
25        }
26    });
27
28    // transform.omit object keys → filtered object
29    rp.register_builtin("transform.omit", |args, _| {
30        let data = args.first().cloned().unwrap_or(Value::Null);
31        let keys = args.get(1).cloned().unwrap_or(Value::Null);
32        if let (Value::Object(obj), Value::Array(key_list)) = (&data, &keys) {
33            let omit_keys: Vec<String> = key_list.iter().map(|v| v.to_display_string()).collect();
34            let mut result = indexmap::IndexMap::new();
35            for (k, v) in obj {
36                if !omit_keys.contains(k) {
37                    result.insert(k.clone(), v.clone());
38                }
39            }
40            Ok(Value::Object(result))
41        } else {
42            Ok(data)
43        }
44    });
45
46    // transform.rename object mapping → renamed object
47    rp.register_builtin("transform.rename", |args, _| {
48        let data = args.first().cloned().unwrap_or(Value::Null);
49        let mapping = args.get(1).cloned().unwrap_or(Value::Null);
50        if let (Value::Object(obj), Value::Object(map)) = (&data, &mapping) {
51            let mut result = indexmap::IndexMap::new();
52            for (k, v) in obj {
53                let new_key = map.get(k).map(|m| m.to_display_string()).unwrap_or_else(|| k.clone());
54                result.insert(new_key, v.clone());
55            }
56            Ok(Value::Object(result))
57        } else {
58            Ok(data)
59        }
60    });
61
62    // transform.mapValues object mapping → transformed values
63    rp.register_builtin("transform.mapValues", |args, _| {
64        let data = args.first().cloned().unwrap_or(Value::Null);
65        let mapping = args.get(1).cloned().unwrap_or(Value::Null);
66        if let (Value::Object(obj), Value::Object(map)) = (&data, &mapping) {
67            let mut result = indexmap::IndexMap::new();
68            for (k, v) in obj {
69                let transformed = if let Some(Value::String(transform)) = map.get(k) {
70                    apply_transform(v, transform)
71                } else {
72                    v.clone()
73                };
74                result.insert(k.clone(), transformed);
75            }
76            Ok(Value::Object(result))
77        } else {
78            Ok(data)
79        }
80    });
81
82    // transform.coerce value type → coerced value
83    rp.register_builtin("transform.coerce", |args, _| {
84        let value = args.first().cloned().unwrap_or(Value::Null);
85        let target_type = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
86        Ok(coerce_value(&value, &target_type))
87    });
88
89    // transform.flatten object separator? → flat object with dot keys
90    rp.register_builtin("transform.flatten", |args, _| {
91        let data = args.first().cloned().unwrap_or(Value::Null);
92        let sep = args.get(1).map(|v| v.to_display_string()).unwrap_or_else(|| ".".to_string());
93        let mut result = indexmap::IndexMap::new();
94        flatten_object(&data, "", &sep, &mut result);
95        Ok(Value::Object(result))
96    });
97
98    // transform.unflatten object separator? → nested object
99    rp.register_builtin("transform.unflatten", |args, _| {
100        let data = args.first().cloned().unwrap_or(Value::Null);
101        let sep = args.get(1).map(|v| v.to_display_string()).unwrap_or_else(|| ".".to_string());
102        if let Value::Object(obj) = data {
103            let mut result = indexmap::IndexMap::new();
104            for (key, value) in obj {
105                set_nested_path(&mut result, &key, &sep, value);
106            }
107            Ok(Value::Object(result))
108        } else {
109            Ok(data)
110        }
111    });
112
113    // transform.merge ...objects → deep merged object
114    rp.register_builtin("transform.merge", |args, _| {
115        let mut result = indexmap::IndexMap::new();
116        for arg in args {
117            if let Value::Object(obj) = arg {
118                deep_merge(&mut result, obj);
119            }
120        }
121        Ok(Value::Object(result))
122    });
123
124    // transform.defaults object defaults → filled object
125    rp.register_builtin("transform.defaults", |args, _| {
126        let data = args.first().cloned().unwrap_or(Value::Null);
127        let defaults = args.get(1).cloned().unwrap_or(Value::Null);
128        if let (Value::Object(mut obj), Value::Object(defs)) = (data, defaults) {
129            for (k, v) in defs {
130                if !obj.contains_key(&k) || matches!(obj.get(&k), Some(Value::Null)) {
131                    obj.insert(k, v);
132                }
133            }
134            Ok(Value::Object(obj))
135        } else {
136            Ok(args.first().cloned().unwrap_or(Value::Null))
137        }
138    });
139
140    // transform.template templateStr data → rendered string
141    rp.register_builtin("transform.template", |args, _| {
142        let template = args.first().map(|v| v.to_display_string()).unwrap_or_default();
143        let data = args.get(1).cloned().unwrap_or(Value::Null);
144        let mut result = template.clone();
145        if let Value::Object(obj) = &data {
146            for (key, value) in obj {
147                let placeholder = format!("{{{{{}}}}}", key);
148                result = result.replace(&placeholder, &value.to_display_string());
149            }
150        }
151        Ok(Value::String(result))
152    });
153
154    // transform.group array key → grouped object
155    rp.register_builtin("transform.group", |args, _| {
156        let data = args.first().cloned().unwrap_or(Value::Null);
157        let key = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
158        if let Value::Array(arr) = data {
159            let mut groups: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
160            for item in arr {
161                let group_val = if let Value::Object(obj) = &item {
162                    obj.get(&key).map(|v| v.to_display_string()).unwrap_or_else(|| "undefined".to_string())
163                } else {
164                    "undefined".to_string()
165                };
166                groups.entry(group_val).or_default().push(item);
167            }
168            let mut result = indexmap::IndexMap::new();
169            for (k, v) in groups {
170                result.insert(k, Value::Array(v));
171            }
172            Ok(Value::Object(result))
173        } else {
174            Ok(data)
175        }
176    });
177
178    // transform.mapArray array mapping → mapped array
179    rp.register_builtin("transform.mapArray", |args, _| {
180        let data = args.first().cloned().unwrap_or(Value::Null);
181        let mapping = args.get(1).cloned().unwrap_or(Value::Null);
182        if let (Value::Array(arr), Value::Object(map)) = (data, mapping) {
183            let result: Vec<Value> = arr.iter().map(|item| {
184                if let Value::Object(obj) = item {
185                    let mut new_obj = indexmap::IndexMap::new();
186                    for (new_key, source) in &map {
187                        let source_key = source.to_display_string();
188                        let val = obj.get(&source_key).cloned().unwrap_or(Value::Null);
189                        new_obj.insert(new_key.clone(), val);
190                    }
191                    Value::Object(new_obj)
192                } else {
193                    item.clone()
194                }
195            }).collect();
196            Ok(Value::Array(result))
197        } else {
198            Ok(args.first().cloned().unwrap_or(Value::Null))
199        }
200    });
201
202    // transform.filter array conditions → filtered array
203    rp.register_builtin("transform.filter", |args, _| {
204        let data = args.first().cloned().unwrap_or(Value::Null);
205        let conditions = args.get(1).cloned().unwrap_or(Value::Null);
206        if let (Value::Array(arr), Value::Object(conds)) = (data, conditions) {
207            let result: Vec<Value> = arr.into_iter().filter(|item| {
208                if let Value::Object(obj) = item {
209                    conds.iter().all(|(k, v)| {
210                        obj.get(k).map(|actual| actual.to_display_string() == v.to_display_string()).unwrap_or(false)
211                    })
212                } else {
213                    false
214                }
215            }).collect();
216            Ok(Value::Array(result))
217        } else {
218            Ok(args.first().cloned().unwrap_or(Value::Null))
219        }
220    });
221
222    // transform.sort array key order? → sorted array
223    rp.register_builtin("transform.sort", |args, _| {
224        let data = args.first().cloned().unwrap_or(Value::Null);
225        let key = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
226        let order = args.get(2).map(|v| v.to_display_string()).unwrap_or_else(|| "asc".to_string());
227        if let Value::Array(mut arr) = data {
228            arr.sort_by(|a, b| {
229                let av = if let Value::Object(obj) = a { obj.get(&key).cloned().unwrap_or(Value::Null) } else { Value::Null };
230                let bv = if let Value::Object(obj) = b { obj.get(&key).cloned().unwrap_or(Value::Null) } else { Value::Null };
231                let cmp = match (&av, &bv) {
232                    (Value::Number(a), Value::Number(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
233                    _ => av.to_display_string().cmp(&bv.to_display_string()),
234                };
235                if order == "desc" { cmp.reverse() } else { cmp }
236            });
237            Ok(Value::Array(arr))
238        } else {
239            Ok(data)
240        }
241    });
242}
243
244fn get_nested(val: &Value, path: &str) -> Value {
245    let mut current = val.clone();
246    for part in path.split('.') {
247        if let Value::Object(obj) = &current {
248            current = obj.get(part).cloned().unwrap_or(Value::Null);
249        } else {
250            return Value::Null;
251        }
252    }
253    current
254}
255
256fn apply_transform(val: &Value, transform: &str) -> Value {
257    match transform {
258        "toString" => Value::String(val.to_display_string()),
259        "toNumber" => Value::Number(val.to_number()),
260        "toBoolean" => Value::Bool(match val {
261            Value::Bool(b) => *b,
262            Value::Number(n) => *n != 0.0,
263            Value::String(s) => !s.is_empty() && s != "false" && s != "0",
264            Value::Null => false,
265            _ => true,
266        }),
267        "toUpperCase" => Value::String(val.to_display_string().to_uppercase()),
268        "toLowerCase" => Value::String(val.to_display_string().to_lowercase()),
269        "trim" => Value::String(val.to_display_string().trim().to_string()),
270        _ => val.clone(),
271    }
272}
273
274fn coerce_value(val: &Value, target: &str) -> Value {
275    match target {
276        "string" => Value::String(val.to_display_string()),
277        "number" | "float" => Value::Number(val.to_number()),
278        "integer" => Value::Number((val.to_number() as i64) as f64),
279        "boolean" => Value::Bool(match val {
280            Value::Bool(b) => *b,
281            Value::Number(n) => *n != 0.0,
282            Value::String(s) => s == "true" || s == "1",
283            Value::Null => false,
284            _ => true,
285        }),
286        "array" => match val {
287            Value::Array(_) => val.clone(),
288            _ => Value::Array(vec![val.clone()]),
289        },
290        _ => val.clone(),
291    }
292}
293
294fn flatten_object(val: &Value, prefix: &str, sep: &str, result: &mut indexmap::IndexMap<String, Value>) {
295    match val {
296        Value::Object(obj) => {
297            for (k, v) in obj {
298                let key = if prefix.is_empty() { k.clone() } else { format!("{}{}{}", prefix, sep, k) };
299                flatten_object(v, &key, sep, result);
300            }
301        }
302        _ => {
303            result.insert(prefix.to_string(), val.clone());
304        }
305    }
306}
307
308fn set_nested_path(obj: &mut indexmap::IndexMap<String, Value>, path: &str, sep: &str, value: Value) {
309    let parts: Vec<&str> = path.split(sep).collect();
310    if parts.len() == 1 {
311        obj.insert(path.to_string(), value);
312        return;
313    }
314    let key = parts[0].to_string();
315    let rest = parts[1..].join(sep);
316    if !obj.contains_key(&key) {
317        obj.insert(key.clone(), Value::Object(indexmap::IndexMap::new()));
318    }
319    if let Some(Value::Object(inner)) = obj.get_mut(&key) {
320        set_nested_path(inner, &rest, sep, value);
321    }
322}
323
324fn deep_merge(target: &mut indexmap::IndexMap<String, Value>, source: &indexmap::IndexMap<String, Value>) {
325    for (k, v) in source {
326        if let (Some(Value::Object(existing)), Value::Object(incoming)) = (target.get_mut(k), v) {
327            deep_merge(existing, incoming);
328        } else {
329            target.insert(k.clone(), v.clone());
330        }
331    }
332}