1use crate::compiler::prelude::*;
2use crate::path::{OwnedSegment, OwnedValuePath};
3
4#[allow(clippy::cast_possible_truncation)] fn get(value: &Value, value_path: Value) -> Resolved {
6 let path = match value_path {
7 Value::Array(array) => {
8 let mut path = OwnedValuePath::root();
9
10 for segment in array {
11 let segment = match segment {
12 Value::Bytes(field) => {
13 OwnedSegment::field(String::from_utf8_lossy(&field).as_ref())
14 }
15 Value::Integer(index) => OwnedSegment::index(index as isize),
16 value => {
17 return Err(format!(
18 "path segment must be either string or integer, not {}",
19 value.kind()
20 )
21 .into());
22 }
23 };
24 path.push(segment);
25 }
26
27 path
28 }
29 value => {
30 return Err(ValueError::Expected {
31 got: value.kind(),
32 expected: Kind::array(Collection::any()),
33 }
34 .into());
35 }
36 };
37 Ok(value.get(&path).cloned().unwrap_or(Value::Null))
38}
39
40#[derive(Clone, Copy, Debug)]
41pub struct Get;
42
43impl Function for Get {
44 fn identifier(&self) -> &'static str {
45 "get"
46 }
47
48 fn usage(&self) -> &'static str {
49 indoc! {"
50 Dynamically get the value of a given path.
51
52 If you know the path you want to look up, use
53 static paths such as `.foo.bar[1]` to get the value of that
54 path. However, if you do not know the path names,
55 use the dynamic `get` function to get the requested value.
56 "}
57 }
58
59 fn category(&self) -> &'static str {
60 Category::Path.as_ref()
61 }
62
63 fn internal_failure_reasons(&self) -> &'static [&'static str] {
64 &["The `path` segment must be a string or an integer."]
65 }
66
67 fn return_kind(&self) -> u16 {
68 kind::ANY
69 }
70
71 fn parameters(&self) -> &'static [Parameter] {
72 const PARAMETERS: &[Parameter] = &[
73 Parameter::required(
74 "value",
75 kind::OBJECT | kind::ARRAY,
76 "The object or array to query.",
77 ),
78 Parameter::required(
79 "path",
80 kind::ARRAY,
81 "An array of path segments to look for the value.",
82 ),
83 ];
84 PARAMETERS
85 }
86
87 fn examples(&self) -> &'static [Example] {
88 &[
89 example! {
90 title: "Single-segment top-level field",
91 source: r#"get!(value: {"foo": "bar"}, path: ["foo"])"#,
92 result: Ok(r#""bar""#),
93 },
94 example! {
95 title: "Returns null for unknown field",
96 source: r#"get!(value: {"foo": "bar"}, path: ["baz"])"#,
97 result: Ok("null"),
98 },
99 example! {
100 title: "Multi-segment nested field",
101 source: r#"get!(value: {"foo": { "bar": true }}, path: ["foo", "bar"])"#,
102 result: Ok("true"),
103 },
104 example! {
105 title: "Array indexing",
106 source: "get!(value: [92, 42], path: [0])",
107 result: Ok("92"),
108 },
109 example! {
110 title: "Array indexing (negative)",
111 source: r#"get!(value: ["foo", "bar", "baz"], path: [-2])"#,
112 result: Ok(r#""bar""#),
113 },
114 example! {
115 title: "Nested indexing",
116 source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1])"#,
117 result: Ok("42"),
118 },
119 example! {
120 title: "External target",
121 source: r#"get!(value: ., path: ["foo"])"#,
122 input: r#"{ "foo": true }"#,
123 result: Ok("true"),
124 },
125 example! {
126 title: "Variable",
127 source: indoc! {r#"
128 var = { "foo": true }
129 get!(value: var, path: ["foo"])
130 "#},
131 result: Ok("true"),
132 },
133 example! {
134 title: "Missing index",
135 source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1, -1])"#,
136 result: Ok("null"),
137 },
138 example! {
139 title: "Invalid indexing",
140 source: r#"get!(value: [42], path: ["foo"])"#,
141 result: Ok("null"),
142 },
143 example! {
144 title: "Invalid segment type",
145 source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", true])"#,
146 result: Err(
147 r#"function call error for "get" at (0:62): path segment must be either string or integer, not boolean"#,
148 ),
149 },
150 ]
151 }
152
153 fn compile(
154 &self,
155 _state: &state::TypeState,
156 _ctx: &mut FunctionCompileContext,
157 arguments: ArgumentList,
158 ) -> Compiled {
159 let value = arguments.required("value");
160 let path = arguments.required("path");
161
162 Ok(GetFn { value, path }.as_expr())
163 }
164}
165
166#[derive(Debug, Clone)]
167pub(crate) struct GetFn {
168 value: Box<dyn Expression>,
169 path: Box<dyn Expression>,
170}
171
172impl FunctionExpression for GetFn {
173 fn resolve(&self, ctx: &mut Context) -> Resolved {
174 let path = self.path.resolve(ctx)?;
175 let value = self.value.resolve(ctx)?;
176
177 get(&value, path)
178 }
179
180 fn type_def(&self, _: &state::TypeState) -> TypeDef {
181 TypeDef::any().fallible()
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::value;
189
190 test_function![
191 get => Get;
192
193 any {
194 args: func_args![value: value!([42]), path: value!([0])],
195 want: Ok(42),
196 tdef: TypeDef::any().fallible(),
197 }
198 ];
199}