use crate::compiler::prelude::*;
use crate::path::{OwnedSegment, OwnedValuePath};
#[allow(clippy::cast_possible_truncation)] fn get(value: &Value, value_path: Value) -> Resolved {
let path = match value_path {
Value::Array(array) => {
let mut path = OwnedValuePath::root();
for segment in array {
let segment = match segment {
Value::Bytes(field) => {
OwnedSegment::field(String::from_utf8_lossy(&field).as_ref())
}
Value::Integer(index) => OwnedSegment::index(index as isize),
value => {
return Err(format!(
"path segment must be either string or integer, not {}",
value.kind()
)
.into());
}
};
path.push(segment);
}
path
}
value => {
return Err(ValueError::Expected {
got: value.kind(),
expected: Kind::array(Collection::any()),
}
.into());
}
};
Ok(value.get(&path).cloned().unwrap_or(Value::Null))
}
#[derive(Clone, Copy, Debug)]
pub struct Get;
impl Function for Get {
fn identifier(&self) -> &'static str {
"get"
}
fn usage(&self) -> &'static str {
indoc! {"
Dynamically get the value of a given path.
If you know the path you want to look up, use
static paths such as `.foo.bar[1]` to get the value of that
path. However, if you do not know the path names,
use the dynamic `get` function to get the requested value.
"}
}
fn category(&self) -> &'static str {
Category::Path.as_ref()
}
fn internal_failure_reasons(&self) -> &'static [&'static str] {
&["The `path` segment must be a string or an integer."]
}
fn return_kind(&self) -> u16 {
kind::ANY
}
fn parameters(&self) -> &'static [Parameter] {
const PARAMETERS: &[Parameter] = &[
Parameter::required(
"value",
kind::OBJECT | kind::ARRAY,
"The object or array to query.",
),
Parameter::required(
"path",
kind::ARRAY,
"An array of path segments to look for the value.",
),
];
PARAMETERS
}
fn examples(&self) -> &'static [Example] {
&[
example! {
title: "Single-segment top-level field",
source: r#"get!(value: {"foo": "bar"}, path: ["foo"])"#,
result: Ok(r#""bar""#),
},
example! {
title: "Returns null for unknown field",
source: r#"get!(value: {"foo": "bar"}, path: ["baz"])"#,
result: Ok("null"),
},
example! {
title: "Multi-segment nested field",
source: r#"get!(value: {"foo": { "bar": true }}, path: ["foo", "bar"])"#,
result: Ok("true"),
},
example! {
title: "Array indexing",
source: "get!(value: [92, 42], path: [0])",
result: Ok("92"),
},
example! {
title: "Array indexing (negative)",
source: r#"get!(value: ["foo", "bar", "baz"], path: [-2])"#,
result: Ok(r#""bar""#),
},
example! {
title: "Nested indexing",
source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1])"#,
result: Ok("42"),
},
example! {
title: "External target",
source: r#"get!(value: ., path: ["foo"])"#,
input: r#"{ "foo": true }"#,
result: Ok("true"),
},
example! {
title: "Variable",
source: indoc! {r#"
var = { "foo": true }
get!(value: var, path: ["foo"])
"#},
result: Ok("true"),
},
example! {
title: "Missing index",
source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", "bar", 1, -1])"#,
result: Ok("null"),
},
example! {
title: "Invalid indexing",
source: r#"get!(value: [42], path: ["foo"])"#,
result: Ok("null"),
},
example! {
title: "Invalid segment type",
source: r#"get!(value: {"foo": { "bar": [92, 42] }}, path: ["foo", true])"#,
result: Err(
r#"function call error for "get" at (0:62): path segment must be either string or integer, not boolean"#,
),
},
]
}
fn compile(
&self,
_state: &state::TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let value = arguments.required("value");
let path = arguments.required("path");
Ok(GetFn { value, path }.as_expr())
}
}
#[derive(Debug, Clone)]
pub(crate) struct GetFn {
value: Box<dyn Expression>,
path: Box<dyn Expression>,
}
impl FunctionExpression for GetFn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let path = self.path.resolve(ctx)?;
let value = self.value.resolve(ctx)?;
get(&value, path)
}
fn type_def(&self, _: &state::TypeState) -> TypeDef {
TypeDef::any().fallible()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value;
test_function![
get => Get;
any {
args: func_args![value: value!([42]), path: value!([0])],
want: Ok(42),
tdef: TypeDef::any().fallible(),
}
];
}