Skip to main content

jpx_core/extensions/
object.rs

1//! Object/map manipulation functions.
2
3use std::collections::HashSet;
4
5use heck::{
6    ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTrainCase,
7    ToUpperCamelCase,
8};
9use serde_json::{Map, Number, Value};
10
11use crate::functions::{Function, custom_error, number_value};
12use crate::interpreter::SearchResult;
13use crate::registry::register_if_enabled;
14use crate::{Context, Runtime, arg, defn};
15
16/// Register object functions filtered by the enabled set.
17pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
18    register_if_enabled(runtime, "items", enabled, Box::new(EntriesFn::new()));
19    register_if_enabled(
20        runtime,
21        "from_items",
22        enabled,
23        Box::new(FromEntriesFn::new()),
24    );
25    register_if_enabled(
26        runtime,
27        "from_entries",
28        enabled,
29        Box::new(FromEntriesFn::new()),
30    );
31    register_if_enabled(
32        runtime,
33        "with_entries",
34        enabled,
35        Box::new(WithEntriesFn::new()),
36    );
37    register_if_enabled(runtime, "pick", enabled, Box::new(PickFn::new()));
38    register_if_enabled(runtime, "omit", enabled, Box::new(OmitFn::new()));
39    register_if_enabled(runtime, "invert", enabled, Box::new(InvertFn::new()));
40    register_if_enabled(
41        runtime,
42        "rename_keys",
43        enabled,
44        Box::new(RenameKeysFn::new()),
45    );
46    register_if_enabled(
47        runtime,
48        "flatten_keys",
49        enabled,
50        Box::new(FlattenKeysFn::new()),
51    );
52    // NB: object key-flattening is registered only as `flatten_keys`. The array
53    // `flatten` (array.rs) owns the `flatten` name; registering FlattenKeysFn
54    // under `flatten` too created a nondeterministic collision.
55    register_if_enabled(
56        runtime,
57        "unflatten_keys",
58        enabled,
59        Box::new(UnflattenKeysFn::new()),
60    );
61    register_if_enabled(
62        runtime,
63        "unflatten",
64        enabled,
65        Box::new(UnflattenKeysFn::new()),
66    );
67    register_if_enabled(
68        runtime,
69        "flatten_array",
70        enabled,
71        Box::new(FlattenArrayFn::new()),
72    );
73    register_if_enabled(runtime, "deep_merge", enabled, Box::new(DeepMergeFn::new()));
74    register_if_enabled(
75        runtime,
76        "deep_equals",
77        enabled,
78        Box::new(DeepEqualsFn::new()),
79    );
80    register_if_enabled(runtime, "deep_diff", enabled, Box::new(DeepDiffFn::new()));
81    register_if_enabled(runtime, "get", enabled, Box::new(GetFn::new()));
82    register_if_enabled(runtime, "get_path", enabled, Box::new(GetFn::new()));
83    register_if_enabled(runtime, "has", enabled, Box::new(HasFn::new()));
84    register_if_enabled(runtime, "has_path", enabled, Box::new(HasFn::new()));
85    register_if_enabled(runtime, "defaults", enabled, Box::new(DefaultsFn::new()));
86    register_if_enabled(
87        runtime,
88        "defaults_deep",
89        enabled,
90        Box::new(DefaultsDeepFn::new()),
91    );
92    register_if_enabled(runtime, "set_path", enabled, Box::new(SetPathFn::new()));
93    register_if_enabled(
94        runtime,
95        "delete_path",
96        enabled,
97        Box::new(DeletePathFn::new()),
98    );
99    register_if_enabled(runtime, "paths", enabled, Box::new(PathsFn::new()));
100    register_if_enabled(runtime, "leaves", enabled, Box::new(LeavesFn::new()));
101    register_if_enabled(
102        runtime,
103        "leaves_with_paths",
104        enabled,
105        Box::new(LeavesWithPathsFn::new()),
106    );
107    register_if_enabled(
108        runtime,
109        "remove_nulls",
110        enabled,
111        Box::new(RemoveNullsFn::new()),
112    );
113    register_if_enabled(
114        runtime,
115        "remove_empty",
116        enabled,
117        Box::new(RemoveEmptyFn::new()),
118    );
119    register_if_enabled(
120        runtime,
121        "remove_empty_strings",
122        enabled,
123        Box::new(RemoveEmptyStringsFn::new()),
124    );
125    register_if_enabled(
126        runtime,
127        "compact_deep",
128        enabled,
129        Box::new(CompactDeepFn::new()),
130    );
131    register_if_enabled(
132        runtime,
133        "completeness",
134        enabled,
135        Box::new(CompletenessFn::new()),
136    );
137    register_if_enabled(
138        runtime,
139        "type_consistency",
140        enabled,
141        Box::new(TypeConsistencyFn::new()),
142    );
143    register_if_enabled(
144        runtime,
145        "data_quality_score",
146        enabled,
147        Box::new(DataQualityScoreFn::new()),
148    );
149    register_if_enabled(runtime, "redact", enabled, Box::new(RedactFn::new()));
150    register_if_enabled(
151        runtime,
152        "redact_keys",
153        enabled,
154        Box::new(RedactKeysFn::new()),
155    );
156    register_if_enabled(runtime, "mask", enabled, Box::new(MaskFn::new()));
157    register_if_enabled(runtime, "pluck_deep", enabled, Box::new(PluckDeepFn::new()));
158    register_if_enabled(runtime, "paths_to", enabled, Box::new(PathsToFn::new()));
159    register_if_enabled(runtime, "snake_keys", enabled, Box::new(SnakeKeysFn::new()));
160    register_if_enabled(runtime, "camel_keys", enabled, Box::new(CamelKeysFn::new()));
161    register_if_enabled(runtime, "kebab_keys", enabled, Box::new(KebabKeysFn::new()));
162    register_if_enabled(
163        runtime,
164        "pascal_keys",
165        enabled,
166        Box::new(PascalKeysFn::new()),
167    );
168    register_if_enabled(
169        runtime,
170        "shouty_snake_keys",
171        enabled,
172        Box::new(ShoutySnakeKeysFn::new()),
173    );
174    register_if_enabled(
175        runtime,
176        "shouty_kebab_keys",
177        enabled,
178        Box::new(ShoutyKebabKeysFn::new()),
179    );
180    register_if_enabled(runtime, "train_keys", enabled, Box::new(TrainKeysFn::new()));
181    register_if_enabled(
182        runtime,
183        "structural_diff",
184        enabled,
185        Box::new(StructuralDiffFn::new()),
186    );
187    register_if_enabled(
188        runtime,
189        "has_same_shape",
190        enabled,
191        Box::new(HasSameShapeFn::new()),
192    );
193    register_if_enabled(
194        runtime,
195        "infer_schema",
196        enabled,
197        Box::new(InferSchemaFn::new()),
198    );
199    register_if_enabled(
200        runtime,
201        "chunk_by_size",
202        enabled,
203        Box::new(ChunkBySizeFn::new()),
204    );
205    register_if_enabled(runtime, "paginate", enabled, Box::new(PaginateFn::new()));
206    register_if_enabled(
207        runtime,
208        "estimate_size",
209        enabled,
210        Box::new(EstimateSizeFn::new()),
211    );
212    register_if_enabled(
213        runtime,
214        "truncate_to_size",
215        enabled,
216        Box::new(TruncateToSizeFn::new()),
217    );
218    register_if_enabled(runtime, "template", enabled, Box::new(TemplateFn::new()));
219    register_if_enabled(
220        runtime,
221        "template_strict",
222        enabled,
223        Box::new(TemplateStrictFn::new()),
224    );
225}
226
227// =============================================================================
228// items(object) -> array of [key, value] pairs (JEP-013)
229// =============================================================================
230
231defn!(EntriesFn, vec![arg!(object)], None);
232
233impl Function for EntriesFn {
234    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
235        self.signature.validate(args, ctx)?;
236
237        let obj = args[0]
238            .as_object()
239            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
240
241        let entries: Vec<Value> = obj
242            .iter()
243            .map(|(k, v)| {
244                let pair = vec![Value::String(k.clone()), v.clone()];
245                Value::Array(pair)
246            })
247            .collect();
248
249        Ok(Value::Array(entries))
250    }
251}
252
253// =============================================================================
254// from_items(array) -> object from array of [key, value] pairs (JEP-013)
255// =============================================================================
256
257defn!(FromEntriesFn, vec![arg!(array)], None);
258
259impl Function for FromEntriesFn {
260    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
261        self.signature.validate(args, ctx)?;
262
263        let arr = args[0]
264            .as_array()
265            .ok_or_else(|| custom_error(ctx, "Expected array argument"))?;
266
267        let mut result = Map::new();
268
269        for item in arr {
270            if let Some(pair) = item.as_array()
271                && pair.len() >= 2
272                && let Some(key_str) = pair[0].as_str()
273            {
274                result.insert(key_str.to_string(), pair[1].clone());
275            }
276        }
277
278        Ok(Value::Object(result))
279    }
280}
281
282// =============================================================================
283// with_entries(object, expr) -> object (transform entries, jq parity)
284// =============================================================================
285
286defn!(WithEntriesFn, vec![arg!(object), arg!(string)], None);
287
288impl Function for WithEntriesFn {
289    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
290        self.signature.validate(args, ctx)?;
291
292        let obj = args[0]
293            .as_object()
294            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
295
296        let expr_str = args[1]
297            .as_str()
298            .ok_or_else(|| custom_error(ctx, "Expected expression string"))?;
299
300        let compiled = ctx
301            .runtime
302            .compile(expr_str)
303            .map_err(|_| custom_error(ctx, "Invalid expression in with_entries"))?;
304
305        let mut result = Map::new();
306
307        for (key, value) in obj.iter() {
308            let entry = Value::Array(vec![Value::String(key.clone()), value.clone()]);
309
310            let transformed = compiled
311                .search(&entry)
312                .map_err(|_| custom_error(ctx, "Expression error in with_entries"))?;
313
314            if transformed.is_null() {
315                continue;
316            }
317
318            if let Some(pair) = transformed.as_array()
319                && pair.len() >= 2
320                && let Some(new_key) = pair[0].as_str()
321            {
322                result.insert(new_key.to_string(), pair[1].clone());
323            }
324        }
325
326        Ok(Value::Object(result))
327    }
328}
329
330// =============================================================================
331// pick(object, keys) -> object (select specific keys)
332// =============================================================================
333
334defn!(PickFn, vec![arg!(object), arg!(array)], None);
335
336impl Function for PickFn {
337    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
338        self.signature.validate(args, ctx)?;
339
340        let obj = args[0]
341            .as_object()
342            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
343
344        let keys_arr = args[1]
345            .as_array()
346            .ok_or_else(|| custom_error(ctx, "Expected array of keys"))?;
347
348        let keys: HashSet<String> = keys_arr
349            .iter()
350            .filter_map(|k| k.as_str().map(|s| s.to_string()))
351            .collect();
352
353        let result: Map<String, Value> = obj
354            .iter()
355            .filter(|(k, _)| keys.contains(k.as_str()))
356            .map(|(k, v)| (k.clone(), v.clone()))
357            .collect();
358
359        Ok(Value::Object(result))
360    }
361}
362
363// =============================================================================
364// omit(object, keys) -> object (exclude specific keys)
365// =============================================================================
366
367defn!(OmitFn, vec![arg!(object), arg!(array)], None);
368
369impl Function for OmitFn {
370    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
371        self.signature.validate(args, ctx)?;
372
373        let obj = args[0]
374            .as_object()
375            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
376
377        let keys_arr = args[1]
378            .as_array()
379            .ok_or_else(|| custom_error(ctx, "Expected array of keys"))?;
380
381        let keys: HashSet<String> = keys_arr
382            .iter()
383            .filter_map(|k| k.as_str().map(|s| s.to_string()))
384            .collect();
385
386        let result: Map<String, Value> = obj
387            .iter()
388            .filter(|(k, _)| !keys.contains(k.as_str()))
389            .map(|(k, v)| (k.clone(), v.clone()))
390            .collect();
391
392        Ok(Value::Object(result))
393    }
394}
395
396// =============================================================================
397// invert(object) -> object (swap keys and values)
398// =============================================================================
399
400defn!(InvertFn, vec![arg!(object)], None);
401
402impl Function for InvertFn {
403    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
404        self.signature.validate(args, ctx)?;
405
406        let obj = args[0]
407            .as_object()
408            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
409
410        let mut result = Map::new();
411
412        for (k, v) in obj.iter() {
413            let new_key = match v {
414                Value::String(s) => s.clone(),
415                Value::Number(n) => n.to_string(),
416                Value::Bool(b) => b.to_string(),
417                Value::Null => "null".to_string(),
418                _ => continue,
419            };
420            result.insert(new_key, Value::String(k.clone()));
421        }
422
423        Ok(Value::Object(result))
424    }
425}
426
427// =============================================================================
428// rename_keys(object, mapping) -> object
429// =============================================================================
430
431defn!(RenameKeysFn, vec![arg!(object), arg!(object)], None);
432
433impl Function for RenameKeysFn {
434    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
435        self.signature.validate(args, ctx)?;
436
437        let obj = args[0]
438            .as_object()
439            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
440
441        let mapping = args[1]
442            .as_object()
443            .ok_or_else(|| custom_error(ctx, "Expected mapping object"))?;
444
445        let rename_map: std::collections::HashMap<String, String> = mapping
446            .iter()
447            .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
448            .collect();
449
450        let result: Map<String, Value> = obj
451            .iter()
452            .map(|(k, v)| {
453                let new_key = rename_map.get(k).cloned().unwrap_or_else(|| k.clone());
454                (new_key, v.clone())
455            })
456            .collect();
457
458        Ok(Value::Object(result))
459    }
460}
461
462// =============================================================================
463// flatten_keys(object, separator?) -> object
464// =============================================================================
465
466defn!(FlattenKeysFn, vec![arg!(object)], Some(arg!(string)));
467
468fn flatten_object(
469    obj: &Map<String, Value>,
470    prefix: &str,
471    separator: &str,
472    result: &mut Map<String, Value>,
473) {
474    for (k, v) in obj.iter() {
475        let new_key = if prefix.is_empty() {
476            k.clone()
477        } else {
478            format!("{}{}{}", prefix, separator, k)
479        };
480
481        if let Some(nested) = v.as_object() {
482            flatten_object(nested, &new_key, separator, result);
483        } else {
484            result.insert(new_key, v.clone());
485        }
486    }
487}
488
489impl Function for FlattenKeysFn {
490    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
491        self.signature.validate(args, ctx)?;
492
493        let obj = args[0]
494            .as_object()
495            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
496
497        let default_sep = ".".to_string();
498        let separator = args
499            .get(1)
500            .and_then(|s| s.as_str().map(|s| s.to_string()))
501            .unwrap_or(default_sep);
502
503        let mut result = Map::new();
504        flatten_object(obj, "", &separator, &mut result);
505
506        Ok(Value::Object(result))
507    }
508}
509
510// =============================================================================
511// unflatten_keys(object, separator?) -> object
512// =============================================================================
513
514defn!(UnflattenKeysFn, vec![arg!(object)], Some(arg!(string)));
515
516fn insert_nested(obj: &mut Map<String, Value>, parts: &[&str], value: Value) {
517    if parts.is_empty() {
518        return;
519    }
520
521    if parts.len() == 1 {
522        obj.insert(parts[0].to_string(), value);
523        return;
524    }
525
526    let key = parts[0].to_string();
527    let rest = &parts[1..];
528
529    let nested = obj
530        .entry(key.clone())
531        .or_insert_with(|| Value::Object(Map::new()));
532
533    if let Some(nested_obj) = nested.as_object() {
534        let mut new_obj = nested_obj.clone();
535        insert_nested(&mut new_obj, rest, value);
536        *obj.get_mut(&key).unwrap() = Value::Object(new_obj);
537    }
538}
539
540impl Function for UnflattenKeysFn {
541    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
542        self.signature.validate(args, ctx)?;
543
544        let obj = args[0]
545            .as_object()
546            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
547
548        let default_sep = ".".to_string();
549        let separator = args
550            .get(1)
551            .and_then(|s| s.as_str().map(|s| s.to_string()))
552            .unwrap_or(default_sep);
553
554        let mut result = Map::new();
555
556        for (key, value) in obj.iter() {
557            let parts: Vec<&str> = key.split(&separator).collect();
558            insert_nested(&mut result, &parts, value.clone());
559        }
560
561        Ok(Value::Object(result))
562    }
563}
564
565// =============================================================================
566// flatten_array(any, separator?) -> object
567// Flattens nested objects AND arrays with numeric indices
568// =============================================================================
569
570defn!(FlattenArrayFn, vec![arg!(any)], Some(arg!(string)));
571
572fn flatten_value(value: &Value, prefix: &str, separator: &str, result: &mut Map<String, Value>) {
573    match value {
574        Value::Object(obj) => {
575            if obj.is_empty() {
576                if !prefix.is_empty() {
577                    result.insert(prefix.to_string(), Value::Object(obj.clone()));
578                }
579            } else {
580                for (k, v) in obj.iter() {
581                    let new_key = if prefix.is_empty() {
582                        k.clone()
583                    } else {
584                        format!("{}{}{}", prefix, separator, k)
585                    };
586                    flatten_value(v, &new_key, separator, result);
587                }
588            }
589        }
590        Value::Array(arr) => {
591            if arr.is_empty() {
592                if !prefix.is_empty() {
593                    result.insert(prefix.to_string(), Value::Array(arr.clone()));
594                }
595            } else {
596                for (idx, v) in arr.iter().enumerate() {
597                    let new_key = if prefix.is_empty() {
598                        idx.to_string()
599                    } else {
600                        format!("{}{}{}", prefix, separator, idx)
601                    };
602                    flatten_value(v, &new_key, separator, result);
603                }
604            }
605        }
606        _ => {
607            if !prefix.is_empty() {
608                result.insert(prefix.to_string(), value.clone());
609            } else {
610                result.insert(String::new(), value.clone());
611            }
612        }
613    }
614}
615
616impl Function for FlattenArrayFn {
617    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
618        self.signature.validate(args, ctx)?;
619
620        let default_sep = ".".to_string();
621        let separator = args
622            .get(1)
623            .and_then(|s| s.as_str().map(|s| s.to_string()))
624            .unwrap_or(default_sep);
625
626        let mut result = Map::new();
627        flatten_value(&args[0], "", &separator, &mut result);
628
629        Ok(Value::Object(result))
630    }
631}
632
633// =============================================================================
634// deep_merge(obj1, obj2) -> object
635// =============================================================================
636
637defn!(DeepMergeFn, vec![arg!(object), arg!(object)], None);
638
639fn deep_merge_objects(
640    base: &Map<String, Value>,
641    overlay: &Map<String, Value>,
642) -> Map<String, Value> {
643    let mut result = base.clone();
644
645    for (key, overlay_value) in overlay {
646        if let Some(base_value) = result.get(key) {
647            if let (Some(base_obj), Some(overlay_obj)) =
648                (base_value.as_object(), overlay_value.as_object())
649            {
650                let merged = deep_merge_objects(base_obj, overlay_obj);
651                result.insert(key.clone(), Value::Object(merged));
652            } else {
653                result.insert(key.clone(), overlay_value.clone());
654            }
655        } else {
656            result.insert(key.clone(), overlay_value.clone());
657        }
658    }
659
660    result
661}
662
663impl Function for DeepMergeFn {
664    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
665        self.signature.validate(args, ctx)?;
666
667        let obj1 = args[0]
668            .as_object()
669            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
670
671        let obj2 = args[1]
672            .as_object()
673            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
674
675        let merged = deep_merge_objects(obj1, obj2);
676        Ok(Value::Object(merged))
677    }
678}
679
680// =============================================================================
681// deep_equals(a, b) -> boolean
682// =============================================================================
683
684defn!(DeepEqualsFn, vec![arg!(any), arg!(any)], None);
685
686impl Function for DeepEqualsFn {
687    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
688        self.signature.validate(args, ctx)?;
689
690        let a_json = serde_json::to_string(&args[0]).unwrap_or_default();
691        let b_json = serde_json::to_string(&args[1]).unwrap_or_default();
692
693        Ok(Value::Bool(a_json == b_json))
694    }
695}
696
697// =============================================================================
698// deep_diff(a, b) -> object with added, removed, changed
699// =============================================================================
700
701defn!(DeepDiffFn, vec![arg!(object), arg!(object)], None);
702
703fn compute_deep_diff(a: &Map<String, Value>, b: &Map<String, Value>) -> Map<String, Value> {
704    let mut added = Map::new();
705    let mut removed = Map::new();
706    let mut changed = Map::new();
707
708    for (key, a_value) in a.iter() {
709        match b.get(key) {
710            None => {
711                removed.insert(key.clone(), a_value.clone());
712            }
713            Some(b_value) => {
714                let a_json = serde_json::to_string(a_value).unwrap_or_default();
715                let b_json = serde_json::to_string(b_value).unwrap_or_default();
716
717                if a_json != b_json {
718                    if let (Some(a_obj), Some(b_obj)) = (a_value.as_object(), b_value.as_object()) {
719                        let nested_diff = compute_deep_diff(a_obj, b_obj);
720                        changed.insert(key.clone(), Value::Object(nested_diff));
721                    } else {
722                        let mut change_obj = Map::new();
723                        change_obj.insert("from".to_string(), a_value.clone());
724                        change_obj.insert("to".to_string(), b_value.clone());
725                        changed.insert(key.clone(), Value::Object(change_obj));
726                    }
727                }
728            }
729        }
730    }
731
732    for (key, b_value) in b.iter() {
733        if !a.contains_key(key) {
734            added.insert(key.clone(), b_value.clone());
735        }
736    }
737
738    let mut result = Map::new();
739    result.insert("added".to_string(), Value::Object(added));
740    result.insert("removed".to_string(), Value::Object(removed));
741    result.insert("changed".to_string(), Value::Object(changed));
742
743    result
744}
745
746impl Function for DeepDiffFn {
747    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
748        self.signature.validate(args, ctx)?;
749
750        let obj_a = args[0]
751            .as_object()
752            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
753
754        let obj_b = args[1]
755            .as_object()
756            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
757
758        let diff = compute_deep_diff(obj_a, obj_b);
759        Ok(Value::Object(diff))
760    }
761}
762
763// =============================================================================
764// get(object, path, default?) -> value at path or default
765// =============================================================================
766
767defn!(GetFn, vec![arg!(any), arg!(string)], Some(arg!(any)));
768
769fn get_at_path(value: &Value, path: &str) -> Option<Value> {
770    if path.is_empty() {
771        return Some(value.clone());
772    }
773
774    let mut current = value.clone();
775
776    let parts = parse_path_parts(path);
777
778    for part in parts {
779        if let Some(idx) = part.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
780            if let Ok(index) = idx.parse::<usize>() {
781                let arr = current.as_array()?;
782                current = arr.get(index)?.clone();
783            } else {
784                return None;
785            }
786        } else if let Ok(index) = part.parse::<usize>() {
787            if let Some(arr) = current.as_array() {
788                current = arr.get(index)?.clone();
789            } else {
790                let obj = current.as_object()?;
791                current = obj.get(&part)?.clone();
792            }
793        } else {
794            let obj = current.as_object()?;
795            current = obj.get(&part)?.clone();
796        }
797    }
798
799    Some(current)
800}
801
802fn parse_path_parts(path: &str) -> Vec<String> {
803    let mut parts = Vec::new();
804    let mut current = String::new();
805    let mut chars = path.chars().peekable();
806
807    while let Some(c) = chars.next() {
808        match c {
809            '.' => {
810                if !current.is_empty() {
811                    parts.push(current.clone());
812                    current.clear();
813                }
814            }
815            '[' => {
816                if !current.is_empty() {
817                    parts.push(current.clone());
818                    current.clear();
819                }
820                let mut bracket = String::from("[");
821                while let Some(&next) = chars.peek() {
822                    bracket.push(chars.next().unwrap());
823                    if next == ']' {
824                        break;
825                    }
826                }
827                parts.push(bracket);
828            }
829            _ => {
830                current.push(c);
831            }
832        }
833    }
834
835    if !current.is_empty() {
836        parts.push(current);
837    }
838
839    parts
840}
841
842impl Function for GetFn {
843    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
844        self.signature.validate(args, ctx)?;
845
846        let path = args[1]
847            .as_str()
848            .ok_or_else(|| custom_error(ctx, "Expected string path argument"))?;
849
850        let default_val = if args.len() > 2 {
851            args[2].clone()
852        } else {
853            Value::Null
854        };
855
856        match get_at_path(&args[0], path) {
857            Some(val) => Ok(val),
858            None => Ok(default_val),
859        }
860    }
861}
862
863// =============================================================================
864// has(object, path) -> boolean
865// =============================================================================
866
867defn!(HasFn, vec![arg!(any), arg!(string)], None);
868
869impl Function for HasFn {
870    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
871        self.signature.validate(args, ctx)?;
872
873        let path = args[1]
874            .as_str()
875            .ok_or_else(|| custom_error(ctx, "Expected string path argument"))?;
876
877        let exists = get_at_path(&args[0], path).is_some();
878        Ok(Value::Bool(exists))
879    }
880}
881
882// =============================================================================
883// defaults(object, defaults) -> object with defaults applied
884// =============================================================================
885
886defn!(DefaultsFn, vec![arg!(object), arg!(object)], None);
887
888impl Function for DefaultsFn {
889    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
890        self.signature.validate(args, ctx)?;
891
892        let obj = args[0]
893            .as_object()
894            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
895
896        let defaults = args[1]
897            .as_object()
898            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
899
900        let mut result = obj.clone();
901
902        for (key, value) in defaults.iter() {
903            if !result.contains_key(key) {
904                result.insert(key.clone(), value.clone());
905            }
906        }
907
908        Ok(Value::Object(result))
909    }
910}
911
912// =============================================================================
913// defaults_deep(object, defaults) -> object with deep defaults applied
914// =============================================================================
915
916defn!(DefaultsDeepFn, vec![arg!(object), arg!(object)], None);
917
918fn apply_defaults_deep(
919    obj: &Map<String, Value>,
920    defaults: &Map<String, Value>,
921) -> Map<String, Value> {
922    let mut result = obj.clone();
923
924    for (key, default_value) in defaults.iter() {
925        if let Some(existing) = result.get(key) {
926            if let (Some(existing_obj), Some(default_obj)) =
927                (existing.as_object(), default_value.as_object())
928            {
929                let merged = apply_defaults_deep(existing_obj, default_obj);
930                result.insert(key.clone(), Value::Object(merged));
931            }
932        } else {
933            result.insert(key.clone(), default_value.clone());
934        }
935    }
936
937    result
938}
939
940impl Function for DefaultsDeepFn {
941    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
942        self.signature.validate(args, ctx)?;
943
944        let obj = args[0]
945            .as_object()
946            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
947
948        let defaults = args[1]
949            .as_object()
950            .ok_or_else(|| custom_error(ctx, "Expected object argument"))?;
951
952        let result = apply_defaults_deep(obj, defaults);
953        Ok(Value::Object(result))
954    }
955}
956
957// =============================================================================
958// set_path(object, path, value) -> new object with value set at path
959// =============================================================================
960
961defn!(SetPathFn, vec![arg!(any), arg!(string), arg!(any)], None);
962
963impl Function for SetPathFn {
964    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
965        self.signature.validate(args, ctx)?;
966
967        let path = args[1]
968            .as_str()
969            .ok_or_else(|| custom_error(ctx, "Expected string path argument"))?;
970
971        let value = args[2].clone();
972
973        let parts = parse_path_for_mutation(path);
974        if parts.is_empty() {
975            return Ok(value);
976        }
977
978        let result = set_at_path(&args[0], &parts, value);
979        Ok(result)
980    }
981}
982
983fn parse_path_for_mutation(path: &str) -> Vec<String> {
984    if path.is_empty() {
985        return vec![];
986    }
987
988    if path.starts_with('/') {
989        parse_json_pointer(path)
990    } else {
991        parse_path_parts_for_mutation(path)
992    }
993}
994
995fn parse_path_parts_for_mutation(path: &str) -> Vec<String> {
996    let mut parts = Vec::new();
997    let mut current = String::new();
998    let mut chars = path.chars().peekable();
999
1000    while let Some(c) = chars.next() {
1001        match c {
1002            '.' => {
1003                if !current.is_empty() {
1004                    parts.push(current.clone());
1005                    current.clear();
1006                }
1007            }
1008            '[' => {
1009                if !current.is_empty() {
1010                    parts.push(current.clone());
1011                    current.clear();
1012                }
1013                let mut index = String::new();
1014                while let Some(&next) = chars.peek() {
1015                    if next == ']' {
1016                        chars.next();
1017                        break;
1018                    }
1019                    index.push(chars.next().unwrap());
1020                }
1021                parts.push(index);
1022            }
1023            _ => {
1024                current.push(c);
1025            }
1026        }
1027    }
1028
1029    if !current.is_empty() {
1030        parts.push(current);
1031    }
1032
1033    parts
1034}
1035
1036fn parse_json_pointer(path: &str) -> Vec<String> {
1037    if path.is_empty() {
1038        return vec![];
1039    }
1040
1041    let path = path.strip_prefix('/').unwrap_or(path);
1042
1043    if path.is_empty() {
1044        return vec![];
1045    }
1046
1047    path.split('/')
1048        .map(|s| s.replace("~1", "/").replace("~0", "~"))
1049        .collect()
1050}
1051
1052fn set_at_path(value: &Value, parts: &[String], new_value: Value) -> Value {
1053    if parts.is_empty() {
1054        return new_value;
1055    }
1056
1057    let key = &parts[0];
1058    let remaining = &parts[1..];
1059
1060    match value {
1061        Value::Object(obj) => {
1062            let mut new_obj = obj.clone();
1063            if remaining.is_empty() {
1064                new_obj.insert(key.clone(), new_value);
1065            } else {
1066                let existing = obj.get(key).cloned().unwrap_or(Value::Null);
1067                new_obj.insert(key.clone(), set_at_path(&existing, remaining, new_value));
1068            }
1069            Value::Object(new_obj)
1070        }
1071        Value::Array(arr) => {
1072            if let Ok(idx) = key.parse::<usize>() {
1073                let mut new_arr = arr.clone();
1074                while new_arr.len() <= idx {
1075                    new_arr.push(Value::Null);
1076                }
1077                if remaining.is_empty() {
1078                    new_arr[idx] = new_value;
1079                } else {
1080                    new_arr[idx] = set_at_path(
1081                        &arr.get(idx).cloned().unwrap_or(Value::Null),
1082                        remaining,
1083                        new_value,
1084                    );
1085                }
1086                Value::Array(new_arr)
1087            } else {
1088                value.clone()
1089            }
1090        }
1091        _ => {
1092            if remaining.is_empty() {
1093                let mut new_obj = Map::new();
1094                new_obj.insert(key.clone(), new_value);
1095                Value::Object(new_obj)
1096            } else {
1097                let mut new_obj = Map::new();
1098                new_obj.insert(key.clone(), set_at_path(&Value::Null, remaining, new_value));
1099                Value::Object(new_obj)
1100            }
1101        }
1102    }
1103}
1104
1105// =============================================================================
1106// delete_path(object, path) -> new object with value removed at path
1107// =============================================================================
1108
1109defn!(DeletePathFn, vec![arg!(any), arg!(string)], None);
1110
1111impl Function for DeletePathFn {
1112    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1113        self.signature.validate(args, ctx)?;
1114
1115        let path = args[1]
1116            .as_str()
1117            .ok_or_else(|| custom_error(ctx, "Expected string path argument"))?;
1118
1119        let parts = parse_path_for_mutation(path);
1120        if parts.is_empty() {
1121            return Ok(Value::Null);
1122        }
1123
1124        let result = delete_at_path(&args[0], &parts);
1125        Ok(result)
1126    }
1127}
1128
1129fn delete_at_path(value: &Value, parts: &[String]) -> Value {
1130    if parts.is_empty() {
1131        return Value::Null;
1132    }
1133
1134    let key = &parts[0];
1135    let remaining = &parts[1..];
1136
1137    match value {
1138        Value::Object(obj) => {
1139            let mut new_obj = obj.clone();
1140            if remaining.is_empty() {
1141                new_obj.remove(key);
1142            } else if let Some(existing) = obj.get(key) {
1143                new_obj.insert(key.clone(), delete_at_path(existing, remaining));
1144            }
1145            Value::Object(new_obj)
1146        }
1147        Value::Array(arr) => {
1148            if let Ok(idx) = key.parse::<usize>() {
1149                if idx < arr.len() {
1150                    let mut new_arr = arr.clone();
1151                    if remaining.is_empty() {
1152                        new_arr.remove(idx);
1153                    } else {
1154                        new_arr[idx] = delete_at_path(&arr[idx], remaining);
1155                    }
1156                    Value::Array(new_arr)
1157                } else {
1158                    value.clone()
1159                }
1160            } else {
1161                value.clone()
1162            }
1163        }
1164        _ => value.clone(),
1165    }
1166}
1167
1168// =============================================================================
1169// paths(value) -> array of all JSON pointer paths in the value
1170// =============================================================================
1171
1172defn!(PathsFn, vec![arg!(any)], None);
1173
1174impl Function for PathsFn {
1175    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1176        self.signature.validate(args, ctx)?;
1177
1178        let mut paths = Vec::new();
1179        collect_paths(&args[0], String::new(), &mut paths);
1180
1181        let result: Vec<Value> = paths.into_iter().map(Value::String).collect();
1182
1183        Ok(Value::Array(result))
1184    }
1185}
1186
1187fn collect_paths(value: &Value, current_path: String, paths: &mut Vec<String>) {
1188    match value {
1189        Value::Object(obj) => {
1190            if !current_path.is_empty() {
1191                paths.push(current_path.clone());
1192            }
1193            for (key, val) in obj.iter() {
1194                let escaped_key = key.replace('~', "~0").replace('/', "~1");
1195                let new_path = format!("{}/{}", current_path, escaped_key);
1196                collect_paths(val, new_path, paths);
1197            }
1198        }
1199        Value::Array(arr) => {
1200            if !current_path.is_empty() {
1201                paths.push(current_path.clone());
1202            }
1203            for (idx, val) in arr.iter().enumerate() {
1204                let new_path = format!("{}/{}", current_path, idx);
1205                collect_paths(val, new_path, paths);
1206            }
1207        }
1208        _ => {
1209            if !current_path.is_empty() {
1210                paths.push(current_path);
1211            }
1212        }
1213    }
1214}
1215
1216// =============================================================================
1217// leaves(value) -> array of all leaf values (non-object, non-array)
1218// =============================================================================
1219
1220defn!(LeavesFn, vec![arg!(any)], None);
1221
1222impl Function for LeavesFn {
1223    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1224        self.signature.validate(args, ctx)?;
1225
1226        let mut leaves = Vec::new();
1227        collect_leaves(&args[0], &mut leaves);
1228
1229        Ok(Value::Array(leaves))
1230    }
1231}
1232
1233fn collect_leaves(value: &Value, leaves: &mut Vec<Value>) {
1234    match value {
1235        Value::Object(obj) => {
1236            for (_, val) in obj.iter() {
1237                collect_leaves(val, leaves);
1238            }
1239        }
1240        Value::Array(arr) => {
1241            for val in arr.iter() {
1242                collect_leaves(val, leaves);
1243            }
1244        }
1245        _ => {
1246            leaves.push(value.clone());
1247        }
1248    }
1249}
1250
1251// =============================================================================
1252// leaves_with_paths(value) -> array of {path, value} objects for all leaves
1253// =============================================================================
1254
1255defn!(LeavesWithPathsFn, vec![arg!(any)], None);
1256
1257impl Function for LeavesWithPathsFn {
1258    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1259        self.signature.validate(args, ctx)?;
1260
1261        let mut leaves = Vec::new();
1262        collect_leaves_with_paths(&args[0], String::new(), &mut leaves);
1263
1264        let result: Vec<Value> = leaves
1265            .into_iter()
1266            .map(|(path, value)| {
1267                let mut obj = Map::new();
1268                obj.insert("path".to_string(), Value::String(path));
1269                obj.insert("value".to_string(), value);
1270                Value::Object(obj)
1271            })
1272            .collect();
1273
1274        Ok(Value::Array(result))
1275    }
1276}
1277
1278fn collect_leaves_with_paths(
1279    value: &Value,
1280    current_path: String,
1281    leaves: &mut Vec<(String, Value)>,
1282) {
1283    match value {
1284        Value::Object(obj) => {
1285            if obj.is_empty() && !current_path.is_empty() {
1286                leaves.push((current_path, value.clone()));
1287            } else {
1288                for (key, val) in obj.iter() {
1289                    let escaped_key = key.replace('~', "~0").replace('/', "~1");
1290                    let new_path = format!("{}/{}", current_path, escaped_key);
1291                    collect_leaves_with_paths(val, new_path, leaves);
1292                }
1293            }
1294        }
1295        Value::Array(arr) => {
1296            if arr.is_empty() && !current_path.is_empty() {
1297                leaves.push((current_path, value.clone()));
1298            } else {
1299                for (idx, val) in arr.iter().enumerate() {
1300                    let new_path = format!("{}/{}", current_path, idx);
1301                    collect_leaves_with_paths(val, new_path, leaves);
1302                }
1303            }
1304        }
1305        _ => {
1306            let path = if current_path.is_empty() {
1307                "/".to_string()
1308            } else {
1309                current_path
1310            };
1311            leaves.push((path, value.clone()));
1312        }
1313    }
1314}
1315
1316// =============================================================================
1317// remove_nulls(any) -> any (recursively remove null values)
1318// =============================================================================
1319
1320defn!(RemoveNullsFn, vec![arg!(any)], None);
1321
1322impl Function for RemoveNullsFn {
1323    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1324        self.signature.validate(args, ctx)?;
1325        Ok(remove_nulls_recursive(&args[0]))
1326    }
1327}
1328
1329fn is_null_value(value: &Value) -> bool {
1330    value.is_null()
1331}
1332
1333fn remove_nulls_recursive(value: &Value) -> Value {
1334    match value {
1335        Value::Object(obj) => {
1336            let cleaned: Map<String, Value> = obj
1337                .iter()
1338                .filter(|(_, v)| !is_null_value(v))
1339                .map(|(k, v)| (k.clone(), remove_nulls_recursive(v)))
1340                .collect();
1341            Value::Object(cleaned)
1342        }
1343        Value::Array(arr) => {
1344            let cleaned: Vec<Value> = arr
1345                .iter()
1346                .filter(|v| !is_null_value(v))
1347                .map(remove_nulls_recursive)
1348                .collect();
1349            Value::Array(cleaned)
1350        }
1351        _ => value.clone(),
1352    }
1353}
1354
1355// =============================================================================
1356// remove_empty(any) -> any
1357// =============================================================================
1358
1359defn!(RemoveEmptyFn, vec![arg!(any)], None);
1360
1361impl Function for RemoveEmptyFn {
1362    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1363        self.signature.validate(args, ctx)?;
1364        Ok(remove_empty_recursive(&args[0]))
1365    }
1366}
1367
1368fn is_empty_value(value: &Value) -> bool {
1369    match value {
1370        Value::Null => true,
1371        Value::String(s) => s.is_empty(),
1372        Value::Array(arr) => arr.is_empty(),
1373        Value::Object(obj) => obj.is_empty(),
1374        _ => false,
1375    }
1376}
1377
1378fn remove_empty_recursive(value: &Value) -> Value {
1379    match value {
1380        Value::Object(obj) => {
1381            let cleaned: Map<String, Value> = obj
1382                .iter()
1383                .map(|(k, v)| (k.clone(), remove_empty_recursive(v)))
1384                .filter(|(_, v)| !is_empty_value(v))
1385                .collect();
1386            Value::Object(cleaned)
1387        }
1388        Value::Array(arr) => {
1389            let cleaned: Vec<Value> = arr
1390                .iter()
1391                .map(remove_empty_recursive)
1392                .filter(|v| !is_empty_value(v))
1393                .collect();
1394            Value::Array(cleaned)
1395        }
1396        _ => value.clone(),
1397    }
1398}
1399
1400// =============================================================================
1401// remove_empty_strings(any) -> any
1402// =============================================================================
1403
1404defn!(RemoveEmptyStringsFn, vec![arg!(any)], None);
1405
1406impl Function for RemoveEmptyStringsFn {
1407    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1408        self.signature.validate(args, ctx)?;
1409        Ok(remove_empty_strings_recursive(&args[0]))
1410    }
1411}
1412
1413fn is_empty_string(value: &Value) -> bool {
1414    matches!(value, Value::String(s) if s.is_empty())
1415}
1416
1417fn remove_empty_strings_recursive(value: &Value) -> Value {
1418    match value {
1419        Value::Object(obj) => {
1420            let cleaned: Map<String, Value> = obj
1421                .iter()
1422                .filter(|(_, v)| !is_empty_string(v))
1423                .map(|(k, v)| (k.clone(), remove_empty_strings_recursive(v)))
1424                .collect();
1425            Value::Object(cleaned)
1426        }
1427        Value::Array(arr) => {
1428            let cleaned: Vec<Value> = arr
1429                .iter()
1430                .filter(|v| !is_empty_string(v))
1431                .map(remove_empty_strings_recursive)
1432                .collect();
1433            Value::Array(cleaned)
1434        }
1435        _ => value.clone(),
1436    }
1437}
1438
1439// =============================================================================
1440// compact_deep(array) -> array
1441// =============================================================================
1442
1443defn!(CompactDeepFn, vec![arg!(array)], None);
1444
1445impl Function for CompactDeepFn {
1446    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1447        self.signature.validate(args, ctx)?;
1448        Ok(compact_deep_recursive(&args[0]))
1449    }
1450}
1451
1452fn compact_deep_recursive(value: &Value) -> Value {
1453    match value {
1454        Value::Array(arr) => {
1455            let cleaned: Vec<Value> = arr
1456                .iter()
1457                .filter(|v| !is_null_value(v))
1458                .map(compact_deep_recursive)
1459                .collect();
1460            Value::Array(cleaned)
1461        }
1462        Value::Object(obj) => {
1463            let cleaned: Map<String, Value> = obj
1464                .iter()
1465                .map(|(k, v)| (k.clone(), compact_deep_recursive(v)))
1466                .collect();
1467            Value::Object(cleaned)
1468        }
1469        _ => value.clone(),
1470    }
1471}
1472
1473// =============================================================================
1474// completeness(object) -> number (0-100)
1475// =============================================================================
1476
1477defn!(CompletenessFn, vec![arg!(object)], None);
1478
1479impl Function for CompletenessFn {
1480    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1481        self.signature.validate(args, ctx)?;
1482        let obj = args[0]
1483            .as_object()
1484            .ok_or_else(|| custom_error(ctx, "completeness: expected object"))?;
1485
1486        if obj.is_empty() {
1487            return Ok(number_value(100.0));
1488        }
1489
1490        let mut total_fields = 0;
1491        let mut non_null_fields = 0;
1492
1493        count_completeness(&args[0], &mut total_fields, &mut non_null_fields);
1494
1495        let score = if total_fields > 0 {
1496            (non_null_fields as f64 / total_fields as f64) * 100.0
1497        } else {
1498            100.0
1499        };
1500
1501        Ok(number_value(score))
1502    }
1503}
1504
1505fn count_completeness(value: &Value, total: &mut usize, non_null: &mut usize) {
1506    match value {
1507        Value::Object(obj) => {
1508            for (_, v) in obj.iter() {
1509                *total += 1;
1510                if !v.is_null() {
1511                    *non_null += 1;
1512                }
1513                count_completeness(v, total, non_null);
1514            }
1515        }
1516        Value::Array(arr) => {
1517            for item in arr.iter() {
1518                count_completeness(item, total, non_null);
1519            }
1520        }
1521        _ => {}
1522    }
1523}
1524
1525// =============================================================================
1526// type_consistency(array) -> object
1527// =============================================================================
1528
1529defn!(TypeConsistencyFn, vec![arg!(array)], None);
1530
1531impl Function for TypeConsistencyFn {
1532    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1533        self.signature.validate(args, ctx)?;
1534        let arr = args[0]
1535            .as_array()
1536            .ok_or_else(|| custom_error(ctx, "type_consistency: expected array"))?;
1537
1538        if arr.is_empty() {
1539            let mut result = Map::new();
1540            result.insert("consistent".to_string(), Value::Bool(true));
1541            result.insert("types".to_string(), Value::Array(vec![]));
1542            result.insert("inconsistencies".to_string(), Value::Array(vec![]));
1543            return Ok(Value::Object(result));
1544        }
1545
1546        let first_element = &arr[0];
1547        if let Some(first_obj) = first_element.as_object() {
1548            return check_object_array_consistency(arr, first_obj);
1549        }
1550
1551        let mut type_counts: std::collections::BTreeMap<String, usize> =
1552            std::collections::BTreeMap::new();
1553        for item in arr.iter() {
1554            let type_name = get_type_name(item);
1555            *type_counts.entry(type_name).or_insert(0) += 1;
1556        }
1557
1558        let types: Vec<Value> = type_counts
1559            .keys()
1560            .map(|t| Value::String(t.clone()))
1561            .collect();
1562
1563        let consistent = type_counts.len() == 1;
1564
1565        let mut result = Map::new();
1566        result.insert("consistent".to_string(), Value::Bool(consistent));
1567        result.insert("types".to_string(), Value::Array(types));
1568        result.insert("inconsistencies".to_string(), Value::Array(vec![]));
1569
1570        Ok(Value::Object(result))
1571    }
1572}
1573
1574fn check_object_array_consistency(arr: &[Value], first_obj: &Map<String, Value>) -> SearchResult {
1575    let mut expected_types: std::collections::BTreeMap<String, String> =
1576        std::collections::BTreeMap::new();
1577    for (key, val) in first_obj.iter() {
1578        expected_types.insert(key.clone(), get_type_name(val));
1579    }
1580
1581    let mut inconsistencies: Vec<Value> = Vec::new();
1582
1583    for (idx, item) in arr.iter().enumerate().skip(1) {
1584        if let Some(obj) = item.as_object() {
1585            for (key, val) in obj.iter() {
1586                let actual_type = get_type_name(val);
1587                if let Some(expected) = expected_types.get(key)
1588                    && &actual_type != expected
1589                    && actual_type != "null"
1590                    && expected != "null"
1591                {
1592                    let mut issue = Map::new();
1593                    issue.insert("index".to_string(), Value::Number(Number::from(idx as i64)));
1594                    issue.insert("field".to_string(), Value::String(key.clone()));
1595                    issue.insert("expected".to_string(), Value::String(expected.clone()));
1596                    issue.insert("got".to_string(), Value::String(actual_type));
1597                    inconsistencies.push(Value::Object(issue));
1598                }
1599            }
1600        }
1601    }
1602
1603    let types: Vec<Value> = expected_types
1604        .iter()
1605        .map(|(k, v)| {
1606            let mut obj = Map::new();
1607            obj.insert("field".to_string(), Value::String(k.clone()));
1608            obj.insert("type".to_string(), Value::String(v.clone()));
1609            Value::Object(obj)
1610        })
1611        .collect();
1612
1613    let mut result = Map::new();
1614    result.insert(
1615        "consistent".to_string(),
1616        Value::Bool(inconsistencies.is_empty()),
1617    );
1618    result.insert("types".to_string(), Value::Array(types));
1619    result.insert("inconsistencies".to_string(), Value::Array(inconsistencies));
1620
1621    Ok(Value::Object(result))
1622}
1623
1624fn get_type_name(value: &Value) -> String {
1625    match value {
1626        Value::Null => "null".to_string(),
1627        Value::Bool(_) => "boolean".to_string(),
1628        Value::Number(_) => "number".to_string(),
1629        Value::String(_) => "string".to_string(),
1630        Value::Array(_) => "array".to_string(),
1631        Value::Object(_) => "object".to_string(),
1632    }
1633}
1634
1635// =============================================================================
1636// data_quality_score(any) -> object
1637// =============================================================================
1638
1639defn!(DataQualityScoreFn, vec![arg!(any)], None);
1640
1641impl Function for DataQualityScoreFn {
1642    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1643        self.signature.validate(args, ctx)?;
1644        let value = &args[0];
1645
1646        let mut stats = QualityStats::default();
1647        analyze_quality(value, String::new(), &mut stats);
1648
1649        let total_issues = stats.null_count + stats.empty_string_count + stats.type_issues.len();
1650        let score = if stats.total_fields == 0 {
1651            100.0
1652        } else {
1653            let issue_ratio = total_issues as f64 / stats.total_fields as f64;
1654            (100.0 * (1.0 - issue_ratio)).max(0.0)
1655        };
1656
1657        let mut issues: Vec<Value> = Vec::new();
1658
1659        for path in &stats.null_paths {
1660            let mut issue = Map::new();
1661            issue.insert("path".to_string(), Value::String(path.clone()));
1662            issue.insert("issue".to_string(), Value::String("null".to_string()));
1663            issues.push(Value::Object(issue));
1664        }
1665
1666        for path in &stats.empty_string_paths {
1667            let mut issue = Map::new();
1668            issue.insert("path".to_string(), Value::String(path.clone()));
1669            issue.insert(
1670                "issue".to_string(),
1671                Value::String("empty_string".to_string()),
1672            );
1673            issues.push(Value::Object(issue));
1674        }
1675
1676        for ti in &stats.type_issues {
1677            let mut issue = Map::new();
1678            issue.insert("path".to_string(), Value::String(ti.path.clone()));
1679            issue.insert(
1680                "issue".to_string(),
1681                Value::String("type_mismatch".to_string()),
1682            );
1683            issue.insert("expected".to_string(), Value::String(ti.expected.clone()));
1684            issue.insert("got".to_string(), Value::String(ti.got.clone()));
1685            issues.push(Value::Object(issue));
1686        }
1687
1688        let mut result = Map::new();
1689        result.insert("score".to_string(), number_value(score));
1690        result.insert(
1691            "total_fields".to_string(),
1692            Value::Number(Number::from(stats.total_fields as i64)),
1693        );
1694        result.insert(
1695            "null_count".to_string(),
1696            Value::Number(Number::from(stats.null_count as i64)),
1697        );
1698        result.insert(
1699            "empty_string_count".to_string(),
1700            Value::Number(Number::from(stats.empty_string_count as i64)),
1701        );
1702        result.insert(
1703            "type_inconsistencies".to_string(),
1704            Value::Number(Number::from(stats.type_issues.len() as i64)),
1705        );
1706        result.insert("issues".to_string(), Value::Array(issues));
1707
1708        Ok(Value::Object(result))
1709    }
1710}
1711
1712#[derive(Default)]
1713struct QualityStats {
1714    total_fields: usize,
1715    null_count: usize,
1716    empty_string_count: usize,
1717    null_paths: Vec<String>,
1718    empty_string_paths: Vec<String>,
1719    type_issues: Vec<TypeIssue>,
1720}
1721
1722struct TypeIssue {
1723    path: String,
1724    expected: String,
1725    got: String,
1726}
1727
1728fn analyze_quality(value: &Value, path: String, stats: &mut QualityStats) {
1729    match value {
1730        Value::Object(obj) => {
1731            for (key, val) in obj.iter() {
1732                let field_path = if path.is_empty() {
1733                    key.clone()
1734                } else {
1735                    format!("{}.{}", path, key)
1736                };
1737                stats.total_fields += 1;
1738
1739                match val {
1740                    Value::Null => {
1741                        stats.null_count += 1;
1742                        stats.null_paths.push(field_path.clone());
1743                    }
1744                    Value::String(s) if s.is_empty() => {
1745                        stats.empty_string_count += 1;
1746                        stats.empty_string_paths.push(field_path.clone());
1747                    }
1748                    _ => {}
1749                }
1750
1751                analyze_quality(val, field_path, stats);
1752            }
1753        }
1754        Value::Array(arr) => {
1755            if arr.len() > 1
1756                && let Some(Value::Object(first_obj)) = arr.first()
1757            {
1758                let expected_types: std::collections::BTreeMap<String, String> = first_obj
1759                    .iter()
1760                    .map(|(k, v)| (k.clone(), get_type_name(v)))
1761                    .collect();
1762
1763                for (idx, item) in arr.iter().enumerate().skip(1) {
1764                    if let Value::Object(obj) = item {
1765                        for (key, val) in obj.iter() {
1766                            let actual_type = get_type_name(val);
1767                            if let Some(expected) = expected_types.get(key)
1768                                && &actual_type != expected
1769                                && actual_type != "null"
1770                                && expected != "null"
1771                            {
1772                                stats.type_issues.push(TypeIssue {
1773                                    path: format!("{}[{}].{}", path, idx, key),
1774                                    expected: expected.clone(),
1775                                    got: actual_type,
1776                                });
1777                            }
1778                        }
1779                    }
1780                }
1781            }
1782
1783            for (idx, item) in arr.iter().enumerate() {
1784                let item_path = format!("{}[{}]", path, idx);
1785                analyze_quality(item, item_path, stats);
1786            }
1787        }
1788        _ => {}
1789    }
1790}
1791
1792// =============================================================================
1793// redact(any, keys) -> any (replace values at keys with [REDACTED])
1794// =============================================================================
1795
1796defn!(RedactFn, vec![arg!(any), arg!(array)], None);
1797
1798impl Function for RedactFn {
1799    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1800        self.signature.validate(args, ctx)?;
1801
1802        let keys_arr = args[1]
1803            .as_array()
1804            .ok_or_else(|| custom_error(ctx, "Expected array of keys"))?;
1805
1806        let keys: HashSet<String> = keys_arr
1807            .iter()
1808            .filter_map(|k| k.as_str().map(|s| s.to_string()))
1809            .collect();
1810
1811        Ok(redact_recursive(&args[0], &keys))
1812    }
1813}
1814
1815fn redact_recursive(value: &Value, keys: &HashSet<String>) -> Value {
1816    match value {
1817        Value::Object(obj) => {
1818            let redacted: Map<String, Value> = obj
1819                .iter()
1820                .map(|(k, v)| {
1821                    if keys.contains(k) {
1822                        (k.clone(), Value::String("[REDACTED]".to_string()))
1823                    } else {
1824                        (k.clone(), redact_recursive(v, keys))
1825                    }
1826                })
1827                .collect();
1828            Value::Object(redacted)
1829        }
1830        Value::Array(arr) => {
1831            let redacted: Vec<Value> = arr.iter().map(|v| redact_recursive(v, keys)).collect();
1832            Value::Array(redacted)
1833        }
1834        _ => value.clone(),
1835    }
1836}
1837
1838// =============================================================================
1839// redact_keys(any, pattern) -> any (redact keys matching regex pattern)
1840// =============================================================================
1841
1842defn!(RedactKeysFn, vec![arg!(any), arg!(string)], None);
1843
1844impl Function for RedactKeysFn {
1845    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1846        self.signature.validate(args, ctx)?;
1847
1848        let pattern = args[1]
1849            .as_str()
1850            .ok_or_else(|| custom_error(ctx, "Expected pattern string"))?;
1851
1852        let regex = regex::Regex::new(pattern)
1853            .map_err(|e| custom_error(ctx, &format!("Invalid regex pattern: {}", e)))?;
1854
1855        Ok(redact_keys_recursive(&args[0], &regex))
1856    }
1857}
1858
1859fn redact_keys_recursive(value: &Value, pattern: &regex::Regex) -> Value {
1860    match value {
1861        Value::Object(obj) => {
1862            let redacted: Map<String, Value> = obj
1863                .iter()
1864                .map(|(k, v)| {
1865                    if pattern.is_match(k) {
1866                        (k.clone(), Value::String("[REDACTED]".to_string()))
1867                    } else {
1868                        (k.clone(), redact_keys_recursive(v, pattern))
1869                    }
1870                })
1871                .collect();
1872            Value::Object(redacted)
1873        }
1874        Value::Array(arr) => {
1875            let redacted: Vec<Value> = arr
1876                .iter()
1877                .map(|v| redact_keys_recursive(v, pattern))
1878                .collect();
1879            Value::Array(redacted)
1880        }
1881        _ => value.clone(),
1882    }
1883}
1884
1885// =============================================================================
1886// mask(string, show_last?) -> string (mask all but last N chars)
1887// =============================================================================
1888
1889defn!(MaskFn, vec![arg!(string)], Some(arg!(number)));
1890
1891impl Function for MaskFn {
1892    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1893        self.signature.validate(args, ctx)?;
1894
1895        let s = args[0]
1896            .as_str()
1897            .ok_or_else(|| custom_error(ctx, "Expected string argument"))?;
1898
1899        let show_last = if args.len() > 1 {
1900            args[1].as_f64().unwrap_or(4.0) as usize
1901        } else {
1902            4
1903        };
1904
1905        // Count and slice by characters so "show the last N characters" is
1906        // correct for multibyte input and never slices mid-code-point.
1907        let chars: Vec<char> = s.chars().collect();
1908        let len = chars.len();
1909        let masked = if len <= show_last {
1910            "*".repeat(len)
1911        } else {
1912            let mask_count = len - show_last;
1913            let visible: String = chars[mask_count..].iter().collect();
1914            format!("{}{}", "*".repeat(mask_count), visible)
1915        };
1916
1917        Ok(Value::String(masked))
1918    }
1919}
1920
1921// =============================================================================
1922// pluck_deep(any, key) -> array
1923// =============================================================================
1924
1925defn!(PluckDeepFn, vec![arg!(any), arg!(string)], None);
1926
1927impl Function for PluckDeepFn {
1928    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1929        self.signature.validate(args, ctx)?;
1930
1931        let key = args[1]
1932            .as_str()
1933            .ok_or_else(|| custom_error(ctx, "Expected key string"))?;
1934
1935        let mut results: Vec<Value> = Vec::new();
1936        pluck_deep_recursive(&args[0], key, &mut results);
1937        Ok(Value::Array(results))
1938    }
1939}
1940
1941fn pluck_deep_recursive(value: &Value, key: &str, results: &mut Vec<Value>) {
1942    match value {
1943        Value::Object(obj) => {
1944            if let Some(v) = obj.get(key) {
1945                results.push(v.clone());
1946            }
1947            for (_, v) in obj.iter() {
1948                pluck_deep_recursive(v, key, results);
1949            }
1950        }
1951        Value::Array(arr) => {
1952            for v in arr {
1953                pluck_deep_recursive(v, key, results);
1954            }
1955        }
1956        _ => {}
1957    }
1958}
1959
1960// =============================================================================
1961// paths_to(any, key) -> array
1962// =============================================================================
1963
1964defn!(PathsToFn, vec![arg!(any), arg!(string)], None);
1965
1966impl Function for PathsToFn {
1967    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1968        self.signature.validate(args, ctx)?;
1969
1970        let key = args[1]
1971            .as_str()
1972            .ok_or_else(|| custom_error(ctx, "Expected key string"))?;
1973
1974        let mut paths: Vec<String> = Vec::new();
1975        paths_to_recursive(&args[0], key, String::new(), &mut paths);
1976
1977        let result: Vec<Value> = paths.into_iter().map(Value::String).collect();
1978        Ok(Value::Array(result))
1979    }
1980}
1981
1982fn paths_to_recursive(value: &Value, key: &str, current_path: String, paths: &mut Vec<String>) {
1983    match value {
1984        Value::Object(obj) => {
1985            for (k, v) in obj.iter() {
1986                let new_path = if current_path.is_empty() {
1987                    k.clone()
1988                } else {
1989                    format!("{}.{}", current_path, k)
1990                };
1991                if k == key {
1992                    paths.push(new_path.clone());
1993                }
1994                paths_to_recursive(v, key, new_path, paths);
1995            }
1996        }
1997        Value::Array(arr) => {
1998            for (idx, v) in arr.iter().enumerate() {
1999                let new_path = if current_path.is_empty() {
2000                    idx.to_string()
2001                } else {
2002                    format!("{}.{}", current_path, idx)
2003                };
2004                paths_to_recursive(v, key, new_path, paths);
2005            }
2006        }
2007        _ => {}
2008    }
2009}
2010
2011// =============================================================================
2012// snake_keys(any) -> any
2013// =============================================================================
2014
2015defn!(SnakeKeysFn, vec![arg!(any)], None);
2016
2017impl Function for SnakeKeysFn {
2018    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2019        self.signature.validate(args, ctx)?;
2020        Ok(transform_keys_recursive(&args[0], |s| s.to_snake_case()))
2021    }
2022}
2023
2024// =============================================================================
2025// camel_keys(any) -> any
2026// =============================================================================
2027
2028defn!(CamelKeysFn, vec![arg!(any)], None);
2029
2030impl Function for CamelKeysFn {
2031    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2032        self.signature.validate(args, ctx)?;
2033        Ok(transform_keys_recursive(&args[0], |s| {
2034            s.to_lower_camel_case()
2035        }))
2036    }
2037}
2038
2039// =============================================================================
2040// kebab_keys(any) -> any
2041// =============================================================================
2042
2043defn!(KebabKeysFn, vec![arg!(any)], None);
2044
2045impl Function for KebabKeysFn {
2046    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2047        self.signature.validate(args, ctx)?;
2048        Ok(transform_keys_recursive(&args[0], |s| s.to_kebab_case()))
2049    }
2050}
2051
2052// =============================================================================
2053// pascal_keys(any) -> any
2054// =============================================================================
2055
2056defn!(PascalKeysFn, vec![arg!(any)], None);
2057
2058impl Function for PascalKeysFn {
2059    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2060        self.signature.validate(args, ctx)?;
2061        Ok(transform_keys_recursive(&args[0], |s| {
2062            s.to_upper_camel_case()
2063        }))
2064    }
2065}
2066
2067// =============================================================================
2068// shouty_snake_keys(any) -> any
2069// =============================================================================
2070
2071defn!(ShoutySnakeKeysFn, vec![arg!(any)], None);
2072
2073impl Function for ShoutySnakeKeysFn {
2074    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2075        self.signature.validate(args, ctx)?;
2076        Ok(transform_keys_recursive(&args[0], |s| {
2077            s.to_shouty_snake_case()
2078        }))
2079    }
2080}
2081
2082// =============================================================================
2083// shouty_kebab_keys(any) -> any
2084// =============================================================================
2085
2086defn!(ShoutyKebabKeysFn, vec![arg!(any)], None);
2087
2088impl Function for ShoutyKebabKeysFn {
2089    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2090        self.signature.validate(args, ctx)?;
2091        Ok(transform_keys_recursive(&args[0], |s| {
2092            s.to_shouty_kebab_case()
2093        }))
2094    }
2095}
2096
2097// =============================================================================
2098// train_keys(any) -> any
2099// =============================================================================
2100
2101defn!(TrainKeysFn, vec![arg!(any)], None);
2102
2103impl Function for TrainKeysFn {
2104    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2105        self.signature.validate(args, ctx)?;
2106        Ok(transform_keys_recursive(&args[0], |s| s.to_train_case()))
2107    }
2108}
2109
2110fn transform_keys_recursive<F>(value: &Value, transform: F) -> Value
2111where
2112    F: Fn(&str) -> String + Copy,
2113{
2114    match value {
2115        Value::Object(obj) => {
2116            let transformed: Map<String, Value> = obj
2117                .iter()
2118                .map(|(k, v)| (transform(k), transform_keys_recursive(v, transform)))
2119                .collect();
2120            Value::Object(transformed)
2121        }
2122        Value::Array(arr) => {
2123            let transformed: Vec<Value> = arr
2124                .iter()
2125                .map(|v| transform_keys_recursive(v, transform))
2126                .collect();
2127            Value::Array(transformed)
2128        }
2129        _ => value.clone(),
2130    }
2131}
2132
2133// =============================================================================
2134// structural_diff(obj1, obj2) -> object
2135// =============================================================================
2136
2137defn!(StructuralDiffFn, vec![arg!(any), arg!(any)], None);
2138
2139impl Function for StructuralDiffFn {
2140    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2141        self.signature.validate(args, ctx)?;
2142
2143        let mut added: Vec<String> = Vec::new();
2144        let mut removed: Vec<String> = Vec::new();
2145        let mut type_changed: Vec<Map<String, Value>> = Vec::new();
2146        let mut unchanged: Vec<String> = Vec::new();
2147
2148        compare_structure(
2149            &args[0],
2150            &args[1],
2151            String::new(),
2152            &mut added,
2153            &mut removed,
2154            &mut type_changed,
2155            &mut unchanged,
2156        );
2157
2158        let mut result = Map::new();
2159        result.insert(
2160            "added".to_string(),
2161            Value::Array(added.into_iter().map(Value::String).collect()),
2162        );
2163        result.insert(
2164            "removed".to_string(),
2165            Value::Array(removed.into_iter().map(Value::String).collect()),
2166        );
2167        result.insert(
2168            "type_changed".to_string(),
2169            Value::Array(type_changed.into_iter().map(Value::Object).collect()),
2170        );
2171        result.insert(
2172            "unchanged".to_string(),
2173            Value::Array(unchanged.into_iter().map(Value::String).collect()),
2174        );
2175
2176        Ok(Value::Object(result))
2177    }
2178}
2179
2180fn get_structural_type(value: &Value) -> &'static str {
2181    match value {
2182        Value::Null => "null",
2183        Value::Bool(_) => "boolean",
2184        Value::Number(_) => "number",
2185        Value::String(_) => "string",
2186        Value::Array(_) => "array",
2187        Value::Object(_) => "object",
2188    }
2189}
2190
2191fn compare_structure(
2192    a: &Value,
2193    b: &Value,
2194    path: String,
2195    added: &mut Vec<String>,
2196    removed: &mut Vec<String>,
2197    type_changed: &mut Vec<Map<String, Value>>,
2198    unchanged: &mut Vec<String>,
2199) {
2200    let type_a = get_structural_type(a);
2201    let type_b = get_structural_type(b);
2202
2203    if type_a != type_b {
2204        let mut change = Map::new();
2205        change.insert(
2206            "path".to_string(),
2207            Value::String(if path.is_empty() {
2208                "$".to_string()
2209            } else {
2210                path
2211            }),
2212        );
2213        change.insert("from".to_string(), Value::String(type_a.to_string()));
2214        change.insert("to".to_string(), Value::String(type_b.to_string()));
2215        type_changed.push(change);
2216        return;
2217    }
2218
2219    match (a, b) {
2220        (Value::Object(obj_a), Value::Object(obj_b)) => {
2221            for key in obj_a.keys() {
2222                let new_path = if path.is_empty() {
2223                    key.clone()
2224                } else {
2225                    format!("{}.{}", path, key)
2226                };
2227                if let Some(val_b) = obj_b.get(key) {
2228                    compare_structure(
2229                        obj_a.get(key).unwrap(),
2230                        val_b,
2231                        new_path,
2232                        added,
2233                        removed,
2234                        type_changed,
2235                        unchanged,
2236                    );
2237                } else {
2238                    removed.push(new_path);
2239                }
2240            }
2241            for key in obj_b.keys() {
2242                if !obj_a.contains_key(key) {
2243                    let new_path = if path.is_empty() {
2244                        key.clone()
2245                    } else {
2246                        format!("{}.{}", path, key)
2247                    };
2248                    added.push(new_path);
2249                }
2250            }
2251        }
2252        _ => {
2253            if !path.is_empty() {
2254                unchanged.push(path);
2255            }
2256        }
2257    }
2258}
2259
2260// =============================================================================
2261// has_same_shape(obj1, obj2) -> boolean
2262// =============================================================================
2263
2264defn!(HasSameShapeFn, vec![arg!(any), arg!(any)], None);
2265
2266impl Function for HasSameShapeFn {
2267    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2268        self.signature.validate(args, ctx)?;
2269        let same = check_same_shape(&args[0], &args[1]);
2270        Ok(Value::Bool(same))
2271    }
2272}
2273
2274fn check_same_shape(a: &Value, b: &Value) -> bool {
2275    let type_a = get_structural_type(a);
2276    let type_b = get_structural_type(b);
2277
2278    if type_a != type_b {
2279        return false;
2280    }
2281
2282    match (a, b) {
2283        (Value::Object(obj_a), Value::Object(obj_b)) => {
2284            if obj_a.keys().collect::<HashSet<_>>() != obj_b.keys().collect::<HashSet<_>>() {
2285                return false;
2286            }
2287            for key in obj_a.keys() {
2288                if !check_same_shape(obj_a.get(key).unwrap(), obj_b.get(key).unwrap()) {
2289                    return false;
2290                }
2291            }
2292            true
2293        }
2294        (Value::Array(arr_a), Value::Array(arr_b)) => {
2295            if arr_a.is_empty() && arr_b.is_empty() {
2296                return true;
2297            }
2298            if arr_a.is_empty() || arr_b.is_empty() {
2299                return true;
2300            }
2301            check_same_shape(&arr_a[0], &arr_b[0])
2302        }
2303        _ => true,
2304    }
2305}
2306
2307// =============================================================================
2308// infer_schema(any) -> object
2309// =============================================================================
2310
2311defn!(InferSchemaFn, vec![arg!(any)], None);
2312
2313impl Function for InferSchemaFn {
2314    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2315        self.signature.validate(args, ctx)?;
2316        Ok(infer_schema_recursive(&args[0]))
2317    }
2318}
2319
2320fn infer_schema_recursive(value: &Value) -> Value {
2321    match value {
2322        Value::Null => {
2323            let mut schema = Map::new();
2324            schema.insert("type".to_string(), Value::String("null".to_string()));
2325            Value::Object(schema)
2326        }
2327        Value::Bool(_) => {
2328            let mut schema = Map::new();
2329            schema.insert("type".to_string(), Value::String("boolean".to_string()));
2330            Value::Object(schema)
2331        }
2332        Value::Number(_) => {
2333            let mut schema = Map::new();
2334            schema.insert("type".to_string(), Value::String("number".to_string()));
2335            Value::Object(schema)
2336        }
2337        Value::String(_) => {
2338            let mut schema = Map::new();
2339            schema.insert("type".to_string(), Value::String("string".to_string()));
2340            Value::Object(schema)
2341        }
2342        Value::Array(arr) => {
2343            let mut schema = Map::new();
2344            schema.insert("type".to_string(), Value::String("array".to_string()));
2345            if !arr.is_empty() {
2346                let items_schema = infer_schema_recursive(&arr[0]);
2347                schema.insert("items".to_string(), items_schema);
2348            }
2349            Value::Object(schema)
2350        }
2351        Value::Object(obj) => {
2352            let mut schema = Map::new();
2353            schema.insert("type".to_string(), Value::String("object".to_string()));
2354
2355            let mut properties = Map::new();
2356            for (key, val) in obj.iter() {
2357                let prop_schema = infer_schema_recursive(val);
2358                properties.insert(key.clone(), prop_schema);
2359            }
2360            schema.insert("properties".to_string(), Value::Object(properties));
2361            Value::Object(schema)
2362        }
2363    }
2364}
2365
2366// =============================================================================
2367// chunk_by_size(array, max_bytes) -> array of arrays
2368// =============================================================================
2369
2370defn!(ChunkBySizeFn, vec![arg!(array), arg!(number)], None);
2371
2372impl Function for ChunkBySizeFn {
2373    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2374        self.signature.validate(args, ctx)?;
2375
2376        let arr = args[0]
2377            .as_array()
2378            .ok_or_else(|| custom_error(ctx, "Expected array"))?;
2379
2380        let max_bytes = args[1].as_f64().unwrap_or(4000.0) as usize;
2381
2382        let mut chunks: Vec<Value> = Vec::new();
2383        let mut current_chunk: Vec<Value> = Vec::new();
2384        let mut current_size: usize = 2;
2385
2386        for item in arr {
2387            let item_size = serde_json::to_string(item).map(|s| s.len()).unwrap_or(0);
2388
2389            if current_size + item_size + 1 > max_bytes && !current_chunk.is_empty() {
2390                chunks.push(Value::Array(current_chunk));
2391                current_chunk = Vec::new();
2392                current_size = 2;
2393            }
2394
2395            current_chunk.push(item.clone());
2396            current_size += item_size + 1;
2397        }
2398
2399        if !current_chunk.is_empty() {
2400            chunks.push(Value::Array(current_chunk));
2401        }
2402
2403        Ok(Value::Array(chunks))
2404    }
2405}
2406
2407// =============================================================================
2408// paginate(array, page, per_page) -> object with pagination metadata
2409// =============================================================================
2410
2411defn!(
2412    PaginateFn,
2413    vec![arg!(array), arg!(number), arg!(number)],
2414    None
2415);
2416
2417impl Function for PaginateFn {
2418    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2419        self.signature.validate(args, ctx)?;
2420
2421        let arr = args[0]
2422            .as_array()
2423            .ok_or_else(|| custom_error(ctx, "Expected array"))?;
2424
2425        let page = args[1].as_f64().unwrap_or(1.0).max(1.0) as usize;
2426        let per_page = args[2].as_f64().unwrap_or(10.0).max(1.0) as usize;
2427
2428        let total = arr.len();
2429        let total_pages = total.div_ceil(per_page);
2430        let start = (page - 1) * per_page;
2431        let end = (start + per_page).min(total);
2432
2433        let data: Vec<Value> = if start < total {
2434            arr[start..end].to_vec()
2435        } else {
2436            vec![]
2437        };
2438
2439        let mut result = Map::new();
2440        result.insert("data".to_string(), Value::Array(data));
2441        result.insert("page".to_string(), Value::Number(Number::from(page as i64)));
2442        result.insert(
2443            "per_page".to_string(),
2444            Value::Number(Number::from(per_page as i64)),
2445        );
2446        result.insert(
2447            "total".to_string(),
2448            Value::Number(Number::from(total as i64)),
2449        );
2450        result.insert(
2451            "total_pages".to_string(),
2452            Value::Number(Number::from(total_pages as i64)),
2453        );
2454        result.insert("has_next".to_string(), Value::Bool(page < total_pages));
2455        result.insert("has_prev".to_string(), Value::Bool(page > 1));
2456
2457        Ok(Value::Object(result))
2458    }
2459}
2460
2461// =============================================================================
2462// estimate_size(any) -> number
2463// =============================================================================
2464
2465defn!(EstimateSizeFn, vec![arg!(any)], None);
2466
2467impl Function for EstimateSizeFn {
2468    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2469        self.signature.validate(args, ctx)?;
2470        let size = serde_json::to_string(&args[0])
2471            .map(|s| s.len())
2472            .unwrap_or(0);
2473        Ok(Value::Number(Number::from(size as i64)))
2474    }
2475}
2476
2477// =============================================================================
2478// truncate_to_size(any, max_bytes) -> any
2479// =============================================================================
2480
2481defn!(TruncateToSizeFn, vec![arg!(any), arg!(number)], None);
2482
2483impl Function for TruncateToSizeFn {
2484    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2485        self.signature.validate(args, ctx)?;
2486
2487        let max_bytes = args[1].as_f64().unwrap_or(1000.0) as usize;
2488        let current_size = serde_json::to_string(&args[0])
2489            .map(|s| s.len())
2490            .unwrap_or(0);
2491
2492        if current_size <= max_bytes {
2493            return Ok(args[0].clone());
2494        }
2495
2496        if let Some(arr) = args[0].as_array() {
2497            let mut result: Vec<Value> = Vec::new();
2498            let mut size = 2;
2499
2500            for item in arr {
2501                let item_size = serde_json::to_string(item).map(|s| s.len()).unwrap_or(0);
2502                if size + item_size + 1 > max_bytes {
2503                    break;
2504                }
2505                result.push(item.clone());
2506                size += item_size + 1;
2507            }
2508            return Ok(Value::Array(result));
2509        }
2510
2511        if let Some(s) = args[0].as_str() {
2512            let target_len = max_bytes.saturating_sub(2);
2513            let truncated: String = s.chars().take(target_len).collect();
2514            return Ok(Value::String(truncated));
2515        }
2516
2517        Ok(args[0].clone())
2518    }
2519}
2520
2521// =============================================================================
2522// template(object, template_string) -> string
2523// =============================================================================
2524
2525defn!(TemplateFn, vec![arg!(any), arg!(string)], None);
2526
2527impl Function for TemplateFn {
2528    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2529        if args.len() >= 2 && args[1].is_null() {
2530            return Err(custom_error(
2531                ctx,
2532                "template: second argument is null. Template strings must be JMESPath \
2533                 literals using backticks, e.g., template(@, `\"Hello {{name}}\"`)",
2534            ));
2535        }
2536
2537        self.signature.validate(args, ctx)?;
2538
2539        let template = args[1]
2540            .as_str()
2541            .ok_or_else(|| custom_error(ctx, "Expected template string"))?;
2542
2543        let result =
2544            expand_template(&args[0], template, false).map_err(|e| custom_error(ctx, &e))?;
2545        Ok(Value::String(result))
2546    }
2547}
2548
2549// =============================================================================
2550// template_strict(object, template_string) -> string
2551// =============================================================================
2552
2553defn!(TemplateStrictFn, vec![arg!(any), arg!(string)], None);
2554
2555impl Function for TemplateStrictFn {
2556    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2557        if args.len() >= 2 && args[1].is_null() {
2558            return Err(custom_error(
2559                ctx,
2560                "template_strict: second argument is null. Template strings must be JMESPath \
2561                 literals using backticks, e.g., template_strict(@, `\"Hello {{name}}\"`)",
2562            ));
2563        }
2564
2565        self.signature.validate(args, ctx)?;
2566
2567        let template = args[1]
2568            .as_str()
2569            .ok_or_else(|| custom_error(ctx, "Expected template string"))?;
2570
2571        let result =
2572            expand_template(&args[0], template, true).map_err(|e| custom_error(ctx, &e))?;
2573        Ok(Value::String(result))
2574    }
2575}
2576
2577fn expand_template(data: &Value, template: &str, strict: bool) -> Result<String, String> {
2578    let mut result = String::new();
2579    let mut chars = template.chars().peekable();
2580
2581    while let Some(c) = chars.next() {
2582        if c == '{' && chars.peek() == Some(&'{') {
2583            chars.next();
2584
2585            let mut var_name = String::new();
2586            let mut fallback: Option<String> = None;
2587
2588            while let Some(&next) = chars.peek() {
2589                if next == '}' {
2590                    chars.next();
2591                    if chars.peek() == Some(&'}') {
2592                        chars.next();
2593                        break;
2594                    }
2595                } else if next == '|' {
2596                    chars.next();
2597                    let mut fb = String::new();
2598                    while let Some(&fc) = chars.peek() {
2599                        if fc == '}' {
2600                            break;
2601                        }
2602                        fb.push(chars.next().unwrap());
2603                    }
2604                    fallback = Some(fb);
2605                } else {
2606                    var_name.push(chars.next().unwrap());
2607                }
2608            }
2609
2610            let value = get_template_value(data, &var_name);
2611
2612            match value {
2613                Some(v) => result.push_str(&value_to_string(&v)),
2614                None => {
2615                    if strict {
2616                        return Err(format!("missing variable '{}'", var_name));
2617                    }
2618                    if let Some(fb) = fallback {
2619                        result.push_str(&fb);
2620                    }
2621                }
2622            }
2623        } else if c == '\\' && chars.peek() == Some(&'{') {
2624            result.push(chars.next().unwrap());
2625        } else {
2626            result.push(c);
2627        }
2628    }
2629
2630    Ok(result)
2631}
2632
2633fn get_template_value(data: &Value, path: &str) -> Option<Value> {
2634    let parts: Vec<&str> = path.trim().split('.').collect();
2635    let mut current = data.clone();
2636
2637    for part in parts {
2638        if let Ok(idx) = part.parse::<usize>() {
2639            if let Some(arr) = current.as_array()
2640                && idx < arr.len()
2641            {
2642                current = arr[idx].clone();
2643                continue;
2644            }
2645            return None;
2646        }
2647
2648        let obj = current.as_object()?;
2649        current = obj.get(part)?.clone();
2650    }
2651
2652    if current.is_null() {
2653        None
2654    } else {
2655        Some(current)
2656    }
2657}
2658
2659fn value_to_string(value: &Value) -> String {
2660    match value {
2661        Value::String(s) => s.clone(),
2662        Value::Number(n) => n.to_string(),
2663        Value::Bool(b) => b.to_string(),
2664        Value::Null => String::new(),
2665        _ => serde_json::to_string(value).unwrap_or_default(),
2666    }
2667}
2668
2669#[cfg(test)]
2670mod tests {
2671    use crate::Runtime;
2672    use serde_json::json;
2673
2674    fn setup_runtime() -> Runtime {
2675        Runtime::builder()
2676            .with_standard()
2677            .with_all_extensions()
2678            .build()
2679    }
2680
2681    #[test]
2682    fn test_items() {
2683        let runtime = setup_runtime();
2684        let expr = runtime.compile("items(@)").unwrap();
2685        let data = json!({"a": 1, "b": 2});
2686        let result = expr.search(&data).unwrap();
2687        let arr = result.as_array().unwrap();
2688        assert_eq!(arr.len(), 2);
2689        // JEP-013: Each item should be a [key, value] pair
2690        let first = arr[0].as_array().unwrap();
2691        assert_eq!(first.len(), 2);
2692        assert_eq!(first[0].as_str().unwrap(), "a");
2693        assert_eq!(first[1].as_f64().unwrap() as i64, 1);
2694    }
2695
2696    #[test]
2697    fn test_items_empty() {
2698        let runtime = setup_runtime();
2699        let expr = runtime.compile("items(@)").unwrap();
2700        let data = json!({});
2701        let result = expr.search(&data).unwrap();
2702        let arr = result.as_array().unwrap();
2703        assert_eq!(arr.len(), 0);
2704    }
2705
2706    #[test]
2707    fn test_from_items() {
2708        let runtime = setup_runtime();
2709        let expr = runtime.compile("from_items(@)").unwrap();
2710        let data = json!([["a", 1], ["b", 2]]);
2711        let result = expr.search(&data).unwrap();
2712        let obj = result.as_object().unwrap();
2713        assert_eq!(obj.len(), 2);
2714        assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
2715        assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 2);
2716    }
2717
2718    #[test]
2719    fn test_from_items_empty() {
2720        let runtime = setup_runtime();
2721        let expr = runtime.compile("from_items(@)").unwrap();
2722        let data = json!([]);
2723        let result = expr.search(&data).unwrap();
2724        let obj = result.as_object().unwrap();
2725        assert_eq!(obj.len(), 0);
2726    }
2727
2728    #[test]
2729    fn test_from_items_duplicate_keys() {
2730        let runtime = setup_runtime();
2731        let expr = runtime.compile("from_items(@)").unwrap();
2732        let data = json!([["x", 1], ["x", 2]]);
2733        let result = expr.search(&data).unwrap();
2734        let obj = result.as_object().unwrap();
2735        assert_eq!(obj.len(), 1);
2736        // Last value wins
2737        assert_eq!(obj.get("x").unwrap().as_f64().unwrap() as i64, 2);
2738    }
2739
2740    #[test]
2741    fn test_items_from_items_roundtrip() {
2742        let runtime = setup_runtime();
2743        let expr = runtime.compile("from_items(items(@))").unwrap();
2744        let data = json!({"a": 1, "b": "hello", "c": true});
2745        let result = expr.search(&data).unwrap();
2746        let obj = result.as_object().unwrap();
2747        assert_eq!(obj.len(), 3);
2748        assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
2749        assert_eq!(obj.get("b").unwrap().as_str().unwrap(), "hello");
2750        assert!(obj.get("c").unwrap().as_bool().unwrap());
2751    }
2752
2753    #[test]
2754    fn test_with_entries_identity() {
2755        let runtime = setup_runtime();
2756        let expr = runtime.compile("with_entries(@, '[@[0], @[1]]')").unwrap();
2757        let data = json!({"a": 1, "b": 2});
2758        let result = expr.search(&data).unwrap();
2759        let obj = result.as_object().unwrap();
2760        assert_eq!(obj.len(), 2);
2761        assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
2762        assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 2);
2763    }
2764
2765    #[test]
2766    fn test_with_entries_transform_keys() {
2767        // Test that we can transform keys by prepending a prefix
2768        // Using join to concatenate strings (built-in)
2769        let runtime = setup_runtime();
2770        let expr = runtime
2771            .compile(r#"with_entries(@, '[join(`""`, [`"prefix_"`, @[0]]), @[1]]')"#)
2772            .unwrap();
2773        let data = json!({"a": 1, "b": 2});
2774        let result = expr.search(&data).unwrap();
2775        let obj = result.as_object().unwrap();
2776        assert_eq!(obj.len(), 2);
2777        assert!(obj.contains_key("prefix_a"));
2778        assert!(obj.contains_key("prefix_b"));
2779    }
2780
2781    #[test]
2782    fn test_with_entries_swap_key_value() {
2783        // Test swapping keys and values (for string values)
2784        let runtime = setup_runtime();
2785        let expr = runtime.compile("with_entries(@, '[@[1], @[0]]')").unwrap();
2786        let data = json!({"a": "x", "b": "y"});
2787        let result = expr.search(&data).unwrap();
2788        let obj = result.as_object().unwrap();
2789        assert_eq!(obj.len(), 2);
2790        assert_eq!(obj.get("x").unwrap().as_str().unwrap(), "a");
2791        assert_eq!(obj.get("y").unwrap().as_str().unwrap(), "b");
2792    }
2793
2794    #[test]
2795    fn test_with_entries_filter_null() {
2796        // Test that returning null from the expression skips entries
2797        // This tests the filtering behavior of with_entries
2798        let runtime = setup_runtime();
2799        // Return null for all entries - result should be empty object
2800        let expr = runtime.compile(r#"with_entries(@, '`null`')"#).unwrap();
2801        let data = json!({"a": 1, "b": 2});
2802        let result = expr.search(&data).unwrap();
2803        let obj = result.as_object().unwrap();
2804        assert_eq!(obj.len(), 0);
2805    }
2806
2807    #[test]
2808    fn test_with_entries_empty() {
2809        let runtime = setup_runtime();
2810        let expr = runtime.compile("with_entries(@, '[@[0], @[1]]')").unwrap();
2811        let data = json!({});
2812        let result = expr.search(&data).unwrap();
2813        let obj = result.as_object().unwrap();
2814        assert_eq!(obj.len(), 0);
2815    }
2816
2817    #[test]
2818    fn test_pick() {
2819        let runtime = setup_runtime();
2820        let expr = runtime.compile("pick(@, `[\"a\"]`)").unwrap();
2821        let data = json!({"a": 1, "b": 2});
2822        let result = expr.search(&data).unwrap();
2823        let result_obj = result.as_object().unwrap();
2824        assert_eq!(result_obj.len(), 1);
2825        assert!(result_obj.contains_key("a"));
2826    }
2827
2828    #[test]
2829    fn test_deep_equals_objects() {
2830        let runtime = setup_runtime();
2831        let data = json!({"a": {"b": 1}, "c": {"b": 1}});
2832        let expr = runtime.compile("deep_equals(a, c)").unwrap();
2833        let result = expr.search(&data).unwrap();
2834        assert!(result.as_bool().unwrap());
2835    }
2836
2837    #[test]
2838    fn test_deep_equals_objects_different() {
2839        let runtime = setup_runtime();
2840        let data = json!({"a": {"b": 1}, "c": {"b": 2}});
2841        let expr = runtime.compile("deep_equals(a, c)").unwrap();
2842        let result = expr.search(&data).unwrap();
2843        assert!(!result.as_bool().unwrap());
2844    }
2845
2846    #[test]
2847    fn test_deep_equals_arrays() {
2848        let runtime = setup_runtime();
2849        let data = json!({"a": [1, [2, 3]], "b": [1, [2, 3]]});
2850        let expr = runtime.compile("deep_equals(a, b)").unwrap();
2851        let result = expr.search(&data).unwrap();
2852        assert!(result.as_bool().unwrap());
2853    }
2854
2855    #[test]
2856    fn test_deep_equals_arrays_order_matters() {
2857        let runtime = setup_runtime();
2858        let data = json!({"a": [1, 2], "b": [2, 1]});
2859        let expr = runtime.compile("deep_equals(a, b)").unwrap();
2860        let result = expr.search(&data).unwrap();
2861        assert!(!result.as_bool().unwrap());
2862    }
2863
2864    #[test]
2865    fn test_deep_equals_primitives() {
2866        let runtime = setup_runtime();
2867        let data = json!({"a": "hello", "b": "hello", "c": "world"});
2868
2869        let expr = runtime.compile("deep_equals(a, b)").unwrap();
2870        let result = expr.search(&data).unwrap();
2871        assert!(result.as_bool().unwrap());
2872
2873        let expr = runtime.compile("deep_equals(a, c)").unwrap();
2874        let result = expr.search(&data).unwrap();
2875        assert!(!result.as_bool().unwrap());
2876    }
2877
2878    #[test]
2879    fn test_deep_diff_added() {
2880        let runtime = setup_runtime();
2881        let data = json!({"a": {"x": 1}, "b": {"x": 1, "y": 2}});
2882        let expr = runtime.compile("deep_diff(a, b)").unwrap();
2883        let result = expr.search(&data).unwrap();
2884        let diff = result.as_object().unwrap();
2885
2886        let added = diff.get("added").unwrap().as_object().unwrap();
2887        assert!(added.contains_key("y"));
2888        assert!(diff.get("removed").unwrap().as_object().unwrap().is_empty());
2889        assert!(diff.get("changed").unwrap().as_object().unwrap().is_empty());
2890    }
2891
2892    #[test]
2893    fn test_deep_diff_removed() {
2894        let runtime = setup_runtime();
2895        let data = json!({"a": {"x": 1, "y": 2}, "b": {"x": 1}});
2896        let expr = runtime.compile("deep_diff(a, b)").unwrap();
2897        let result = expr.search(&data).unwrap();
2898        let diff = result.as_object().unwrap();
2899
2900        let removed = diff.get("removed").unwrap().as_object().unwrap();
2901        assert!(removed.contains_key("y"));
2902        assert!(diff.get("added").unwrap().as_object().unwrap().is_empty());
2903        assert!(diff.get("changed").unwrap().as_object().unwrap().is_empty());
2904    }
2905
2906    #[test]
2907    fn test_deep_diff_changed() {
2908        let runtime = setup_runtime();
2909        let data = json!({"a": {"x": 1}, "b": {"x": 2}});
2910        let expr = runtime.compile("deep_diff(a, b)").unwrap();
2911        let result = expr.search(&data).unwrap();
2912        let diff = result.as_object().unwrap();
2913
2914        let changed = diff.get("changed").unwrap().as_object().unwrap();
2915        assert!(changed.contains_key("x"));
2916        let x_change = changed.get("x").unwrap().as_object().unwrap();
2917        assert!(x_change.contains_key("from"));
2918        assert!(x_change.contains_key("to"));
2919    }
2920
2921    #[test]
2922    fn test_deep_diff_nested() {
2923        let runtime = setup_runtime();
2924        let data = json!({"a": {"x": {"y": 1}}, "b": {"x": {"y": 2}}});
2925        let expr = runtime.compile("deep_diff(a, b)").unwrap();
2926        let result = expr.search(&data).unwrap();
2927        let diff = result.as_object().unwrap();
2928
2929        // The change should be nested under x
2930        let changed = diff.get("changed").unwrap().as_object().unwrap();
2931        assert!(changed.contains_key("x"));
2932    }
2933
2934    #[test]
2935    fn test_deep_diff_no_changes() {
2936        let runtime = setup_runtime();
2937        let data = json!({"a": {"x": 1}, "b": {"x": 1}});
2938        let expr = runtime.compile("deep_diff(a, b)").unwrap();
2939        let result = expr.search(&data).unwrap();
2940        let diff = result.as_object().unwrap();
2941
2942        assert!(diff.get("added").unwrap().as_object().unwrap().is_empty());
2943        assert!(diff.get("removed").unwrap().as_object().unwrap().is_empty());
2944        assert!(diff.get("changed").unwrap().as_object().unwrap().is_empty());
2945    }
2946
2947    #[test]
2948    fn test_get_nested() {
2949        let runtime = setup_runtime();
2950        let data = json!({"a": {"b": {"c": 1}}});
2951        let expr = runtime.compile("get(@, 'a.b.c')").unwrap();
2952        let result = expr.search(&data).unwrap();
2953        assert_eq!(result.as_f64().unwrap(), 1.0);
2954    }
2955
2956    #[test]
2957    fn test_get_with_default() {
2958        let runtime = setup_runtime();
2959        let data = json!({"a": 1});
2960        let expr = runtime.compile("get(@, 'b.c', 'default')").unwrap();
2961        let result = expr.search(&data).unwrap();
2962        assert_eq!(result.as_str().unwrap(), "default");
2963    }
2964
2965    #[test]
2966    fn test_get_array_index() {
2967        let runtime = setup_runtime();
2968        let data = json!({"a": [{"b": 1}, {"b": 2}]});
2969        let expr = runtime.compile("get(@, 'a[0].b')").unwrap();
2970        let result = expr.search(&data).unwrap();
2971        assert_eq!(result.as_f64().unwrap(), 1.0);
2972    }
2973
2974    #[test]
2975    fn test_get_missing_returns_null() {
2976        let runtime = setup_runtime();
2977        let data = json!({"a": 1});
2978        let expr = runtime.compile("get(@, 'x.y.z')").unwrap();
2979        let result = expr.search(&data).unwrap();
2980        assert!(result.is_null());
2981    }
2982
2983    #[test]
2984    fn test_has_exists() {
2985        let runtime = setup_runtime();
2986        let data = json!({"a": {"b": 1}});
2987        let expr = runtime.compile("has(@, 'a.b')").unwrap();
2988        let result = expr.search(&data).unwrap();
2989        assert!(result.as_bool().unwrap());
2990    }
2991
2992    #[test]
2993    fn test_has_not_exists() {
2994        let runtime = setup_runtime();
2995        let data = json!({"a": 1});
2996        let expr = runtime.compile("has(@, 'a.b.c')").unwrap();
2997        let result = expr.search(&data).unwrap();
2998        assert!(!result.as_bool().unwrap());
2999    }
3000
3001    #[test]
3002    fn test_has_array_index() {
3003        let runtime = setup_runtime();
3004        let data = json!({"a": [1, 2, 3]});
3005        let expr = runtime.compile("has(@, 'a[1]')").unwrap();
3006        let result = expr.search(&data).unwrap();
3007        assert!(result.as_bool().unwrap());
3008    }
3009
3010    #[test]
3011    fn test_has_array_index_out_of_bounds() {
3012        let runtime = setup_runtime();
3013        let data = json!({"a": [1, 2]});
3014        let expr = runtime.compile("has(@, 'a[5]')").unwrap();
3015        let result = expr.search(&data).unwrap();
3016        assert!(!result.as_bool().unwrap());
3017    }
3018
3019    #[test]
3020    fn test_defaults_shallow() {
3021        let runtime = setup_runtime();
3022        let data = json!({"obj": {"a": 1}, "defs": {"a": 2, "b": 3}});
3023        let expr = runtime.compile("defaults(obj, defs)").unwrap();
3024        let result = expr.search(&data).unwrap();
3025        let obj = result.as_object().unwrap();
3026        assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0); // original kept
3027        assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 3.0); // default added
3028    }
3029
3030    #[test]
3031    fn test_defaults_empty_object() {
3032        let runtime = setup_runtime();
3033        let data = json!({"obj": {}, "defs": {"a": 1, "b": 2}});
3034        let expr = runtime.compile("defaults(obj, defs)").unwrap();
3035        let result = expr.search(&data).unwrap();
3036        let obj = result.as_object().unwrap();
3037        assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0);
3038        assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 2.0);
3039    }
3040
3041    #[test]
3042    fn test_defaults_deep_nested() {
3043        let runtime = setup_runtime();
3044        let data = json!({"obj": {"a": {"b": 1}}, "defs": {"a": {"b": 2, "c": 3}}});
3045        let expr = runtime.compile("defaults_deep(obj, defs)").unwrap();
3046        let result = expr.search(&data).unwrap();
3047        let obj = result.as_object().unwrap();
3048        let a = obj.get("a").unwrap().as_object().unwrap();
3049        assert_eq!(a.get("b").unwrap().as_f64().unwrap(), 1.0); // original kept
3050        assert_eq!(a.get("c").unwrap().as_f64().unwrap(), 3.0); // default added
3051    }
3052
3053    #[test]
3054    fn test_defaults_deep_new_nested() {
3055        let runtime = setup_runtime();
3056        let data = json!({"obj": {"x": 1}, "defs": {"x": 2, "y": {"z": 3}}});
3057        let expr = runtime.compile("defaults_deep(obj, defs)").unwrap();
3058        let result = expr.search(&data).unwrap();
3059        let obj = result.as_object().unwrap();
3060        assert_eq!(obj.get("x").unwrap().as_f64().unwrap(), 1.0); // original kept
3061        let y = obj.get("y").unwrap().as_object().unwrap();
3062        assert_eq!(y.get("z").unwrap().as_f64().unwrap(), 3.0); // default added
3063    }
3064
3065    #[test]
3066    fn test_set_path_basic() {
3067        let runtime = setup_runtime();
3068        let data = json!({"a": 1, "b": 2});
3069        let expr = runtime.compile("set_path(@, '/c', `3`)").unwrap();
3070        let result = expr.search(&data).unwrap();
3071        let obj = result.as_object().unwrap();
3072        assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0);
3073        assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 2.0);
3074        assert_eq!(obj.get("c").unwrap().as_f64().unwrap(), 3.0);
3075    }
3076
3077    #[test]
3078    fn test_set_path_nested() {
3079        let runtime = setup_runtime();
3080        let data = json!({"a": {"b": 1}});
3081        let expr = runtime.compile("set_path(@, '/a/c', `2`)").unwrap();
3082        let result = expr.search(&data).unwrap();
3083        let obj = result.as_object().unwrap();
3084        let a = obj.get("a").unwrap().as_object().unwrap();
3085        assert_eq!(a.get("b").unwrap().as_f64().unwrap(), 1.0);
3086        assert_eq!(a.get("c").unwrap().as_f64().unwrap(), 2.0);
3087    }
3088
3089    #[test]
3090    fn test_set_path_create_nested() {
3091        let runtime = setup_runtime();
3092        let data = json!({});
3093        let expr = runtime
3094            .compile("set_path(@, '/a/b/c', `\"deep\"`)")
3095            .unwrap();
3096        let result = expr.search(&data).unwrap();
3097        let obj = result.as_object().unwrap();
3098        let a = obj.get("a").unwrap().as_object().unwrap();
3099        let b = a.get("b").unwrap().as_object().unwrap();
3100        assert_eq!(b.get("c").unwrap().as_str().unwrap(), "deep");
3101    }
3102
3103    #[test]
3104    fn test_set_path_array_index() {
3105        let runtime = setup_runtime();
3106        let data = json!({"items": [1, 2, 3]});
3107        let expr = runtime.compile("set_path(@, '/items/1', `99`)").unwrap();
3108        let result = expr.search(&data).unwrap();
3109        let obj = result.as_object().unwrap();
3110        let items = obj.get("items").unwrap().as_array().unwrap();
3111        assert_eq!(items[0].as_f64().unwrap(), 1.0);
3112        assert_eq!(items[1].as_f64().unwrap(), 99.0);
3113        assert_eq!(items[2].as_f64().unwrap(), 3.0);
3114    }
3115
3116    #[test]
3117    fn test_delete_path_basic() {
3118        let runtime = setup_runtime();
3119        let data = json!({"a": 1, "b": 2, "c": 3});
3120        let expr = runtime.compile("delete_path(@, '/b')").unwrap();
3121        let result = expr.search(&data).unwrap();
3122        let obj = result.as_object().unwrap();
3123        assert_eq!(obj.len(), 2);
3124        assert!(obj.contains_key("a"));
3125        assert!(obj.contains_key("c"));
3126        assert!(!obj.contains_key("b"));
3127    }
3128
3129    #[test]
3130    fn test_delete_path_nested() {
3131        let runtime = setup_runtime();
3132        let data = json!({"a": {"b": 1, "c": 2}});
3133        let expr = runtime.compile("delete_path(@, '/a/b')").unwrap();
3134        let result = expr.search(&data).unwrap();
3135        let obj = result.as_object().unwrap();
3136        let a = obj.get("a").unwrap().as_object().unwrap();
3137        assert_eq!(a.len(), 1);
3138        assert!(a.contains_key("c"));
3139        assert!(!a.contains_key("b"));
3140    }
3141
3142    #[test]
3143    fn test_delete_path_array() {
3144        let runtime = setup_runtime();
3145        let data = json!({"items": [1, 2, 3]});
3146        let expr = runtime.compile("delete_path(@, '/items/1')").unwrap();
3147        let result = expr.search(&data).unwrap();
3148        let obj = result.as_object().unwrap();
3149        let items = obj.get("items").unwrap().as_array().unwrap();
3150        assert_eq!(items.len(), 2);
3151        assert_eq!(items[0].as_f64().unwrap(), 1.0);
3152        assert_eq!(items[1].as_f64().unwrap(), 3.0);
3153    }
3154
3155    #[test]
3156    fn test_paths_basic() {
3157        let runtime = setup_runtime();
3158        let data = json!({"a": {"b": 1}, "c": 2});
3159        let expr = runtime.compile("paths(@)").unwrap();
3160        let result = expr.search(&data).unwrap();
3161        let paths = result.as_array().unwrap();
3162        assert!(paths.len() >= 3); // /a, /a/b, /c
3163    }
3164
3165    #[test]
3166    fn test_paths_with_array() {
3167        let runtime = setup_runtime();
3168        let data = json!({"items": [1, 2]});
3169        let expr = runtime.compile("paths(@)").unwrap();
3170        let result = expr.search(&data).unwrap();
3171        let paths: Vec<String> = result
3172            .as_array()
3173            .unwrap()
3174            .iter()
3175            .map(|p| p.as_str().unwrap().to_string())
3176            .collect();
3177        assert!(paths.contains(&"/items".to_string()));
3178        assert!(paths.contains(&"/items/0".to_string()));
3179        assert!(paths.contains(&"/items/1".to_string()));
3180    }
3181
3182    #[test]
3183    fn test_leaves_basic() {
3184        let runtime = setup_runtime();
3185        let data = json!({"a": 1, "b": {"c": 2}, "d": [3, 4]});
3186        let expr = runtime.compile("leaves(@)").unwrap();
3187        let result = expr.search(&data).unwrap();
3188        let leaves = result.as_array().unwrap();
3189        assert_eq!(leaves.len(), 4); // 1, 2, 3, 4
3190    }
3191
3192    #[test]
3193    fn test_leaves_strings() {
3194        let runtime = setup_runtime();
3195        let data = json!({"name": "alice", "tags": ["a", "b"]});
3196        let expr = runtime.compile("leaves(@)").unwrap();
3197        let result = expr.search(&data).unwrap();
3198        let leaves = result.as_array().unwrap();
3199        assert_eq!(leaves.len(), 3); // "alice", "a", "b"
3200    }
3201
3202    #[test]
3203    fn test_leaves_with_paths_basic() {
3204        let runtime = setup_runtime();
3205        let data = json!({"a": 1, "b": {"c": 2}});
3206        let expr = runtime.compile("leaves_with_paths(@)").unwrap();
3207        let result = expr.search(&data).unwrap();
3208        let leaves = result.as_array().unwrap();
3209        assert_eq!(leaves.len(), 2);
3210        // Each leaf should have path and value
3211        let first = leaves[0].as_object().unwrap();
3212        assert!(first.contains_key("path"));
3213        assert!(first.contains_key("value"));
3214    }
3215
3216    #[test]
3217    fn test_set_path_immutable() {
3218        let runtime = setup_runtime();
3219        let data = json!({"a": 1});
3220        let expr = runtime.compile("set_path(@, '/b', `2`)").unwrap();
3221        let result = expr.search(&data).unwrap();
3222        // Original should be unchanged (immutable semantics)
3223        let original = data.as_object().unwrap();
3224        assert!(!original.contains_key("b"));
3225        // Result should have the new key
3226        let new_obj = result.as_object().unwrap();
3227        assert!(new_obj.contains_key("b"));
3228    }
3229
3230    // =========================================================================
3231    // Dot notation path tests (for set_path, delete_path, get_path, has_path)
3232    // =========================================================================
3233
3234    #[test]
3235    fn test_set_path_dot_notation() {
3236        let runtime = setup_runtime();
3237        let data = json!({"a": {"c": 1}});
3238        let expr = runtime.compile("set_path(@, `\"a.b\"`, `99`)").unwrap();
3239        let result = expr.search(&data).unwrap();
3240        let obj = result.as_object().unwrap();
3241        let nested = obj.get("a").unwrap().as_object().unwrap();
3242        assert_eq!(nested.get("b").unwrap().as_f64().unwrap() as i64, 99);
3243        assert_eq!(nested.get("c").unwrap().as_f64().unwrap() as i64, 1);
3244    }
3245
3246    #[test]
3247    fn test_set_path_dot_notation_deep() {
3248        let runtime = setup_runtime();
3249        let data = json!({});
3250        let expr = runtime
3251            .compile("set_path(@, `\"a.b.c\"`, `\"deep\"`)")
3252            .unwrap();
3253        let result = expr.search(&data).unwrap();
3254        let obj = result.as_object().unwrap();
3255        let a = obj.get("a").unwrap().as_object().unwrap();
3256        let b = a.get("b").unwrap().as_object().unwrap();
3257        assert_eq!(b.get("c").unwrap().as_str().unwrap(), "deep");
3258    }
3259
3260    #[test]
3261    fn test_set_path_dot_notation_array_index() {
3262        let runtime = setup_runtime();
3263        let data = json!({"items": [1, 2, 3]});
3264        let expr = runtime.compile("set_path(@, `\"items.1\"`, `99`)").unwrap();
3265        let result = expr.search(&data).unwrap();
3266        let obj = result.as_object().unwrap();
3267        let items = obj.get("items").unwrap().as_array().unwrap();
3268        assert_eq!(items[1].as_f64().unwrap() as i64, 99);
3269    }
3270
3271    #[test]
3272    fn test_delete_path_dot_notation() {
3273        let runtime = setup_runtime();
3274        let data = json!({"a": {"b": 1, "c": 2}});
3275        let expr = runtime.compile("delete_path(@, `\"a.b\"`)").unwrap();
3276        let result = expr.search(&data).unwrap();
3277        let obj = result.as_object().unwrap();
3278        let nested = obj.get("a").unwrap().as_object().unwrap();
3279        assert!(!nested.contains_key("b"));
3280        assert!(nested.contains_key("c"));
3281    }
3282
3283    #[test]
3284    fn test_delete_path_dot_notation_array() {
3285        let runtime = setup_runtime();
3286        let data = json!({"items": [1, 2, 3]});
3287        let expr = runtime.compile("delete_path(@, `\"items.1\"`)").unwrap();
3288        let result = expr.search(&data).unwrap();
3289        let obj = result.as_object().unwrap();
3290        let items = obj.get("items").unwrap().as_array().unwrap();
3291        assert_eq!(items.len(), 2);
3292        assert_eq!(items[0].as_f64().unwrap() as i64, 1);
3293        assert_eq!(items[1].as_f64().unwrap() as i64, 3);
3294    }
3295
3296    #[test]
3297    fn test_get_path_alias() {
3298        let runtime = setup_runtime();
3299        let data = json!({"a": {"b": {"c": 42}}});
3300        let expr = runtime.compile("get_path(@, `\"a.b.c\"`)").unwrap();
3301        let result = expr.search(&data).unwrap();
3302        assert_eq!(result.as_f64().unwrap() as i64, 42);
3303    }
3304
3305    #[test]
3306    fn test_get_path_with_default() {
3307        let runtime = setup_runtime();
3308        let data = json!({"a": 1});
3309        let expr = runtime
3310            .compile("get_path(@, `\"a.b.c\"`, `\"default\"`)")
3311            .unwrap();
3312        let result = expr.search(&data).unwrap();
3313        assert_eq!(result.as_str().unwrap(), "default");
3314    }
3315
3316    #[test]
3317    fn test_get_path_array_index() {
3318        let runtime = setup_runtime();
3319        let data = json!({"users": [{"name": "alice"}, {"name": "bob"}]});
3320        let expr = runtime.compile("get_path(@, `\"users.0.name\"`)").unwrap();
3321        let result = expr.search(&data).unwrap();
3322        assert_eq!(result.as_str().unwrap(), "alice");
3323    }
3324
3325    #[test]
3326    fn test_get_path_array_index_out_of_bounds() {
3327        let runtime = setup_runtime();
3328        let data = json!({"users": [{"name": "alice"}]});
3329        let expr = runtime
3330            .compile("get_path(@, `\"users.5.name\"`, `\"unknown\"`)")
3331            .unwrap();
3332        let result = expr.search(&data).unwrap();
3333        assert_eq!(result.as_str().unwrap(), "unknown");
3334    }
3335
3336    #[test]
3337    fn test_has_path_alias() {
3338        let runtime = setup_runtime();
3339        let data = json!({"a": {"b": 1}});
3340        let expr = runtime.compile("has_path(@, `\"a.b\"`)").unwrap();
3341        let result = expr.search(&data).unwrap();
3342        assert!(result.as_bool().unwrap());
3343    }
3344
3345    #[test]
3346    fn test_has_path_missing() {
3347        let runtime = setup_runtime();
3348        let data = json!({"a": {"b": 1}});
3349        let expr = runtime.compile("has_path(@, `\"a.c\"`)").unwrap();
3350        let result = expr.search(&data).unwrap();
3351        assert!(!result.as_bool().unwrap());
3352    }
3353
3354    #[test]
3355    fn test_has_path_array_index() {
3356        let runtime = setup_runtime();
3357        let data = json!({"items": [1, 2, 3]});
3358        let expr = runtime.compile("has_path(@, `\"items.1\"`)").unwrap();
3359        let result = expr.search(&data).unwrap();
3360        assert!(result.as_bool().unwrap());
3361    }
3362
3363    // =========================================================================
3364    // remove_nulls tests
3365    // =========================================================================
3366
3367    #[test]
3368    fn test_remove_nulls_basic() {
3369        let runtime = setup_runtime();
3370        let data = json!({"a": 1, "b": null, "c": 2});
3371        let expr = runtime.compile("remove_nulls(@)").unwrap();
3372        let result = expr.search(&data).unwrap();
3373        let obj = result.as_object().unwrap();
3374        assert_eq!(obj.len(), 2);
3375        assert!(obj.contains_key("a"));
3376        assert!(obj.contains_key("c"));
3377        assert!(!obj.contains_key("b"));
3378    }
3379
3380    #[test]
3381    fn test_remove_nulls_nested() {
3382        let runtime = setup_runtime();
3383        let data = json!({"a": 1, "b": {"c": null, "d": 2}});
3384        let expr = runtime.compile("remove_nulls(@)").unwrap();
3385        let result = expr.search(&data).unwrap();
3386        let obj = result.as_object().unwrap();
3387        let nested = obj.get("b").unwrap().as_object().unwrap();
3388        assert_eq!(nested.len(), 1);
3389        assert!(nested.contains_key("d"));
3390        assert!(!nested.contains_key("c"));
3391    }
3392
3393    #[test]
3394    fn test_remove_nulls_array() {
3395        let runtime = setup_runtime();
3396        let data = json!([1, null, 2, null, 3]);
3397        let expr = runtime.compile("remove_nulls(@)").unwrap();
3398        let result = expr.search(&data).unwrap();
3399        let arr = result.as_array().unwrap();
3400        assert_eq!(arr.len(), 3);
3401    }
3402
3403    // =========================================================================
3404    // remove_empty tests
3405    // =========================================================================
3406
3407    #[test]
3408    fn test_remove_empty_basic() {
3409        let runtime = setup_runtime();
3410        let data = json!({"a": "", "b": [], "c": {}, "d": null, "e": "hello"});
3411        let expr = runtime.compile("remove_empty(@)").unwrap();
3412        let result = expr.search(&data).unwrap();
3413        let obj = result.as_object().unwrap();
3414        assert_eq!(obj.len(), 1);
3415        assert!(obj.contains_key("e"));
3416    }
3417
3418    #[test]
3419    fn test_remove_empty_nested() {
3420        let runtime = setup_runtime();
3421        let data = json!({"a": {"b": "", "c": 1}, "d": []});
3422        let expr = runtime.compile("remove_empty(@)").unwrap();
3423        let result = expr.search(&data).unwrap();
3424        let obj = result.as_object().unwrap();
3425        assert_eq!(obj.len(), 1);
3426        let nested = obj.get("a").unwrap().as_object().unwrap();
3427        assert_eq!(nested.len(), 1);
3428        assert!(nested.contains_key("c"));
3429    }
3430
3431    #[test]
3432    fn test_remove_empty_array() {
3433        let runtime = setup_runtime();
3434        let data = json!(["", "hello", [], null, "world"]);
3435        let expr = runtime.compile("remove_empty(@)").unwrap();
3436        let result = expr.search(&data).unwrap();
3437        let arr = result.as_array().unwrap();
3438        assert_eq!(arr.len(), 2);
3439    }
3440
3441    // =========================================================================
3442    // remove_empty_strings tests
3443    // =========================================================================
3444
3445    #[test]
3446    fn test_remove_empty_strings_basic() {
3447        let runtime = setup_runtime();
3448        let data = json!({"name": "alice", "bio": "", "age": 30});
3449        let expr = runtime.compile("remove_empty_strings(@)").unwrap();
3450        let result = expr.search(&data).unwrap();
3451        let obj = result.as_object().unwrap();
3452        assert_eq!(obj.len(), 2);
3453        assert!(obj.contains_key("name"));
3454        assert!(obj.contains_key("age"));
3455        assert!(!obj.contains_key("bio"));
3456    }
3457
3458    #[test]
3459    fn test_remove_empty_strings_array() {
3460        let runtime = setup_runtime();
3461        let data = json!(["hello", "", "world", ""]);
3462        let expr = runtime.compile("remove_empty_strings(@)").unwrap();
3463        let result = expr.search(&data).unwrap();
3464        let arr = result.as_array().unwrap();
3465        assert_eq!(arr.len(), 2);
3466    }
3467
3468    // =========================================================================
3469    // compact_deep tests
3470    // =========================================================================
3471
3472    #[test]
3473    fn test_compact_deep_basic() {
3474        let runtime = setup_runtime();
3475        let data = json!([[1, null], [null, 2]]);
3476        let expr = runtime.compile("compact_deep(@)").unwrap();
3477        let result = expr.search(&data).unwrap();
3478        let arr = result.as_array().unwrap();
3479        assert_eq!(arr.len(), 2);
3480        let first = arr[0].as_array().unwrap();
3481        assert_eq!(first.len(), 1);
3482        let second = arr[1].as_array().unwrap();
3483        assert_eq!(second.len(), 1);
3484    }
3485
3486    #[test]
3487    fn test_compact_deep_nested() {
3488        let runtime = setup_runtime();
3489        let data = json!([[1, null], [null, [2, null, 3]]]);
3490        let expr = runtime.compile("compact_deep(@)").unwrap();
3491        let result = expr.search(&data).unwrap();
3492        let arr = result.as_array().unwrap();
3493        let second = arr[1].as_array().unwrap();
3494        let inner = second[0].as_array().unwrap();
3495        assert_eq!(inner.len(), 2); // [2, 3]
3496    }
3497
3498    // =========================================================================
3499    // completeness tests
3500    // =========================================================================
3501
3502    #[test]
3503    fn test_completeness_all_filled() {
3504        let runtime = setup_runtime();
3505        let data = json!({"a": 1, "b": "hello", "c": true});
3506        let expr = runtime.compile("completeness(@)").unwrap();
3507        let result = expr.search(&data).unwrap();
3508        let score = result.as_f64().unwrap();
3509        assert_eq!(score, 100.0);
3510    }
3511
3512    #[test]
3513    fn test_completeness_with_nulls() {
3514        let runtime = setup_runtime();
3515        let data = json!({"a": 1, "b": null, "c": null});
3516        let expr = runtime.compile("completeness(@)").unwrap();
3517        let result = expr.search(&data).unwrap();
3518        let score = result.as_f64().unwrap();
3519        // 1 out of 3 fields is non-null = 33.33%
3520        assert!((score - 33.33).abs() < 1.0);
3521    }
3522
3523    #[test]
3524    fn test_completeness_nested() {
3525        let runtime = setup_runtime();
3526        let data = json!({"a": 1, "b": {"c": null, "d": 2}, "e": null});
3527        let expr = runtime.compile("completeness(@)").unwrap();
3528        let result = expr.search(&data).unwrap();
3529        let score = result.as_f64().unwrap();
3530        // 5 total fields: a(1), b(obj), b.c(null), b.d(2), e(null)
3531        // 3 non-null: a, b, b.d
3532        // 3/5 = 60%
3533        assert!((score - 60.0).abs() < 1.0);
3534    }
3535
3536    // =========================================================================
3537    // type_consistency tests
3538    // =========================================================================
3539
3540    #[test]
3541    fn test_type_consistency_consistent() {
3542        let runtime = setup_runtime();
3543        let data = json!([1, 2, 3]);
3544        let expr = runtime.compile("type_consistency(@)").unwrap();
3545        let result = expr.search(&data).unwrap();
3546        let obj = result.as_object().unwrap();
3547        assert!(obj.get("consistent").unwrap().as_bool().unwrap());
3548    }
3549
3550    #[test]
3551    fn test_type_consistency_inconsistent() {
3552        let runtime = setup_runtime();
3553        let data = json!([1, "two", 3]);
3554        let expr = runtime.compile("type_consistency(@)").unwrap();
3555        let result = expr.search(&data).unwrap();
3556        let obj = result.as_object().unwrap();
3557        assert!(!obj.get("consistent").unwrap().as_bool().unwrap());
3558    }
3559
3560    #[test]
3561    fn test_type_consistency_object_array() {
3562        let runtime = setup_runtime();
3563        let data = json!([{"name": "alice", "age": 30}, {"name": "bob", "age": "unknown"}]);
3564        let expr = runtime.compile("type_consistency(@)").unwrap();
3565        let result = expr.search(&data).unwrap();
3566        let obj = result.as_object().unwrap();
3567        assert!(!obj.get("consistent").unwrap().as_bool().unwrap());
3568        let inconsistencies = obj.get("inconsistencies").unwrap().as_array().unwrap();
3569        assert_eq!(inconsistencies.len(), 1);
3570    }
3571
3572    // =========================================================================
3573    // data_quality_score tests
3574    // =========================================================================
3575
3576    #[test]
3577    fn test_data_quality_score_perfect() {
3578        let runtime = setup_runtime();
3579        let data = json!({"a": 1, "b": "hello"});
3580        let expr = runtime.compile("data_quality_score(@)").unwrap();
3581        let result = expr.search(&data).unwrap();
3582        let obj = result.as_object().unwrap();
3583        let score = obj.get("score").unwrap().as_f64().unwrap();
3584        assert_eq!(score, 100.0);
3585        assert_eq!(obj.get("null_count").unwrap().as_f64().unwrap() as i64, 0);
3586    }
3587
3588    #[test]
3589    fn test_data_quality_score_with_issues() {
3590        let runtime = setup_runtime();
3591        let data = json!({"a": 1, "b": null, "c": ""});
3592        let expr = runtime.compile("data_quality_score(@)").unwrap();
3593        let result = expr.search(&data).unwrap();
3594        let obj = result.as_object().unwrap();
3595        assert_eq!(obj.get("null_count").unwrap().as_f64().unwrap() as i64, 1);
3596        assert_eq!(
3597            obj.get("empty_string_count").unwrap().as_f64().unwrap() as i64,
3598            1
3599        );
3600        let issues = obj.get("issues").unwrap().as_array().unwrap();
3601        assert_eq!(issues.len(), 2);
3602    }
3603
3604    #[test]
3605    fn test_data_quality_score_type_mismatch() {
3606        let runtime = setup_runtime();
3607        let data = json!({"users": [{"age": 30}, {"age": "thirty"}]});
3608        let expr = runtime.compile("data_quality_score(@)").unwrap();
3609        let result = expr.search(&data).unwrap();
3610        let obj = result.as_object().unwrap();
3611        assert_eq!(
3612            obj.get("type_inconsistencies").unwrap().as_f64().unwrap() as i64,
3613            1
3614        );
3615    }
3616
3617    // =========================================================================
3618    // redact tests
3619    // =========================================================================
3620
3621    #[test]
3622    fn test_redact_basic() {
3623        let runtime = setup_runtime();
3624        let data = json!({"name": "alice", "password": "secret123", "ssn": "123-45-6789"});
3625        let expr = runtime
3626            .compile(r#"redact(@, `["password", "ssn"]`)"#)
3627            .unwrap();
3628        let result = expr.search(&data).unwrap();
3629        let obj = result.as_object().unwrap();
3630        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "alice");
3631        assert_eq!(obj.get("password").unwrap().as_str().unwrap(), "[REDACTED]");
3632        assert_eq!(obj.get("ssn").unwrap().as_str().unwrap(), "[REDACTED]");
3633    }
3634
3635    #[test]
3636    fn test_redact_nested() {
3637        let runtime = setup_runtime();
3638        let data = json!({"user": {"name": "bob", "password": "secret"}});
3639        let expr = runtime.compile(r#"redact(@, `["password"]`)"#).unwrap();
3640        let result = expr.search(&data).unwrap();
3641        let obj = result.as_object().unwrap();
3642        let user = obj.get("user").unwrap().as_object().unwrap();
3643        assert_eq!(user.get("name").unwrap().as_str().unwrap(), "bob");
3644        assert_eq!(
3645            user.get("password").unwrap().as_str().unwrap(),
3646            "[REDACTED]"
3647        );
3648    }
3649
3650    #[test]
3651    fn test_redact_array_of_objects() {
3652        let runtime = setup_runtime();
3653        let data = json!([
3654            {"name": "alice", "token": "abc"},
3655            {"name": "bob", "token": "xyz"}
3656        ]);
3657        let expr = runtime.compile(r#"redact(@, `["token"]`)"#).unwrap();
3658        let result = expr.search(&data).unwrap();
3659        let arr = result.as_array().unwrap();
3660        let first = arr[0].as_object().unwrap();
3661        assert_eq!(first.get("token").unwrap().as_str().unwrap(), "[REDACTED]");
3662    }
3663
3664    // =========================================================================
3665    // mask tests
3666    // =========================================================================
3667
3668    #[test]
3669    fn test_mask_default() {
3670        let runtime = setup_runtime();
3671        let data = json!("4111111111111111");
3672        let expr = runtime.compile("mask(@)").unwrap();
3673        let result = expr.search(&data).unwrap();
3674        assert_eq!(result.as_str().unwrap(), "************1111");
3675    }
3676
3677    #[test]
3678    fn test_mask_custom_length() {
3679        let runtime = setup_runtime();
3680        let data = json!("555-123-4567");
3681        let expr = runtime.compile("mask(@, `3`)").unwrap();
3682        let result = expr.search(&data).unwrap();
3683        assert_eq!(result.as_str().unwrap(), "*********567");
3684    }
3685
3686    #[test]
3687    fn test_mask_short_string() {
3688        let runtime = setup_runtime();
3689        let data = json!("abc");
3690        let expr = runtime.compile("mask(@)").unwrap();
3691        let result = expr.search(&data).unwrap();
3692        // If string is shorter than show_last, mask everything
3693        assert_eq!(result.as_str().unwrap(), "***");
3694    }
3695
3696    // =========================================================================
3697    // redact_keys tests
3698    // =========================================================================
3699
3700    #[test]
3701    fn test_redact_keys_basic() {
3702        let runtime = setup_runtime();
3703        let data = json!({"password": "secret", "api_key": "abc123", "name": "test"});
3704        let expr = runtime
3705            .compile(r#"redact_keys(@, `"password|api_key"`)"#)
3706            .unwrap();
3707        let result = expr.search(&data).unwrap();
3708        let obj = result.as_object().unwrap();
3709        assert_eq!(obj.get("password").unwrap().as_str().unwrap(), "[REDACTED]");
3710        assert_eq!(obj.get("api_key").unwrap().as_str().unwrap(), "[REDACTED]");
3711        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "test");
3712    }
3713
3714    #[test]
3715    fn test_redact_keys_pattern() {
3716        let runtime = setup_runtime();
3717        let data = json!({"secret_key": "a", "secret_token": "b", "name": "test"});
3718        let expr = runtime.compile(r#"redact_keys(@, `"secret.*"`)"#).unwrap();
3719        let result = expr.search(&data).unwrap();
3720        let obj = result.as_object().unwrap();
3721        assert_eq!(
3722            obj.get("secret_key").unwrap().as_str().unwrap(),
3723            "[REDACTED]"
3724        );
3725        assert_eq!(
3726            obj.get("secret_token").unwrap().as_str().unwrap(),
3727            "[REDACTED]"
3728        );
3729        assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "test");
3730    }
3731
3732    // =========================================================================
3733    // pluck_deep tests
3734    // =========================================================================
3735
3736    #[test]
3737    fn test_pluck_deep_basic() {
3738        let runtime = setup_runtime();
3739        let data = json!({"users": [{"id": 1}, {"id": 2}], "meta": {"id": 99}});
3740        let expr = runtime.compile(r#"pluck_deep(@, `"id"`)"#).unwrap();
3741        let result = expr.search(&data).unwrap();
3742        let arr = result.as_array().unwrap();
3743        assert_eq!(arr.len(), 3);
3744    }
3745
3746    #[test]
3747    fn test_pluck_deep_nested() {
3748        let runtime = setup_runtime();
3749        let data = json!({"a": {"b": {"c": 1}}, "d": {"c": 2}});
3750        let expr = runtime.compile(r#"pluck_deep(@, `"c"`)"#).unwrap();
3751        let result = expr.search(&data).unwrap();
3752        let arr = result.as_array().unwrap();
3753        assert_eq!(arr.len(), 2);
3754    }
3755
3756    #[test]
3757    fn test_pluck_deep_not_found() {
3758        let runtime = setup_runtime();
3759        let data = json!({"a": 1});
3760        let expr = runtime.compile(r#"pluck_deep(@, `"x"`)"#).unwrap();
3761        let result = expr.search(&data).unwrap();
3762        let arr = result.as_array().unwrap();
3763        assert_eq!(arr.len(), 0);
3764    }
3765
3766    // =========================================================================
3767    // paths_to tests
3768    // =========================================================================
3769
3770    #[test]
3771    fn test_paths_to_basic() {
3772        let runtime = setup_runtime();
3773        let data = json!({"a": {"id": 1}, "b": {"id": 2}});
3774        let expr = runtime.compile(r#"paths_to(@, `"id"`)"#).unwrap();
3775        let result = expr.search(&data).unwrap();
3776        let arr = result.as_array().unwrap();
3777        assert_eq!(arr.len(), 2);
3778        let paths: Vec<String> = arr
3779            .iter()
3780            .map(|p| p.as_str().unwrap().to_string())
3781            .collect();
3782        assert!(paths.contains(&"a.id".to_string()));
3783        assert!(paths.contains(&"b.id".to_string()));
3784    }
3785
3786    #[test]
3787    fn test_paths_to_array() {
3788        let runtime = setup_runtime();
3789        let data = json!({"users": [{"id": 1}]});
3790        let expr = runtime.compile(r#"paths_to(@, `"id"`)"#).unwrap();
3791        let result = expr.search(&data).unwrap();
3792        let arr = result.as_array().unwrap();
3793        assert_eq!(arr.len(), 1);
3794        assert_eq!(arr[0].as_str().unwrap(), "users.0.id");
3795    }
3796
3797    // =========================================================================
3798    // snake_keys tests
3799    // =========================================================================
3800
3801    #[test]
3802    fn test_snake_keys_camel() {
3803        let runtime = setup_runtime();
3804        let data = json!({"userName": "alice"});
3805        let expr = runtime.compile("snake_keys(@)").unwrap();
3806        let result = expr.search(&data).unwrap();
3807        let obj = result.as_object().unwrap();
3808        assert!(obj.contains_key("user_name"));
3809        assert_eq!(obj.get("user_name").unwrap().as_str().unwrap(), "alice");
3810    }
3811
3812    #[test]
3813    fn test_snake_keys_nested() {
3814        let runtime = setup_runtime();
3815        let data = json!({"userInfo": {"firstName": "bob"}});
3816        let expr = runtime.compile("snake_keys(@)").unwrap();
3817        let result = expr.search(&data).unwrap();
3818        let obj = result.as_object().unwrap();
3819        assert!(obj.contains_key("user_info"));
3820        let nested = obj.get("user_info").unwrap().as_object().unwrap();
3821        assert!(nested.contains_key("first_name"));
3822    }
3823
3824    // =========================================================================
3825    // camel_keys tests
3826    // =========================================================================
3827
3828    #[test]
3829    fn test_camel_keys_snake() {
3830        let runtime = setup_runtime();
3831        let data = json!({"user_name": "alice"});
3832        let expr = runtime.compile("camel_keys(@)").unwrap();
3833        let result = expr.search(&data).unwrap();
3834        let obj = result.as_object().unwrap();
3835        assert!(obj.contains_key("userName"));
3836        assert_eq!(obj.get("userName").unwrap().as_str().unwrap(), "alice");
3837    }
3838
3839    #[test]
3840    fn test_camel_keys_nested() {
3841        let runtime = setup_runtime();
3842        let data = json!({"user_info": {"first_name": "bob"}});
3843        let expr = runtime.compile("camel_keys(@)").unwrap();
3844        let result = expr.search(&data).unwrap();
3845        let obj = result.as_object().unwrap();
3846        assert!(obj.contains_key("userInfo"));
3847        let nested = obj.get("userInfo").unwrap().as_object().unwrap();
3848        assert!(nested.contains_key("firstName"));
3849    }
3850
3851    // =========================================================================
3852    // kebab_keys tests
3853    // =========================================================================
3854
3855    #[test]
3856    fn test_kebab_keys_camel() {
3857        let runtime = setup_runtime();
3858        let data = json!({"userName": "alice"});
3859        let expr = runtime.compile("kebab_keys(@)").unwrap();
3860        let result = expr.search(&data).unwrap();
3861        let obj = result.as_object().unwrap();
3862        assert!(obj.contains_key("user-name"));
3863        assert_eq!(obj.get("user-name").unwrap().as_str().unwrap(), "alice");
3864    }
3865
3866    #[test]
3867    fn test_kebab_keys_snake() {
3868        let runtime = setup_runtime();
3869        let data = json!({"user_name": "bob"});
3870        let expr = runtime.compile("kebab_keys(@)").unwrap();
3871        let result = expr.search(&data).unwrap();
3872        let obj = result.as_object().unwrap();
3873        assert!(obj.contains_key("user-name"));
3874        assert_eq!(obj.get("user-name").unwrap().as_str().unwrap(), "bob");
3875    }
3876
3877    // =========================================================================
3878    // structural_diff tests
3879    // =========================================================================
3880
3881    #[test]
3882    fn test_structural_diff_added() {
3883        let runtime = setup_runtime();
3884        let data = json!({"a": {"x": 1}, "b": {"x": 1, "y": 2}});
3885        let expr = runtime.compile("structural_diff(a, b)").unwrap();
3886        let result = expr.search(&data).unwrap();
3887        let obj = result.as_object().unwrap();
3888        let added = obj.get("added").unwrap().as_array().unwrap();
3889        assert_eq!(added.len(), 1);
3890        assert_eq!(added[0].as_str().unwrap(), "y");
3891    }
3892
3893    #[test]
3894    fn test_structural_diff_removed() {
3895        let runtime = setup_runtime();
3896        let data = json!({"a": {"x": 1, "y": 2}, "b": {"x": 1}});
3897        let expr = runtime.compile("structural_diff(a, b)").unwrap();
3898        let result = expr.search(&data).unwrap();
3899        let obj = result.as_object().unwrap();
3900        let removed = obj.get("removed").unwrap().as_array().unwrap();
3901        assert_eq!(removed.len(), 1);
3902        assert_eq!(removed[0].as_str().unwrap(), "y");
3903    }
3904
3905    #[test]
3906    fn test_structural_diff_type_changed() {
3907        let runtime = setup_runtime();
3908        let data = json!({"a": {"x": 1}, "b": {"x": "string"}});
3909        let expr = runtime.compile("structural_diff(a, b)").unwrap();
3910        let result = expr.search(&data).unwrap();
3911        let obj = result.as_object().unwrap();
3912        let type_changed = obj.get("type_changed").unwrap().as_array().unwrap();
3913        assert_eq!(type_changed.len(), 1);
3914    }
3915
3916    #[test]
3917    fn test_has_same_shape_true() {
3918        let runtime = setup_runtime();
3919        let data = json!({"a": {"x": 1}, "b": {"x": 2}});
3920        let expr = runtime.compile("has_same_shape(a, b)").unwrap();
3921        let result = expr.search(&data).unwrap();
3922        assert!(result.as_bool().unwrap());
3923    }
3924
3925    #[test]
3926    fn test_has_same_shape_false() {
3927        let runtime = setup_runtime();
3928        let data = json!({"a": {"x": 1}, "b": {"y": 2}});
3929        let expr = runtime.compile("has_same_shape(a, b)").unwrap();
3930        let result = expr.search(&data).unwrap();
3931        assert!(!result.as_bool().unwrap());
3932    }
3933
3934    // =========================================================================
3935    // infer_schema tests
3936    // =========================================================================
3937
3938    #[test]
3939    fn test_infer_schema_object() {
3940        let runtime = setup_runtime();
3941        let data = json!({"name": "alice", "age": 30});
3942        let expr = runtime.compile("infer_schema(@)").unwrap();
3943        let result = expr.search(&data).unwrap();
3944        let schema = result.as_object().unwrap();
3945        assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "object");
3946        let props = schema.get("properties").unwrap().as_object().unwrap();
3947        assert!(props.contains_key("name"));
3948        assert!(props.contains_key("age"));
3949    }
3950
3951    #[test]
3952    fn test_infer_schema_array() {
3953        let runtime = setup_runtime();
3954        let data = json!([1, 2, 3]);
3955        let expr = runtime.compile("infer_schema(@)").unwrap();
3956        let result = expr.search(&data).unwrap();
3957        let schema = result.as_object().unwrap();
3958        assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "array");
3959        let items = schema.get("items").unwrap().as_object().unwrap();
3960        assert_eq!(items.get("type").unwrap().as_str().unwrap(), "number");
3961    }
3962
3963    // =========================================================================
3964    // chunk_by_size tests
3965    // =========================================================================
3966
3967    #[test]
3968    fn test_chunk_by_size() {
3969        let runtime = setup_runtime();
3970        let data = json!([1, 2, 3, 4, 5]);
3971        let expr = runtime.compile("chunk_by_size(@, `10`)").unwrap();
3972        let result = expr.search(&data).unwrap();
3973        let chunks = result.as_array().unwrap();
3974        assert!(chunks.len() > 1); // Should be split into multiple chunks
3975    }
3976
3977    // =========================================================================
3978    // paginate tests
3979    // =========================================================================
3980
3981    #[test]
3982    fn test_paginate() {
3983        let runtime = setup_runtime();
3984        let data = json!([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
3985        let expr = runtime.compile("paginate(@, `2`, `3`)").unwrap();
3986        let result = expr.search(&data).unwrap();
3987        let obj = result.as_object().unwrap();
3988
3989        let page_data = obj.get("data").unwrap().as_array().unwrap();
3990        assert_eq!(page_data.len(), 3);
3991        assert_eq!(page_data[0].as_f64().unwrap() as i64, 4); // Page 2 starts at index 3
3992
3993        assert_eq!(obj.get("page").unwrap().as_f64().unwrap() as i64, 2);
3994        assert_eq!(obj.get("total").unwrap().as_f64().unwrap() as i64, 10);
3995        assert_eq!(obj.get("total_pages").unwrap().as_f64().unwrap() as i64, 4);
3996        assert!(obj.get("has_next").unwrap().as_bool().unwrap());
3997        assert!(obj.get("has_prev").unwrap().as_bool().unwrap());
3998    }
3999
4000    // =========================================================================
4001    // estimate_size tests
4002    // =========================================================================
4003
4004    #[test]
4005    fn test_estimate_size() {
4006        let runtime = setup_runtime();
4007        let data = json!({"hello": "world"});
4008        let expr = runtime.compile("estimate_size(@)").unwrap();
4009        let result = expr.search(&data).unwrap();
4010        let size = result.as_f64().unwrap() as i64;
4011        assert!(size > 0);
4012    }
4013
4014    // =========================================================================
4015    // truncate_to_size tests
4016    // =========================================================================
4017
4018    #[test]
4019    fn test_truncate_to_size_array() {
4020        let runtime = setup_runtime();
4021        let data = json!([1, 2, 3, 4, 5]);
4022        let expr = runtime.compile("truncate_to_size(@, `5`)").unwrap();
4023        let result = expr.search(&data).unwrap();
4024        let arr = result.as_array().unwrap();
4025        assert!(arr.len() < 5); // Should be truncated
4026    }
4027
4028    // =========================================================================
4029    // template tests
4030    // =========================================================================
4031
4032    #[test]
4033    fn test_template_basic() {
4034        let runtime = setup_runtime();
4035        let data = json!({"name": "alice", "age": 30});
4036        let expr = runtime
4037            .compile(r#"template(@, `"Hello {{name}}, you are {{age}} years old"`)"#)
4038            .unwrap();
4039        let result = expr.search(&data).unwrap();
4040        assert_eq!(
4041            result.as_str().unwrap(),
4042            "Hello alice, you are 30 years old"
4043        );
4044    }
4045
4046    #[test]
4047    fn test_template_nested() {
4048        let runtime = setup_runtime();
4049        let data = json!({"user": {"name": "bob"}});
4050        let expr = runtime
4051            .compile(r#"template(@, `"Welcome {{user.name}}!"`)"#)
4052            .unwrap();
4053        let result = expr.search(&data).unwrap();
4054        assert_eq!(result.as_str().unwrap(), "Welcome bob!");
4055    }
4056
4057    #[test]
4058    fn test_template_missing_default() {
4059        let runtime = setup_runtime();
4060        let data = json!({"name": "alice"});
4061        let expr = runtime
4062            .compile(r#"template(@, `"{{name}} - {{title}}"`)"#)
4063            .unwrap();
4064        let result = expr.search(&data).unwrap();
4065        assert_eq!(result.as_str().unwrap(), "alice - ");
4066    }
4067
4068    #[test]
4069    fn test_template_fallback() {
4070        let runtime = setup_runtime();
4071        let data = json!({});
4072        let expr = runtime
4073            .compile(r#"template(@, `"Hello {{name|Guest}}"`)"#)
4074            .unwrap();
4075        let result = expr.search(&data).unwrap();
4076        assert_eq!(result.as_str().unwrap(), "Hello Guest");
4077    }
4078
4079    #[test]
4080    fn test_template_null_template_error() {
4081        let runtime = setup_runtime();
4082        let data = json!({"name": "alice"});
4083        // Simulate what happens when user forgets backticks - the template string
4084        // evaluates as a field reference which returns null
4085        let expr = runtime.compile(r#"template(@, missing_field)"#).unwrap();
4086        let result = expr.search(&data);
4087        assert!(result.is_err());
4088        let err = result.unwrap_err();
4089        let err_msg = err.to_string();
4090        assert!(
4091            err_msg.contains("second argument is null"),
4092            "Error should mention null argument: {}",
4093            err_msg
4094        );
4095        assert!(
4096            err_msg.contains("backticks"),
4097            "Error should mention backticks: {}",
4098            err_msg
4099        );
4100    }
4101}