1use super::util::ConstOrExpr;
2use crate::compiler::prelude::*;
3
4fn make_object_1(values: Vec<Value>) -> Resolved {
5 values
6 .into_iter()
7 .filter_map(|kv| make_key_value(kv).transpose())
8 .collect::<Result<_, _>>()
9 .map(Value::Object)
10}
11
12fn make_object_2(keys: Vec<Value>, values: Vec<Value>) -> Resolved {
13 keys.into_iter()
14 .zip(values)
15 .filter_map(|(key, value)| {
16 make_key_string(key)
17 .transpose()
18 .map(|key| key.map(|key| (key, value)))
19 })
20 .collect::<Result<_, _>>()
21 .map(Value::Object)
22}
23
24fn make_key_value(value: Value) -> ExpressionResult<Option<(KeyString, Value)>> {
25 let array = value.try_array()?;
26 let mut iter = array.into_iter();
27 let Some(key) = iter.next() else {
28 return Err("array value too short".into());
29 };
30 Ok(make_key_string(key)?.map(|key| (key, iter.next().unwrap_or(Value::Null))))
31}
32
33fn make_key_string(key: Value) -> ExpressionResult<Option<KeyString>> {
34 match key {
35 Value::Bytes(key) => Ok(Some(String::from_utf8_lossy(&key).into())),
36 Value::Null => Ok(None),
37 _ => Err("object keys must be strings".into()),
38 }
39}
40
41#[derive(Clone, Copy, Debug)]
42pub struct ObjectFromArray;
43
44impl Function for ObjectFromArray {
45 fn identifier(&self) -> &'static str {
46 "object_from_array"
47 }
48
49 fn usage(&self) -> &'static str {
50 indoc! {"
51 Iterate over either one array of arrays or a pair of arrays and create an object out of all the key-value pairs contained in them.
52 With one array of arrays, any entries with no value use `null` instead.
53 Any keys that are `null` skip the corresponding value.
54
55 If a single parameter is given, it must contain an array of all the input arrays.
56 "}
57 }
58
59 fn category(&self) -> &'static str {
60 Category::Object.as_ref()
61 }
62
63 fn internal_failure_reasons(&self) -> &'static [&'static str] {
64 &[
65 "`values` and `keys` must be arrays.",
66 "If `keys` is not present, `values` must contain only arrays.",
67 ]
68 }
69
70 fn return_kind(&self) -> u16 {
71 kind::OBJECT
72 }
73
74 fn return_rules(&self) -> &'static [&'static str] {
75 &[
76 "`object_from_array` is considered fallible in the following cases: if any of the parameters is not an array; if only the `value` parameter is present and it is not an array of arrays; or if any of the keys are not either a string or `null`.",
77 ]
78 }
79
80 fn parameters(&self) -> &'static [Parameter] {
81 const PARAMETERS: &[Parameter] = &[
82 Parameter::required(
83 "values",
84 kind::ARRAY,
85 "The first array of elements, or the array of input arrays if no other parameter is present.",
86 ),
87 Parameter::optional(
88 "keys",
89 kind::ARRAY,
90 "The second array of elements. If not present, the first parameter must contain all the arrays.",
91 ),
92 ];
93 PARAMETERS
94 }
95
96 fn examples(&self) -> &'static [Example] {
97 &[
98 example! {
99 title: "Create an object from one array",
100 source: r#"object_from_array([["one", 1], [null, 2], ["two", 3]])"#,
101 result: Ok(r#"{ "one": 1, "two": 3 }"#),
102 },
103 example! {
104 title: "Create an object from separate key and value arrays",
105 source: r#"object_from_array([1, 2, 3], keys: ["one", null, "two"])"#,
106 result: Ok(r#"{ "one": 1, "two": 3 }"#),
107 },
108 example! {
109 title: "Create an object from a separate arrays of keys and values",
110 source: r#"object_from_array(values: [1, null, true], keys: ["a", "b", "c"])"#,
111 result: Ok(r#"{"a": 1, "b": null, "c": true}"#),
112 },
113 ]
114 }
115
116 fn compile(
117 &self,
118 state: &TypeState,
119 _ctx: &mut FunctionCompileContext,
120 arguments: ArgumentList,
121 ) -> Compiled {
122 let values = ConstOrExpr::new(arguments.required("values"), state);
123 let keys = arguments
124 .optional("keys")
125 .map(|keys| ConstOrExpr::new(keys, state));
126
127 Ok(OFAFn { keys, values }.as_expr())
128 }
129}
130
131#[derive(Clone, Debug)]
132struct OFAFn {
133 keys: Option<ConstOrExpr>,
134 values: ConstOrExpr,
135}
136
137impl FunctionExpression for OFAFn {
138 fn resolve(&self, ctx: &mut Context) -> Resolved {
139 let values = self.values.resolve(ctx)?.try_array()?;
140 match &self.keys {
141 None => make_object_1(values),
142 Some(keys) => make_object_2(keys.resolve(ctx)?.try_array()?, values),
143 }
144 }
145
146 fn type_def(&self, _state: &TypeState) -> TypeDef {
147 TypeDef::object(Collection::any())
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use crate::value;
154
155 use super::*;
156
157 test_function![
158 object_from_array => ObjectFromArray;
159
160 makes_object_simple {
161 args: func_args![values: value!([["foo", 1], ["bar", 2]])],
162 want: Ok(value!({"foo": 1, "bar": 2})),
163 tdef: TypeDef::object(Collection::any()),
164 }
165
166 uses_keys_parameter {
167 args: func_args![keys: value!(["foo", "bar"]), values: value!([1, 2])],
168 want: Ok(value!({"foo": 1, "bar": 2})),
169 tdef: TypeDef::object(Collection::any()),
170 }
171
172 handles_missing_values {
173 args: func_args![values: value!([["foo", 1], ["bar"]])],
174 want: Ok(value!({"foo": 1, "bar": null})),
175 tdef: TypeDef::object(Collection::any()),
176 }
177
178 drops_extra_values {
179 args: func_args![values: value!([["foo", 1, 2, 3, 4]])],
180 want: Ok(value!({"foo": 1})),
181 tdef: TypeDef::object(Collection::any()),
182 }
183
184 errors_on_missing_keys {
185 args: func_args![values: value!([["foo", 1], []])],
186 want: Err("array value too short"),
187 tdef: TypeDef::object(Collection::any()),
188 }
189
190 skips_null_keys1 {
191 args: func_args![values: value!([["foo", 1], [null, 2], ["bar", 3]])],
192 want: Ok(value!({"foo": 1, "bar": 3})),
193 tdef: TypeDef::object(Collection::any()),
194 }
195
196 skips_null_keys2 {
197 args: func_args![values: value!([1, 2, 3]), keys: value!(["foo", null, "bar"])],
198 want: Ok(value!({"foo": 1, "bar": 3})),
199 tdef: TypeDef::object(Collection::any()),
200 }
201 ];
202}