vrl/stdlib/
push.rs

1use crate::compiler::prelude::*;
2
3fn push(list: Value, item: Value) -> Resolved {
4    let mut list = list.try_array()?;
5    list.push(item);
6    Ok(list.into())
7}
8
9#[derive(Clone, Copy, Debug)]
10pub struct Push;
11
12impl Function for Push {
13    fn identifier(&self) -> &'static str {
14        "push"
15    }
16
17    fn usage(&self) -> &'static str {
18        "Adds the `item` to the end of the `value` array."
19    }
20
21    fn category(&self) -> &'static str {
22        Category::Array.as_ref()
23    }
24
25    fn return_kind(&self) -> u16 {
26        kind::ARRAY
27    }
28
29    fn return_rules(&self) -> &'static [&'static str] {
30        &["Returns a new array. The `value` is _not_ modified in place."]
31    }
32
33    fn parameters(&self) -> &'static [Parameter] {
34        const PARAMETERS: &[Parameter] = &[
35            Parameter::required("value", kind::ARRAY, "The target array."),
36            Parameter::required("item", kind::ANY, "The item to push."),
37        ];
38        PARAMETERS
39    }
40
41    fn examples(&self) -> &'static [Example] {
42        &[
43            example! {
44                title: "Push an item onto an array",
45                source: r"push([1, 2], 3)",
46                result: Ok(r"[1, 2, 3]"),
47            },
48            example! {
49                title: "Empty array",
50                source: r#"push([], "bar")"#,
51                result: Ok(r#"["bar"]"#),
52            },
53        ]
54    }
55
56    fn compile(
57        &self,
58        _state: &state::TypeState,
59        _ctx: &mut FunctionCompileContext,
60        arguments: ArgumentList,
61    ) -> Compiled {
62        let value = arguments.required("value");
63        let item = arguments.required("item");
64
65        Ok(PushFn { value, item }.as_expr())
66    }
67}
68
69#[derive(Debug, Clone)]
70struct PushFn {
71    value: Box<dyn Expression>,
72    item: Box<dyn Expression>,
73}
74
75impl FunctionExpression for PushFn {
76    fn resolve(&self, ctx: &mut Context) -> Resolved {
77        let list = self.value.resolve(ctx)?;
78        let item = self.item.resolve(ctx)?;
79
80        push(list, item)
81    }
82
83    fn type_def(&self, state: &state::TypeState) -> TypeDef {
84        let item = self.item.type_def(state).kind().clone().upgrade_undefined();
85        let mut typedef = self.value.type_def(state).restrict_array();
86
87        let array = typedef.as_array_mut().expect("must be an array");
88
89        if let Some(exact_len) = array.exact_length() {
90            // The exact array length is known, so just add the item to the correct index.
91            array.known_mut().insert(exact_len.into(), item);
92        } else {
93            // We don't know where the item will be inserted, so just add it to the unknown.
94            array.set_unknown(array.unknown_kind().union(item));
95        }
96
97        typedef.infallible()
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::btreemap;
105    use crate::value;
106
107    test_function![
108        push => Push;
109
110        empty_array {
111            args: func_args![value: value!([]), item: value!("foo")],
112            want: Ok(value!(["foo"])),
113            tdef: TypeDef::array(btreemap! {
114                Index::from(0) => Kind::bytes(),
115            }),
116        }
117
118        new_item {
119            args: func_args![value: value!([11, false, 42.5]), item: value!("foo")],
120            want: Ok(value!([11, false, 42.5, "foo"])),
121            tdef: TypeDef::array(btreemap! {
122                Index::from(0) => Kind::integer(),
123                Index::from(1) => Kind::boolean(),
124                Index::from(2) => Kind::float(),
125                Index::from(3) => Kind::bytes(),
126            }),
127        }
128
129        already_exists_item {
130            args: func_args![value: value!([11, false, 42.5]), item: value!(42.5)],
131            want: Ok(value!([11, false, 42.5, 42.5])),
132            tdef: TypeDef::array(btreemap! {
133                Index::from(0) => Kind::integer(),
134                Index::from(1) => Kind::boolean(),
135                Index::from(2) => Kind::float(),
136                Index::from(3) => Kind::float(),
137            }),
138        }
139    ];
140}