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