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