diff --git a/apollo-federation/src/sources/connect/json_selection/methods.rs b/apollo-federation/src/sources/connect/json_selection/methods.rs index 30291bafed..6caa0329e2 100644 --- a/apollo-federation/src/sources/connect/json_selection/methods.rs +++ b/apollo-federation/src/sources/connect/json_selection/methods.rs @@ -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"); @@ -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); diff --git a/apollo-federation/src/sources/connect/json_selection/methods/future.rs b/apollo-federation/src/sources/connect/json_selection/methods/future.rs index cc98d542f9..edfec6dfac 100644 --- a/apollo-federation/src/sources/connect/json_selection/methods/future.rs +++ b/apollo-federation/src/sources/connect/json_selection/methods/future.rs @@ -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; @@ -133,6 +134,58 @@ pub(super) fn match_if_method( ) } +pub(super) fn map_values_method( + method_name: &WithRange, + method_args: Option<&MethodArgs>, + data: &JSON, + vars: &VarsWithPathsMap, + input_path: &InputPath, + tail: &WithRange, +) -> (Option, Vec) { + 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, method_args: Option<&MethodArgs>, diff --git a/apollo-federation/src/sources/connect/json_selection/methods/tests.rs b/apollo-federation/src/sources/connect/json_selection/methods/tests.rs index 6b6a7b6dc3..69fd2305e6 100644 --- a/apollo-federation/src/sources/connect/json_selection/methods/tests.rs +++ b/apollo-federation/src/sources/connect/json_selection/methods/tests.rs @@ -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!(