1use 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
16pub 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
225defn!(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
251defn!(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
280defn!(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
328defn!(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
361defn!(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
394defn!(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
425defn!(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
460defn!(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
508defn!(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
563defn!(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
631defn!(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
678defn!(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
695defn!(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
761defn!(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
882defn!(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
901defn!(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
931defn!(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
976defn!(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
1124defn!(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
1187defn!(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
1235defn!(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
1270defn!(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
1335defn!(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
1374defn!(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
1419defn!(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
1458defn!(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
1492defn!(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
1544defn!(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
1654defn!(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
1811defn!(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
1857defn!(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], ®ex))
1875 }
1876}
1877
1878fn redact_keys_recursive(value: &Value, pattern: ®ex::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
1904defn!(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
1936defn!(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
1975defn!(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
2026defn!(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
2039defn!(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
2054defn!(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
2067defn!(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
2082defn!(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
2097defn!(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
2112defn!(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
2148defn!(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
2275defn!(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
2322defn!(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
2381defn!(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
2422defn!(
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
2476defn!(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
2492defn!(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
2536defn!(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
2564defn!(TemplateStrictFn, vec![arg!(any), arg!(string)], None);
2569
2570impl Function for TemplateStrictFn {
2571 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
2572 if args.len() >= 2 && args[1].is_null() {
2573 return Err(custom_error(
2574 ctx,
2575 "template_strict: second argument is null. Template strings must be JMESPath \
2576 literals using backticks, e.g., template_strict(@, `\"Hello {{name}}\"`)",
2577 ));
2578 }
2579
2580 self.signature.validate(args, ctx)?;
2581
2582 let template = args[1]
2583 .as_str()
2584 .ok_or_else(|| custom_error(ctx, "Expected template string"))?;
2585
2586 let result =
2587 expand_template(&args[0], template, true).map_err(|e| custom_error(ctx, &e))?;
2588 Ok(Value::String(result))
2589 }
2590}
2591
2592fn expand_template(data: &Value, template: &str, strict: bool) -> Result<String, String> {
2593 let mut result = String::new();
2594 let mut chars = template.chars().peekable();
2595
2596 while let Some(c) = chars.next() {
2597 if c == '{' && chars.peek() == Some(&'{') {
2598 chars.next();
2599
2600 let mut var_name = String::new();
2601 let mut fallback: Option<String> = None;
2602
2603 while let Some(&next) = chars.peek() {
2604 if next == '}' {
2605 chars.next();
2606 if chars.peek() == Some(&'}') {
2607 chars.next();
2608 break;
2609 }
2610 } else if next == '|' {
2611 chars.next();
2612 let mut fb = String::new();
2613 while let Some(&fc) = chars.peek() {
2614 if fc == '}' {
2615 break;
2616 }
2617 fb.push(chars.next().unwrap());
2618 }
2619 fallback = Some(fb);
2620 } else {
2621 var_name.push(chars.next().unwrap());
2622 }
2623 }
2624
2625 let value = get_template_value(data, &var_name);
2626
2627 match value {
2628 Some(v) => result.push_str(&value_to_string(&v)),
2629 None => {
2630 if strict {
2631 return Err(format!("missing variable '{}'", var_name));
2632 }
2633 if let Some(fb) = fallback {
2634 result.push_str(&fb);
2635 }
2636 }
2637 }
2638 } else if c == '\\' && chars.peek() == Some(&'{') {
2639 result.push(chars.next().unwrap());
2640 } else {
2641 result.push(c);
2642 }
2643 }
2644
2645 Ok(result)
2646}
2647
2648fn get_template_value(data: &Value, path: &str) -> Option<Value> {
2649 let parts: Vec<&str> = path.trim().split('.').collect();
2650 let mut current = data.clone();
2651
2652 for part in parts {
2653 if let Ok(idx) = part.parse::<usize>() {
2654 if let Some(arr) = current.as_array()
2655 && idx < arr.len()
2656 {
2657 current = arr[idx].clone();
2658 continue;
2659 }
2660 return None;
2661 }
2662
2663 if let Some(obj) = current.as_object() {
2664 if let Some(val) = obj.get(part) {
2665 current = val.clone();
2666 } else {
2667 return None;
2668 }
2669 } else {
2670 return None;
2671 }
2672 }
2673
2674 if current.is_null() {
2675 None
2676 } else {
2677 Some(current)
2678 }
2679}
2680
2681fn value_to_string(value: &Value) -> String {
2682 match value {
2683 Value::String(s) => s.clone(),
2684 Value::Number(n) => n.to_string(),
2685 Value::Bool(b) => b.to_string(),
2686 Value::Null => String::new(),
2687 _ => serde_json::to_string(value).unwrap_or_default(),
2688 }
2689}
2690
2691#[cfg(test)]
2692mod tests {
2693 use crate::Runtime;
2694 use serde_json::json;
2695
2696 fn setup_runtime() -> Runtime {
2697 Runtime::builder()
2698 .with_standard()
2699 .with_all_extensions()
2700 .build()
2701 }
2702
2703 #[test]
2704 fn test_items() {
2705 let runtime = setup_runtime();
2706 let expr = runtime.compile("items(@)").unwrap();
2707 let data = json!({"a": 1, "b": 2});
2708 let result = expr.search(&data).unwrap();
2709 let arr = result.as_array().unwrap();
2710 assert_eq!(arr.len(), 2);
2711 let first = arr[0].as_array().unwrap();
2713 assert_eq!(first.len(), 2);
2714 assert_eq!(first[0].as_str().unwrap(), "a");
2715 assert_eq!(first[1].as_f64().unwrap() as i64, 1);
2716 }
2717
2718 #[test]
2719 fn test_items_empty() {
2720 let runtime = setup_runtime();
2721 let expr = runtime.compile("items(@)").unwrap();
2722 let data = json!({});
2723 let result = expr.search(&data).unwrap();
2724 let arr = result.as_array().unwrap();
2725 assert_eq!(arr.len(), 0);
2726 }
2727
2728 #[test]
2729 fn test_from_items() {
2730 let runtime = setup_runtime();
2731 let expr = runtime.compile("from_items(@)").unwrap();
2732 let data = json!([["a", 1], ["b", 2]]);
2733 let result = expr.search(&data).unwrap();
2734 let obj = result.as_object().unwrap();
2735 assert_eq!(obj.len(), 2);
2736 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
2737 assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 2);
2738 }
2739
2740 #[test]
2741 fn test_from_items_empty() {
2742 let runtime = setup_runtime();
2743 let expr = runtime.compile("from_items(@)").unwrap();
2744 let data = json!([]);
2745 let result = expr.search(&data).unwrap();
2746 let obj = result.as_object().unwrap();
2747 assert_eq!(obj.len(), 0);
2748 }
2749
2750 #[test]
2751 fn test_from_items_duplicate_keys() {
2752 let runtime = setup_runtime();
2753 let expr = runtime.compile("from_items(@)").unwrap();
2754 let data = json!([["x", 1], ["x", 2]]);
2755 let result = expr.search(&data).unwrap();
2756 let obj = result.as_object().unwrap();
2757 assert_eq!(obj.len(), 1);
2758 assert_eq!(obj.get("x").unwrap().as_f64().unwrap() as i64, 2);
2760 }
2761
2762 #[test]
2763 fn test_items_from_items_roundtrip() {
2764 let runtime = setup_runtime();
2765 let expr = runtime.compile("from_items(items(@))").unwrap();
2766 let data = json!({"a": 1, "b": "hello", "c": true});
2767 let result = expr.search(&data).unwrap();
2768 let obj = result.as_object().unwrap();
2769 assert_eq!(obj.len(), 3);
2770 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
2771 assert_eq!(obj.get("b").unwrap().as_str().unwrap(), "hello");
2772 assert!(obj.get("c").unwrap().as_bool().unwrap());
2773 }
2774
2775 #[test]
2776 fn test_with_entries_identity() {
2777 let runtime = setup_runtime();
2778 let expr = runtime.compile("with_entries(@, '[@[0], @[1]]')").unwrap();
2779 let data = json!({"a": 1, "b": 2});
2780 let result = expr.search(&data).unwrap();
2781 let obj = result.as_object().unwrap();
2782 assert_eq!(obj.len(), 2);
2783 assert_eq!(obj.get("a").unwrap().as_f64().unwrap() as i64, 1);
2784 assert_eq!(obj.get("b").unwrap().as_f64().unwrap() as i64, 2);
2785 }
2786
2787 #[test]
2788 fn test_with_entries_transform_keys() {
2789 let runtime = setup_runtime();
2792 let expr = runtime
2793 .compile(r#"with_entries(@, '[join(`""`, [`"prefix_"`, @[0]]), @[1]]')"#)
2794 .unwrap();
2795 let data = json!({"a": 1, "b": 2});
2796 let result = expr.search(&data).unwrap();
2797 let obj = result.as_object().unwrap();
2798 assert_eq!(obj.len(), 2);
2799 assert!(obj.contains_key("prefix_a"));
2800 assert!(obj.contains_key("prefix_b"));
2801 }
2802
2803 #[test]
2804 fn test_with_entries_swap_key_value() {
2805 let runtime = setup_runtime();
2807 let expr = runtime.compile("with_entries(@, '[@[1], @[0]]')").unwrap();
2808 let data = json!({"a": "x", "b": "y"});
2809 let result = expr.search(&data).unwrap();
2810 let obj = result.as_object().unwrap();
2811 assert_eq!(obj.len(), 2);
2812 assert_eq!(obj.get("x").unwrap().as_str().unwrap(), "a");
2813 assert_eq!(obj.get("y").unwrap().as_str().unwrap(), "b");
2814 }
2815
2816 #[test]
2817 fn test_with_entries_filter_null() {
2818 let runtime = setup_runtime();
2821 let expr = runtime.compile(r#"with_entries(@, '`null`')"#).unwrap();
2823 let data = json!({"a": 1, "b": 2});
2824 let result = expr.search(&data).unwrap();
2825 let obj = result.as_object().unwrap();
2826 assert_eq!(obj.len(), 0);
2827 }
2828
2829 #[test]
2830 fn test_with_entries_empty() {
2831 let runtime = setup_runtime();
2832 let expr = runtime.compile("with_entries(@, '[@[0], @[1]]')").unwrap();
2833 let data = json!({});
2834 let result = expr.search(&data).unwrap();
2835 let obj = result.as_object().unwrap();
2836 assert_eq!(obj.len(), 0);
2837 }
2838
2839 #[test]
2840 fn test_pick() {
2841 let runtime = setup_runtime();
2842 let expr = runtime.compile("pick(@, `[\"a\"]`)").unwrap();
2843 let data = json!({"a": 1, "b": 2});
2844 let result = expr.search(&data).unwrap();
2845 let result_obj = result.as_object().unwrap();
2846 assert_eq!(result_obj.len(), 1);
2847 assert!(result_obj.contains_key("a"));
2848 }
2849
2850 #[test]
2851 fn test_deep_equals_objects() {
2852 let runtime = setup_runtime();
2853 let data = json!({"a": {"b": 1}, "c": {"b": 1}});
2854 let expr = runtime.compile("deep_equals(a, c)").unwrap();
2855 let result = expr.search(&data).unwrap();
2856 assert!(result.as_bool().unwrap());
2857 }
2858
2859 #[test]
2860 fn test_deep_equals_objects_different() {
2861 let runtime = setup_runtime();
2862 let data = json!({"a": {"b": 1}, "c": {"b": 2}});
2863 let expr = runtime.compile("deep_equals(a, c)").unwrap();
2864 let result = expr.search(&data).unwrap();
2865 assert!(!result.as_bool().unwrap());
2866 }
2867
2868 #[test]
2869 fn test_deep_equals_arrays() {
2870 let runtime = setup_runtime();
2871 let data = json!({"a": [1, [2, 3]], "b": [1, [2, 3]]});
2872 let expr = runtime.compile("deep_equals(a, b)").unwrap();
2873 let result = expr.search(&data).unwrap();
2874 assert!(result.as_bool().unwrap());
2875 }
2876
2877 #[test]
2878 fn test_deep_equals_arrays_order_matters() {
2879 let runtime = setup_runtime();
2880 let data = json!({"a": [1, 2], "b": [2, 1]});
2881 let expr = runtime.compile("deep_equals(a, b)").unwrap();
2882 let result = expr.search(&data).unwrap();
2883 assert!(!result.as_bool().unwrap());
2884 }
2885
2886 #[test]
2887 fn test_deep_equals_primitives() {
2888 let runtime = setup_runtime();
2889 let data = json!({"a": "hello", "b": "hello", "c": "world"});
2890
2891 let expr = runtime.compile("deep_equals(a, b)").unwrap();
2892 let result = expr.search(&data).unwrap();
2893 assert!(result.as_bool().unwrap());
2894
2895 let expr = runtime.compile("deep_equals(a, c)").unwrap();
2896 let result = expr.search(&data).unwrap();
2897 assert!(!result.as_bool().unwrap());
2898 }
2899
2900 #[test]
2901 fn test_deep_diff_added() {
2902 let runtime = setup_runtime();
2903 let data = json!({"a": {"x": 1}, "b": {"x": 1, "y": 2}});
2904 let expr = runtime.compile("deep_diff(a, b)").unwrap();
2905 let result = expr.search(&data).unwrap();
2906 let diff = result.as_object().unwrap();
2907
2908 let added = diff.get("added").unwrap().as_object().unwrap();
2909 assert!(added.contains_key("y"));
2910 assert!(diff.get("removed").unwrap().as_object().unwrap().is_empty());
2911 assert!(diff.get("changed").unwrap().as_object().unwrap().is_empty());
2912 }
2913
2914 #[test]
2915 fn test_deep_diff_removed() {
2916 let runtime = setup_runtime();
2917 let data = json!({"a": {"x": 1, "y": 2}, "b": {"x": 1}});
2918 let expr = runtime.compile("deep_diff(a, b)").unwrap();
2919 let result = expr.search(&data).unwrap();
2920 let diff = result.as_object().unwrap();
2921
2922 let removed = diff.get("removed").unwrap().as_object().unwrap();
2923 assert!(removed.contains_key("y"));
2924 assert!(diff.get("added").unwrap().as_object().unwrap().is_empty());
2925 assert!(diff.get("changed").unwrap().as_object().unwrap().is_empty());
2926 }
2927
2928 #[test]
2929 fn test_deep_diff_changed() {
2930 let runtime = setup_runtime();
2931 let data = json!({"a": {"x": 1}, "b": {"x": 2}});
2932 let expr = runtime.compile("deep_diff(a, b)").unwrap();
2933 let result = expr.search(&data).unwrap();
2934 let diff = result.as_object().unwrap();
2935
2936 let changed = diff.get("changed").unwrap().as_object().unwrap();
2937 assert!(changed.contains_key("x"));
2938 let x_change = changed.get("x").unwrap().as_object().unwrap();
2939 assert!(x_change.contains_key("from"));
2940 assert!(x_change.contains_key("to"));
2941 }
2942
2943 #[test]
2944 fn test_deep_diff_nested() {
2945 let runtime = setup_runtime();
2946 let data = json!({"a": {"x": {"y": 1}}, "b": {"x": {"y": 2}}});
2947 let expr = runtime.compile("deep_diff(a, b)").unwrap();
2948 let result = expr.search(&data).unwrap();
2949 let diff = result.as_object().unwrap();
2950
2951 let changed = diff.get("changed").unwrap().as_object().unwrap();
2953 assert!(changed.contains_key("x"));
2954 }
2955
2956 #[test]
2957 fn test_deep_diff_no_changes() {
2958 let runtime = setup_runtime();
2959 let data = json!({"a": {"x": 1}, "b": {"x": 1}});
2960 let expr = runtime.compile("deep_diff(a, b)").unwrap();
2961 let result = expr.search(&data).unwrap();
2962 let diff = result.as_object().unwrap();
2963
2964 assert!(diff.get("added").unwrap().as_object().unwrap().is_empty());
2965 assert!(diff.get("removed").unwrap().as_object().unwrap().is_empty());
2966 assert!(diff.get("changed").unwrap().as_object().unwrap().is_empty());
2967 }
2968
2969 #[test]
2970 fn test_get_nested() {
2971 let runtime = setup_runtime();
2972 let data = json!({"a": {"b": {"c": 1}}});
2973 let expr = runtime.compile("get(@, 'a.b.c')").unwrap();
2974 let result = expr.search(&data).unwrap();
2975 assert_eq!(result.as_f64().unwrap(), 1.0);
2976 }
2977
2978 #[test]
2979 fn test_get_with_default() {
2980 let runtime = setup_runtime();
2981 let data = json!({"a": 1});
2982 let expr = runtime.compile("get(@, 'b.c', 'default')").unwrap();
2983 let result = expr.search(&data).unwrap();
2984 assert_eq!(result.as_str().unwrap(), "default");
2985 }
2986
2987 #[test]
2988 fn test_get_array_index() {
2989 let runtime = setup_runtime();
2990 let data = json!({"a": [{"b": 1}, {"b": 2}]});
2991 let expr = runtime.compile("get(@, 'a[0].b')").unwrap();
2992 let result = expr.search(&data).unwrap();
2993 assert_eq!(result.as_f64().unwrap(), 1.0);
2994 }
2995
2996 #[test]
2997 fn test_get_missing_returns_null() {
2998 let runtime = setup_runtime();
2999 let data = json!({"a": 1});
3000 let expr = runtime.compile("get(@, 'x.y.z')").unwrap();
3001 let result = expr.search(&data).unwrap();
3002 assert!(result.is_null());
3003 }
3004
3005 #[test]
3006 fn test_has_exists() {
3007 let runtime = setup_runtime();
3008 let data = json!({"a": {"b": 1}});
3009 let expr = runtime.compile("has(@, 'a.b')").unwrap();
3010 let result = expr.search(&data).unwrap();
3011 assert!(result.as_bool().unwrap());
3012 }
3013
3014 #[test]
3015 fn test_has_not_exists() {
3016 let runtime = setup_runtime();
3017 let data = json!({"a": 1});
3018 let expr = runtime.compile("has(@, 'a.b.c')").unwrap();
3019 let result = expr.search(&data).unwrap();
3020 assert!(!result.as_bool().unwrap());
3021 }
3022
3023 #[test]
3024 fn test_has_array_index() {
3025 let runtime = setup_runtime();
3026 let data = json!({"a": [1, 2, 3]});
3027 let expr = runtime.compile("has(@, 'a[1]')").unwrap();
3028 let result = expr.search(&data).unwrap();
3029 assert!(result.as_bool().unwrap());
3030 }
3031
3032 #[test]
3033 fn test_has_array_index_out_of_bounds() {
3034 let runtime = setup_runtime();
3035 let data = json!({"a": [1, 2]});
3036 let expr = runtime.compile("has(@, 'a[5]')").unwrap();
3037 let result = expr.search(&data).unwrap();
3038 assert!(!result.as_bool().unwrap());
3039 }
3040
3041 #[test]
3042 fn test_defaults_shallow() {
3043 let runtime = setup_runtime();
3044 let data = json!({"obj": {"a": 1}, "defs": {"a": 2, "b": 3}});
3045 let expr = runtime.compile("defaults(obj, defs)").unwrap();
3046 let result = expr.search(&data).unwrap();
3047 let obj = result.as_object().unwrap();
3048 assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0); assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 3.0); }
3051
3052 #[test]
3053 fn test_defaults_empty_object() {
3054 let runtime = setup_runtime();
3055 let data = json!({"obj": {}, "defs": {"a": 1, "b": 2}});
3056 let expr = runtime.compile("defaults(obj, defs)").unwrap();
3057 let result = expr.search(&data).unwrap();
3058 let obj = result.as_object().unwrap();
3059 assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0);
3060 assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 2.0);
3061 }
3062
3063 #[test]
3064 fn test_defaults_deep_nested() {
3065 let runtime = setup_runtime();
3066 let data = json!({"obj": {"a": {"b": 1}}, "defs": {"a": {"b": 2, "c": 3}}});
3067 let expr = runtime.compile("defaults_deep(obj, defs)").unwrap();
3068 let result = expr.search(&data).unwrap();
3069 let obj = result.as_object().unwrap();
3070 let a = obj.get("a").unwrap().as_object().unwrap();
3071 assert_eq!(a.get("b").unwrap().as_f64().unwrap(), 1.0); assert_eq!(a.get("c").unwrap().as_f64().unwrap(), 3.0); }
3074
3075 #[test]
3076 fn test_defaults_deep_new_nested() {
3077 let runtime = setup_runtime();
3078 let data = json!({"obj": {"x": 1}, "defs": {"x": 2, "y": {"z": 3}}});
3079 let expr = runtime.compile("defaults_deep(obj, defs)").unwrap();
3080 let result = expr.search(&data).unwrap();
3081 let obj = result.as_object().unwrap();
3082 assert_eq!(obj.get("x").unwrap().as_f64().unwrap(), 1.0); let y = obj.get("y").unwrap().as_object().unwrap();
3084 assert_eq!(y.get("z").unwrap().as_f64().unwrap(), 3.0); }
3086
3087 #[test]
3088 fn test_set_path_basic() {
3089 let runtime = setup_runtime();
3090 let data = json!({"a": 1, "b": 2});
3091 let expr = runtime.compile("set_path(@, '/c', `3`)").unwrap();
3092 let result = expr.search(&data).unwrap();
3093 let obj = result.as_object().unwrap();
3094 assert_eq!(obj.get("a").unwrap().as_f64().unwrap(), 1.0);
3095 assert_eq!(obj.get("b").unwrap().as_f64().unwrap(), 2.0);
3096 assert_eq!(obj.get("c").unwrap().as_f64().unwrap(), 3.0);
3097 }
3098
3099 #[test]
3100 fn test_set_path_nested() {
3101 let runtime = setup_runtime();
3102 let data = json!({"a": {"b": 1}});
3103 let expr = runtime.compile("set_path(@, '/a/c', `2`)").unwrap();
3104 let result = expr.search(&data).unwrap();
3105 let obj = result.as_object().unwrap();
3106 let a = obj.get("a").unwrap().as_object().unwrap();
3107 assert_eq!(a.get("b").unwrap().as_f64().unwrap(), 1.0);
3108 assert_eq!(a.get("c").unwrap().as_f64().unwrap(), 2.0);
3109 }
3110
3111 #[test]
3112 fn test_set_path_create_nested() {
3113 let runtime = setup_runtime();
3114 let data = json!({});
3115 let expr = runtime
3116 .compile("set_path(@, '/a/b/c', `\"deep\"`)")
3117 .unwrap();
3118 let result = expr.search(&data).unwrap();
3119 let obj = result.as_object().unwrap();
3120 let a = obj.get("a").unwrap().as_object().unwrap();
3121 let b = a.get("b").unwrap().as_object().unwrap();
3122 assert_eq!(b.get("c").unwrap().as_str().unwrap(), "deep");
3123 }
3124
3125 #[test]
3126 fn test_set_path_array_index() {
3127 let runtime = setup_runtime();
3128 let data = json!({"items": [1, 2, 3]});
3129 let expr = runtime.compile("set_path(@, '/items/1', `99`)").unwrap();
3130 let result = expr.search(&data).unwrap();
3131 let obj = result.as_object().unwrap();
3132 let items = obj.get("items").unwrap().as_array().unwrap();
3133 assert_eq!(items[0].as_f64().unwrap(), 1.0);
3134 assert_eq!(items[1].as_f64().unwrap(), 99.0);
3135 assert_eq!(items[2].as_f64().unwrap(), 3.0);
3136 }
3137
3138 #[test]
3139 fn test_delete_path_basic() {
3140 let runtime = setup_runtime();
3141 let data = json!({"a": 1, "b": 2, "c": 3});
3142 let expr = runtime.compile("delete_path(@, '/b')").unwrap();
3143 let result = expr.search(&data).unwrap();
3144 let obj = result.as_object().unwrap();
3145 assert_eq!(obj.len(), 2);
3146 assert!(obj.contains_key("a"));
3147 assert!(obj.contains_key("c"));
3148 assert!(!obj.contains_key("b"));
3149 }
3150
3151 #[test]
3152 fn test_delete_path_nested() {
3153 let runtime = setup_runtime();
3154 let data = json!({"a": {"b": 1, "c": 2}});
3155 let expr = runtime.compile("delete_path(@, '/a/b')").unwrap();
3156 let result = expr.search(&data).unwrap();
3157 let obj = result.as_object().unwrap();
3158 let a = obj.get("a").unwrap().as_object().unwrap();
3159 assert_eq!(a.len(), 1);
3160 assert!(a.contains_key("c"));
3161 assert!(!a.contains_key("b"));
3162 }
3163
3164 #[test]
3165 fn test_delete_path_array() {
3166 let runtime = setup_runtime();
3167 let data = json!({"items": [1, 2, 3]});
3168 let expr = runtime.compile("delete_path(@, '/items/1')").unwrap();
3169 let result = expr.search(&data).unwrap();
3170 let obj = result.as_object().unwrap();
3171 let items = obj.get("items").unwrap().as_array().unwrap();
3172 assert_eq!(items.len(), 2);
3173 assert_eq!(items[0].as_f64().unwrap(), 1.0);
3174 assert_eq!(items[1].as_f64().unwrap(), 3.0);
3175 }
3176
3177 #[test]
3178 fn test_paths_basic() {
3179 let runtime = setup_runtime();
3180 let data = json!({"a": {"b": 1}, "c": 2});
3181 let expr = runtime.compile("paths(@)").unwrap();
3182 let result = expr.search(&data).unwrap();
3183 let paths = result.as_array().unwrap();
3184 assert!(paths.len() >= 3); }
3186
3187 #[test]
3188 fn test_paths_with_array() {
3189 let runtime = setup_runtime();
3190 let data = json!({"items": [1, 2]});
3191 let expr = runtime.compile("paths(@)").unwrap();
3192 let result = expr.search(&data).unwrap();
3193 let paths: Vec<String> = result
3194 .as_array()
3195 .unwrap()
3196 .iter()
3197 .map(|p| p.as_str().unwrap().to_string())
3198 .collect();
3199 assert!(paths.contains(&"/items".to_string()));
3200 assert!(paths.contains(&"/items/0".to_string()));
3201 assert!(paths.contains(&"/items/1".to_string()));
3202 }
3203
3204 #[test]
3205 fn test_leaves_basic() {
3206 let runtime = setup_runtime();
3207 let data = json!({"a": 1, "b": {"c": 2}, "d": [3, 4]});
3208 let expr = runtime.compile("leaves(@)").unwrap();
3209 let result = expr.search(&data).unwrap();
3210 let leaves = result.as_array().unwrap();
3211 assert_eq!(leaves.len(), 4); }
3213
3214 #[test]
3215 fn test_leaves_strings() {
3216 let runtime = setup_runtime();
3217 let data = json!({"name": "alice", "tags": ["a", "b"]});
3218 let expr = runtime.compile("leaves(@)").unwrap();
3219 let result = expr.search(&data).unwrap();
3220 let leaves = result.as_array().unwrap();
3221 assert_eq!(leaves.len(), 3); }
3223
3224 #[test]
3225 fn test_leaves_with_paths_basic() {
3226 let runtime = setup_runtime();
3227 let data = json!({"a": 1, "b": {"c": 2}});
3228 let expr = runtime.compile("leaves_with_paths(@)").unwrap();
3229 let result = expr.search(&data).unwrap();
3230 let leaves = result.as_array().unwrap();
3231 assert_eq!(leaves.len(), 2);
3232 let first = leaves[0].as_object().unwrap();
3234 assert!(first.contains_key("path"));
3235 assert!(first.contains_key("value"));
3236 }
3237
3238 #[test]
3239 fn test_set_path_immutable() {
3240 let runtime = setup_runtime();
3241 let data = json!({"a": 1});
3242 let expr = runtime.compile("set_path(@, '/b', `2`)").unwrap();
3243 let result = expr.search(&data).unwrap();
3244 let original = data.as_object().unwrap();
3246 assert!(!original.contains_key("b"));
3247 let new_obj = result.as_object().unwrap();
3249 assert!(new_obj.contains_key("b"));
3250 }
3251
3252 #[test]
3257 fn test_set_path_dot_notation() {
3258 let runtime = setup_runtime();
3259 let data = json!({"a": {"c": 1}});
3260 let expr = runtime.compile("set_path(@, `\"a.b\"`, `99`)").unwrap();
3261 let result = expr.search(&data).unwrap();
3262 let obj = result.as_object().unwrap();
3263 let nested = obj.get("a").unwrap().as_object().unwrap();
3264 assert_eq!(nested.get("b").unwrap().as_f64().unwrap() as i64, 99);
3265 assert_eq!(nested.get("c").unwrap().as_f64().unwrap() as i64, 1);
3266 }
3267
3268 #[test]
3269 fn test_set_path_dot_notation_deep() {
3270 let runtime = setup_runtime();
3271 let data = json!({});
3272 let expr = runtime
3273 .compile("set_path(@, `\"a.b.c\"`, `\"deep\"`)")
3274 .unwrap();
3275 let result = expr.search(&data).unwrap();
3276 let obj = result.as_object().unwrap();
3277 let a = obj.get("a").unwrap().as_object().unwrap();
3278 let b = a.get("b").unwrap().as_object().unwrap();
3279 assert_eq!(b.get("c").unwrap().as_str().unwrap(), "deep");
3280 }
3281
3282 #[test]
3283 fn test_set_path_dot_notation_array_index() {
3284 let runtime = setup_runtime();
3285 let data = json!({"items": [1, 2, 3]});
3286 let expr = runtime.compile("set_path(@, `\"items.1\"`, `99`)").unwrap();
3287 let result = expr.search(&data).unwrap();
3288 let obj = result.as_object().unwrap();
3289 let items = obj.get("items").unwrap().as_array().unwrap();
3290 assert_eq!(items[1].as_f64().unwrap() as i64, 99);
3291 }
3292
3293 #[test]
3294 fn test_delete_path_dot_notation() {
3295 let runtime = setup_runtime();
3296 let data = json!({"a": {"b": 1, "c": 2}});
3297 let expr = runtime.compile("delete_path(@, `\"a.b\"`)").unwrap();
3298 let result = expr.search(&data).unwrap();
3299 let obj = result.as_object().unwrap();
3300 let nested = obj.get("a").unwrap().as_object().unwrap();
3301 assert!(!nested.contains_key("b"));
3302 assert!(nested.contains_key("c"));
3303 }
3304
3305 #[test]
3306 fn test_delete_path_dot_notation_array() {
3307 let runtime = setup_runtime();
3308 let data = json!({"items": [1, 2, 3]});
3309 let expr = runtime.compile("delete_path(@, `\"items.1\"`)").unwrap();
3310 let result = expr.search(&data).unwrap();
3311 let obj = result.as_object().unwrap();
3312 let items = obj.get("items").unwrap().as_array().unwrap();
3313 assert_eq!(items.len(), 2);
3314 assert_eq!(items[0].as_f64().unwrap() as i64, 1);
3315 assert_eq!(items[1].as_f64().unwrap() as i64, 3);
3316 }
3317
3318 #[test]
3319 fn test_get_path_alias() {
3320 let runtime = setup_runtime();
3321 let data = json!({"a": {"b": {"c": 42}}});
3322 let expr = runtime.compile("get_path(@, `\"a.b.c\"`)").unwrap();
3323 let result = expr.search(&data).unwrap();
3324 assert_eq!(result.as_f64().unwrap() as i64, 42);
3325 }
3326
3327 #[test]
3328 fn test_get_path_with_default() {
3329 let runtime = setup_runtime();
3330 let data = json!({"a": 1});
3331 let expr = runtime
3332 .compile("get_path(@, `\"a.b.c\"`, `\"default\"`)")
3333 .unwrap();
3334 let result = expr.search(&data).unwrap();
3335 assert_eq!(result.as_str().unwrap(), "default");
3336 }
3337
3338 #[test]
3339 fn test_get_path_array_index() {
3340 let runtime = setup_runtime();
3341 let data = json!({"users": [{"name": "alice"}, {"name": "bob"}]});
3342 let expr = runtime.compile("get_path(@, `\"users.0.name\"`)").unwrap();
3343 let result = expr.search(&data).unwrap();
3344 assert_eq!(result.as_str().unwrap(), "alice");
3345 }
3346
3347 #[test]
3348 fn test_get_path_array_index_out_of_bounds() {
3349 let runtime = setup_runtime();
3350 let data = json!({"users": [{"name": "alice"}]});
3351 let expr = runtime
3352 .compile("get_path(@, `\"users.5.name\"`, `\"unknown\"`)")
3353 .unwrap();
3354 let result = expr.search(&data).unwrap();
3355 assert_eq!(result.as_str().unwrap(), "unknown");
3356 }
3357
3358 #[test]
3359 fn test_has_path_alias() {
3360 let runtime = setup_runtime();
3361 let data = json!({"a": {"b": 1}});
3362 let expr = runtime.compile("has_path(@, `\"a.b\"`)").unwrap();
3363 let result = expr.search(&data).unwrap();
3364 assert!(result.as_bool().unwrap());
3365 }
3366
3367 #[test]
3368 fn test_has_path_missing() {
3369 let runtime = setup_runtime();
3370 let data = json!({"a": {"b": 1}});
3371 let expr = runtime.compile("has_path(@, `\"a.c\"`)").unwrap();
3372 let result = expr.search(&data).unwrap();
3373 assert!(!result.as_bool().unwrap());
3374 }
3375
3376 #[test]
3377 fn test_has_path_array_index() {
3378 let runtime = setup_runtime();
3379 let data = json!({"items": [1, 2, 3]});
3380 let expr = runtime.compile("has_path(@, `\"items.1\"`)").unwrap();
3381 let result = expr.search(&data).unwrap();
3382 assert!(result.as_bool().unwrap());
3383 }
3384
3385 #[test]
3390 fn test_remove_nulls_basic() {
3391 let runtime = setup_runtime();
3392 let data = json!({"a": 1, "b": null, "c": 2});
3393 let expr = runtime.compile("remove_nulls(@)").unwrap();
3394 let result = expr.search(&data).unwrap();
3395 let obj = result.as_object().unwrap();
3396 assert_eq!(obj.len(), 2);
3397 assert!(obj.contains_key("a"));
3398 assert!(obj.contains_key("c"));
3399 assert!(!obj.contains_key("b"));
3400 }
3401
3402 #[test]
3403 fn test_remove_nulls_nested() {
3404 let runtime = setup_runtime();
3405 let data = json!({"a": 1, "b": {"c": null, "d": 2}});
3406 let expr = runtime.compile("remove_nulls(@)").unwrap();
3407 let result = expr.search(&data).unwrap();
3408 let obj = result.as_object().unwrap();
3409 let nested = obj.get("b").unwrap().as_object().unwrap();
3410 assert_eq!(nested.len(), 1);
3411 assert!(nested.contains_key("d"));
3412 assert!(!nested.contains_key("c"));
3413 }
3414
3415 #[test]
3416 fn test_remove_nulls_array() {
3417 let runtime = setup_runtime();
3418 let data = json!([1, null, 2, null, 3]);
3419 let expr = runtime.compile("remove_nulls(@)").unwrap();
3420 let result = expr.search(&data).unwrap();
3421 let arr = result.as_array().unwrap();
3422 assert_eq!(arr.len(), 3);
3423 }
3424
3425 #[test]
3430 fn test_remove_empty_basic() {
3431 let runtime = setup_runtime();
3432 let data = json!({"a": "", "b": [], "c": {}, "d": null, "e": "hello"});
3433 let expr = runtime.compile("remove_empty(@)").unwrap();
3434 let result = expr.search(&data).unwrap();
3435 let obj = result.as_object().unwrap();
3436 assert_eq!(obj.len(), 1);
3437 assert!(obj.contains_key("e"));
3438 }
3439
3440 #[test]
3441 fn test_remove_empty_nested() {
3442 let runtime = setup_runtime();
3443 let data = json!({"a": {"b": "", "c": 1}, "d": []});
3444 let expr = runtime.compile("remove_empty(@)").unwrap();
3445 let result = expr.search(&data).unwrap();
3446 let obj = result.as_object().unwrap();
3447 assert_eq!(obj.len(), 1);
3448 let nested = obj.get("a").unwrap().as_object().unwrap();
3449 assert_eq!(nested.len(), 1);
3450 assert!(nested.contains_key("c"));
3451 }
3452
3453 #[test]
3454 fn test_remove_empty_array() {
3455 let runtime = setup_runtime();
3456 let data = json!(["", "hello", [], null, "world"]);
3457 let expr = runtime.compile("remove_empty(@)").unwrap();
3458 let result = expr.search(&data).unwrap();
3459 let arr = result.as_array().unwrap();
3460 assert_eq!(arr.len(), 2);
3461 }
3462
3463 #[test]
3468 fn test_remove_empty_strings_basic() {
3469 let runtime = setup_runtime();
3470 let data = json!({"name": "alice", "bio": "", "age": 30});
3471 let expr = runtime.compile("remove_empty_strings(@)").unwrap();
3472 let result = expr.search(&data).unwrap();
3473 let obj = result.as_object().unwrap();
3474 assert_eq!(obj.len(), 2);
3475 assert!(obj.contains_key("name"));
3476 assert!(obj.contains_key("age"));
3477 assert!(!obj.contains_key("bio"));
3478 }
3479
3480 #[test]
3481 fn test_remove_empty_strings_array() {
3482 let runtime = setup_runtime();
3483 let data = json!(["hello", "", "world", ""]);
3484 let expr = runtime.compile("remove_empty_strings(@)").unwrap();
3485 let result = expr.search(&data).unwrap();
3486 let arr = result.as_array().unwrap();
3487 assert_eq!(arr.len(), 2);
3488 }
3489
3490 #[test]
3495 fn test_compact_deep_basic() {
3496 let runtime = setup_runtime();
3497 let data = json!([[1, null], [null, 2]]);
3498 let expr = runtime.compile("compact_deep(@)").unwrap();
3499 let result = expr.search(&data).unwrap();
3500 let arr = result.as_array().unwrap();
3501 assert_eq!(arr.len(), 2);
3502 let first = arr[0].as_array().unwrap();
3503 assert_eq!(first.len(), 1);
3504 let second = arr[1].as_array().unwrap();
3505 assert_eq!(second.len(), 1);
3506 }
3507
3508 #[test]
3509 fn test_compact_deep_nested() {
3510 let runtime = setup_runtime();
3511 let data = json!([[1, null], [null, [2, null, 3]]]);
3512 let expr = runtime.compile("compact_deep(@)").unwrap();
3513 let result = expr.search(&data).unwrap();
3514 let arr = result.as_array().unwrap();
3515 let second = arr[1].as_array().unwrap();
3516 let inner = second[0].as_array().unwrap();
3517 assert_eq!(inner.len(), 2); }
3519
3520 #[test]
3525 fn test_completeness_all_filled() {
3526 let runtime = setup_runtime();
3527 let data = json!({"a": 1, "b": "hello", "c": true});
3528 let expr = runtime.compile("completeness(@)").unwrap();
3529 let result = expr.search(&data).unwrap();
3530 let score = result.as_f64().unwrap();
3531 assert_eq!(score, 100.0);
3532 }
3533
3534 #[test]
3535 fn test_completeness_with_nulls() {
3536 let runtime = setup_runtime();
3537 let data = json!({"a": 1, "b": null, "c": null});
3538 let expr = runtime.compile("completeness(@)").unwrap();
3539 let result = expr.search(&data).unwrap();
3540 let score = result.as_f64().unwrap();
3541 assert!((score - 33.33).abs() < 1.0);
3543 }
3544
3545 #[test]
3546 fn test_completeness_nested() {
3547 let runtime = setup_runtime();
3548 let data = json!({"a": 1, "b": {"c": null, "d": 2}, "e": null});
3549 let expr = runtime.compile("completeness(@)").unwrap();
3550 let result = expr.search(&data).unwrap();
3551 let score = result.as_f64().unwrap();
3552 assert!((score - 60.0).abs() < 1.0);
3556 }
3557
3558 #[test]
3563 fn test_type_consistency_consistent() {
3564 let runtime = setup_runtime();
3565 let data = json!([1, 2, 3]);
3566 let expr = runtime.compile("type_consistency(@)").unwrap();
3567 let result = expr.search(&data).unwrap();
3568 let obj = result.as_object().unwrap();
3569 assert!(obj.get("consistent").unwrap().as_bool().unwrap());
3570 }
3571
3572 #[test]
3573 fn test_type_consistency_inconsistent() {
3574 let runtime = setup_runtime();
3575 let data = json!([1, "two", 3]);
3576 let expr = runtime.compile("type_consistency(@)").unwrap();
3577 let result = expr.search(&data).unwrap();
3578 let obj = result.as_object().unwrap();
3579 assert!(!obj.get("consistent").unwrap().as_bool().unwrap());
3580 }
3581
3582 #[test]
3583 fn test_type_consistency_object_array() {
3584 let runtime = setup_runtime();
3585 let data = json!([{"name": "alice", "age": 30}, {"name": "bob", "age": "unknown"}]);
3586 let expr = runtime.compile("type_consistency(@)").unwrap();
3587 let result = expr.search(&data).unwrap();
3588 let obj = result.as_object().unwrap();
3589 assert!(!obj.get("consistent").unwrap().as_bool().unwrap());
3590 let inconsistencies = obj.get("inconsistencies").unwrap().as_array().unwrap();
3591 assert_eq!(inconsistencies.len(), 1);
3592 }
3593
3594 #[test]
3599 fn test_data_quality_score_perfect() {
3600 let runtime = setup_runtime();
3601 let data = json!({"a": 1, "b": "hello"});
3602 let expr = runtime.compile("data_quality_score(@)").unwrap();
3603 let result = expr.search(&data).unwrap();
3604 let obj = result.as_object().unwrap();
3605 let score = obj.get("score").unwrap().as_f64().unwrap();
3606 assert_eq!(score, 100.0);
3607 assert_eq!(obj.get("null_count").unwrap().as_f64().unwrap() as i64, 0);
3608 }
3609
3610 #[test]
3611 fn test_data_quality_score_with_issues() {
3612 let runtime = setup_runtime();
3613 let data = json!({"a": 1, "b": null, "c": ""});
3614 let expr = runtime.compile("data_quality_score(@)").unwrap();
3615 let result = expr.search(&data).unwrap();
3616 let obj = result.as_object().unwrap();
3617 assert_eq!(obj.get("null_count").unwrap().as_f64().unwrap() as i64, 1);
3618 assert_eq!(
3619 obj.get("empty_string_count").unwrap().as_f64().unwrap() as i64,
3620 1
3621 );
3622 let issues = obj.get("issues").unwrap().as_array().unwrap();
3623 assert_eq!(issues.len(), 2);
3624 }
3625
3626 #[test]
3627 fn test_data_quality_score_type_mismatch() {
3628 let runtime = setup_runtime();
3629 let data = json!({"users": [{"age": 30}, {"age": "thirty"}]});
3630 let expr = runtime.compile("data_quality_score(@)").unwrap();
3631 let result = expr.search(&data).unwrap();
3632 let obj = result.as_object().unwrap();
3633 assert_eq!(
3634 obj.get("type_inconsistencies").unwrap().as_f64().unwrap() as i64,
3635 1
3636 );
3637 }
3638
3639 #[test]
3644 fn test_redact_basic() {
3645 let runtime = setup_runtime();
3646 let data = json!({"name": "alice", "password": "secret123", "ssn": "123-45-6789"});
3647 let expr = runtime
3648 .compile(r#"redact(@, `["password", "ssn"]`)"#)
3649 .unwrap();
3650 let result = expr.search(&data).unwrap();
3651 let obj = result.as_object().unwrap();
3652 assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "alice");
3653 assert_eq!(obj.get("password").unwrap().as_str().unwrap(), "[REDACTED]");
3654 assert_eq!(obj.get("ssn").unwrap().as_str().unwrap(), "[REDACTED]");
3655 }
3656
3657 #[test]
3658 fn test_redact_nested() {
3659 let runtime = setup_runtime();
3660 let data = json!({"user": {"name": "bob", "password": "secret"}});
3661 let expr = runtime.compile(r#"redact(@, `["password"]`)"#).unwrap();
3662 let result = expr.search(&data).unwrap();
3663 let obj = result.as_object().unwrap();
3664 let user = obj.get("user").unwrap().as_object().unwrap();
3665 assert_eq!(user.get("name").unwrap().as_str().unwrap(), "bob");
3666 assert_eq!(
3667 user.get("password").unwrap().as_str().unwrap(),
3668 "[REDACTED]"
3669 );
3670 }
3671
3672 #[test]
3673 fn test_redact_array_of_objects() {
3674 let runtime = setup_runtime();
3675 let data = json!([
3676 {"name": "alice", "token": "abc"},
3677 {"name": "bob", "token": "xyz"}
3678 ]);
3679 let expr = runtime.compile(r#"redact(@, `["token"]`)"#).unwrap();
3680 let result = expr.search(&data).unwrap();
3681 let arr = result.as_array().unwrap();
3682 let first = arr[0].as_object().unwrap();
3683 assert_eq!(first.get("token").unwrap().as_str().unwrap(), "[REDACTED]");
3684 }
3685
3686 #[test]
3691 fn test_mask_default() {
3692 let runtime = setup_runtime();
3693 let data = json!("4111111111111111");
3694 let expr = runtime.compile("mask(@)").unwrap();
3695 let result = expr.search(&data).unwrap();
3696 assert_eq!(result.as_str().unwrap(), "************1111");
3697 }
3698
3699 #[test]
3700 fn test_mask_custom_length() {
3701 let runtime = setup_runtime();
3702 let data = json!("555-123-4567");
3703 let expr = runtime.compile("mask(@, `3`)").unwrap();
3704 let result = expr.search(&data).unwrap();
3705 assert_eq!(result.as_str().unwrap(), "*********567");
3706 }
3707
3708 #[test]
3709 fn test_mask_short_string() {
3710 let runtime = setup_runtime();
3711 let data = json!("abc");
3712 let expr = runtime.compile("mask(@)").unwrap();
3713 let result = expr.search(&data).unwrap();
3714 assert_eq!(result.as_str().unwrap(), "***");
3716 }
3717
3718 #[test]
3723 fn test_redact_keys_basic() {
3724 let runtime = setup_runtime();
3725 let data = json!({"password": "secret", "api_key": "abc123", "name": "test"});
3726 let expr = runtime
3727 .compile(r#"redact_keys(@, `"password|api_key"`)"#)
3728 .unwrap();
3729 let result = expr.search(&data).unwrap();
3730 let obj = result.as_object().unwrap();
3731 assert_eq!(obj.get("password").unwrap().as_str().unwrap(), "[REDACTED]");
3732 assert_eq!(obj.get("api_key").unwrap().as_str().unwrap(), "[REDACTED]");
3733 assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "test");
3734 }
3735
3736 #[test]
3737 fn test_redact_keys_pattern() {
3738 let runtime = setup_runtime();
3739 let data = json!({"secret_key": "a", "secret_token": "b", "name": "test"});
3740 let expr = runtime.compile(r#"redact_keys(@, `"secret.*"`)"#).unwrap();
3741 let result = expr.search(&data).unwrap();
3742 let obj = result.as_object().unwrap();
3743 assert_eq!(
3744 obj.get("secret_key").unwrap().as_str().unwrap(),
3745 "[REDACTED]"
3746 );
3747 assert_eq!(
3748 obj.get("secret_token").unwrap().as_str().unwrap(),
3749 "[REDACTED]"
3750 );
3751 assert_eq!(obj.get("name").unwrap().as_str().unwrap(), "test");
3752 }
3753
3754 #[test]
3759 fn test_pluck_deep_basic() {
3760 let runtime = setup_runtime();
3761 let data = json!({"users": [{"id": 1}, {"id": 2}], "meta": {"id": 99}});
3762 let expr = runtime.compile(r#"pluck_deep(@, `"id"`)"#).unwrap();
3763 let result = expr.search(&data).unwrap();
3764 let arr = result.as_array().unwrap();
3765 assert_eq!(arr.len(), 3);
3766 }
3767
3768 #[test]
3769 fn test_pluck_deep_nested() {
3770 let runtime = setup_runtime();
3771 let data = json!({"a": {"b": {"c": 1}}, "d": {"c": 2}});
3772 let expr = runtime.compile(r#"pluck_deep(@, `"c"`)"#).unwrap();
3773 let result = expr.search(&data).unwrap();
3774 let arr = result.as_array().unwrap();
3775 assert_eq!(arr.len(), 2);
3776 }
3777
3778 #[test]
3779 fn test_pluck_deep_not_found() {
3780 let runtime = setup_runtime();
3781 let data = json!({"a": 1});
3782 let expr = runtime.compile(r#"pluck_deep(@, `"x"`)"#).unwrap();
3783 let result = expr.search(&data).unwrap();
3784 let arr = result.as_array().unwrap();
3785 assert_eq!(arr.len(), 0);
3786 }
3787
3788 #[test]
3793 fn test_paths_to_basic() {
3794 let runtime = setup_runtime();
3795 let data = json!({"a": {"id": 1}, "b": {"id": 2}});
3796 let expr = runtime.compile(r#"paths_to(@, `"id"`)"#).unwrap();
3797 let result = expr.search(&data).unwrap();
3798 let arr = result.as_array().unwrap();
3799 assert_eq!(arr.len(), 2);
3800 let paths: Vec<String> = arr
3801 .iter()
3802 .map(|p| p.as_str().unwrap().to_string())
3803 .collect();
3804 assert!(paths.contains(&"a.id".to_string()));
3805 assert!(paths.contains(&"b.id".to_string()));
3806 }
3807
3808 #[test]
3809 fn test_paths_to_array() {
3810 let runtime = setup_runtime();
3811 let data = json!({"users": [{"id": 1}]});
3812 let expr = runtime.compile(r#"paths_to(@, `"id"`)"#).unwrap();
3813 let result = expr.search(&data).unwrap();
3814 let arr = result.as_array().unwrap();
3815 assert_eq!(arr.len(), 1);
3816 assert_eq!(arr[0].as_str().unwrap(), "users.0.id");
3817 }
3818
3819 #[test]
3824 fn test_snake_keys_camel() {
3825 let runtime = setup_runtime();
3826 let data = json!({"userName": "alice"});
3827 let expr = runtime.compile("snake_keys(@)").unwrap();
3828 let result = expr.search(&data).unwrap();
3829 let obj = result.as_object().unwrap();
3830 assert!(obj.contains_key("user_name"));
3831 assert_eq!(obj.get("user_name").unwrap().as_str().unwrap(), "alice");
3832 }
3833
3834 #[test]
3835 fn test_snake_keys_nested() {
3836 let runtime = setup_runtime();
3837 let data = json!({"userInfo": {"firstName": "bob"}});
3838 let expr = runtime.compile("snake_keys(@)").unwrap();
3839 let result = expr.search(&data).unwrap();
3840 let obj = result.as_object().unwrap();
3841 assert!(obj.contains_key("user_info"));
3842 let nested = obj.get("user_info").unwrap().as_object().unwrap();
3843 assert!(nested.contains_key("first_name"));
3844 }
3845
3846 #[test]
3851 fn test_camel_keys_snake() {
3852 let runtime = setup_runtime();
3853 let data = json!({"user_name": "alice"});
3854 let expr = runtime.compile("camel_keys(@)").unwrap();
3855 let result = expr.search(&data).unwrap();
3856 let obj = result.as_object().unwrap();
3857 assert!(obj.contains_key("userName"));
3858 assert_eq!(obj.get("userName").unwrap().as_str().unwrap(), "alice");
3859 }
3860
3861 #[test]
3862 fn test_camel_keys_nested() {
3863 let runtime = setup_runtime();
3864 let data = json!({"user_info": {"first_name": "bob"}});
3865 let expr = runtime.compile("camel_keys(@)").unwrap();
3866 let result = expr.search(&data).unwrap();
3867 let obj = result.as_object().unwrap();
3868 assert!(obj.contains_key("userInfo"));
3869 let nested = obj.get("userInfo").unwrap().as_object().unwrap();
3870 assert!(nested.contains_key("firstName"));
3871 }
3872
3873 #[test]
3878 fn test_kebab_keys_camel() {
3879 let runtime = setup_runtime();
3880 let data = json!({"userName": "alice"});
3881 let expr = runtime.compile("kebab_keys(@)").unwrap();
3882 let result = expr.search(&data).unwrap();
3883 let obj = result.as_object().unwrap();
3884 assert!(obj.contains_key("user-name"));
3885 assert_eq!(obj.get("user-name").unwrap().as_str().unwrap(), "alice");
3886 }
3887
3888 #[test]
3889 fn test_kebab_keys_snake() {
3890 let runtime = setup_runtime();
3891 let data = json!({"user_name": "bob"});
3892 let expr = runtime.compile("kebab_keys(@)").unwrap();
3893 let result = expr.search(&data).unwrap();
3894 let obj = result.as_object().unwrap();
3895 assert!(obj.contains_key("user-name"));
3896 assert_eq!(obj.get("user-name").unwrap().as_str().unwrap(), "bob");
3897 }
3898
3899 #[test]
3904 fn test_structural_diff_added() {
3905 let runtime = setup_runtime();
3906 let data = json!({"a": {"x": 1}, "b": {"x": 1, "y": 2}});
3907 let expr = runtime.compile("structural_diff(a, b)").unwrap();
3908 let result = expr.search(&data).unwrap();
3909 let obj = result.as_object().unwrap();
3910 let added = obj.get("added").unwrap().as_array().unwrap();
3911 assert_eq!(added.len(), 1);
3912 assert_eq!(added[0].as_str().unwrap(), "y");
3913 }
3914
3915 #[test]
3916 fn test_structural_diff_removed() {
3917 let runtime = setup_runtime();
3918 let data = json!({"a": {"x": 1, "y": 2}, "b": {"x": 1}});
3919 let expr = runtime.compile("structural_diff(a, b)").unwrap();
3920 let result = expr.search(&data).unwrap();
3921 let obj = result.as_object().unwrap();
3922 let removed = obj.get("removed").unwrap().as_array().unwrap();
3923 assert_eq!(removed.len(), 1);
3924 assert_eq!(removed[0].as_str().unwrap(), "y");
3925 }
3926
3927 #[test]
3928 fn test_structural_diff_type_changed() {
3929 let runtime = setup_runtime();
3930 let data = json!({"a": {"x": 1}, "b": {"x": "string"}});
3931 let expr = runtime.compile("structural_diff(a, b)").unwrap();
3932 let result = expr.search(&data).unwrap();
3933 let obj = result.as_object().unwrap();
3934 let type_changed = obj.get("type_changed").unwrap().as_array().unwrap();
3935 assert_eq!(type_changed.len(), 1);
3936 }
3937
3938 #[test]
3939 fn test_has_same_shape_true() {
3940 let runtime = setup_runtime();
3941 let data = json!({"a": {"x": 1}, "b": {"x": 2}});
3942 let expr = runtime.compile("has_same_shape(a, b)").unwrap();
3943 let result = expr.search(&data).unwrap();
3944 assert!(result.as_bool().unwrap());
3945 }
3946
3947 #[test]
3948 fn test_has_same_shape_false() {
3949 let runtime = setup_runtime();
3950 let data = json!({"a": {"x": 1}, "b": {"y": 2}});
3951 let expr = runtime.compile("has_same_shape(a, b)").unwrap();
3952 let result = expr.search(&data).unwrap();
3953 assert!(!result.as_bool().unwrap());
3954 }
3955
3956 #[test]
3961 fn test_infer_schema_object() {
3962 let runtime = setup_runtime();
3963 let data = json!({"name": "alice", "age": 30});
3964 let expr = runtime.compile("infer_schema(@)").unwrap();
3965 let result = expr.search(&data).unwrap();
3966 let schema = result.as_object().unwrap();
3967 assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "object");
3968 let props = schema.get("properties").unwrap().as_object().unwrap();
3969 assert!(props.contains_key("name"));
3970 assert!(props.contains_key("age"));
3971 }
3972
3973 #[test]
3974 fn test_infer_schema_array() {
3975 let runtime = setup_runtime();
3976 let data = json!([1, 2, 3]);
3977 let expr = runtime.compile("infer_schema(@)").unwrap();
3978 let result = expr.search(&data).unwrap();
3979 let schema = result.as_object().unwrap();
3980 assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "array");
3981 let items = schema.get("items").unwrap().as_object().unwrap();
3982 assert_eq!(items.get("type").unwrap().as_str().unwrap(), "number");
3983 }
3984
3985 #[test]
3990 fn test_chunk_by_size() {
3991 let runtime = setup_runtime();
3992 let data = json!([1, 2, 3, 4, 5]);
3993 let expr = runtime.compile("chunk_by_size(@, `10`)").unwrap();
3994 let result = expr.search(&data).unwrap();
3995 let chunks = result.as_array().unwrap();
3996 assert!(chunks.len() > 1); }
3998
3999 #[test]
4004 fn test_paginate() {
4005 let runtime = setup_runtime();
4006 let data = json!([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4007 let expr = runtime.compile("paginate(@, `2`, `3`)").unwrap();
4008 let result = expr.search(&data).unwrap();
4009 let obj = result.as_object().unwrap();
4010
4011 let page_data = obj.get("data").unwrap().as_array().unwrap();
4012 assert_eq!(page_data.len(), 3);
4013 assert_eq!(page_data[0].as_f64().unwrap() as i64, 4); assert_eq!(obj.get("page").unwrap().as_f64().unwrap() as i64, 2);
4016 assert_eq!(obj.get("total").unwrap().as_f64().unwrap() as i64, 10);
4017 assert_eq!(obj.get("total_pages").unwrap().as_f64().unwrap() as i64, 4);
4018 assert!(obj.get("has_next").unwrap().as_bool().unwrap());
4019 assert!(obj.get("has_prev").unwrap().as_bool().unwrap());
4020 }
4021
4022 #[test]
4027 fn test_estimate_size() {
4028 let runtime = setup_runtime();
4029 let data = json!({"hello": "world"});
4030 let expr = runtime.compile("estimate_size(@)").unwrap();
4031 let result = expr.search(&data).unwrap();
4032 let size = result.as_f64().unwrap() as i64;
4033 assert!(size > 0);
4034 }
4035
4036 #[test]
4041 fn test_truncate_to_size_array() {
4042 let runtime = setup_runtime();
4043 let data = json!([1, 2, 3, 4, 5]);
4044 let expr = runtime.compile("truncate_to_size(@, `5`)").unwrap();
4045 let result = expr.search(&data).unwrap();
4046 let arr = result.as_array().unwrap();
4047 assert!(arr.len() < 5); }
4049
4050 #[test]
4055 fn test_template_basic() {
4056 let runtime = setup_runtime();
4057 let data = json!({"name": "alice", "age": 30});
4058 let expr = runtime
4059 .compile(r#"template(@, `"Hello {{name}}, you are {{age}} years old"`)"#)
4060 .unwrap();
4061 let result = expr.search(&data).unwrap();
4062 assert_eq!(
4063 result.as_str().unwrap(),
4064 "Hello alice, you are 30 years old"
4065 );
4066 }
4067
4068 #[test]
4069 fn test_template_nested() {
4070 let runtime = setup_runtime();
4071 let data = json!({"user": {"name": "bob"}});
4072 let expr = runtime
4073 .compile(r#"template(@, `"Welcome {{user.name}}!"`)"#)
4074 .unwrap();
4075 let result = expr.search(&data).unwrap();
4076 assert_eq!(result.as_str().unwrap(), "Welcome bob!");
4077 }
4078
4079 #[test]
4080 fn test_template_missing_default() {
4081 let runtime = setup_runtime();
4082 let data = json!({"name": "alice"});
4083 let expr = runtime
4084 .compile(r#"template(@, `"{{name}} - {{title}}"`)"#)
4085 .unwrap();
4086 let result = expr.search(&data).unwrap();
4087 assert_eq!(result.as_str().unwrap(), "alice - ");
4088 }
4089
4090 #[test]
4091 fn test_template_fallback() {
4092 let runtime = setup_runtime();
4093 let data = json!({});
4094 let expr = runtime
4095 .compile(r#"template(@, `"Hello {{name|Guest}}"`)"#)
4096 .unwrap();
4097 let result = expr.search(&data).unwrap();
4098 assert_eq!(result.as_str().unwrap(), "Hello Guest");
4099 }
4100
4101 #[test]
4102 fn test_template_null_template_error() {
4103 let runtime = setup_runtime();
4104 let data = json!({"name": "alice"});
4105 let expr = runtime.compile(r#"template(@, missing_field)"#).unwrap();
4108 let result = expr.search(&data);
4109 assert!(result.is_err());
4110 let err = result.unwrap_err();
4111 let err_msg = err.to_string();
4112 assert!(
4113 err_msg.contains("second argument is null"),
4114 "Error should mention null argument: {}",
4115 err_msg
4116 );
4117 assert!(
4118 err_msg.contains("backticks"),
4119 "Error should mention backticks: {}",
4120 err_msg
4121 );
4122 }
4123}