Skip to content

Commit

Permalink
[JSONSelection] Add ->mapValues method to future:: namespace.
Browse files Browse the repository at this point in the history
PR #6185 removed support for the following syntax for preserving dynamic
keys of dictionary objects while mapping their values:
```graphql
booksByISBN: books { * { title author }}
```

As suggested in the description of PR #6185, this pattern can be
replaced with a `->mapValues` method call:
```graphql
booksByISBN: books->mapValues(@ { title author })
```

This PR tentatively implements that `->mapValues` method, filing it away
in the `future::` namespace to indicate it hasn't been shipped yet.
  • Loading branch information
benjamn committed Oct 23, 2024
1 parent 82e262b commit a26e478
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ lazy_static! {
public_methods.insert("match");
// public_methods.insert("matchIf");
// public_methods.insert("match_if");
// public_methods.insert("mapValues");
// public_methods.insert("add");
// public_methods.insert("sub");
// public_methods.insert("mul");
Expand Down Expand Up @@ -115,6 +116,11 @@ lazy_static! {
methods.insert("matchIf".to_string(), future::match_if_method);
methods.insert("match_if".to_string(), future::match_if_method);

// Takes an object with unknown keys, binds @ to each of those keys'
// values, evaluates the first argument against each @ value, then
// produces a new object with the same keys but newly mapped values.
methods.insert("mapValues".to_string(), future::map_values_method);

// Arithmetic methods
methods.insert("add".to_string(), future::add_method);
methods.insert("sub".to_string(), future::sub_method);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// and tests. After careful review, they may one day move to public.rs.

use serde_json::Number;
use serde_json_bytes::Map as JSONMap;
use serde_json_bytes::Value as JSON;

use crate::sources::connect::json_selection::apply_to::ApplyToResultMethods;
Expand Down Expand Up @@ -133,6 +134,58 @@ pub(super) fn match_if_method(
)
}

pub(super) fn map_values_method(
method_name: &WithRange<String>,
method_args: Option<&MethodArgs>,
data: &JSON,
vars: &VarsWithPathsMap,
input_path: &InputPath<JSON>,
tail: &WithRange<PathList>,
) -> (Option<JSON>, Vec<ApplyToError>) {
if let Some(first_arg) = method_args.and_then(|args| args.args.first()) {
if let JSON::Object(map) = data {
let mut new_map = JSONMap::new();
let mut errors = Vec::new();
for (key, value) in map {
let new_key = key.clone();
let (new_value_opt, value_errors) =
first_arg.apply_to_path(value, vars, input_path);
errors.extend(value_errors);
if let Some(new_value) = new_value_opt {
new_map.insert(new_key, new_value);
}
}
tail.apply_to_path(&JSON::Object(new_map), vars, input_path)
.prepend_errors(errors)
} else {
(
None,
vec![ApplyToError::new(
format!(
"Method ->{} requires an object input, not {}",
method_name.as_ref(),
json_type_name(data),
),
input_path.to_vec(),
method_name.range(),
)],
)
}
} else {
(
None,
vec![ApplyToError::new(
format!(
"Method ->{} requires exactly one argument",
method_name.as_ref()
),
input_path.to_vec(),
method_name.range(),
)],
)
}
}

pub(super) fn arithmetic_method(
method_name: &WithRange<String>,
method_args: Option<&MethodArgs>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,67 @@ fn test_match_methods() {
);
}

#[test]
fn test_map_values_method() {
assert_eq!(
selection!("$->mapValues(@->add(10))").apply_to(&json!({
"a": 1,
"b": 2,
"c": 3,
})),
(
Some(json!({
"a": 11,
"b": 12,
"c": 13,
})),
vec![],
),
);

assert_eq!(
selection!("$->mapValues(@->typeof)").apply_to(&json!({
"a": 1,
"b": "two",
"c": false,
})),
(
Some(json!({
"a": "number",
"b": "string",
"c": "boolean",
})),
vec![],
),
);

assert_eq!(
selection!("$->mapValues(@->typeof)").apply_to(&json!({})),
(Some(json!({})), vec![]),
);

assert_eq!(
selection!("$->map(@->mapValues(@->typeof))").apply_to(&json!([])),
(Some(json!([])), vec![]),
);

assert_eq!(
selection!("$->map(@->mapValues(@->typeof))").apply_to(&json!([{
"x": 1,
"y": "two",
"z": false,
}])),
(
Some(json!([{
"x": "number",
"y": "string",
"z": "boolean",
}])),
vec![],
),
);
}

#[test]
fn test_arithmetic_methods() {
assert_eq!(
Expand Down

0 comments on commit a26e478

Please sign in to comment.