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}