Skip to main content

jpx_core/functions/
mod.rs

1//! JMESPath functions.
2
3use std::fmt;
4
5use serde_json::{Number, Value};
6
7use crate::ast::Ast;
8use crate::interpreter::{SearchResult, interpret};
9use crate::value_ext::{JmespathType, ValueExt};
10use crate::{Context, ErrorReason, JmespathError, RuntimeError, get_expref_id};
11
12/// Represents a JMESPath function.
13pub trait Function: Sync + Send {
14    /// Evaluates the function against a slice of Value arguments.
15    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult;
16}
17
18/// Function argument types used when validating.
19#[derive(Clone, PartialEq, Eq, Debug)]
20pub enum ArgumentType {
21    Any,
22    Null,
23    String,
24    Number,
25    Bool,
26    Object,
27    Array,
28    Expref,
29    /// Each element of the array must match the provided type.
30    TypedArray(Box<ArgumentType>),
31    /// Accepts one of a number of `ArgumentType`s.
32    Union(Vec<ArgumentType>),
33}
34
35impl ArgumentType {
36    /// Returns true/false if the value is valid for the type.
37    pub fn is_valid(&self, value: &Value) -> bool {
38        use self::ArgumentType::*;
39        match *self {
40            Any => true,
41            Null if value.is_null() => true,
42            String if value.is_string() => true,
43            Number if value.is_number() => true,
44            Object if value.is_object() && !value.is_expref() => true,
45            Bool if value.is_boolean() => true,
46            Expref if value.is_expref() => true,
47            Array if value.is_array() => true,
48            TypedArray(ref t) if value.is_array() => {
49                if let Some(array) = value.as_array() {
50                    array.iter().all(|v| t.is_valid(v))
51                } else {
52                    false
53                }
54            }
55            Union(ref types) => types.iter().any(|t| t.is_valid(value)),
56            _ => false,
57        }
58    }
59}
60
61impl fmt::Display for ArgumentType {
62    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
63        use self::ArgumentType::*;
64        match *self {
65            Any => write!(fmt, "any"),
66            String => write!(fmt, "string"),
67            Number => write!(fmt, "number"),
68            Bool => write!(fmt, "boolean"),
69            Array => write!(fmt, "array"),
70            Object => write!(fmt, "object"),
71            Null => write!(fmt, "null"),
72            Expref => write!(fmt, "expref"),
73            TypedArray(ref t) => write!(fmt, "array[{t}]"),
74            Union(ref types) => {
75                let str_value = types
76                    .iter()
77                    .map(|t| t.to_string())
78                    .collect::<Vec<_>>()
79                    .join("|");
80                write!(fmt, "{str_value}")
81            }
82        }
83    }
84}
85
86#[macro_export]
87macro_rules! arg {
88    (any) => ($crate::functions::ArgumentType::Any);
89    (null) => ($crate::functions::ArgumentType::Null);
90    (string) => ($crate::functions::ArgumentType::String);
91    (bool) => ($crate::functions::ArgumentType::Bool);
92    (number) => ($crate::functions::ArgumentType::Number);
93    (object) => ($crate::functions::ArgumentType::Object);
94    (expref) => ($crate::functions::ArgumentType::Expref);
95    (array_number) => ($crate::functions::ArgumentType::TypedArray(Box::new($crate::functions::ArgumentType::Number)));
96    (array_string) => ($crate::functions::ArgumentType::TypedArray(Box::new($crate::functions::ArgumentType::String)));
97    (array) => ($crate::functions::ArgumentType::Array);
98    ($($x:ident) | *) => ($crate::functions::ArgumentType::Union(vec![$($crate::arg!($x)), *]));
99}
100
101type InvokedFunction = dyn Fn(&[Value], &mut Context<'_>) -> SearchResult + Sync + Send;
102
103/// Custom function that allows the creation of runtime functions with signature validation.
104pub struct CustomFunction {
105    signature: Signature,
106    f: Box<InvokedFunction>,
107}
108
109impl CustomFunction {
110    /// Creates a new custom function.
111    pub fn new(fn_signature: Signature, f: Box<InvokedFunction>) -> CustomFunction {
112        CustomFunction {
113            signature: fn_signature,
114            f,
115        }
116    }
117}
118
119impl Function for CustomFunction {
120    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
121        self.signature.validate(args, ctx)?;
122        (self.f)(args, ctx)
123    }
124}
125
126/// Normal closures can be used as functions.
127impl<F> Function for F
128where
129    F: Send + Sync + Fn(&[Value], &mut Context<'_>) -> SearchResult,
130{
131    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
132        (self)(args, ctx)
133    }
134}
135
136/// Represents a function's signature.
137#[derive(Clone, PartialEq, Eq, Debug)]
138pub struct Signature {
139    pub inputs: Vec<ArgumentType>,
140    pub variadic: Option<ArgumentType>,
141}
142
143impl Signature {
144    /// Creates a new Signature struct.
145    pub fn new(inputs: Vec<ArgumentType>, variadic: Option<ArgumentType>) -> Signature {
146        Signature { inputs, variadic }
147    }
148
149    /// Validates the arity of a function.
150    pub fn validate_arity(&self, actual: usize, ctx: &Context<'_>) -> Result<(), JmespathError> {
151        let expected = self.inputs.len();
152        if self.variadic.is_some() {
153            if actual >= expected {
154                Ok(())
155            } else {
156                let reason =
157                    ErrorReason::Runtime(RuntimeError::NotEnoughArguments { expected, actual });
158                Err(JmespathError::from_ctx(ctx, reason))
159            }
160        } else if actual == expected {
161            Ok(())
162        } else if actual < expected {
163            let reason =
164                ErrorReason::Runtime(RuntimeError::NotEnoughArguments { expected, actual });
165            Err(JmespathError::from_ctx(ctx, reason))
166        } else {
167            let reason = ErrorReason::Runtime(RuntimeError::TooManyArguments { expected, actual });
168            Err(JmespathError::from_ctx(ctx, reason))
169        }
170    }
171
172    /// Validates the provided function arguments against the signature.
173    pub fn validate(&self, args: &[Value], ctx: &Context<'_>) -> Result<(), JmespathError> {
174        self.validate_arity(args.len(), ctx)?;
175        if let Some(ref variadic) = self.variadic {
176            for (k, v) in args.iter().enumerate() {
177                let validator = self.inputs.get(k).unwrap_or(variadic);
178                self.validate_arg(ctx, k, v, validator)?;
179            }
180        } else {
181            for (k, v) in args.iter().enumerate() {
182                self.validate_arg(ctx, k, v, &self.inputs[k])?;
183            }
184        }
185        Ok(())
186    }
187
188    fn validate_arg(
189        &self,
190        ctx: &Context<'_>,
191        position: usize,
192        value: &Value,
193        validator: &ArgumentType,
194    ) -> Result<(), JmespathError> {
195        if validator.is_valid(value) {
196            Ok(())
197        } else {
198            let reason = ErrorReason::Runtime(RuntimeError::InvalidType {
199                expected: validator.to_string(),
200                actual: value.jmespath_type().to_string(),
201                position,
202            });
203            Err(JmespathError::from_ctx(ctx, reason))
204        }
205    }
206}
207
208/// Helper to extract an expref AST from a function argument.
209fn get_expref_ast<'a>(value: &Value, ctx: &'a Context<'_>) -> Option<&'a Ast> {
210    get_expref_id(value).and_then(|id| ctx.get_expref(id))
211}
212
213/// Helper to create a number Value from f64, returning Null on NaN/Inf.
214pub fn number_value(n: f64) -> Value {
215    Number::from_f64(n).map_or(Value::Null, Value::Number)
216}
217
218/// Creates a JmespathError for a custom runtime error message.
219pub fn custom_error(ctx: &Context<'_>, message: &str) -> JmespathError {
220    JmespathError::from_ctx(ctx, ErrorReason::Parse(message.to_owned()))
221}
222
223/// Creates a JmespathError for an invalid argument type at a given position.
224pub fn invalid_type_error(
225    ctx: &Context<'_>,
226    position: usize,
227    expected: &str,
228    actual: &Value,
229) -> JmespathError {
230    JmespathError::from_ctx(
231        ctx,
232        ErrorReason::Runtime(RuntimeError::InvalidType {
233            expected: expected.to_owned(),
234            actual: actual.jmespath_type().to_string(),
235            position,
236        }),
237    )
238}
239
240#[macro_export]
241macro_rules! defn {
242    ($name:ident, $args:expr, $variadic:expr) => {
243        pub struct $name {
244            signature: $crate::functions::Signature,
245        }
246
247        impl Default for $name {
248            fn default() -> Self {
249                Self::new()
250            }
251        }
252
253        impl $name {
254            pub fn new() -> $name {
255                $name {
256                    signature: $crate::functions::Signature::new($args, $variadic),
257                }
258            }
259        }
260    };
261}
262
263// ---- 26 Built-in JMESPath Functions ----
264
265defn!(AbsFn, vec![arg!(number)], None);
266
267impl Function for AbsFn {
268    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
269        self.signature.validate(args, ctx)?;
270        let n = args[0].as_f64().unwrap();
271        Ok(number_value(n.abs()))
272    }
273}
274
275defn!(AvgFn, vec![arg!(array_number)], None);
276
277impl Function for AvgFn {
278    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
279        self.signature.validate(args, ctx)?;
280        let arr = args[0].as_array().unwrap();
281        if arr.is_empty() {
282            return Ok(Value::Null);
283        }
284        let sum: f64 = arr.iter().map(|v| v.as_f64().unwrap_or(0.0)).sum();
285        Ok(number_value(sum / arr.len() as f64))
286    }
287}
288
289defn!(CeilFn, vec![arg!(number)], None);
290
291impl Function for CeilFn {
292    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
293        self.signature.validate(args, ctx)?;
294        let n = args[0].as_f64().unwrap();
295        Ok(number_value(n.ceil()))
296    }
297}
298
299defn!(ContainsFn, vec![arg!(string | array), arg!(any)], None);
300
301impl Function for ContainsFn {
302    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
303        self.signature.validate(args, ctx)?;
304        match &args[0] {
305            Value::Array(arr) => Ok(Value::Bool(arr.contains(&args[1]))),
306            Value::String(s) => match args[1].as_str() {
307                Some(needle) => Ok(Value::Bool(s.contains(needle))),
308                None => Ok(Value::Bool(false)),
309            },
310            _ => unreachable!(),
311        }
312    }
313}
314
315defn!(EndsWithFn, vec![arg!(string), arg!(string)], None);
316
317impl Function for EndsWithFn {
318    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
319        self.signature.validate(args, ctx)?;
320        let subject = args[0].as_str().unwrap();
321        let search = args[1].as_str().unwrap();
322        Ok(Value::Bool(subject.ends_with(search)))
323    }
324}
325
326defn!(FloorFn, vec![arg!(number)], None);
327
328impl Function for FloorFn {
329    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
330        self.signature.validate(args, ctx)?;
331        let n = args[0].as_f64().unwrap();
332        Ok(number_value(n.floor()))
333    }
334}
335
336defn!(JoinFn, vec![arg!(string), arg!(array_string)], None);
337
338impl Function for JoinFn {
339    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
340        self.signature.validate(args, ctx)?;
341        let glue = args[0].as_str().unwrap();
342        let values = args[1].as_array().unwrap();
343        let result: String = values
344            .iter()
345            .map(|v| v.as_str().unwrap().to_owned())
346            .collect::<Vec<String>>()
347            .join(glue);
348        Ok(Value::String(result))
349    }
350}
351
352defn!(KeysFn, vec![arg!(object)], None);
353
354impl Function for KeysFn {
355    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
356        self.signature.validate(args, ctx)?;
357        let obj = args[0].as_object().unwrap();
358        let keys: Vec<Value> = obj.keys().map(|k| Value::String(k.clone())).collect();
359        Ok(Value::Array(keys))
360    }
361}
362
363defn!(LengthFn, vec![arg!(array | object | string)], None);
364
365impl Function for LengthFn {
366    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
367        self.signature.validate(args, ctx)?;
368        let len = match &args[0] {
369            Value::Array(a) => a.len(),
370            Value::Object(m) => m.len(),
371            Value::String(s) => s.chars().count(),
372            _ => unreachable!(),
373        };
374        Ok(Value::Number(Number::from(len)))
375    }
376}
377
378defn!(MapFn, vec![arg!(expref), arg!(array)], None);
379
380impl Function for MapFn {
381    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
382        self.signature.validate(args, ctx)?;
383        let ast = get_expref_ast(&args[0], ctx).ok_or_else(|| {
384            JmespathError::new("", 0, ErrorReason::Parse("Expected expref".to_owned()))
385        })?;
386        // Clone the AST since we need to borrow ctx mutably
387        let ast = ast.clone();
388        let values = args[1].as_array().unwrap();
389        let mut results = vec![];
390        for value in values {
391            results.push(interpret(value, &ast, ctx)?);
392        }
393        Ok(Value::Array(results))
394    }
395}
396
397defn!(MaxFn, vec![arg!(array_string | array_number)], None);
398
399impl Function for MaxFn {
400    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
401        self.signature.validate(args, ctx)?;
402        let values = args[0].as_array().unwrap();
403        if values.is_empty() {
404            return Ok(Value::Null);
405        }
406        let result = values
407            .iter()
408            .skip(1)
409            .fold(values[0].clone(), |acc, item| max_value(acc, item.clone()));
410        Ok(result)
411    }
412}
413
414defn!(MinFn, vec![arg!(array_string | array_number)], None);
415
416impl Function for MinFn {
417    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
418        self.signature.validate(args, ctx)?;
419        let values = args[0].as_array().unwrap();
420        if values.is_empty() {
421            return Ok(Value::Null);
422        }
423        let result = values
424            .iter()
425            .skip(1)
426            .fold(values[0].clone(), |acc, item| min_value(acc, item.clone()));
427        Ok(result)
428    }
429}
430
431defn!(MaxByFn, vec![arg!(array), arg!(expref)], None);
432
433impl Function for MaxByFn {
434    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
435        self.signature.validate(args, ctx)?;
436        min_max_by(ctx, &args[0], &args[1], |a, b| compare_values(a, b).is_gt())
437    }
438}
439
440defn!(MinByFn, vec![arg!(array), arg!(expref)], None);
441
442impl Function for MinByFn {
443    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
444        self.signature.validate(args, ctx)?;
445        min_max_by(ctx, &args[0], &args[1], |a, b| compare_values(a, b).is_lt())
446    }
447}
448
449defn!(MergeFn, vec![arg!(object)], Some(arg!(object)));
450
451impl Function for MergeFn {
452    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
453        self.signature.validate(args, ctx)?;
454        let mut result = serde_json::Map::new();
455        for arg in args {
456            if let Some(obj) = arg.as_object() {
457                result.extend(obj.clone());
458            }
459        }
460        Ok(Value::Object(result))
461    }
462}
463
464defn!(NotNullFn, vec![arg!(any)], Some(arg!(any)));
465
466impl Function for NotNullFn {
467    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
468        self.signature.validate(args, ctx)?;
469        for arg in args {
470            if !arg.is_null() {
471                return Ok(arg.clone());
472            }
473        }
474        Ok(Value::Null)
475    }
476}
477
478defn!(ReverseFn, vec![arg!(array | string)], None);
479
480impl Function for ReverseFn {
481    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
482        self.signature.validate(args, ctx)?;
483        match &args[0] {
484            Value::Array(arr) => {
485                let mut reversed = arr.clone();
486                reversed.reverse();
487                Ok(Value::Array(reversed))
488            }
489            Value::String(s) => {
490                let reversed: String = s.chars().rev().collect();
491                Ok(Value::String(reversed))
492            }
493            _ => unreachable!(),
494        }
495    }
496}
497
498defn!(SortFn, vec![arg!(array_string | array_number)], None);
499
500impl Function for SortFn {
501    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
502        self.signature.validate(args, ctx)?;
503        let mut values = args[0].as_array().unwrap().clone();
504        values.sort_by(compare_values);
505        Ok(Value::Array(values))
506    }
507}
508
509defn!(SortByFn, vec![arg!(array), arg!(expref)], None);
510
511impl Function for SortByFn {
512    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
513        self.signature.validate(args, ctx)?;
514        let vals = args[0].as_array().unwrap();
515        if vals.is_empty() {
516            return Ok(Value::Array(vec![]));
517        }
518        let ast = get_expref_ast(&args[1], ctx)
519            .ok_or_else(|| {
520                JmespathError::new("", 0, ErrorReason::Parse("Expected expref".to_owned()))
521            })?
522            .clone();
523
524        // Map each value to (original, mapped_key)
525        let first_value = interpret(&vals[0], &ast, ctx)?;
526        let first_type = first_value.jmespath_type();
527        if first_type != JmespathType::String && first_type != JmespathType::Number {
528            let reason = ErrorReason::Runtime(RuntimeError::InvalidReturnType {
529                expected: "expression->string|expression->number".to_owned(),
530                actual: first_type.to_string(),
531                position: 1,
532                invocation: 1,
533            });
534            return Err(JmespathError::from_ctx(ctx, reason));
535        }
536
537        let mut mapped: Vec<(Value, Value)> = vec![(vals[0].clone(), first_value)];
538        for (invocation, v) in vals.iter().enumerate().skip(1) {
539            let mapped_value = interpret(v, &ast, ctx)?;
540            if mapped_value.jmespath_type() != first_type {
541                return Err(JmespathError::from_ctx(
542                    ctx,
543                    ErrorReason::Runtime(RuntimeError::InvalidReturnType {
544                        expected: format!("expression->{first_type}"),
545                        actual: mapped_value.jmespath_type().to_string(),
546                        position: 1,
547                        invocation,
548                    }),
549                ));
550            }
551            mapped.push((v.clone(), mapped_value));
552        }
553        mapped.sort_by(|a, b| compare_values(&a.1, &b.1));
554        let result = mapped.into_iter().map(|(orig, _)| orig).collect();
555        Ok(Value::Array(result))
556    }
557}
558
559defn!(StartsWithFn, vec![arg!(string), arg!(string)], None);
560
561impl Function for StartsWithFn {
562    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
563        self.signature.validate(args, ctx)?;
564        let subject = args[0].as_str().unwrap();
565        let search = args[1].as_str().unwrap();
566        Ok(Value::Bool(subject.starts_with(search)))
567    }
568}
569
570defn!(SumFn, vec![arg!(array_number)], None);
571
572impl Function for SumFn {
573    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
574        self.signature.validate(args, ctx)?;
575        let sum: f64 = args[0]
576            .as_array()
577            .unwrap()
578            .iter()
579            .map(|v| v.as_f64().unwrap_or(0.0))
580            .sum();
581        Ok(number_value(sum))
582    }
583}
584
585defn!(ToArrayFn, vec![arg!(any)], None);
586
587impl Function for ToArrayFn {
588    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
589        self.signature.validate(args, ctx)?;
590        match &args[0] {
591            Value::Array(_) => Ok(args[0].clone()),
592            _ => Ok(Value::Array(vec![args[0].clone()])),
593        }
594    }
595}
596
597defn!(ToNumberFn, vec![arg!(any)], None);
598
599impl Function for ToNumberFn {
600    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
601        self.signature.validate(args, ctx)?;
602        match &args[0] {
603            Value::Number(_) => Ok(args[0].clone()),
604            Value::String(s) => match serde_json::from_str::<Value>(s) {
605                Ok(Value::Number(n)) => Ok(Value::Number(n)),
606                _ => Ok(Value::Null),
607            },
608            _ => Ok(Value::Null),
609        }
610    }
611}
612
613defn!(
614    ToStringFn,
615    vec![arg!(object | array | bool | number | string | null)],
616    None
617);
618
619impl Function for ToStringFn {
620    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
621        self.signature.validate(args, ctx)?;
622        match &args[0] {
623            Value::String(_) => Ok(args[0].clone()),
624            other => Ok(Value::String(other.to_string())),
625        }
626    }
627}
628
629defn!(TypeFn, vec![arg!(any)], None);
630
631impl Function for TypeFn {
632    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
633        self.signature.validate(args, ctx)?;
634        Ok(Value::String(args[0].jmespath_type().to_string()))
635    }
636}
637
638defn!(ValuesFn, vec![arg!(object)], None);
639
640impl Function for ValuesFn {
641    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
642        self.signature.validate(args, ctx)?;
643        let map = args[0].as_object().unwrap();
644        Ok(Value::Array(map.values().cloned().collect()))
645    }
646}
647
648// ---- Helper functions ----
649
650/// Compare two Values for sorting. Numbers compare numerically, strings lexicographically.
651fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
652    match (a, b) {
653        (Value::Number(a), Value::Number(b)) => {
654            let af = a.as_f64().unwrap_or(0.0);
655            let bf = b.as_f64().unwrap_or(0.0);
656            af.partial_cmp(&bf).unwrap_or(std::cmp::Ordering::Equal)
657        }
658        (Value::String(a), Value::String(b)) => a.cmp(b),
659        _ => std::cmp::Ordering::Equal,
660    }
661}
662
663fn max_value(a: Value, b: Value) -> Value {
664    if compare_values(&a, &b).is_ge() { a } else { b }
665}
666
667fn min_value(a: Value, b: Value) -> Value {
668    if compare_values(&a, &b).is_le() { a } else { b }
669}
670
671/// Shared implementation for max_by and min_by.
672fn min_max_by(
673    ctx: &mut Context<'_>,
674    array_arg: &Value,
675    expref_arg: &Value,
676    is_better: fn(&Value, &Value) -> bool,
677) -> SearchResult {
678    let vals = array_arg.as_array().ok_or_else(|| {
679        JmespathError::new("", 0, ErrorReason::Parse("Expected array".to_owned()))
680    })?;
681    if vals.is_empty() {
682        return Ok(Value::Null);
683    }
684    let ast = get_expref_ast(expref_arg, ctx)
685        .ok_or_else(|| JmespathError::new("", 0, ErrorReason::Parse("Expected expref".to_owned())))?
686        .clone();
687
688    let initial = interpret(&vals[0], &ast, ctx)?;
689    let entered_type = initial.jmespath_type();
690    if entered_type != JmespathType::String && entered_type != JmespathType::Number {
691        return Err(JmespathError::from_ctx(
692            ctx,
693            ErrorReason::Runtime(RuntimeError::InvalidReturnType {
694                expected: "expression->number|expression->string".to_owned(),
695                actual: entered_type.to_string(),
696                position: 1,
697                invocation: 1,
698            }),
699        ));
700    }
701
702    let mut candidate = (vals[0].clone(), initial);
703    for (invocation, v) in vals.iter().enumerate().skip(1) {
704        let mapped = interpret(v, &ast, ctx)?;
705        if mapped.jmespath_type() != entered_type {
706            return Err(JmespathError::from_ctx(
707                ctx,
708                ErrorReason::Runtime(RuntimeError::InvalidReturnType {
709                    expected: format!("expression->{entered_type}"),
710                    actual: mapped.jmespath_type().to_string(),
711                    position: 1,
712                    invocation,
713                }),
714            ));
715        }
716        if is_better(&mapped, &candidate.1) {
717            candidate = (v.clone(), mapped);
718        }
719    }
720    Ok(candidate.0)
721}