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