1use crate::expression::{AggregateOp, Expression, RankMethod, Transform};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq, Default)]
8pub enum Value {
9 #[default]
11 Null,
12 Bool(bool),
14 Number(f64),
16 String(String),
18 Array(Vec<Value>),
20 Object(HashMap<String, Value>),
22}
23
24impl Value {
25 #[must_use]
27 pub const fn null() -> Self {
28 Self::Null
29 }
30
31 #[must_use]
33 pub const fn bool(v: bool) -> Self {
34 Self::Bool(v)
35 }
36
37 #[must_use]
39 pub const fn number(v: f64) -> Self {
40 Self::Number(v)
41 }
42
43 #[must_use]
45 pub fn string(v: impl Into<String>) -> Self {
46 Self::String(v.into())
47 }
48
49 #[must_use]
51 pub const fn array(v: Vec<Self>) -> Self {
52 Self::Array(v)
53 }
54
55 #[must_use]
57 pub const fn object(v: HashMap<String, Self>) -> Self {
58 Self::Object(v)
59 }
60
61 #[must_use]
63 pub const fn is_null(&self) -> bool {
64 matches!(self, Self::Null)
65 }
66
67 #[must_use]
69 pub const fn is_bool(&self) -> bool {
70 matches!(self, Self::Bool(_))
71 }
72
73 #[must_use]
75 pub const fn is_number(&self) -> bool {
76 matches!(self, Self::Number(_))
77 }
78
79 #[must_use]
81 pub const fn is_string(&self) -> bool {
82 matches!(self, Self::String(_))
83 }
84
85 #[must_use]
87 pub const fn is_array(&self) -> bool {
88 matches!(self, Self::Array(_))
89 }
90
91 #[must_use]
93 pub const fn is_object(&self) -> bool {
94 matches!(self, Self::Object(_))
95 }
96
97 #[must_use]
99 pub const fn as_bool(&self) -> Option<bool> {
100 match self {
101 Self::Bool(v) => Some(*v),
102 _ => None,
103 }
104 }
105
106 #[must_use]
108 pub const fn as_number(&self) -> Option<f64> {
109 match self {
110 Self::Number(v) => Some(*v),
111 _ => None,
112 }
113 }
114
115 #[must_use]
117 pub fn as_str(&self) -> Option<&str> {
118 match self {
119 Self::String(v) => Some(v),
120 _ => None,
121 }
122 }
123
124 #[must_use]
126 pub const fn as_array(&self) -> Option<&Vec<Self>> {
127 match self {
128 Self::Array(v) => Some(v),
129 _ => None,
130 }
131 }
132
133 #[must_use]
135 pub fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
136 match self {
137 Self::Array(v) => Some(v),
138 _ => None,
139 }
140 }
141
142 #[must_use]
144 pub const fn as_object(&self) -> Option<&HashMap<String, Self>> {
145 match self {
146 Self::Object(v) => Some(v),
147 _ => None,
148 }
149 }
150
151 #[must_use]
153 pub fn get(&self, key: &str) -> Option<&Self> {
154 match self {
155 Self::Object(map) => map.get(key),
156 _ => None,
157 }
158 }
159
160 #[must_use]
162 pub fn len(&self) -> usize {
163 match self {
164 Self::Array(arr) => arr.len(),
165 Self::Object(obj) => obj.len(),
166 Self::String(s) => s.len(),
167 _ => 0,
168 }
169 }
170
171 #[must_use]
173 pub fn is_empty(&self) -> bool {
174 self.len() == 0
175 }
176}
177
178impl From<bool> for Value {
179 fn from(v: bool) -> Self {
180 Self::Bool(v)
181 }
182}
183
184impl From<f64> for Value {
185 fn from(v: f64) -> Self {
186 Self::Number(v)
187 }
188}
189
190impl From<i32> for Value {
191 fn from(v: i32) -> Self {
192 Self::Number(f64::from(v))
193 }
194}
195
196impl From<i64> for Value {
197 fn from(v: i64) -> Self {
198 Self::Number(v as f64)
199 }
200}
201
202impl From<&str> for Value {
203 fn from(v: &str) -> Self {
204 Self::String(v.to_string())
205 }
206}
207
208impl From<String> for Value {
209 fn from(v: String) -> Self {
210 Self::String(v)
211 }
212}
213
214impl<T: Into<Self>> From<Vec<T>> for Value {
215 fn from(v: Vec<T>) -> Self {
216 Self::Array(v.into_iter().map(Into::into).collect())
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum ExecutionError {
223 SourceNotFound(String),
225 ExpectedArray,
227 ExpectedObject,
229 FieldNotFound(String),
231 TypeMismatch(String),
233 InvalidTransform(String),
235}
236
237impl std::fmt::Display for ExecutionError {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 match self {
240 Self::SourceNotFound(name) => write!(f, "source not found: {name}"),
241 Self::ExpectedArray => write!(f, "expected an array"),
242 Self::ExpectedObject => write!(f, "expected an object"),
243 Self::FieldNotFound(name) => write!(f, "field not found: {name}"),
244 Self::TypeMismatch(msg) => write!(f, "type mismatch: {msg}"),
245 Self::InvalidTransform(msg) => write!(f, "invalid transform: {msg}"),
246 }
247 }
248}
249
250impl std::error::Error for ExecutionError {}
251
252#[derive(Debug, Clone, Default)]
254pub struct DataContext {
255 sources: HashMap<String, Value>,
257}
258
259impl DataContext {
260 #[must_use]
262 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn insert(&mut self, name: impl Into<String>, value: Value) {
268 self.sources.insert(name.into(), value);
269 }
270
271 #[must_use]
273 pub fn get(&self, name: &str) -> Option<&Value> {
274 let parts: Vec<&str> = name.split('.').collect();
276 let mut current = self.sources.get(parts[0])?;
277
278 for part in &parts[1..] {
279 current = current.get(part)?;
280 }
281
282 Some(current)
283 }
284
285 #[must_use]
287 pub fn contains(&self, name: &str) -> bool {
288 self.get(name).is_some()
289 }
290}
291
292#[derive(Debug, Default)]
294pub struct ExpressionExecutor;
295
296impl ExpressionExecutor {
297 #[must_use]
299 pub const fn new() -> Self {
300 Self
301 }
302
303 pub fn execute(&self, expr: &Expression, ctx: &DataContext) -> Result<Value, ExecutionError> {
309 let mut value = ctx
311 .get(&expr.source)
312 .cloned()
313 .ok_or_else(|| ExecutionError::SourceNotFound(expr.source.clone()))?;
314
315 for transform in &expr.transforms {
317 value = self.apply_transform(&value, transform, ctx)?;
318 }
319
320 Ok(value)
321 }
322
323 fn apply_transform(
324 &self,
325 value: &Value,
326 transform: &Transform,
327 ctx: &DataContext,
328 ) -> Result<Value, ExecutionError> {
329 match transform {
330 Transform::Filter {
331 field,
332 value: match_value,
333 } => self.apply_filter(value, field, match_value),
334 Transform::Select { fields } => self.apply_select(value, fields),
335 Transform::Sort { field, desc } => self.apply_sort(value, field, *desc),
336 Transform::Count => Ok(self.apply_count(value)),
337 Transform::Sum { field } => self.apply_sum(value, field),
338 Transform::Mean { field } => self.apply_mean(value, field),
339 Transform::Sample { n } => self.apply_sample(value, *n),
340 Transform::Percentage => self.apply_percentage(value),
341 Transform::Rate { window } => self.apply_rate(value, window),
342 Transform::Join { other, on } => self.apply_join(value, other, on, ctx),
343 Transform::GroupBy { field } => self.apply_group_by(value, field),
344 Transform::Distinct { field } => self.apply_distinct(value, field.as_deref()),
345 Transform::Where {
346 field,
347 op,
348 value: match_value,
349 } => self.apply_where(value, field, op, match_value),
350 Transform::Offset { n } => self.apply_offset(value, *n),
351 Transform::Min { field } => self.apply_min(value, field),
352 Transform::Max { field } => self.apply_max(value, field),
353 Transform::First { n } | Transform::Limit { n } => self.apply_limit(value, *n),
354 Transform::Last { n } => self.apply_last(value, *n),
355 Transform::Flatten => self.apply_flatten(value),
356 Transform::Reverse => self.apply_reverse(value),
357 Transform::Map { expr } => self.apply_map(value, expr),
359 Transform::Reduce { initial, expr } => self.apply_reduce(value, initial, expr),
360 Transform::Aggregate { field, op } => self.apply_aggregate(value, field, *op),
361 Transform::Pivot {
362 row_field,
363 col_field,
364 value_field,
365 } => self.apply_pivot(value, row_field, col_field, value_field),
366 Transform::CumulativeSum { field } => self.apply_cumsum(value, field),
367 Transform::Rank { field, method } => self.apply_rank(value, field, *method),
368 Transform::MovingAverage { field, window } => {
369 self.apply_moving_avg(value, field, *window)
370 }
371 Transform::PercentChange { field } => self.apply_pct_change(value, field),
372 Transform::Suggest { prefix, count } => self.apply_suggest(value, prefix, *count),
373 }
374 }
375
376 fn apply_filter(
377 &self,
378 value: &Value,
379 field: &str,
380 match_value: &str,
381 ) -> Result<Value, ExecutionError> {
382 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
383
384 let filtered: Vec<Value> = arr
385 .iter()
386 .filter(|item| {
387 if let Some(obj) = item.as_object() {
388 if let Some(val) = obj.get(field) {
389 return self.value_matches(val, match_value);
390 }
391 }
392 false
393 })
394 .cloned()
395 .collect();
396
397 Ok(Value::Array(filtered))
398 }
399
400 fn value_matches(&self, value: &Value, target: &str) -> bool {
401 match value {
402 Value::String(s) => s == target,
403 Value::Number(n) => {
404 if let Ok(t) = target.parse::<f64>() {
405 (*n - t).abs() < f64::EPSILON
406 } else {
407 false
408 }
409 }
410 Value::Bool(b) => {
411 matches!((b, target), (true, "true") | (false, "false"))
412 }
413 _ => false,
414 }
415 }
416
417 fn apply_select(&self, value: &Value, fields: &[String]) -> Result<Value, ExecutionError> {
418 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
419
420 let selected: Vec<Value> = arr
421 .iter()
422 .map(|item| {
423 if let Some(obj) = item.as_object() {
424 let mut new_obj = HashMap::new();
425 for field in fields {
426 if let Some(val) = obj.get(field) {
427 new_obj.insert(field.clone(), val.clone());
428 }
429 }
430 Value::Object(new_obj)
431 } else {
432 item.clone()
433 }
434 })
435 .collect();
436
437 Ok(Value::Array(selected))
438 }
439
440 fn apply_sort(&self, value: &Value, field: &str, desc: bool) -> Result<Value, ExecutionError> {
441 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
442 let mut sorted = arr.clone();
443
444 sorted.sort_by(|a, b| {
445 let a_val = a.get(field);
446 let b_val = b.get(field);
447
448 let cmp = match (a_val, b_val) {
449 (Some(Value::Number(a)), Some(Value::Number(b))) => {
450 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
451 }
452 (Some(Value::String(a)), Some(Value::String(b))) => a.cmp(b),
453 _ => std::cmp::Ordering::Equal,
454 };
455
456 if desc {
457 cmp.reverse()
458 } else {
459 cmp
460 }
461 });
462
463 Ok(Value::Array(sorted))
464 }
465
466 fn apply_limit(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
467 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
468 Ok(Value::Array(arr.iter().take(n).cloned().collect()))
469 }
470
471 fn apply_count(&self, value: &Value) -> Value {
472 match value {
473 Value::Array(arr) => Value::Number(arr.len() as f64),
474 Value::Object(obj) => Value::Number(obj.len() as f64),
475 Value::String(s) => Value::Number(s.len() as f64),
476 _ => Value::Number(0.0),
477 }
478 }
479
480 fn apply_sum(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
481 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
482
483 let sum: f64 = arr
484 .iter()
485 .filter_map(|item| item.get(field)?.as_number())
486 .sum();
487
488 Ok(Value::Number(sum))
489 }
490
491 fn apply_mean(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
492 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
493
494 let values: Vec<f64> = arr
495 .iter()
496 .filter_map(|item| item.get(field)?.as_number())
497 .collect();
498
499 if values.is_empty() {
500 return Ok(Value::Number(0.0));
501 }
502
503 let sum: f64 = values.iter().sum();
504 let mean = sum / values.len() as f64;
505
506 Ok(Value::Number(mean))
507 }
508
509 fn apply_sample(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
510 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
511
512 Ok(Value::Array(arr.iter().take(n).cloned().collect()))
515 }
516
517 fn apply_percentage(&self, value: &Value) -> Result<Value, ExecutionError> {
518 match value {
519 Value::Number(n) => Ok(Value::Number(n * 100.0)),
520 _ => Err(ExecutionError::TypeMismatch(
521 "percentage requires a number".to_string(),
522 )),
523 }
524 }
525
526 fn apply_rate(&self, value: &Value, window: &str) -> Result<Value, ExecutionError> {
527 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
528
529 let window_ms = self.parse_window(window)?;
531
532 let mut values_with_time: Vec<(f64, f64)> = arr
535 .iter()
536 .filter_map(|item| {
537 let obj = item.as_object()?;
538 let time = obj
539 .get("timestamp")
540 .or_else(|| obj.get("time"))
541 .and_then(Value::as_number)?;
542 let val = obj
543 .get("value")
544 .or_else(|| obj.get("count"))
545 .and_then(Value::as_number)
546 .unwrap_or(1.0);
547 Some((time, val))
548 })
549 .collect();
550
551 if values_with_time.len() < 2 {
552 return Ok(Value::Number(0.0));
553 }
554
555 values_with_time.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
557
558 let window_ms_f64 = window_ms as f64;
560 let last_time = values_with_time.last().map_or(0.0, |v| v.0);
561 let window_start = last_time - window_ms_f64;
562
563 let sum_in_window: f64 = values_with_time
564 .iter()
565 .filter(|(t, _)| *t >= window_start)
566 .map(|(_, v)| v)
567 .sum();
568
569 let rate = sum_in_window / (window_ms_f64 / 1000.0);
571
572 Ok(Value::Number(rate))
573 }
574
575 fn parse_window(&self, window: &str) -> Result<u64, ExecutionError> {
576 let window = window.trim();
577 if window.is_empty() {
578 return Err(ExecutionError::InvalidTransform("empty window".to_string()));
579 }
580
581 let (num_str, unit) = if let Some(s) = window.strip_suffix("ms") {
582 (s, "ms")
583 } else if let Some(s) = window.strip_suffix('s') {
584 (s, "s")
585 } else if let Some(s) = window.strip_suffix('m') {
586 (s, "m")
587 } else if let Some(s) = window.strip_suffix('h') {
588 (s, "h")
589 } else if let Some(s) = window.strip_suffix('d') {
590 (s, "d")
591 } else {
592 (window, "ms")
594 };
595
596 let num: u64 = num_str
597 .parse()
598 .map_err(|_| ExecutionError::InvalidTransform(format!("invalid window: {window}")))?;
599
600 let ms = match unit {
601 "s" => num * 1000,
602 "m" => num * 60 * 1000,
603 "h" => num * 60 * 60 * 1000,
604 "d" => num * 24 * 60 * 60 * 1000,
605 _ => num,
607 };
608
609 Ok(ms)
610 }
611
612 fn apply_join(
613 &self,
614 value: &Value,
615 other: &str,
616 on: &str,
617 ctx: &DataContext,
618 ) -> Result<Value, ExecutionError> {
619 let left_arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
620
621 let right_value = ctx
623 .get(other)
624 .ok_or_else(|| ExecutionError::SourceNotFound(other.to_string()))?;
625 let right_arr = right_value
626 .as_array()
627 .ok_or(ExecutionError::ExpectedArray)?;
628
629 let mut right_lookup: HashMap<String, Vec<&Value>> = HashMap::new();
631 for item in right_arr {
632 if let Some(obj) = item.as_object() {
633 if let Some(key_val) = obj.get(on) {
634 let key = self.value_to_string(key_val);
635 right_lookup.entry(key).or_default().push(item);
636 }
637 }
638 }
639
640 let mut result = Vec::new();
642 for left_item in left_arr {
643 if let Some(left_obj) = left_item.as_object() {
644 if let Some(key_val) = left_obj.get(on) {
645 let key = self.value_to_string(key_val);
646 if let Some(right_items) = right_lookup.get(&key) {
647 for right_item in right_items {
649 if let Some(right_obj) = right_item.as_object() {
650 let mut merged = left_obj.clone();
652 for (k, v) in right_obj {
653 if merged.contains_key(k) && k != on {
655 merged.insert(format!("{other}_{k}"), v.clone());
656 } else if k != on {
657 merged.insert(k.clone(), v.clone());
658 }
659 }
660 result.push(Value::Object(merged));
661 }
662 }
663 } else {
664 result.push(left_item.clone());
666 }
667 } else {
668 result.push(left_item.clone());
670 }
671 } else {
672 result.push(left_item.clone());
674 }
675 }
676
677 Ok(Value::Array(result))
678 }
679
680 fn apply_group_by(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
681 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
682
683 let mut groups: HashMap<String, Vec<Value>> = HashMap::new();
684
685 for item in arr {
686 let key = if let Some(obj) = item.as_object() {
687 if let Some(val) = obj.get(field) {
688 self.value_to_string(val)
689 } else {
690 "_null".to_string()
691 }
692 } else {
693 "_null".to_string()
694 };
695
696 groups.entry(key).or_default().push(item.clone());
697 }
698
699 let result: Vec<Value> = groups
701 .into_iter()
702 .map(|(key, items)| {
703 let mut obj = HashMap::new();
704 obj.insert("key".to_string(), Value::String(key));
705 obj.insert("items".to_string(), Value::Array(items.clone()));
706 obj.insert("count".to_string(), Value::Number(items.len() as f64));
707 Value::Object(obj)
708 })
709 .collect();
710
711 Ok(Value::Array(result))
712 }
713
714 fn value_to_string(&self, value: &Value) -> String {
715 match value {
716 Value::Null => "_null".to_string(),
717 Value::Bool(b) => b.to_string(),
718 Value::Number(n) => n.to_string(),
719 Value::String(s) => s.clone(),
720 Value::Array(_) => "_array".to_string(),
721 Value::Object(_) => "_object".to_string(),
722 }
723 }
724
725 fn apply_distinct(&self, value: &Value, field: Option<&str>) -> Result<Value, ExecutionError> {
726 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
727
728 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
729 let mut result = Vec::new();
730
731 for item in arr {
732 let key = if let Some(f) = field {
733 if let Some(obj) = item.as_object() {
734 obj.get(f)
735 .map(|v| self.value_to_string(v))
736 .unwrap_or_default()
737 } else {
738 self.value_to_string(item)
739 }
740 } else {
741 self.value_to_string(item)
742 };
743
744 if seen.insert(key) {
745 result.push(item.clone());
746 }
747 }
748
749 Ok(Value::Array(result))
750 }
751
752 fn apply_where(
753 &self,
754 value: &Value,
755 field: &str,
756 op: &str,
757 match_value: &str,
758 ) -> Result<Value, ExecutionError> {
759 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
760
761 let filtered: Vec<Value> = arr
762 .iter()
763 .filter(|item| {
764 if let Some(obj) = item.as_object() {
765 if let Some(val) = obj.get(field) {
766 return self.compare_values(val, op, match_value);
767 }
768 }
769 false
770 })
771 .cloned()
772 .collect();
773
774 Ok(Value::Array(filtered))
775 }
776
777 fn compare_values(&self, value: &Value, op: &str, target: &str) -> bool {
778 match op {
779 "eq" | "==" | "=" => self.value_matches(value, target),
780 "ne" | "!=" | "<>" => !self.value_matches(value, target),
781 "gt" | ">" => {
782 if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
783 v > t
784 } else {
785 false
786 }
787 }
788 "lt" | "<" => {
789 if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
790 v < t
791 } else {
792 false
793 }
794 }
795 "gte" | ">=" => {
796 if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
797 v >= t
798 } else {
799 false
800 }
801 }
802 "lte" | "<=" => {
803 if let (Some(v), Ok(t)) = (value.as_number(), target.parse::<f64>()) {
804 v <= t
805 } else {
806 false
807 }
808 }
809 "contains" => {
810 if let Some(s) = value.as_str() {
811 s.contains(target)
812 } else {
813 false
814 }
815 }
816 "starts_with" => {
817 if let Some(s) = value.as_str() {
818 s.starts_with(target)
819 } else {
820 false
821 }
822 }
823 "ends_with" => {
824 if let Some(s) = value.as_str() {
825 s.ends_with(target)
826 } else {
827 false
828 }
829 }
830 _ => false,
831 }
832 }
833
834 fn apply_offset(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
835 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
836 Ok(Value::Array(arr.iter().skip(n).cloned().collect()))
837 }
838
839 fn apply_min(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
840 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
841
842 let min = arr
843 .iter()
844 .filter_map(|item| item.get(field)?.as_number())
845 .fold(f64::INFINITY, f64::min);
846
847 if min.is_infinite() {
848 Ok(Value::Null)
849 } else {
850 Ok(Value::Number(min))
851 }
852 }
853
854 fn apply_max(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
855 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
856
857 let max = arr
858 .iter()
859 .filter_map(|item| item.get(field)?.as_number())
860 .fold(f64::NEG_INFINITY, f64::max);
861
862 if max.is_infinite() {
863 Ok(Value::Null)
864 } else {
865 Ok(Value::Number(max))
866 }
867 }
868
869 fn apply_last(&self, value: &Value, n: usize) -> Result<Value, ExecutionError> {
870 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
871 let len = arr.len();
872 let skip = len.saturating_sub(n);
873 Ok(Value::Array(arr.iter().skip(skip).cloned().collect()))
874 }
875
876 fn apply_flatten(&self, value: &Value) -> Result<Value, ExecutionError> {
877 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
878
879 let mut result = Vec::new();
880 for item in arr {
881 if let Some(inner) = item.as_array() {
882 result.extend(inner.iter().cloned());
883 } else {
884 result.push(item.clone());
885 }
886 }
887
888 Ok(Value::Array(result))
889 }
890
891 fn apply_reverse(&self, value: &Value) -> Result<Value, ExecutionError> {
892 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
893 let mut reversed = arr.clone();
894 reversed.reverse();
895 Ok(Value::Array(reversed))
896 }
897
898 fn apply_map(&self, value: &Value, expr: &str) -> Result<Value, ExecutionError> {
903 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
904
905 let mapped: Vec<Value> = arr
908 .iter()
909 .map(|item| {
910 if let Some(field) = expr.strip_prefix("item.") {
912 if let Some(obj) = item.as_object() {
913 obj.get(field).cloned().unwrap_or(Value::Null)
914 } else {
915 item.clone()
916 }
917 } else {
918 item.clone()
920 }
921 })
922 .collect();
923
924 Ok(Value::Array(mapped))
925 }
926
927 fn apply_reduce(
928 &self,
929 value: &Value,
930 initial: &str,
931 _expr: &str,
932 ) -> Result<Value, ExecutionError> {
933 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
934
935 let mut acc: f64 = initial.parse().unwrap_or(0.0);
937
938 for item in arr {
940 if let Some(n) = item.as_number() {
941 acc += n;
942 }
943 }
944
945 Ok(Value::Number(acc))
946 }
947
948 fn apply_aggregate(
949 &self,
950 value: &Value,
951 field: &str,
952 op: AggregateOp,
953 ) -> Result<Value, ExecutionError> {
954 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
955
956 let values: Vec<f64> = arr
960 .iter()
961 .filter_map(|item| {
962 if let Some(obj) = item.as_object() {
963 if let Some(Value::Array(group_values)) = obj.get("values") {
965 return Some(
966 group_values
967 .iter()
968 .filter_map(|v| v.get(field)?.as_number())
969 .collect::<Vec<_>>(),
970 );
971 }
972 obj.get(field)?.as_number().map(|n| vec![n])
974 } else {
975 None
976 }
977 })
978 .flatten()
979 .collect();
980
981 let result = match op {
982 AggregateOp::Sum => values.iter().sum(),
983 AggregateOp::Count => values.len() as f64,
984 AggregateOp::Mean => {
985 if values.is_empty() {
986 0.0
987 } else {
988 values.iter().sum::<f64>() / values.len() as f64
989 }
990 }
991 AggregateOp::Min => values.iter().cloned().fold(f64::INFINITY, f64::min),
992 AggregateOp::Max => values.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
993 AggregateOp::First => values.first().copied().unwrap_or(0.0),
994 AggregateOp::Last => values.last().copied().unwrap_or(0.0),
995 };
996
997 Ok(Value::Number(result))
998 }
999
1000 fn apply_pivot(
1001 &self,
1002 value: &Value,
1003 row_field: &str,
1004 col_field: &str,
1005 value_field: &str,
1006 ) -> Result<Value, ExecutionError> {
1007 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1008
1009 let mut rows: HashMap<String, HashMap<String, f64>> = HashMap::new();
1011
1012 for item in arr {
1013 if let Some(obj) = item.as_object() {
1014 let row_key = obj
1015 .get(row_field)
1016 .map(|v| self.value_to_string(v))
1017 .unwrap_or_default();
1018 let col_key = obj
1019 .get(col_field)
1020 .map(|v| self.value_to_string(v))
1021 .unwrap_or_default();
1022 let val = obj
1023 .get(value_field)
1024 .and_then(|v| v.as_number())
1025 .unwrap_or(0.0);
1026
1027 rows.entry(row_key)
1028 .or_default()
1029 .entry(col_key)
1030 .and_modify(|v| *v += val)
1031 .or_insert(val);
1032 }
1033 }
1034
1035 let result: Vec<Value> = rows
1037 .into_iter()
1038 .map(|(row_key, cols)| {
1039 let mut obj = HashMap::new();
1040 obj.insert(row_field.to_string(), Value::String(row_key));
1041 for (col_key, val) in cols {
1042 obj.insert(col_key, Value::Number(val));
1043 }
1044 Value::Object(obj)
1045 })
1046 .collect();
1047
1048 Ok(Value::Array(result))
1049 }
1050
1051 fn apply_cumsum(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
1052 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1053
1054 let mut running_sum = 0.0;
1055 let result: Vec<Value> = arr
1056 .iter()
1057 .map(|item| {
1058 if let Some(obj) = item.as_object() {
1059 let val = obj.get(field).and_then(|v| v.as_number()).unwrap_or(0.0);
1060 running_sum += val;
1061
1062 let mut new_obj = obj.clone();
1063 new_obj.insert(format!("{field}_cumsum"), Value::Number(running_sum));
1064 Value::Object(new_obj)
1065 } else {
1066 item.clone()
1067 }
1068 })
1069 .collect();
1070
1071 Ok(Value::Array(result))
1072 }
1073
1074 fn apply_rank(
1075 &self,
1076 value: &Value,
1077 field: &str,
1078 method: RankMethod,
1079 ) -> Result<Value, ExecutionError> {
1080 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1081
1082 let mut indexed: Vec<(usize, f64)> = arr
1084 .iter()
1085 .enumerate()
1086 .filter_map(|(i, item)| item.as_object()?.get(field)?.as_number().map(|n| (i, n)))
1087 .collect();
1088
1089 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1091
1092 let mut ranks = vec![0.0; arr.len()];
1094 match method {
1095 RankMethod::Dense => {
1096 let mut rank = 0;
1097 let mut prev_val: Option<f64> = None;
1098 for (i, val) in indexed {
1099 if prev_val != Some(val) {
1100 rank += 1;
1101 }
1102 ranks[i] = rank as f64;
1103 prev_val = Some(val);
1104 }
1105 }
1106 RankMethod::Ordinal => {
1107 for (rank, (i, _)) in indexed.iter().enumerate() {
1108 ranks[*i] = (rank + 1) as f64;
1109 }
1110 }
1111 RankMethod::Average => {
1112 let mut i = 0;
1113 while i < indexed.len() {
1114 let val = indexed[i].1;
1115 let start = i;
1116 while i < indexed.len() && (indexed[i].1 - val).abs() < f64::EPSILON {
1117 i += 1;
1118 }
1119 let avg_rank =
1120 (start + 1..=i).map(|r| r as f64).sum::<f64>() / (i - start) as f64;
1121 for j in start..i {
1122 ranks[indexed[j].0] = avg_rank;
1123 }
1124 }
1125 }
1126 }
1127
1128 let result: Vec<Value> = arr
1130 .iter()
1131 .enumerate()
1132 .map(|(i, item)| {
1133 if let Some(obj) = item.as_object() {
1134 let mut new_obj = obj.clone();
1135 new_obj.insert(format!("{field}_rank"), Value::Number(ranks[i]));
1136 Value::Object(new_obj)
1137 } else {
1138 item.clone()
1139 }
1140 })
1141 .collect();
1142
1143 Ok(Value::Array(result))
1144 }
1145
1146 fn apply_moving_avg(
1147 &self,
1148 value: &Value,
1149 field: &str,
1150 window: usize,
1151 ) -> Result<Value, ExecutionError> {
1152 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1153
1154 let values: Vec<f64> = arr
1155 .iter()
1156 .filter_map(|item| item.as_object()?.get(field)?.as_number())
1157 .collect();
1158
1159 let result: Vec<Value> = arr
1160 .iter()
1161 .enumerate()
1162 .map(|(i, item)| {
1163 if let Some(obj) = item.as_object() {
1164 let start = i.saturating_sub(window - 1);
1165 let window_values = &values[start..=i.min(values.len() - 1)];
1166 let ma = if window_values.is_empty() {
1167 0.0
1168 } else {
1169 window_values.iter().sum::<f64>() / window_values.len() as f64
1170 };
1171
1172 let mut new_obj = obj.clone();
1173 new_obj.insert(format!("{field}_ma{window}"), Value::Number(ma));
1174 Value::Object(new_obj)
1175 } else {
1176 item.clone()
1177 }
1178 })
1179 .collect();
1180
1181 Ok(Value::Array(result))
1182 }
1183
1184 fn apply_pct_change(&self, value: &Value, field: &str) -> Result<Value, ExecutionError> {
1185 let arr = value.as_array().ok_or(ExecutionError::ExpectedArray)?;
1186
1187 let values: Vec<f64> = arr
1188 .iter()
1189 .filter_map(|item| item.as_object()?.get(field)?.as_number())
1190 .collect();
1191
1192 let result: Vec<Value> = arr
1193 .iter()
1194 .enumerate()
1195 .map(|(i, item)| {
1196 if let Some(obj) = item.as_object() {
1197 let pct = if i == 0 || values.get(i - 1).map_or(true, |&prev| prev == 0.0) {
1198 0.0
1199 } else {
1200 let prev = values[i - 1];
1201 let curr = values.get(i).copied().unwrap_or(prev);
1202 (curr - prev) / prev * 100.0
1203 };
1204
1205 let mut new_obj = obj.clone();
1206 new_obj.insert(format!("{field}_pct_change"), Value::Number(pct));
1207 Value::Object(new_obj)
1208 } else {
1209 item.clone()
1210 }
1211 })
1212 .collect();
1213
1214 Ok(Value::Array(result))
1215 }
1216
1217 #[allow(clippy::unnecessary_wraps)] fn apply_suggest(
1227 &self,
1228 value: &Value,
1229 prefix: &str,
1230 count: usize,
1231 ) -> Result<Value, ExecutionError> {
1232 if let Some(obj) = value.as_object() {
1234 if let Some(suggestions) = obj.get("_suggestions") {
1236 if let Some(arr) = suggestions.as_array() {
1237 return Ok(Value::Array(arr.iter().take(count).cloned().collect()));
1238 }
1239 }
1240
1241 if obj.contains_key("model_type") || obj.contains_key("source") {
1244 return Ok(Value::Array(vec![]));
1246 }
1247 }
1248
1249 if let Some(arr) = value.as_array() {
1251 let filtered: Vec<Value> = arr
1252 .iter()
1253 .filter(|item| {
1254 if let Some(obj) = item.as_object() {
1255 if let Some(text) = obj.get("text").and_then(|v| v.as_str()) {
1256 return text.starts_with(prefix);
1257 }
1258 }
1259 false
1260 })
1261 .take(count)
1262 .cloned()
1263 .collect();
1264 return Ok(Value::Array(filtered));
1265 }
1266
1267 Ok(Value::Array(vec![]))
1269 }
1270}
1271
1272#[cfg(test)]
1273mod tests {
1274 use super::*;
1275 use crate::expression::ExpressionParser;
1276
1277 #[test]
1280 fn test_value_null() {
1281 let v = Value::null();
1282 assert!(v.is_null());
1283 assert!(!v.is_bool());
1284 }
1285
1286 #[test]
1287 fn test_value_bool() {
1288 let v = Value::bool(true);
1289 assert!(v.is_bool());
1290 assert_eq!(v.as_bool(), Some(true));
1291 }
1292
1293 #[test]
1294 fn test_value_number() {
1295 let v = Value::number(42.5);
1296 assert!(v.is_number());
1297 assert_eq!(v.as_number(), Some(42.5));
1298 }
1299
1300 #[test]
1301 fn test_value_string() {
1302 let v = Value::string("hello");
1303 assert!(v.is_string());
1304 assert_eq!(v.as_str(), Some("hello"));
1305 }
1306
1307 #[test]
1308 fn test_value_array() {
1309 let v = Value::array(vec![Value::number(1.0), Value::number(2.0)]);
1310 assert!(v.is_array());
1311 assert_eq!(v.len(), 2);
1312 }
1313
1314 #[test]
1315 fn test_value_object() {
1316 let mut map = HashMap::new();
1317 map.insert("name".to_string(), Value::string("test"));
1318 let v = Value::object(map);
1319 assert!(v.is_object());
1320 assert_eq!(v.get("name").unwrap().as_str(), Some("test"));
1321 }
1322
1323 #[test]
1324 fn test_value_from_bool() {
1325 let v: Value = true.into();
1326 assert_eq!(v, Value::Bool(true));
1327 }
1328
1329 #[test]
1330 fn test_value_from_number() {
1331 let v: Value = 42.0f64.into();
1332 assert_eq!(v, Value::Number(42.0));
1333 }
1334
1335 #[test]
1336 fn test_value_from_i32() {
1337 let v: Value = 42i32.into();
1338 assert_eq!(v, Value::Number(42.0));
1339 }
1340
1341 #[test]
1342 fn test_value_from_str() {
1343 let v: Value = "hello".into();
1344 assert_eq!(v, Value::String("hello".to_string()));
1345 }
1346
1347 #[test]
1348 fn test_value_default() {
1349 assert_eq!(Value::default(), Value::Null);
1350 }
1351
1352 #[test]
1353 fn test_value_is_empty() {
1354 assert!(Value::array(vec![]).is_empty());
1355 assert!(!Value::array(vec![Value::Null]).is_empty());
1356 }
1357
1358 #[test]
1361 fn test_context_new() {
1362 let ctx = DataContext::new();
1363 assert!(!ctx.contains("foo"));
1364 }
1365
1366 #[test]
1367 fn test_context_insert_get() {
1368 let mut ctx = DataContext::new();
1369 ctx.insert("users", Value::array(vec![]));
1370 assert!(ctx.contains("users"));
1371 }
1372
1373 #[test]
1374 fn test_context_dotted_path() {
1375 let mut ctx = DataContext::new();
1376 let mut data = HashMap::new();
1377 data.insert("transactions".to_string(), Value::array(vec![]));
1378 ctx.insert("data", Value::object(data));
1379
1380 assert!(ctx.contains("data.transactions"));
1381 }
1382
1383 #[test]
1386 fn test_error_display() {
1387 assert_eq!(
1388 ExecutionError::SourceNotFound("foo".to_string()).to_string(),
1389 "source not found: foo"
1390 );
1391 assert_eq!(
1392 ExecutionError::ExpectedArray.to_string(),
1393 "expected an array"
1394 );
1395 }
1396
1397 fn make_test_data() -> DataContext {
1400 let mut ctx = DataContext::new();
1401
1402 let transactions: Vec<Value> = vec![
1404 {
1405 let mut t = HashMap::new();
1406 t.insert("id".to_string(), Value::number(1.0));
1407 t.insert("status".to_string(), Value::string("completed"));
1408 t.insert("amount".to_string(), Value::number(100.0));
1409 Value::Object(t)
1410 },
1411 {
1412 let mut t = HashMap::new();
1413 t.insert("id".to_string(), Value::number(2.0));
1414 t.insert("status".to_string(), Value::string("pending"));
1415 t.insert("amount".to_string(), Value::number(50.0));
1416 Value::Object(t)
1417 },
1418 {
1419 let mut t = HashMap::new();
1420 t.insert("id".to_string(), Value::number(3.0));
1421 t.insert("status".to_string(), Value::string("completed"));
1422 t.insert("amount".to_string(), Value::number(75.0));
1423 Value::Object(t)
1424 },
1425 ];
1426
1427 let mut data = HashMap::new();
1428 data.insert("transactions".to_string(), Value::Array(transactions));
1429 ctx.insert("data", Value::object(data));
1430
1431 ctx
1432 }
1433
1434 #[test]
1435 fn test_execute_simple_source() {
1436 let ctx = make_test_data();
1437 let parser = ExpressionParser::new();
1438 let executor = ExpressionExecutor::new();
1439
1440 let expr = parser.parse("data.transactions").unwrap();
1441 let result = executor.execute(&expr, &ctx).unwrap();
1442
1443 assert!(result.is_array());
1444 assert_eq!(result.len(), 3);
1445 }
1446
1447 #[test]
1448 fn test_execute_source_not_found() {
1449 let ctx = DataContext::new();
1450 let parser = ExpressionParser::new();
1451 let executor = ExpressionExecutor::new();
1452
1453 let expr = parser.parse("nonexistent").unwrap();
1454 let result = executor.execute(&expr, &ctx);
1455
1456 assert!(matches!(result, Err(ExecutionError::SourceNotFound(_))));
1457 }
1458
1459 #[test]
1460 fn test_execute_filter() {
1461 let ctx = make_test_data();
1462 let parser = ExpressionParser::new();
1463 let executor = ExpressionExecutor::new();
1464
1465 let expr = parser
1466 .parse("{{ data.transactions | filter(status=completed) }}")
1467 .unwrap();
1468 let result = executor.execute(&expr, &ctx).unwrap();
1469
1470 assert!(result.is_array());
1471 assert_eq!(result.len(), 2);
1472 }
1473
1474 #[test]
1475 fn test_execute_count() {
1476 let ctx = make_test_data();
1477 let parser = ExpressionParser::new();
1478 let executor = ExpressionExecutor::new();
1479
1480 let expr = parser.parse("{{ data.transactions | count }}").unwrap();
1481 let result = executor.execute(&expr, &ctx).unwrap();
1482
1483 assert_eq!(result.as_number(), Some(3.0));
1484 }
1485
1486 #[test]
1487 fn test_execute_filter_then_count() {
1488 let ctx = make_test_data();
1489 let parser = ExpressionParser::new();
1490 let executor = ExpressionExecutor::new();
1491
1492 let expr = parser
1493 .parse("{{ data.transactions | filter(status=completed) | count }}")
1494 .unwrap();
1495 let result = executor.execute(&expr, &ctx).unwrap();
1496
1497 assert_eq!(result.as_number(), Some(2.0));
1498 }
1499
1500 #[test]
1501 fn test_execute_select() {
1502 let ctx = make_test_data();
1503 let parser = ExpressionParser::new();
1504 let executor = ExpressionExecutor::new();
1505
1506 let expr = parser
1507 .parse("{{ data.transactions | select(id, status) }}")
1508 .unwrap();
1509 let result = executor.execute(&expr, &ctx).unwrap();
1510
1511 let arr = result.as_array().unwrap();
1512 assert_eq!(arr.len(), 3);
1513
1514 let first = arr[0].as_object().unwrap();
1516 assert!(first.contains_key("id"));
1517 assert!(first.contains_key("status"));
1518 assert!(!first.contains_key("amount"));
1519 }
1520
1521 #[test]
1522 fn test_execute_sort_asc() {
1523 let ctx = make_test_data();
1524 let parser = ExpressionParser::new();
1525 let executor = ExpressionExecutor::new();
1526
1527 let expr = parser
1528 .parse("{{ data.transactions | sort(amount) }}")
1529 .unwrap();
1530 let result = executor.execute(&expr, &ctx).unwrap();
1531
1532 let arr = result.as_array().unwrap();
1533 let amounts: Vec<f64> = arr
1534 .iter()
1535 .filter_map(|v| v.get("amount")?.as_number())
1536 .collect();
1537 assert_eq!(amounts, vec![50.0, 75.0, 100.0]);
1538 }
1539
1540 #[test]
1541 fn test_execute_sort_desc() {
1542 let ctx = make_test_data();
1543 let parser = ExpressionParser::new();
1544 let executor = ExpressionExecutor::new();
1545
1546 let expr = parser
1547 .parse("{{ data.transactions | sort(amount, desc=true) }}")
1548 .unwrap();
1549 let result = executor.execute(&expr, &ctx).unwrap();
1550
1551 let arr = result.as_array().unwrap();
1552 let amounts: Vec<f64> = arr
1553 .iter()
1554 .filter_map(|v| v.get("amount")?.as_number())
1555 .collect();
1556 assert_eq!(amounts, vec![100.0, 75.0, 50.0]);
1557 }
1558
1559 #[test]
1560 fn test_execute_limit() {
1561 let ctx = make_test_data();
1562 let parser = ExpressionParser::new();
1563 let executor = ExpressionExecutor::new();
1564
1565 let expr = parser.parse("{{ data.transactions | limit(2) }}").unwrap();
1566 let result = executor.execute(&expr, &ctx).unwrap();
1567
1568 assert_eq!(result.len(), 2);
1569 }
1570
1571 #[test]
1572 fn test_execute_sum() {
1573 let ctx = make_test_data();
1574 let parser = ExpressionParser::new();
1575 let executor = ExpressionExecutor::new();
1576
1577 let expr = parser
1578 .parse("{{ data.transactions | sum(amount) }}")
1579 .unwrap();
1580 let result = executor.execute(&expr, &ctx).unwrap();
1581
1582 assert_eq!(result.as_number(), Some(225.0)); }
1584
1585 #[test]
1586 fn test_execute_mean() {
1587 let ctx = make_test_data();
1588 let parser = ExpressionParser::new();
1589 let executor = ExpressionExecutor::new();
1590
1591 let expr = parser
1592 .parse("{{ data.transactions | mean(amount) }}")
1593 .unwrap();
1594 let result = executor.execute(&expr, &ctx).unwrap();
1595
1596 assert_eq!(result.as_number(), Some(75.0)); }
1598
1599 #[test]
1600 fn test_execute_sample() {
1601 let ctx = make_test_data();
1602 let parser = ExpressionParser::new();
1603 let executor = ExpressionExecutor::new();
1604
1605 let expr = parser.parse("{{ data.transactions | sample(2) }}").unwrap();
1606 let result = executor.execute(&expr, &ctx).unwrap();
1607
1608 assert_eq!(result.len(), 2);
1609 }
1610
1611 #[test]
1612 fn test_execute_percentage() {
1613 let mut ctx = DataContext::new();
1614 ctx.insert("ratio", Value::number(0.75));
1615
1616 let parser = ExpressionParser::new();
1617 let executor = ExpressionExecutor::new();
1618
1619 let expr = parser.parse("{{ ratio | percentage }}").unwrap();
1620 let result = executor.execute(&expr, &ctx).unwrap();
1621
1622 assert_eq!(result.as_number(), Some(75.0));
1623 }
1624
1625 #[test]
1626 fn test_execute_chain() {
1627 let ctx = make_test_data();
1628 let parser = ExpressionParser::new();
1629 let executor = ExpressionExecutor::new();
1630
1631 let expr = parser
1632 .parse("{{ data.transactions | filter(status=completed) | sort(amount, desc=true) | limit(1) }}")
1633 .unwrap();
1634 let result = executor.execute(&expr, &ctx).unwrap();
1635
1636 let arr = result.as_array().unwrap();
1637 assert_eq!(arr.len(), 1);
1638 assert_eq!(arr[0].get("amount").unwrap().as_number(), Some(100.0));
1639 }
1640
1641 #[test]
1642 fn test_filter_numeric_match() {
1643 let ctx = make_test_data();
1644 let parser = ExpressionParser::new();
1645 let executor = ExpressionExecutor::new();
1646
1647 let expr = parser
1648 .parse("{{ data.transactions | filter(amount=100) }}")
1649 .unwrap();
1650 let result = executor.execute(&expr, &ctx).unwrap();
1651
1652 assert_eq!(result.len(), 1);
1653 }
1654
1655 #[test]
1656 fn test_execute_on_empty_array() {
1657 let mut ctx = DataContext::new();
1658 ctx.insert("items", Value::array(vec![]));
1659
1660 let parser = ExpressionParser::new();
1661 let executor = ExpressionExecutor::new();
1662
1663 let expr = parser.parse("{{ items | count }}").unwrap();
1664 let result = executor.execute(&expr, &ctx).unwrap();
1665
1666 assert_eq!(result.as_number(), Some(0.0));
1667 }
1668
1669 #[test]
1670 fn test_execute_mean_empty_array() {
1671 let mut ctx = DataContext::new();
1672 ctx.insert("items", Value::array(vec![]));
1673
1674 let parser = ExpressionParser::new();
1675 let executor = ExpressionExecutor::new();
1676
1677 let expr = parser.parse("{{ items | mean(value) }}").unwrap();
1678 let result = executor.execute(&expr, &ctx).unwrap();
1679
1680 assert_eq!(result.as_number(), Some(0.0));
1681 }
1682
1683 fn make_time_series_data() -> DataContext {
1686 let mut ctx = DataContext::new();
1687
1688 let events: Vec<Value> = vec![
1689 {
1690 let mut e = HashMap::new();
1691 e.insert("timestamp".to_string(), Value::number(1000.0));
1692 e.insert("value".to_string(), Value::number(10.0));
1693 Value::Object(e)
1694 },
1695 {
1696 let mut e = HashMap::new();
1697 e.insert("timestamp".to_string(), Value::number(2000.0));
1698 e.insert("value".to_string(), Value::number(20.0));
1699 Value::Object(e)
1700 },
1701 {
1702 let mut e = HashMap::new();
1703 e.insert("timestamp".to_string(), Value::number(3000.0));
1704 e.insert("value".to_string(), Value::number(30.0));
1705 Value::Object(e)
1706 },
1707 ];
1708
1709 ctx.insert("events", Value::Array(events));
1710 ctx
1711 }
1712
1713 #[test]
1714 fn test_execute_rate() {
1715 let ctx = make_time_series_data();
1716 let parser = ExpressionParser::new();
1717 let executor = ExpressionExecutor::new();
1718
1719 let expr = parser.parse("{{ events | rate(5s) }}").unwrap();
1720 let result = executor.execute(&expr, &ctx).unwrap();
1721
1722 assert!(result.is_number());
1725 let rate = result.as_number().unwrap();
1726 assert!(rate > 0.0);
1727 }
1728
1729 #[test]
1730 fn test_execute_rate_minute_window() {
1731 let ctx = make_time_series_data();
1732 let parser = ExpressionParser::new();
1733 let executor = ExpressionExecutor::new();
1734
1735 let expr = parser.parse("{{ events | rate(1m) }}").unwrap();
1736 let result = executor.execute(&expr, &ctx).unwrap();
1737
1738 assert!(result.is_number());
1739 }
1740
1741 #[test]
1744 fn test_execute_group_by() {
1745 let ctx = make_test_data();
1746 let parser = ExpressionParser::new();
1747 let executor = ExpressionExecutor::new();
1748
1749 let expr = parser
1750 .parse("{{ data.transactions | group_by(status) }}")
1751 .unwrap();
1752 let result = executor.execute(&expr, &ctx).unwrap();
1753
1754 let arr = result.as_array().unwrap();
1755 assert_eq!(arr.len(), 2);
1757
1758 for group in arr {
1759 let obj = group.as_object().unwrap();
1760 assert!(obj.contains_key("key"));
1761 assert!(obj.contains_key("items"));
1762 assert!(obj.contains_key("count"));
1763 }
1764 }
1765
1766 #[test]
1769 fn test_execute_distinct_field() {
1770 let ctx = make_test_data();
1771 let parser = ExpressionParser::new();
1772 let executor = ExpressionExecutor::new();
1773
1774 let expr = parser
1775 .parse("{{ data.transactions | distinct(status) }}")
1776 .unwrap();
1777 let result = executor.execute(&expr, &ctx).unwrap();
1778
1779 assert_eq!(result.len(), 2); }
1782
1783 #[test]
1784 fn test_execute_distinct_no_field() {
1785 let mut ctx = DataContext::new();
1786 ctx.insert(
1787 "items",
1788 Value::array(vec![
1789 Value::string("a"),
1790 Value::string("b"),
1791 Value::string("a"),
1792 Value::string("c"),
1793 ]),
1794 );
1795
1796 let parser = ExpressionParser::new();
1797 let executor = ExpressionExecutor::new();
1798
1799 let expr = parser.parse("{{ items | distinct }}").unwrap();
1800 let result = executor.execute(&expr, &ctx).unwrap();
1801
1802 assert_eq!(result.len(), 3); }
1804
1805 #[test]
1808 fn test_execute_where_gt() {
1809 let ctx = make_test_data();
1810 let parser = ExpressionParser::new();
1811 let executor = ExpressionExecutor::new();
1812
1813 let expr = parser
1814 .parse("{{ data.transactions | where(amount, gt, 60) }}")
1815 .unwrap();
1816 let result = executor.execute(&expr, &ctx).unwrap();
1817
1818 assert_eq!(result.len(), 2);
1820 }
1821
1822 #[test]
1823 fn test_execute_where_lt() {
1824 let ctx = make_test_data();
1825 let parser = ExpressionParser::new();
1826 let executor = ExpressionExecutor::new();
1827
1828 let expr = parser
1829 .parse("{{ data.transactions | where(amount, lt, 80) }}")
1830 .unwrap();
1831 let result = executor.execute(&expr, &ctx).unwrap();
1832
1833 assert_eq!(result.len(), 2);
1835 }
1836
1837 #[test]
1838 fn test_execute_where_eq() {
1839 let ctx = make_test_data();
1840 let parser = ExpressionParser::new();
1841 let executor = ExpressionExecutor::new();
1842
1843 let expr = parser
1844 .parse("{{ data.transactions | where(status, eq, pending) }}")
1845 .unwrap();
1846 let result = executor.execute(&expr, &ctx).unwrap();
1847
1848 assert_eq!(result.len(), 1);
1849 }
1850
1851 #[test]
1852 fn test_execute_where_ne() {
1853 let ctx = make_test_data();
1854 let parser = ExpressionParser::new();
1855 let executor = ExpressionExecutor::new();
1856
1857 let expr = parser
1858 .parse("{{ data.transactions | where(status, ne, pending) }}")
1859 .unwrap();
1860 let result = executor.execute(&expr, &ctx).unwrap();
1861
1862 assert_eq!(result.len(), 2);
1863 }
1864
1865 #[test]
1866 fn test_execute_where_contains() {
1867 let mut ctx = DataContext::new();
1868 let items: Vec<Value> = vec![
1869 {
1870 let mut t = HashMap::new();
1871 t.insert("name".to_string(), Value::string("hello world"));
1872 Value::Object(t)
1873 },
1874 {
1875 let mut t = HashMap::new();
1876 t.insert("name".to_string(), Value::string("goodbye"));
1877 Value::Object(t)
1878 },
1879 ];
1880 ctx.insert("items", Value::Array(items));
1881
1882 let parser = ExpressionParser::new();
1883 let executor = ExpressionExecutor::new();
1884
1885 let expr = parser
1886 .parse("{{ items | where(name, contains, world) }}")
1887 .unwrap();
1888 let result = executor.execute(&expr, &ctx).unwrap();
1889
1890 assert_eq!(result.len(), 1);
1891 }
1892
1893 #[test]
1896 fn test_execute_offset() {
1897 let ctx = make_test_data();
1898 let parser = ExpressionParser::new();
1899 let executor = ExpressionExecutor::new();
1900
1901 let expr = parser.parse("{{ data.transactions | offset(1) }}").unwrap();
1902 let result = executor.execute(&expr, &ctx).unwrap();
1903
1904 assert_eq!(result.len(), 2); }
1906
1907 #[test]
1908 fn test_execute_offset_with_limit() {
1909 let ctx = make_test_data();
1910 let parser = ExpressionParser::new();
1911 let executor = ExpressionExecutor::new();
1912
1913 let expr = parser
1914 .parse("{{ data.transactions | offset(1) | limit(1) }}")
1915 .unwrap();
1916 let result = executor.execute(&expr, &ctx).unwrap();
1917
1918 assert_eq!(result.len(), 1);
1919 }
1920
1921 #[test]
1924 fn test_execute_min() {
1925 let ctx = make_test_data();
1926 let parser = ExpressionParser::new();
1927 let executor = ExpressionExecutor::new();
1928
1929 let expr = parser
1930 .parse("{{ data.transactions | min(amount) }}")
1931 .unwrap();
1932 let result = executor.execute(&expr, &ctx).unwrap();
1933
1934 assert_eq!(result.as_number(), Some(50.0));
1935 }
1936
1937 #[test]
1938 fn test_execute_max() {
1939 let ctx = make_test_data();
1940 let parser = ExpressionParser::new();
1941 let executor = ExpressionExecutor::new();
1942
1943 let expr = parser
1944 .parse("{{ data.transactions | max(amount) }}")
1945 .unwrap();
1946 let result = executor.execute(&expr, &ctx).unwrap();
1947
1948 assert_eq!(result.as_number(), Some(100.0));
1949 }
1950
1951 #[test]
1952 fn test_execute_min_empty() {
1953 let mut ctx = DataContext::new();
1954 ctx.insert("items", Value::array(vec![]));
1955
1956 let parser = ExpressionParser::new();
1957 let executor = ExpressionExecutor::new();
1958
1959 let expr = parser.parse("{{ items | min(value) }}").unwrap();
1960 let result = executor.execute(&expr, &ctx).unwrap();
1961
1962 assert!(result.is_null());
1963 }
1964
1965 #[test]
1968 fn test_execute_first() {
1969 let ctx = make_test_data();
1970 let parser = ExpressionParser::new();
1971 let executor = ExpressionExecutor::new();
1972
1973 let expr = parser.parse("{{ data.transactions | first(2) }}").unwrap();
1974 let result = executor.execute(&expr, &ctx).unwrap();
1975
1976 assert_eq!(result.len(), 2);
1977 }
1978
1979 #[test]
1980 fn test_execute_last() {
1981 let ctx = make_test_data();
1982 let parser = ExpressionParser::new();
1983 let executor = ExpressionExecutor::new();
1984
1985 let expr = parser.parse("{{ data.transactions | last(2) }}").unwrap();
1986 let result = executor.execute(&expr, &ctx).unwrap();
1987
1988 let arr = result.as_array().unwrap();
1989 assert_eq!(arr.len(), 2);
1990
1991 assert_eq!(arr[1].get("id").unwrap().as_number(), Some(3.0));
1993 }
1994
1995 #[test]
1998 fn test_execute_flatten() {
1999 let mut ctx = DataContext::new();
2000 ctx.insert(
2001 "nested",
2002 Value::array(vec![
2003 Value::array(vec![Value::number(1.0), Value::number(2.0)]),
2004 Value::array(vec![Value::number(3.0), Value::number(4.0)]),
2005 ]),
2006 );
2007
2008 let parser = ExpressionParser::new();
2009 let executor = ExpressionExecutor::new();
2010
2011 let expr = parser.parse("{{ nested | flatten }}").unwrap();
2012 let result = executor.execute(&expr, &ctx).unwrap();
2013
2014 let arr = result.as_array().unwrap();
2015 assert_eq!(arr.len(), 4);
2016 assert_eq!(arr[0].as_number(), Some(1.0));
2017 assert_eq!(arr[3].as_number(), Some(4.0));
2018 }
2019
2020 #[test]
2021 fn test_execute_flatten_mixed() {
2022 let mut ctx = DataContext::new();
2023 ctx.insert(
2024 "items",
2025 Value::array(vec![
2026 Value::number(1.0),
2027 Value::array(vec![Value::number(2.0), Value::number(3.0)]),
2028 Value::number(4.0),
2029 ]),
2030 );
2031
2032 let parser = ExpressionParser::new();
2033 let executor = ExpressionExecutor::new();
2034
2035 let expr = parser.parse("{{ items | flatten }}").unwrap();
2036 let result = executor.execute(&expr, &ctx).unwrap();
2037
2038 assert_eq!(result.len(), 4);
2039 }
2040
2041 #[test]
2044 fn test_execute_reverse() {
2045 let ctx = make_test_data();
2046 let parser = ExpressionParser::new();
2047 let executor = ExpressionExecutor::new();
2048
2049 let expr = parser.parse("{{ data.transactions | reverse }}").unwrap();
2050 let result = executor.execute(&expr, &ctx).unwrap();
2051
2052 let arr = result.as_array().unwrap();
2053 assert_eq!(arr[0].get("id").unwrap().as_number(), Some(3.0));
2055 }
2056
2057 #[test]
2060 fn test_execute_complex_chain() {
2061 let ctx = make_test_data();
2062 let parser = ExpressionParser::new();
2063 let executor = ExpressionExecutor::new();
2064
2065 let expr = parser
2067 .parse("{{ data.transactions | where(status, eq, completed) | sort(amount, desc=true) | first(1) }}")
2068 .unwrap();
2069 let result = executor.execute(&expr, &ctx).unwrap();
2070
2071 let arr = result.as_array().unwrap();
2072 assert_eq!(arr.len(), 1);
2073 assert_eq!(arr[0].get("amount").unwrap().as_number(), Some(100.0));
2075 }
2076
2077 #[test]
2078 fn test_execute_group_then_count() {
2079 let ctx = make_test_data();
2080 let parser = ExpressionParser::new();
2081 let executor = ExpressionExecutor::new();
2082
2083 let expr = parser
2084 .parse("{{ data.transactions | group_by(status) | count }}")
2085 .unwrap();
2086 let result = executor.execute(&expr, &ctx).unwrap();
2087
2088 assert_eq!(result.as_number(), Some(2.0));
2090 }
2091
2092 fn make_join_test_data() -> DataContext {
2095 let mut ctx = DataContext::new();
2096
2097 let orders = Value::Array(vec![
2099 {
2100 let mut obj = HashMap::new();
2101 obj.insert("id".to_string(), Value::Number(1.0));
2102 obj.insert("customer_id".to_string(), Value::Number(100.0));
2103 obj.insert("amount".to_string(), Value::Number(50.0));
2104 Value::Object(obj)
2105 },
2106 {
2107 let mut obj = HashMap::new();
2108 obj.insert("id".to_string(), Value::Number(2.0));
2109 obj.insert("customer_id".to_string(), Value::Number(101.0));
2110 obj.insert("amount".to_string(), Value::Number(75.0));
2111 Value::Object(obj)
2112 },
2113 {
2114 let mut obj = HashMap::new();
2115 obj.insert("id".to_string(), Value::Number(3.0));
2116 obj.insert("customer_id".to_string(), Value::Number(100.0));
2117 obj.insert("amount".to_string(), Value::Number(25.0));
2118 Value::Object(obj)
2119 },
2120 {
2121 let mut obj = HashMap::new();
2122 obj.insert("id".to_string(), Value::Number(4.0));
2123 obj.insert("customer_id".to_string(), Value::Number(999.0)); obj.insert("amount".to_string(), Value::Number(10.0));
2125 Value::Object(obj)
2126 },
2127 ]);
2128
2129 let customers = Value::Array(vec![
2131 {
2132 let mut obj = HashMap::new();
2133 obj.insert("customer_id".to_string(), Value::Number(100.0));
2134 obj.insert("name".to_string(), Value::String("Alice".to_string()));
2135 obj.insert("tier".to_string(), Value::String("gold".to_string()));
2136 Value::Object(obj)
2137 },
2138 {
2139 let mut obj = HashMap::new();
2140 obj.insert("customer_id".to_string(), Value::Number(101.0));
2141 obj.insert("name".to_string(), Value::String("Bob".to_string()));
2142 obj.insert("tier".to_string(), Value::String("silver".to_string()));
2143 Value::Object(obj)
2144 },
2145 ]);
2146
2147 ctx.insert("orders", orders);
2148 ctx.insert("customers", customers);
2149 ctx
2150 }
2151
2152 #[test]
2153 fn test_execute_join_basic() {
2154 let ctx = make_join_test_data();
2155 let parser = ExpressionParser::new();
2156 let executor = ExpressionExecutor::new();
2157
2158 let expr = parser
2159 .parse("{{ orders | join(customers, on=customer_id) }}")
2160 .unwrap();
2161 let result = executor.execute(&expr, &ctx).unwrap();
2162
2163 let arr = result.as_array().unwrap();
2164 assert_eq!(arr.len(), 4);
2166
2167 let first = arr[0].as_object().unwrap();
2169 assert_eq!(first.get("id").unwrap().as_number(), Some(1.0));
2170 assert_eq!(first.get("name").unwrap().as_str(), Some("Alice"));
2171 assert_eq!(first.get("tier").unwrap().as_str(), Some("gold"));
2172 }
2173
2174 #[test]
2175 fn test_execute_join_multiple_matches() {
2176 let ctx = make_join_test_data();
2177 let executor = ExpressionExecutor::new();
2178
2179 let expr = Expression {
2181 source: "orders".to_string(),
2182 transforms: vec![Transform::Join {
2183 other: "customers".to_string(),
2184 on: "customer_id".to_string(),
2185 }],
2186 };
2187
2188 let result = executor.execute(&expr, &ctx).unwrap();
2189 let arr = result.as_array().unwrap();
2190
2191 let alice_orders: Vec<_> = arr
2193 .iter()
2194 .filter(|v| {
2195 v.as_object()
2196 .and_then(|o| o.get("name"))
2197 .and_then(|n| n.as_str())
2198 == Some("Alice")
2199 })
2200 .collect();
2201 assert_eq!(alice_orders.len(), 2);
2202 }
2203
2204 #[test]
2205 fn test_execute_join_no_match_keeps_left() {
2206 let ctx = make_join_test_data();
2207 let executor = ExpressionExecutor::new();
2208
2209 let expr = Expression {
2210 source: "orders".to_string(),
2211 transforms: vec![Transform::Join {
2212 other: "customers".to_string(),
2213 on: "customer_id".to_string(),
2214 }],
2215 };
2216
2217 let result = executor.execute(&expr, &ctx).unwrap();
2218 let arr = result.as_array().unwrap();
2219
2220 let order4: Vec<_> = arr
2222 .iter()
2223 .filter(|v| {
2224 v.as_object()
2225 .and_then(|o| o.get("id"))
2226 .and_then(|n| n.as_number())
2227 == Some(4.0)
2228 })
2229 .collect();
2230 assert_eq!(order4.len(), 1);
2231
2232 let obj = order4[0].as_object().unwrap();
2234 assert_eq!(obj.get("customer_id").unwrap().as_number(), Some(999.0));
2235 assert!(obj.get("name").is_none());
2236 }
2237
2238 #[test]
2239 fn test_execute_join_other_source_not_found() {
2240 let ctx = make_join_test_data();
2241 let executor = ExpressionExecutor::new();
2242
2243 let expr = Expression {
2244 source: "orders".to_string(),
2245 transforms: vec![Transform::Join {
2246 other: "nonexistent".to_string(),
2247 on: "customer_id".to_string(),
2248 }],
2249 };
2250
2251 let result = executor.execute(&expr, &ctx);
2252 assert!(result.is_err());
2253 assert!(matches!(
2254 result.unwrap_err(),
2255 ExecutionError::SourceNotFound(_)
2256 ));
2257 }
2258
2259 #[test]
2260 fn test_execute_join_chained_with_filter() {
2261 let ctx = make_join_test_data();
2262 let parser = ExpressionParser::new();
2263 let executor = ExpressionExecutor::new();
2264
2265 let expr = parser
2267 .parse("{{ orders | join(customers, on=customer_id) | filter(tier=gold) }}")
2268 .unwrap();
2269 let result = executor.execute(&expr, &ctx).unwrap();
2270
2271 let arr = result.as_array().unwrap();
2272 assert_eq!(arr.len(), 2);
2274 }
2275
2276 #[test]
2277 fn test_execute_join_empty_left() {
2278 let mut ctx = DataContext::new();
2279 ctx.insert("empty", Value::Array(vec![]));
2280 ctx.insert(
2281 "other",
2282 Value::Array(vec![{
2283 let mut obj = HashMap::new();
2284 obj.insert("id".to_string(), Value::Number(1.0));
2285 Value::Object(obj)
2286 }]),
2287 );
2288
2289 let executor = ExpressionExecutor::new();
2290 let expr = Expression {
2291 source: "empty".to_string(),
2292 transforms: vec![Transform::Join {
2293 other: "other".to_string(),
2294 on: "id".to_string(),
2295 }],
2296 };
2297
2298 let result = executor.execute(&expr, &ctx).unwrap();
2299 let arr = result.as_array().unwrap();
2300 assert!(arr.is_empty());
2301 }
2302
2303 #[test]
2304 fn test_execute_join_empty_right() {
2305 let mut ctx = DataContext::new();
2306 ctx.insert(
2307 "orders",
2308 Value::Array(vec![{
2309 let mut obj = HashMap::new();
2310 obj.insert("id".to_string(), Value::Number(1.0));
2311 Value::Object(obj)
2312 }]),
2313 );
2314 ctx.insert("empty", Value::Array(vec![]));
2315
2316 let executor = ExpressionExecutor::new();
2317 let expr = Expression {
2318 source: "orders".to_string(),
2319 transforms: vec![Transform::Join {
2320 other: "empty".to_string(),
2321 on: "id".to_string(),
2322 }],
2323 };
2324
2325 let result = executor.execute(&expr, &ctx).unwrap();
2326 let arr = result.as_array().unwrap();
2327 assert_eq!(arr.len(), 1);
2329 }
2330
2331 #[test]
2332 fn test_execute_join_conflicting_field_names() {
2333 let mut ctx = DataContext::new();
2334
2335 ctx.insert(
2337 "left",
2338 Value::Array(vec![{
2339 let mut obj = HashMap::new();
2340 obj.insert("id".to_string(), Value::Number(1.0));
2341 obj.insert("value".to_string(), Value::String("left_val".to_string()));
2342 Value::Object(obj)
2343 }]),
2344 );
2345 ctx.insert(
2346 "right",
2347 Value::Array(vec![{
2348 let mut obj = HashMap::new();
2349 obj.insert("id".to_string(), Value::Number(1.0));
2350 obj.insert("value".to_string(), Value::String("right_val".to_string()));
2351 obj.insert("extra".to_string(), Value::String("extra_val".to_string()));
2352 Value::Object(obj)
2353 }]),
2354 );
2355
2356 let executor = ExpressionExecutor::new();
2357 let expr = Expression {
2358 source: "left".to_string(),
2359 transforms: vec![Transform::Join {
2360 other: "right".to_string(),
2361 on: "id".to_string(),
2362 }],
2363 };
2364
2365 let result = executor.execute(&expr, &ctx).unwrap();
2366 let arr = result.as_array().unwrap();
2367 assert_eq!(arr.len(), 1);
2368
2369 let obj = arr[0].as_object().unwrap();
2370 assert_eq!(obj.get("value").unwrap().as_str(), Some("left_val"));
2372 assert_eq!(obj.get("right_value").unwrap().as_str(), Some("right_val"));
2374 assert_eq!(obj.get("extra").unwrap().as_str(), Some("extra_val"));
2376 }
2377
2378 #[test]
2379 fn test_execute_join_with_sum() {
2380 let ctx = make_join_test_data();
2381 let parser = ExpressionParser::new();
2382 let executor = ExpressionExecutor::new();
2383
2384 let expr = parser
2386 .parse(
2387 "{{ orders | join(customers, on=customer_id) | filter(tier=gold) | sum(amount) }}",
2388 )
2389 .unwrap();
2390 let result = executor.execute(&expr, &ctx).unwrap();
2391
2392 assert_eq!(result.as_number(), Some(75.0));
2394 }
2395
2396 #[test]
2399 fn test_execute_suggest_with_array() {
2400 let mut ctx = DataContext::new();
2401
2402 let suggestions = Value::Array(vec![
2404 {
2405 let mut obj = HashMap::new();
2406 obj.insert("text".to_string(), Value::String("git status".to_string()));
2407 obj.insert("score".to_string(), Value::Number(0.15));
2408 Value::Object(obj)
2409 },
2410 {
2411 let mut obj = HashMap::new();
2412 obj.insert("text".to_string(), Value::String("git commit".to_string()));
2413 obj.insert("score".to_string(), Value::Number(0.12));
2414 Value::Object(obj)
2415 },
2416 {
2417 let mut obj = HashMap::new();
2418 obj.insert("text".to_string(), Value::String("cargo build".to_string()));
2419 obj.insert("score".to_string(), Value::Number(0.10));
2420 Value::Object(obj)
2421 },
2422 ]);
2423 ctx.insert("suggestions", suggestions);
2424
2425 let parser = ExpressionParser::new();
2426 let executor = ExpressionExecutor::new();
2427
2428 let expr = parser.parse("{{ suggestions | suggest(git, 5) }}").unwrap();
2430 let result = executor.execute(&expr, &ctx).unwrap();
2431
2432 let arr = result.as_array().unwrap();
2433 assert_eq!(arr.len(), 2); }
2435
2436 #[test]
2437 fn test_execute_suggest_with_model_object() {
2438 let mut ctx = DataContext::new();
2439
2440 let mut model = HashMap::new();
2442 model.insert(
2443 "model_type".to_string(),
2444 Value::String("ngram_lm".to_string()),
2445 );
2446 model.insert(
2447 "source".to_string(),
2448 Value::String("./model.apr".to_string()),
2449 );
2450 model.insert(
2451 "_suggestions".to_string(),
2452 Value::Array(vec![
2453 {
2454 let mut obj = HashMap::new();
2455 obj.insert("text".to_string(), Value::String("git status".to_string()));
2456 obj.insert("score".to_string(), Value::Number(0.15));
2457 Value::Object(obj)
2458 },
2459 {
2460 let mut obj = HashMap::new();
2461 obj.insert("text".to_string(), Value::String("git commit".to_string()));
2462 obj.insert("score".to_string(), Value::Number(0.12));
2463 Value::Object(obj)
2464 },
2465 ]),
2466 );
2467 ctx.insert("model", Value::Object(model));
2468
2469 let parser = ExpressionParser::new();
2470 let executor = ExpressionExecutor::new();
2471
2472 let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
2473 let result = executor.execute(&expr, &ctx).unwrap();
2474
2475 let arr = result.as_array().unwrap();
2476 assert_eq!(arr.len(), 2); }
2478
2479 #[test]
2480 fn test_execute_suggest_empty_model() {
2481 let mut ctx = DataContext::new();
2482
2483 let mut model = HashMap::new();
2485 model.insert(
2486 "model_type".to_string(),
2487 Value::String("ngram_lm".to_string()),
2488 );
2489 ctx.insert("model", Value::Object(model));
2490
2491 let parser = ExpressionParser::new();
2492 let executor = ExpressionExecutor::new();
2493
2494 let expr = parser.parse("{{ model | suggest(git, 5) }}").unwrap();
2495 let result = executor.execute(&expr, &ctx).unwrap();
2496
2497 let arr = result.as_array().unwrap();
2498 assert!(arr.is_empty()); }
2500
2501 #[test]
2502 fn test_parse_suggest() {
2503 let parser = ExpressionParser::new();
2504 let expr = parser.parse("{{ model | suggest(git, 8) }}").unwrap();
2505
2506 assert_eq!(expr.source, "model");
2507 assert_eq!(expr.transforms.len(), 1);
2508 assert!(matches!(
2509 &expr.transforms[0],
2510 Transform::Suggest { prefix, count } if prefix == "git" && *count == 8
2511 ));
2512 }
2513}