Skip to main content

zen_expression/functions/
internal.rs

1use crate::functions::defs::{
2    CompositeFunction, FunctionDefinition, FunctionSignature, StaticFunction,
3};
4use std::rc::Rc;
5use strum_macros::{Display, EnumIter, EnumString, IntoStaticStr};
6
7#[derive(Debug, PartialEq, Eq, Hash, Display, EnumString, EnumIter, IntoStaticStr, Clone, Copy)]
8#[strum(serialize_all = "camelCase")]
9pub enum InternalFunction {
10    // General
11    Len,
12    Contains,
13    Flatten,
14    Merge,
15    MergeDeep,
16
17    // String
18    Upper,
19    Lower,
20    Trim,
21    StartsWith,
22    EndsWith,
23    Matches,
24    Extract,
25    FuzzyMatch,
26    Split,
27
28    // Math
29    Abs,
30    Sum,
31    Avg,
32    Min,
33    Max,
34    Rand,
35    Median,
36    Mode,
37    Floor,
38    Ceil,
39    Round,
40    Trunc,
41
42    // Type
43    IsNumeric,
44    String,
45    Number,
46    Bool,
47    Type,
48
49    // Map
50    Keys,
51    Values,
52
53    #[strum(serialize = "d")]
54    Date,
55}
56
57impl From<&InternalFunction> for Rc<dyn FunctionDefinition> {
58    fn from(value: &InternalFunction) -> Self {
59        use crate::variable::VariableType as VT;
60        use InternalFunction as IF;
61
62        let s: Rc<dyn FunctionDefinition> = match value {
63            IF::Len => Rc::new(CompositeFunction {
64                implementation: Rc::new(imp::len),
65                signatures: vec![
66                    FunctionSignature::single(VT::String, VT::Number),
67                    FunctionSignature::single(VT::Any.array(), VT::Number),
68                ],
69            }),
70
71            IF::Contains => Rc::new(CompositeFunction {
72                implementation: Rc::new(imp::contains),
73                signatures: vec![
74                    FunctionSignature {
75                        parameters: vec![VT::String, VT::String],
76                        return_type: VT::Bool,
77                    },
78                    FunctionSignature {
79                        parameters: vec![VT::Any.array(), VT::Any],
80                        return_type: VT::Bool,
81                    },
82                ],
83            }),
84
85            IF::Flatten => Rc::new(StaticFunction {
86                implementation: Rc::new(imp::flatten),
87                signature: FunctionSignature::single(VT::Any.array(), VT::Any.array()),
88            }),
89
90            IF::Merge => Rc::new(CompositeFunction {
91                implementation: Rc::new(imp::merge),
92                signatures: vec![
93                    FunctionSignature::single(VT::Any.array(), VT::Any.array()),
94                    FunctionSignature::single(
95                        VT::Object(Default::default()).array(),
96                        VT::Object(Default::default()),
97                    ),
98                ],
99            }),
100
101            IF::MergeDeep => Rc::new(StaticFunction {
102                implementation: Rc::new(imp::merge_deep),
103                signature: FunctionSignature::single(
104                    VT::Object(Default::default()).array(),
105                    VT::Object(Default::default()),
106                ),
107            }),
108
109            IF::Upper => Rc::new(StaticFunction {
110                implementation: Rc::new(imp::upper),
111                signature: FunctionSignature::single(VT::String, VT::String),
112            }),
113
114            IF::Lower => Rc::new(StaticFunction {
115                implementation: Rc::new(imp::lower),
116                signature: FunctionSignature::single(VT::String, VT::String),
117            }),
118
119            IF::Trim => Rc::new(StaticFunction {
120                implementation: Rc::new(imp::trim),
121                signature: FunctionSignature::single(VT::String, VT::String),
122            }),
123
124            IF::StartsWith => Rc::new(StaticFunction {
125                implementation: Rc::new(imp::starts_with),
126                signature: FunctionSignature {
127                    parameters: vec![VT::String, VT::String],
128                    return_type: VT::Bool,
129                },
130            }),
131
132            IF::EndsWith => Rc::new(StaticFunction {
133                implementation: Rc::new(imp::ends_with),
134                signature: FunctionSignature {
135                    parameters: vec![VT::String, VT::String],
136                    return_type: VT::Bool,
137                },
138            }),
139
140            IF::Matches => Rc::new(StaticFunction {
141                implementation: Rc::new(imp::matches),
142                signature: FunctionSignature {
143                    parameters: vec![VT::String, VT::String],
144                    return_type: VT::Bool,
145                },
146            }),
147
148            IF::Extract => Rc::new(StaticFunction {
149                implementation: Rc::new(imp::extract),
150                signature: FunctionSignature {
151                    parameters: vec![VT::String, VT::String],
152                    return_type: VT::String.array(),
153                },
154            }),
155
156            IF::Split => Rc::new(StaticFunction {
157                implementation: Rc::new(imp::split),
158                signature: FunctionSignature {
159                    parameters: vec![VT::String, VT::String],
160                    return_type: VT::String.array(),
161                },
162            }),
163
164            IF::FuzzyMatch => Rc::new(CompositeFunction {
165                implementation: Rc::new(imp::fuzzy_match),
166                signatures: vec![
167                    FunctionSignature {
168                        parameters: vec![VT::String, VT::String],
169                        return_type: VT::Number,
170                    },
171                    FunctionSignature {
172                        parameters: vec![VT::String.array(), VT::String],
173                        return_type: VT::Number.array(),
174                    },
175                ],
176            }),
177
178            IF::Abs => Rc::new(StaticFunction {
179                implementation: Rc::new(imp::abs),
180                signature: FunctionSignature::single(VT::Number, VT::Number),
181            }),
182
183            IF::Rand => Rc::new(StaticFunction {
184                implementation: Rc::new(imp::rand),
185                signature: FunctionSignature::single(VT::Number, VT::Number),
186            }),
187
188            IF::Floor => Rc::new(StaticFunction {
189                implementation: Rc::new(imp::floor),
190                signature: FunctionSignature::single(VT::Number, VT::Number),
191            }),
192
193            IF::Ceil => Rc::new(StaticFunction {
194                implementation: Rc::new(imp::ceil),
195                signature: FunctionSignature::single(VT::Number, VT::Number),
196            }),
197
198            IF::Round => Rc::new(CompositeFunction {
199                implementation: Rc::new(imp::round),
200                signatures: vec![
201                    FunctionSignature {
202                        parameters: vec![VT::Number],
203                        return_type: VT::Number,
204                    },
205                    FunctionSignature {
206                        parameters: vec![VT::Number, VT::Number],
207                        return_type: VT::Number,
208                    },
209                ],
210            }),
211
212            IF::Trunc => Rc::new(CompositeFunction {
213                implementation: Rc::new(imp::trunc),
214                signatures: vec![
215                    FunctionSignature {
216                        parameters: vec![VT::Number],
217                        return_type: VT::Number,
218                    },
219                    FunctionSignature {
220                        parameters: vec![VT::Number, VT::Number],
221                        return_type: VT::Number,
222                    },
223                ],
224            }),
225
226            IF::Sum => Rc::new(StaticFunction {
227                implementation: Rc::new(imp::sum),
228                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
229            }),
230
231            IF::Avg => Rc::new(StaticFunction {
232                implementation: Rc::new(imp::avg),
233                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
234            }),
235
236            IF::Min => Rc::new(CompositeFunction {
237                implementation: Rc::new(imp::min),
238                signatures: vec![
239                    FunctionSignature::single(VT::Number.array(), VT::Number),
240                    FunctionSignature::single(VT::Date.array(), VT::Date),
241                ],
242            }),
243
244            IF::Max => Rc::new(CompositeFunction {
245                implementation: Rc::new(imp::max),
246                signatures: vec![
247                    FunctionSignature::single(VT::Number.array(), VT::Number),
248                    FunctionSignature::single(VT::Date.array(), VT::Date),
249                ],
250            }),
251
252            IF::Median => Rc::new(StaticFunction {
253                implementation: Rc::new(imp::median),
254                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
255            }),
256
257            IF::Mode => Rc::new(StaticFunction {
258                implementation: Rc::new(imp::mode),
259                signature: FunctionSignature::single(VT::Number.array(), VT::Number),
260            }),
261
262            IF::Type => Rc::new(StaticFunction {
263                implementation: Rc::new(imp::to_type),
264                signature: FunctionSignature::single(VT::Any, VT::String),
265            }),
266
267            IF::String => Rc::new(StaticFunction {
268                implementation: Rc::new(imp::to_string),
269                signature: FunctionSignature::single(VT::Any, VT::String),
270            }),
271
272            IF::Bool => Rc::new(StaticFunction {
273                implementation: Rc::new(imp::to_bool),
274                signature: FunctionSignature::single(VT::Any, VT::Bool),
275            }),
276
277            IF::IsNumeric => Rc::new(StaticFunction {
278                implementation: Rc::new(imp::is_numeric),
279                signature: FunctionSignature::single(VT::Any, VT::Bool),
280            }),
281
282            IF::Number => Rc::new(StaticFunction {
283                implementation: Rc::new(imp::to_number),
284                signature: FunctionSignature::single(VT::Any, VT::Number),
285            }),
286
287            IF::Keys => Rc::new(CompositeFunction {
288                implementation: Rc::new(imp::keys),
289                signatures: vec![
290                    FunctionSignature::single(VT::Object(Default::default()), VT::String.array()),
291                    FunctionSignature::single(VT::Any.array(), VT::Number.array()),
292                ],
293            }),
294
295            IF::Values => Rc::new(StaticFunction {
296                implementation: Rc::new(imp::values),
297                signature: FunctionSignature::single(
298                    VT::Object(Default::default()),
299                    VT::Any.array(),
300                ),
301            }),
302
303            IF::Date => Rc::new(CompositeFunction {
304                implementation: Rc::new(imp::date),
305                signatures: vec![
306                    FunctionSignature {
307                        parameters: vec![],
308                        return_type: VT::Date,
309                    },
310                    FunctionSignature {
311                        parameters: vec![VT::Any],
312                        return_type: VT::Date,
313                    },
314                    FunctionSignature {
315                        parameters: vec![VT::Any, VT::String],
316                        return_type: VT::Date,
317                    },
318                ],
319            }),
320        };
321
322        s
323    }
324}
325
326pub(crate) mod imp {
327    use crate::functions::arguments::Arguments;
328    use crate::vm::date::DynamicVariableExt;
329    use crate::vm::VmDate;
330    use crate::{Variable as V, Variable};
331    use anyhow::{anyhow, Context};
332    use chrono_tz::Tz;
333    #[cfg(not(feature = "regex-lite"))]
334    use regex::Regex;
335    #[cfg(feature = "regex-lite")]
336    use regex_lite::Regex;
337    use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
338    use rust_decimal::{Decimal, RoundingStrategy};
339    use rust_decimal_macros::dec;
340    use std::collections::BTreeMap;
341    use std::rc::Rc;
342    use std::str::FromStr;
343
344    fn __internal_number_array(args: &Arguments, pos: usize) -> anyhow::Result<Vec<Decimal>> {
345        let a = args.array(pos)?;
346        let arr = a.borrow();
347
348        arr.iter()
349            .map(|v| v.as_number())
350            .collect::<Option<Vec<_>>>()
351            .context("Expected a number array")
352    }
353
354    enum Either<A, B> {
355        Left(A),
356        Right(B),
357    }
358
359    fn __internal_number_or_date_array(
360        args: &Arguments,
361        pos: usize,
362    ) -> anyhow::Result<Either<Vec<Decimal>, Vec<VmDate>>> {
363        let a = args.array(pos)?;
364        let arr = a.borrow();
365
366        let is_number = arr.first().map(|v| v.as_number()).flatten().is_some();
367        if is_number {
368            Ok(Either::Left(
369                arr.iter()
370                    .map(|v| v.as_number())
371                    .collect::<Option<Vec<_>>>()
372                    .context("Expected a number array")?,
373            ))
374        } else {
375            Ok(Either::Right(
376                arr.iter()
377                    .map(|v| match v {
378                        Variable::Dynamic(d) => d.as_date().cloned(),
379                        _ => None,
380                    })
381                    .collect::<Option<Vec<_>>>()
382                    .context("Expected a number array")?,
383            ))
384        }
385    }
386
387    pub fn starts_with(args: Arguments) -> anyhow::Result<V> {
388        let a = args.str(0)?;
389        let b = args.str(1)?;
390
391        Ok(V::Bool(a.starts_with(b)))
392    }
393
394    pub fn ends_with(args: Arguments) -> anyhow::Result<V> {
395        let a = args.str(0)?;
396        let b = args.str(1)?;
397
398        Ok(V::Bool(a.ends_with(b)))
399    }
400
401    pub fn matches(args: Arguments) -> anyhow::Result<V> {
402        let a = args.str(0)?;
403        let b = args.str(1)?;
404
405        let regex = Regex::new(b.as_ref()).context("Invalid regular expression")?;
406
407        Ok(V::Bool(regex.is_match(a.as_ref())))
408    }
409
410    pub fn upper(args: Arguments) -> anyhow::Result<V> {
411        let a = args.str(0)?;
412        Ok(V::String(a.to_uppercase().into()))
413    }
414
415    pub fn lower(args: Arguments) -> anyhow::Result<V> {
416        let a = args.str(0)?;
417        Ok(V::String(a.to_lowercase().into()))
418    }
419
420    pub fn trim(args: Arguments) -> anyhow::Result<V> {
421        let a = args.str(0)?;
422        Ok(V::String(a.trim().into()))
423    }
424
425    pub fn extract(args: Arguments) -> anyhow::Result<V> {
426        let a = args.str(0)?;
427        let b = args.str(1)?;
428
429        let regex = Regex::new(b.as_ref()).context("Invalid regular expression")?;
430
431        let captures = regex
432            .captures(a.as_ref())
433            .map(|capture| {
434                capture
435                    .iter()
436                    .map(|c| c.map(|c| c.as_str()))
437                    .filter_map(|c| c)
438                    .map(|s| V::String((s).into()))
439                    .collect()
440            })
441            .unwrap_or_default();
442
443        Ok(V::from_array(captures))
444    }
445
446    pub fn split(args: Arguments) -> anyhow::Result<V> {
447        let a = args.str(0)?;
448        let b = args.str(1)?;
449
450        let arr = Vec::from_iter(
451            a.split(b)
452                .into_iter()
453                .map(|s| V::String(s.to_string().into())),
454        );
455
456        Ok(V::from_array(arr))
457    }
458
459    pub fn flatten(args: Arguments) -> anyhow::Result<V> {
460        let a = args.array(0)?;
461
462        let arr = a.borrow();
463        let mut flat_arr = Vec::with_capacity(arr.len());
464        arr.iter().for_each(|v| match v {
465            V::Array(b) => {
466                let arr = b.borrow();
467                arr.iter().for_each(|v| flat_arr.push(v.clone()))
468            }
469            _ => flat_arr.push(v.clone()),
470        });
471
472        Ok(V::from_array(flat_arr))
473    }
474
475    pub fn merge(args: Arguments) -> anyhow::Result<V> {
476        let a = args.array(0)?;
477        let arr = a.borrow();
478
479        let Some(first) = arr.iter().find(|item| !matches!(item, V::Null)) else {
480            return Ok(V::empty_object());
481        };
482
483        let capacity = arr
484            .iter()
485            .map(|item| match item {
486                V::Object(obj) => obj.borrow().len(),
487                V::Array(arr) => arr.borrow().len(),
488                _ => 0,
489            })
490            .sum();
491
492        match first {
493            V::Array(_) => {
494                let mut merged = Vec::with_capacity(capacity);
495
496                for item in arr.iter() {
497                    match item {
498                        V::Array(inner) => {
499                            let inner = inner.borrow();
500                            merged.extend(inner.iter().cloned());
501                        }
502                        V::Null => {}
503                        _ => return Err(anyhow!("Expected array of arrays")),
504                    }
505                }
506
507                Ok(V::from_array(merged))
508            }
509            V::Object(_) => {
510                let mut merged = zen_types::variable::VariableMap::with_capacity(capacity);
511                for item in arr.iter() {
512                    match item {
513                        V::Object(obj) => {
514                            let obj = obj.borrow();
515                            for (key, value) in obj.iter() {
516                                merged.insert(key.clone(), value.clone());
517                            }
518                        }
519                        V::Null => {}
520                        _ => return Err(anyhow!("Expected array of objects")),
521                    }
522                }
523
524                Ok(V::from_object(merged))
525            }
526            other => Err(anyhow!(
527                "merge expects an array of arrays or objects, got {}",
528                other.type_name()
529            )),
530        }
531    }
532
533    pub fn merge_deep(args: Arguments) -> anyhow::Result<V> {
534        let a = args.array(0)?;
535        let arr = a.borrow();
536
537        let mut result = V::empty_object();
538        for item in arr.iter() {
539            match item {
540                V::Object(_) => {
541                    result = deep_merge_variables(&result, item);
542                }
543                V::Null => {}
544                _ => return Err(anyhow!("Expected array of objects")),
545            }
546        }
547
548        Ok(result)
549    }
550
551    fn deep_merge_variables(base: &V, patch: &V) -> V {
552        match (base, patch) {
553            (V::Object(a), V::Object(b)) => {
554                let a = a.borrow();
555                let b = b.borrow();
556                let mut merged = zen_types::variable::VariableMap::with_capacity(a.len() + b.len());
557
558                for (key, value) in a.iter() {
559                    merged.insert(key.clone(), value.clone());
560                }
561
562                for (key, value) in b.iter() {
563                    let entry = merged
564                        .get(key)
565                        .map(|existing| deep_merge_variables(existing, value))
566                        .unwrap_or_else(|| value.clone());
567                    merged.insert(key.clone(), entry);
568                }
569
570                V::from_object(merged)
571            }
572            (V::Array(a), V::Array(b)) => {
573                let a = a.borrow();
574                let b = b.borrow();
575                let mut merged = Vec::with_capacity(a.len() + b.len());
576                merged.extend(a.iter().cloned());
577                merged.extend(b.iter().cloned());
578                V::from_array(merged)
579            }
580            (_, patch) => patch.clone(),
581        }
582    }
583
584    pub fn abs(args: Arguments) -> anyhow::Result<V> {
585        let a = args.number(0)?;
586        Ok(V::Number(a.abs()))
587    }
588
589    pub fn ceil(args: Arguments) -> anyhow::Result<V> {
590        let a = args.number(0)?;
591        Ok(V::Number(a.ceil()))
592    }
593
594    pub fn floor(args: Arguments) -> anyhow::Result<V> {
595        let a = args.number(0)?;
596        Ok(V::Number(a.floor()))
597    }
598
599    pub fn round(args: Arguments) -> anyhow::Result<V> {
600        let a = args.number(0)?;
601        let dp = args
602            .onumber(1)?
603            .map(|v| v.to_u32().context("Invalid number of decimal places"))
604            .transpose()?
605            .unwrap_or(0);
606
607        Ok(V::Number(a.round_dp_with_strategy(
608            dp,
609            RoundingStrategy::MidpointAwayFromZero,
610        )))
611    }
612
613    pub fn trunc(args: Arguments) -> anyhow::Result<V> {
614        let a = args.number(0)?;
615        let dp = args
616            .onumber(1)?
617            .map(|v| v.to_u32().context("Invalid number of decimal places"))
618            .transpose()?
619            .unwrap_or(0);
620
621        Ok(V::Number(a.trunc_with_scale(dp)))
622    }
623
624    pub fn rand(args: Arguments) -> anyhow::Result<V> {
625        let a = args.number(0)?;
626        let upper_range = a.round().to_i64().context("Invalid upper range")?;
627
628        let random_number = fastrand::i64(0..=upper_range);
629        Ok(V::Number(Decimal::from(random_number)))
630    }
631
632    pub fn min(args: Arguments) -> anyhow::Result<V> {
633        let a = __internal_number_or_date_array(&args, 0)?;
634
635        match a {
636            Either::Left(arr) => {
637                let max = arr.into_iter().min().context("Empty array")?;
638                Ok(V::Number(Decimal::from(max)))
639            }
640            Either::Right(arr) => {
641                let max = arr.into_iter().min().context("Empty array")?;
642                Ok(V::Dynamic(Rc::new(max)))
643            }
644        }
645    }
646
647    pub fn max(args: Arguments) -> anyhow::Result<V> {
648        let a = __internal_number_or_date_array(&args, 0)?;
649
650        match a {
651            Either::Left(arr) => {
652                let max = arr.into_iter().max().context("Empty array")?;
653                Ok(V::Number(Decimal::from(max)))
654            }
655            Either::Right(arr) => {
656                let max = arr.into_iter().max().context("Empty array")?;
657                Ok(V::Dynamic(Rc::new(max)))
658            }
659        }
660    }
661
662    pub fn avg(args: Arguments) -> anyhow::Result<V> {
663        let a = __internal_number_array(&args, 0)?;
664        let sum = a.iter().fold(Decimal::ZERO, |acc, x| acc + x);
665
666        Ok(V::Number(Decimal::from(
667            sum.checked_div(Decimal::from(a.len()))
668                .context("Empty array")?,
669        )))
670    }
671
672    pub fn sum(args: Arguments) -> anyhow::Result<V> {
673        let a = __internal_number_array(&args, 0)?;
674        let sum = a.iter().fold(Decimal::ZERO, |acc, v| acc + v);
675
676        Ok(V::Number(Decimal::from(sum)))
677    }
678
679    pub fn median(args: Arguments) -> anyhow::Result<V> {
680        let mut a = __internal_number_array(&args, 0)?;
681        a.sort();
682
683        let center = a.len() / 2;
684        if a.len() % 2 == 1 {
685            let center_num = a.get(center).context("Index out of bounds")?;
686            Ok(V::Number(*center_num))
687        } else {
688            let center_left = a.get(center - 1).context("Index out of bounds")?;
689            let center_right = a.get(center).context("Index out of bounds")?;
690
691            let median = ((*center_left) + (*center_right)) / dec!(2);
692            Ok(V::Number(median))
693        }
694    }
695
696    pub fn mode(args: Arguments) -> anyhow::Result<V> {
697        let a = __internal_number_array(&args, 0)?;
698        let mut counts = BTreeMap::new();
699        for num in a {
700            *counts.entry(num).or_insert(0) += 1;
701        }
702
703        let most_common = counts
704            .into_iter()
705            .max_by_key(|&(_, count)| count)
706            .map(|(num, _)| num)
707            .context("Empty array")?;
708
709        Ok(V::Number(most_common))
710    }
711
712    pub fn to_type(args: Arguments) -> anyhow::Result<V> {
713        let a = args.var(0)?;
714        Ok(V::String(a.type_name().into()))
715    }
716
717    pub fn to_bool(args: Arguments) -> anyhow::Result<V> {
718        let a = args.var(0)?;
719        let val = match a {
720            V::Null => false,
721            V::Bool(v) => *v,
722            V::Number(n) => !n.is_zero(),
723            V::Array(_) | V::Object(_) | V::Dynamic(_) => true,
724            V::String(s) => match s.as_str().trim() {
725                "true" => true,
726                "false" => false,
727                _ => s.is_empty(),
728            },
729        };
730
731        Ok(V::Bool(val))
732    }
733
734    pub fn to_string(args: Arguments) -> anyhow::Result<V> {
735        let a = args.var(0)?;
736        let val = match a {
737            V::Null => "null".into(),
738            V::Bool(v) => v.to_string().into(),
739            V::Number(n) => n.to_string().into(),
740            V::String(s) => s.clone(),
741            _ => return Err(anyhow!("Cannot convert type {} to string", a.type_name())),
742        };
743
744        Ok(V::String(val))
745    }
746
747    pub fn to_number(args: Arguments) -> anyhow::Result<V> {
748        let a = args.var(0)?;
749        let val = match a {
750            V::Number(n) => *n,
751            V::String(str) => {
752                let s = str.as_str().trim();
753                Decimal::from_str_exact(s)
754                    .or_else(|_| Decimal::from_scientific(s))
755                    .context("Invalid number")?
756            }
757            V::Bool(b) => match *b {
758                true => Decimal::ONE,
759                false => Decimal::ZERO,
760            },
761            _ => return Err(anyhow!("Cannot convert type {} to number", a.type_name())),
762        };
763
764        Ok(V::Number(val))
765    }
766
767    pub fn is_numeric(args: Arguments) -> anyhow::Result<V> {
768        let a = args.var(0)?;
769        let is_ok = match a {
770            V::Number(_) => true,
771            V::String(str) => {
772                let s = str.as_str().trim();
773                Decimal::from_str_exact(s)
774                    .or_else(|_| Decimal::from_scientific(s))
775                    .is_ok()
776            }
777            _ => false,
778        };
779
780        Ok(V::Bool(is_ok))
781    }
782
783    pub fn len(args: Arguments) -> anyhow::Result<V> {
784        let a = args.var(0)?;
785        let len = match a {
786            V::String(s) => s.len(),
787            V::Array(s) => {
788                let arr = s.borrow();
789                arr.len()
790            }
791            _ => {
792                return Err(anyhow!("Cannot determine len of type {}", a.type_name()));
793            }
794        };
795
796        Ok(V::Number(len.into()))
797    }
798
799    pub fn contains(args: Arguments) -> anyhow::Result<V> {
800        let a = args.var(0)?;
801        let b = args.var(1)?;
802
803        let val = match (a, b) {
804            (V::String(a), V::String(b)) => a.contains(b.as_str()),
805            (V::Array(a), _) => {
806                let arr = a.borrow();
807
808                arr.iter().any(|a| match (a, b) {
809                    (V::Number(a), V::Number(b)) => a == b,
810                    (V::String(a), V::String(b)) => a == b,
811                    (V::Bool(a), V::Bool(b)) => a == b,
812                    (V::Null, V::Null) => true,
813                    _ => false,
814                })
815            }
816            _ => {
817                return Err(anyhow!(
818                    "Cannot determine contains for type {} and {}",
819                    a.type_name(),
820                    b.type_name()
821                ));
822            }
823        };
824
825        Ok(V::Bool(val))
826    }
827
828    pub fn fuzzy_match(args: Arguments) -> anyhow::Result<V> {
829        let a = args.var(0)?;
830        let b = args.str(1)?;
831
832        let val = match a {
833            V::String(a) => {
834                let sim = strsim::normalized_damerau_levenshtein(a.as_ref(), b.as_ref());
835                // This is okay, as NDL will return [0, 1]
836                V::Number(Decimal::from_f64(sim).unwrap_or(dec!(0)))
837            }
838            V::Array(_a) => {
839                let a = _a.borrow();
840                let mut sims = Vec::with_capacity(a.len());
841                for v in a.iter() {
842                    let s = v.as_str().context("Expected string array")?;
843
844                    let sim = Decimal::from_f64(strsim::normalized_damerau_levenshtein(
845                        s.as_ref(),
846                        b.as_ref(),
847                    ))
848                    .unwrap_or(dec!(0));
849                    sims.push(V::Number(sim));
850                }
851
852                V::from_array(sims)
853            }
854            _ => return Err(anyhow!("Fuzzy match not available for type")),
855        };
856
857        Ok(val)
858    }
859
860    pub fn keys(args: Arguments) -> anyhow::Result<V> {
861        let a = args.var(0)?;
862        let var = match a {
863            V::Array(a) => {
864                let arr = a.borrow();
865                let indices = arr
866                    .iter()
867                    .enumerate()
868                    .map(|(index, _)| V::Number(index.into()))
869                    .collect();
870
871                V::from_array(indices)
872            }
873            V::Object(a) => {
874                let obj = a.borrow();
875                let keys = obj
876                    .iter()
877                    .map(|(key, _)| V::String((key.as_str()).into()))
878                    .collect();
879
880                V::from_array(keys)
881            }
882            _ => {
883                return Err(anyhow!("Cannot determine keys of type {}", a.type_name()));
884            }
885        };
886
887        Ok(var)
888    }
889
890    pub fn values(args: Arguments) -> anyhow::Result<V> {
891        let a = args.object(0)?;
892        let obj = a.borrow();
893        let values: Vec<_> = obj.values().cloned().collect();
894
895        Ok(V::from_array(values))
896    }
897
898    pub fn date(args: Arguments) -> anyhow::Result<V> {
899        let provided = args.ovar(0);
900        let tz = args
901            .ostr(1)?
902            .map(|v| Tz::from_str(v).context("Invalid timezone"))
903            .transpose()?;
904
905        let date_time = match provided {
906            Some(v) => VmDate::new(v.clone(), tz),
907            None => VmDate::now(),
908        };
909
910        Ok(V::Dynamic(Rc::new(date_time)))
911    }
912}