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