use serde_json_bytes::Value as JSON;
use shape::Shape;
use crate::connectors::json_selection::ApplyToError;
use crate::connectors::json_selection::ApplyToInternal;
use crate::connectors::json_selection::MethodArgs;
use crate::connectors::json_selection::ShapeContext;
use crate::connectors::json_selection::VarsWithPathsMap;
use crate::connectors::json_selection::apply_to::ApplyToResultMethods;
use crate::connectors::json_selection::helpers::vec_push;
use crate::connectors::json_selection::immutable::InputPath;
use crate::connectors::json_selection::lit_expr::LitExpr;
use crate::connectors::json_selection::location::Ranged;
use crate::connectors::json_selection::location::WithRange;
use crate::connectors::json_selection::location::merge_ranges;
use crate::connectors::spec::ConnectSpec;
use crate::impl_arrow_method;
impl_arrow_method!(MatchIfMethod, match_if_method, match_if_shape);
fn match_if_method(
method_name: &WithRange<String>,
method_args: Option<&MethodArgs>,
data: &JSON,
vars: &VarsWithPathsMap,
input_path: &InputPath<JSON>,
spec: ConnectSpec,
) -> (Option<JSON>, Vec<ApplyToError>) {
let mut errors = Vec::new();
if let Some(MethodArgs { args, .. }) = method_args {
for pair in args {
if let LitExpr::Array(pair) = pair.as_ref() {
let (pattern, value) = match pair.as_slice() {
[pattern, value] => (pattern, value),
_ => continue,
};
let (condition_opt, condition_errors) =
pattern.apply_to_path(data, vars, input_path, spec);
errors.extend(condition_errors);
if condition_opt == Some(JSON::Bool(true)) {
return value
.apply_to_path(data, vars, input_path, spec)
.prepend_errors(errors);
};
}
}
}
(
None,
vec_push(
errors,
ApplyToError::new(
format!(
"Method ->{} did not match any [condition, value] pair",
method_name.as_ref(),
),
input_path.to_vec(),
merge_ranges(
method_name.range(),
method_args.and_then(|args| args.range()),
),
spec,
),
),
)
}
#[allow(dead_code)] fn match_if_shape(
context: &ShapeContext,
method_name: &WithRange<String>,
method_args: Option<&MethodArgs>,
input_shape: Shape,
dollar_shape: Shape,
) -> Shape {
use super::super::public::match_shape;
match_shape(context, method_name, method_args, input_shape, dollar_shape)
}
#[cfg(test)]
mod tests {
use serde_json_bytes::json;
use crate::connectors::ConnectSpec;
use crate::connectors::json_selection::ApplyToError;
use crate::selection;
#[test]
fn match_if_should_return_first_element_evaluated_to_true() {
assert_eq!(
selection!(
r#"
num: value->matchIf(
[@->typeof->eq('number'), @],
[true, 'not a number']
)
"#
)
.apply_to(&json!({ "value": 123 })),
(
Some(json!({
"num": 123,
})),
vec![],
),
);
}
#[test]
fn match_if_should_return_default_true_element_when_no_other_matches() {
assert_eq!(
selection!(
r#"
num: value->matchIf(
[@->typeof->eq('number'), @],
[true, 'not a number']
)
"#
)
.apply_to(&json!({ "value": true })),
(
Some(json!({
"num": "not a number",
})),
vec![],
),
);
}
#[rstest::rstest]
#[case::v0_2(ConnectSpec::V0_2)]
#[case::v0_3(ConnectSpec::V0_3)]
#[case::v0_4(ConnectSpec::V0_4)]
fn match_if_should_return_none_when_condition_argument_evaluates_to_none(
#[case] spec: ConnectSpec,
) {
assert_eq!(
selection!("$.a->matchIf([$.missing, 'default'])", spec).apply_to(&json!({
"a": "test",
})),
(
None,
vec![
ApplyToError::from_json(&json!({
"message": "Property .missing not found in object",
"path": ["missing"],
"range": [16, 23],
"spec": spec.to_string(),
})),
ApplyToError::from_json(&json!({
"message": "Method ->matchIf did not match any [condition, value] pair",
"path": ["a", "->matchIf"],
"range": [5, 36],
"spec": spec.to_string(),
}))
]
),
);
}
}