diff --git a/.changesets/feat_clenfest_set_context.md b/.changesets/feat_clenfest_set_context.md new file mode 100644 index 0000000000..025348f883 --- /dev/null +++ b/.changesets/feat_clenfest_set_context.md @@ -0,0 +1,7 @@ +### Add ability for router to deal with query plans with contextual rewrites ([PR #5097](https://github.com/apollographql/router/pull/5097)) + +Adds the ability for the router to execute query plans with context rewrites on them. These are generated by the @fromContext directive and are used to map a Value in the collected data JSON onto a variable which will in turn be used as an argument to a field resolver. + +⚠️ This ships with a new version of federation, which means distributed caches will be repopulated. + +By [@clenfest](https://github.com/clenfest) in https://github.com/apollographql/router/pull/5097 \ No newline at end of file diff --git a/.config/nextest.toml b/.config/nextest.toml index f081bbc39a..df8bab66e6 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -19,6 +19,6 @@ path = "junit.xml" # Integration tests require more than one thread. The default setting of 1 will cause too many integration tests to run # at the same time and causes tests to fail where timing is involved. # This filter applies only to to the integration tests in the apollo-router package. -[[profile.default.overrides]] -filter = 'package(apollo-router) & kind(test)' -threads-required = 2 +[[profile.ci.overrides]] +filter = 'test(/^apollo-router::/)' +threads-required = 4 diff --git a/Cargo.lock b/Cargo.lock index c7e0d1f43d..4b039c23aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1374,9 +1374,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bytes-utils" @@ -1940,9 +1940,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -2419,7 +2419,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve 0.13.8", "rfc6979 0.4.0", - "signature 2.0.0", + "signature 2.2.0", "spki 0.7.2", ] @@ -2659,9 +2659,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" @@ -3954,9 +3954,9 @@ dependencies = [ [[package]] name = "libz-ng-sys" -version = "1.1.12" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd9f43e75536a46ee0f92b758f6b63846e594e86638c61a9251338a65baea63" +checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5" dependencies = [ "cmake", "libc", @@ -4150,9 +4150,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -5195,7 +5195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit 0.19.14", + "toml_edit 0.19.15", ] [[package]] @@ -5776,9 +5776,9 @@ dependencies = [ [[package]] name = "router-bridge" -version = "0.5.21+v2.7.5" +version = "0.5.24+v2.8.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2142445fe3fe2aae7a3c3c5083d1211a448a0dabb489a14dd90d427cf6c0b13" +checksum = "4a4b92a40b68c797d2d624716d5671e80de578f00c5012af37f19c74b175566b" dependencies = [ "anyhow", "async-channel 1.9.0", @@ -6149,9 +6149,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -6167,9 +6167,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2 1.0.76", "quote 1.0.35", @@ -6201,9 +6201,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "indexmap 2.2.3", "itoa", @@ -6385,9 +6385,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -7070,9 +7070,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.2.3", "toml_datetime", @@ -7609,9 +7609,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-id" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" [[package]] name = "unicode-ident" @@ -8205,7 +8205,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", "serde", "zeroize", diff --git a/apollo-federation/src/link/join_spec_definition.rs b/apollo-federation/src/link/join_spec_definition.rs index c2b011e244..bf8734a4e6 100644 --- a/apollo-federation/src/link/join_spec_definition.rs +++ b/apollo-federation/src/link/join_spec_definition.rs @@ -336,6 +336,10 @@ lazy_static! { Version { major: 0, minor: 3 }, Some(Version { major: 2, minor: 0 }), )); + definitions.add(JoinSpecDefinition::new( + Version { major: 0, minor: 5 }, + Some(Version { major: 2, minor: 8 }), + )); definitions }; diff --git a/apollo-federation/src/query_plan/display.rs b/apollo-federation/src/query_plan/display.rs index 8c91f3eac1..37042d08d7 100644 --- a/apollo-federation/src/query_plan/display.rs +++ b/apollo-federation/src/query_plan/display.rs @@ -83,6 +83,7 @@ impl FetchNode { operation_kind: _, input_rewrites: _, output_rewrites: _, + context_rewrites: _, } = self; state.write(format_args!("Fetch(service: {subgraph_name:?}"))?; if let Some(id) = id { diff --git a/apollo-federation/src/query_plan/fetch_dependency_graph.rs b/apollo-federation/src/query_plan/fetch_dependency_graph.rs index c23054a4e8..271cf95dc0 100644 --- a/apollo-federation/src/query_plan/fetch_dependency_graph.rs +++ b/apollo-federation/src/query_plan/fetch_dependency_graph.rs @@ -1320,6 +1320,7 @@ impl FetchDependencyGraphNode { operation_kind: self.root_kind.into(), input_rewrites: self.input_rewrites.clone(), output_rewrites, + context_rewrites: Default::default(), })); Ok(Some(if let Some(path) = self.merge_at.clone() { diff --git a/apollo-federation/src/query_plan/mod.rs b/apollo-federation/src/query_plan/mod.rs index 14659b5935..ce19b6cb7f 100644 --- a/apollo-federation/src/query_plan/mod.rs +++ b/apollo-federation/src/query_plan/mod.rs @@ -86,6 +86,9 @@ pub struct FetchNode { /// Similar to `input_rewrites`, but for optional "rewrites" to apply to the data that is /// received from a fetch (and before it is applied to the current in-memory results). pub output_rewrites: Vec>, + /// Similar to the other kinds of rewrites. This is a mechanism to convert a contextual path into + /// an argument to a resolver + pub context_rewrites: Vec>, } #[derive(Debug, Clone)] diff --git a/apollo-federation/src/query_plan/query_planner.rs b/apollo-federation/src/query_plan/query_planner.rs index 8697dd118c..ae5085c9ab 100644 --- a/apollo-federation/src/query_plan/query_planner.rs +++ b/apollo-federation/src/query_plan/query_planner.rs @@ -349,6 +349,7 @@ impl QueryPlanner { requires: Default::default(), input_rewrites: Default::default(), output_rewrites: Default::default(), + context_rewrites: Default::default(), }; return Ok(QueryPlan::new(node, statistics)); diff --git a/apollo-federation/tests/query_plan/build_query_plan_tests.rs b/apollo-federation/tests/query_plan/build_query_plan_tests.rs index 4c8bf1f13b..0e56f58f87 100644 --- a/apollo-federation/tests/query_plan/build_query_plan_tests.rs +++ b/apollo-federation/tests/query_plan/build_query_plan_tests.rs @@ -153,8 +153,6 @@ fn pick_keys_that_minimize_fetches() { /// (more precisely, this force the query planner to _consider_ type explosion; the generated /// query plan still ends up not type-exploding in practice since as it's not necessary). #[test] -#[should_panic(expected = "snapshot assertion")] -// TODO: investigate this failure fn field_covariance_and_type_explosion() { let planner = planner!( Subgraph1: r#" @@ -195,23 +193,22 @@ fn field_covariance_and_type_explosion() { } "#, @r###" - QueryPlan { - Fetch(service: "Subgraph1") { - { - dummy { + QueryPlan { + Fetch(service: "Subgraph1") { + { + dummy { + field { + __typename + ... on Object { + field { __typename - field { - __typename - ... on Object { - field { - __typename - } - } - } } } - }, + } } - "### + } + }, + } + "### ); } diff --git a/apollo-federation/tests/query_plan/build_query_plan_tests/provides.rs b/apollo-federation/tests/query_plan/build_query_plan_tests/provides.rs index 23ba713f10..79cc960d0e 100644 --- a/apollo-federation/tests/query_plan/build_query_plan_tests/provides.rs +++ b/apollo-federation/tests/query_plan/build_query_plan_tests/provides.rs @@ -302,49 +302,58 @@ fn it_works_on_unions() { // This is our sanity check: we first query _without_ the provides // to make sure we _do_ need to go the the second subgraph. @r###" - QueryPlan { - Sequence { - Fetch(service: "Subgraph1") { + QueryPlan { + Sequence { + Fetch(service: "Subgraph1") { + { + noProvides { + ... on T1 { + __typename + id + } + ... on T2 { + __typename + id + a + } + } + } + }, + Parallel { + Flatten(path: "noProvides") { + Fetch(service: "Subgraph2") { { - noProvides { + ... on T2 { __typename - ... on T1 { - __typename - id - } - ... on T2 { - __typename - id - a - } + id + } + } => + { + ... on T2 { + b } } }, - Flatten(path: "noProvides") { - Fetch(service: "Subgraph2") { - { - ... on T1 { - __typename - id - } - ... on T2 { - __typename - id - } - } => - { - ... on T1 { - a - } - ... on T2 { - b - } + }, + Flatten(path: "noProvides") { + Fetch(service: "Subgraph2") { + { + ... on T1 { + __typename + id } - }, + } => + { + ... on T1 { + a + } + } }, }, - } - "### + }, + }, + } + "### ); // Ensuring that querying only `a` can be done with subgraph1 only when provided. @@ -363,22 +372,21 @@ fn it_works_on_unions() { } "#, @r###" - QueryPlan { - Fetch(service: "Subgraph1") { - { - withProvidesForT1 { - __typename - ... on T1 { - a - } - ... on T2 { - a - } - } + QueryPlan { + Fetch(service: "Subgraph1") { + { + withProvidesForT1 { + ... on T1 { + a } - }, + ... on T2 { + a + } + } } - "### + }, + } + "### ); // But ensure that querying `b` still goes to subgraph2 if only a is provided. @@ -398,41 +406,40 @@ fn it_works_on_unions() { } "#, @r###" - QueryPlan { - Sequence { - Fetch(service: "Subgraph1") { - { - withProvidesForT1 { - __typename - ... on T1 { - a - } - ... on T2 { - __typename - id - a - } - } + QueryPlan { + Sequence { + Fetch(service: "Subgraph1") { + { + withProvidesForT1 { + ... on T1 { + a } - }, - Flatten(path: "withProvidesForT1") { - Fetch(service: "Subgraph2") { - { - ... on T2 { - __typename - id - } - } => - { - ... on T2 { - b - } - } - }, - }, + ... on T2 { + __typename + id + a + } + } + } + }, + Flatten(path: "withProvidesForT1") { + Fetch(service: "Subgraph2") { + { + ... on T2 { + __typename + id + } + } => + { + ... on T2 { + b + } + } }, - } - "### + }, + }, + } + "### ); // Lastly, if both are provided, ensures we only hit subgraph1. diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index 02c72d6e4e..943057738a 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -82,7 +82,7 @@ axum = { version = "0.6.20", features = ["headers", "json", "original-uri"] } base64 = "0.21.7" bloomfilter = "1.0.13" buildstructor = "0.5.4" -bytes = "1.5.0" +bytes = "1.6.0" clap = { version = "4.5.1", default-features = false, features = [ "env", "derive", @@ -188,7 +188,7 @@ regex = "1.10.3" reqwest.workspace = true # note: this dependency should _always_ be pinned, prefix the version with an `=` -router-bridge = "=0.5.21+v2.7.5" +router-bridge = "=0.5.24+v2.8.0-alpha.1" rust-embed = "8.2.0" rustls = "0.21.11" diff --git a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap index d680c0b53f..cb657dcdce 100644 --- a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap +++ b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap @@ -18,6 +18,7 @@ expression: query_plan "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "12dda6193654ae4fe6e38bc09d4f81cc73d0c9e098692096f72d2158eef4776f", "authorization": { "is_authenticated": false, diff --git a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap index 612b147fc2..d18a3e2b11 100644 --- a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap +++ b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap @@ -23,6 +23,7 @@ expression: query_plan "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "00ad582ea45fc1bce436b36b21512f3d2c47b74fdbdc61e4b349289722c9ecf2", "authorization": { "is_authenticated": false, @@ -61,6 +62,7 @@ expression: query_plan "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "a8ebdc2151a2e5207882e43c6906c0c64167fd9a8e0c7c4becc47736a5105096", "authorization": { "is_authenticated": false, diff --git a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap index b32a905c0e..0d6ab611f6 100644 --- a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap +++ b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap @@ -68,6 +68,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "7245d488e97c3b2ac9f5fa4dd4660940b94ad81af070013305b2c0f76337b2f9", "authorization": { "is_authenticated": false, @@ -107,6 +108,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "6e0b4156706ea0cf924500cfdc99dd44b9f0ed07e2d3f888d4aff156e6a33238", "authorization": { "is_authenticated": false, @@ -153,6 +155,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "ff649f3d70241d5a8cd5f5d03ff4c41ecff72b0e4129a480207b05ac92318042", "authorization": { "is_authenticated": false, @@ -196,6 +199,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "bf9f3beda78a7a565e47c862157bad4ec871d724d752218da1168455dddca074", "authorization": { "is_authenticated": false, diff --git a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap index b32a905c0e..0d6ab611f6 100644 --- a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap +++ b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap @@ -68,6 +68,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "7245d488e97c3b2ac9f5fa4dd4660940b94ad81af070013305b2c0f76337b2f9", "authorization": { "is_authenticated": false, @@ -107,6 +108,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "6e0b4156706ea0cf924500cfdc99dd44b9f0ed07e2d3f888d4aff156e6a33238", "authorization": { "is_authenticated": false, @@ -153,6 +155,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "ff649f3d70241d5a8cd5f5d03ff4c41ecff72b0e4129a480207b05ac92318042", "authorization": { "is_authenticated": false, @@ -196,6 +199,7 @@ expression: "serde_json::to_value(response).unwrap()" "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "bf9f3beda78a7a565e47c862157bad4ec871d724d752218da1168455dddca074", "authorization": { "is_authenticated": false, diff --git a/apollo-router/src/query_planner/convert.rs b/apollo-router/src/query_planner/convert.rs index 2f24e35b9d..e75a2474b9 100644 --- a/apollo-router/src/query_planner/convert.rs +++ b/apollo-router/src/query_planner/convert.rs @@ -74,6 +74,7 @@ impl From<&'_ Box> for plan::PlanNode { operation_kind, input_rewrites, output_rewrites, + context_rewrites, } = &**value; Self::Fetch(super::fetch::FetchNode { service_name: subgraph_name.clone(), @@ -86,6 +87,7 @@ impl From<&'_ Box> for plan::PlanNode { id: id.map(|id| id.to_string().into()), input_rewrites: option_vec(input_rewrites), output_rewrites: option_vec(output_rewrites), + context_rewrites: option_vec(context_rewrites), schema_aware_hash: Default::default(), authorization: Default::default(), }) @@ -153,6 +155,7 @@ impl From<&'_ next::FetchNode> for subscription::SubscriptionNode { operation_kind, input_rewrites, output_rewrites, + context_rewrites: _, } = value; Self { service_name: subgraph_name.clone(), diff --git a/apollo-router/src/query_planner/execution.rs b/apollo-router/src/query_planner/execution.rs index 801069afd1..dc1e27123e 100644 --- a/apollo-router/src/query_planner/execution.rs +++ b/apollo-router/src/query_planner/execution.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; +use apollo_compiler::validation::Valid; use apollo_compiler::NodeStr; use futures::future::join_all; use futures::prelude::*; @@ -50,6 +51,7 @@ impl QueryPlan { service_factory: &'a Arc, supergraph_request: &'a Arc>, schema: &'a Arc, + subgraph_schemas: &'a Arc>>>, sender: mpsc::Sender, subscription_handle: Option, subscription_config: &'a Option, @@ -73,6 +75,7 @@ impl QueryPlan { root_node: &self.root, subscription_handle: &subscription_handle, subscription_config, + subgraph_schemas, }, &root, &initial_value.unwrap_or_default(), @@ -100,6 +103,7 @@ pub(crate) struct ExecutionParameters<'a> { pub(crate) context: &'a Context, pub(crate) service_factory: &'a Arc, pub(crate) schema: &'a Arc, + pub(crate) subgraph_schemas: &'a Arc>>>, pub(crate) supergraph_request: &'a Arc>, pub(crate) deferred_fetches: &'a HashMap)>>, pub(crate) query: &'a Arc, @@ -293,6 +297,7 @@ impl PlanNode { root_node: parameters.root_node, subscription_handle: parameters.subscription_handle, subscription_config: parameters.subscription_config, + subgraph_schemas: parameters.subgraph_schemas, }, current_dir, &value, @@ -435,6 +440,7 @@ impl DeferredNode { let label = self.label.as_ref().map(|l| l.to_string()); let tx = sender; let sc = parameters.schema.clone(); + let subgraph_schemas = parameters.subgraph_schemas.clone(); let orig = parameters.supergraph_request.clone(); let sf = parameters.service_factory.clone(); let root_node = parameters.root_node.clone(); @@ -481,6 +487,7 @@ impl DeferredNode { root_node: &root_node, subscription_handle: &subscription_handle, subscription_config: &subscription_config, + subgraph_schemas: &subgraph_schemas, }, &Path::default(), &value, diff --git a/apollo-router/src/query_planner/fetch.rs b/apollo-router/src/query_planner/fetch.rs index f76a6a2d2d..a20ad3ffda 100644 --- a/apollo-router/src/query_planner/fetch.rs +++ b/apollo-router/src/query_planner/fetch.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fmt::Display; use std::sync::Arc; +use apollo_compiler::ast; use apollo_compiler::validation::Valid; use apollo_compiler::ExecutableDocument; use apollo_compiler::NodeStr; @@ -17,6 +18,9 @@ use super::execution::ExecutionParameters; use super::rewrites; use super::selection::execute_selection_set; use super::selection::Selection; +use super::subgraph_context::build_operation_with_aliasing; +use super::subgraph_context::ContextualArguments; +use super::subgraph_context::SubgraphContext; use crate::error::Error; use crate::error::FetchError; use crate::error::ValidationErrors; @@ -70,22 +74,22 @@ impl OperationKind { } } -impl From for apollo_compiler::ast::OperationType { +impl From for ast::OperationType { fn from(value: OperationKind) -> Self { match value { - OperationKind::Query => apollo_compiler::ast::OperationType::Query, - OperationKind::Mutation => apollo_compiler::ast::OperationType::Mutation, - OperationKind::Subscription => apollo_compiler::ast::OperationType::Subscription, + OperationKind::Query => ast::OperationType::Query, + OperationKind::Mutation => ast::OperationType::Mutation, + OperationKind::Subscription => ast::OperationType::Subscription, } } } -impl From for OperationKind { - fn from(value: apollo_compiler::ast::OperationType) -> Self { +impl From for OperationKind { + fn from(value: ast::OperationType) -> Self { match value { - apollo_compiler::ast::OperationType::Query => OperationKind::Query, - apollo_compiler::ast::OperationType::Mutation => OperationKind::Mutation, - apollo_compiler::ast::OperationType::Subscription => OperationKind::Subscription, + ast::OperationType::Query => OperationKind::Query, + ast::OperationType::Mutation => OperationKind::Mutation, + ast::OperationType::Subscription => OperationKind::Subscription, } } } @@ -125,6 +129,9 @@ pub(crate) struct FetchNode { // Optionally describes a number of "rewrites" to apply to the data that received from a fetch (and before it is applied to the current in-memory results). pub(crate) output_rewrites: Option>, + // Optionally describes a number of "rewrites" to apply to the data that has already been received further up the tree + pub(crate) context_rewrites: Option>, + // hash for the query and relevant parts of the schema. if two different schemas provide the exact same types, fields and directives // affecting the query, then they will have the same hash #[serde(default)] @@ -243,6 +250,7 @@ impl Display for QueryHash { pub(crate) struct Variables { pub(crate) variables: Object, pub(crate) inverted_paths: Vec>, + pub(crate) contextual_arguments: Option, } impl Variables { @@ -256,8 +264,10 @@ impl Variables { request: &Arc>, schema: &Schema, input_rewrites: &Option>, + context_rewrites: &Option>, ) -> Option { let body = request.body(); + let mut subgraph_context = SubgraphContext::new(data, schema, context_rewrites); if !requires.is_empty() { let mut variables = Object::with_capacity(1 + variable_usages.len()); @@ -269,8 +279,12 @@ impl Variables { let mut inverted_paths: Vec> = Vec::new(); let mut values: IndexSet = IndexSet::new(); - data.select_values_and_paths(schema, current_dir, |path, value| { + // first get contextual values that are required + if let Some(context) = subgraph_context.as_mut() { + context.execute_on_path(path); + } + let mut value = execute_selection_set(value, requires, schema, None); if value.as_object().map(|o| !o.is_empty()).unwrap_or(false) { rewrites::apply_rewrites(schema, &mut value, input_rewrites); @@ -292,11 +306,16 @@ impl Variables { } let representations = Value::Array(Vec::from_iter(values)); + let contextual_arguments = match subgraph_context.as_mut() { + Some(context) => context.add_variables_and_get_args(&mut variables), + None => None, + }; variables.insert("representations", representations); Some(Variables { variables, inverted_paths, + contextual_arguments, }) } else { // with nested operations (Query or Mutation has an operation returning a Query or Mutation), @@ -323,6 +342,7 @@ impl Variables { }) .collect::(), inverted_paths: Vec::new(), + contextual_arguments: None, }) } } @@ -355,6 +375,7 @@ impl FetchNode { let Variables { variables, inverted_paths: paths, + contextual_arguments, } = match Variables::new( &self.requires, &self.variable_usages, @@ -364,6 +385,7 @@ impl FetchNode { parameters.supergraph_request, parameters.schema, &self.input_rewrites, + &self.context_rewrites, ) { Some(variables) => variables, None => { @@ -371,6 +393,35 @@ impl FetchNode { } }; + let alias_query_string; // this exists outside the if block to allow the as_str() to be longer lived + let aliased_operation = if let Some(ctx_arg) = contextual_arguments { + if let Some(subgraph_schema) = + parameters.subgraph_schemas.get(&service_name.to_string()) + { + match build_operation_with_aliasing(operation, &ctx_arg, subgraph_schema) { + Ok(op) => { + alias_query_string = op.serialize().no_indent().to_string(); + alias_query_string.as_str() + } + Err(errors) => { + tracing::debug!( + "couldn't generate a valid executable document? {:?}", + errors + ); + operation.as_serialized() + } + } + } else { + tracing::debug!( + "couldn't find a subgraph schema for service {:?}", + &service_name + ); + operation.as_serialized() + } + } else { + operation.as_serialized() + }; + let mut subgraph_request = SubgraphRequest::builder() .supergraph_request(parameters.supergraph_request.clone()) .subgraph_request( @@ -389,7 +440,7 @@ impl FetchNode { ) .body( Request::builder() - .query(operation.as_serialized()) + .query(aliased_operation) .and_operation_name(operation_name.as_ref().map(|n| n.to_string())) .variables(variables.clone()) .build(), diff --git a/apollo-router/src/query_planner/mod.rs b/apollo-router/src/query_planner/mod.rs index a11a5cb072..58285e3638 100644 --- a/apollo-router/src/query_planner/mod.rs +++ b/apollo-router/src/query_planner/mod.rs @@ -20,6 +20,7 @@ mod labeler; mod plan; pub(crate) mod rewrites; mod selection; +mod subgraph_context; pub(crate) mod subscription; pub(crate) const FETCH_SPAN_NAME: &str = "fetch"; diff --git a/apollo-router/src/query_planner/rewrites.rs b/apollo-router/src/query_planner/rewrites.rs index 94453630df..f3b10a7fa4 100644 --- a/apollo-router/src/query_planner/rewrites.rs +++ b/apollo-router/src/query_planner/rewrites.rs @@ -47,7 +47,7 @@ pub(crate) struct DataKeyRenamer { } impl DataRewrite { - fn maybe_apply(&self, schema: &Schema, data: &mut Value) { + pub(crate) fn maybe_apply(&self, schema: &Schema, data: &mut Value) { match self { DataRewrite::ValueSetter(setter) => { // The `path` of rewrites can only be either `Key` or `Fragment`, and so far diff --git a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap index 974ccc03c5..d49c351866 100644 --- a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap +++ b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap @@ -13,6 +13,7 @@ Fetch( id: None, input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: QueryHash( "a4ab3ffe0fd7863aea8cd1e85d019d2c64ec0351d62f9759bed3c9dc707ea315", ), diff --git a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__tests__query_plan_from_json.snap b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__tests__query_plan_from_json.snap index ef8a64f2a6..c18018d7a2 100644 --- a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__tests__query_plan_from_json.snap +++ b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__tests__query_plan_from_json.snap @@ -17,6 +17,7 @@ Sequence { id: None, input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: QueryHash( "", ), @@ -81,6 +82,7 @@ Sequence { id: None, input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: QueryHash( "", ), @@ -155,6 +157,7 @@ Sequence { id: None, input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: QueryHash( "", ), @@ -216,6 +219,7 @@ Sequence { id: None, input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: QueryHash( "", ), @@ -287,6 +291,7 @@ Sequence { id: None, input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: QueryHash( "", ), diff --git a/apollo-router/src/query_planner/subgraph_context.rs b/apollo-router/src/query_planner/subgraph_context.rs new file mode 100644 index 0000000000..6f1e03c4ad --- /dev/null +++ b/apollo-router/src/query_planner/subgraph_context.rs @@ -0,0 +1,460 @@ +use std::collections::HashMap; +use std::collections::HashSet; + +use apollo_compiler::ast; +use apollo_compiler::ast::Name; +use apollo_compiler::ast::VariableDefinition; +use apollo_compiler::executable; +use apollo_compiler::executable::Operation; +use apollo_compiler::executable::Selection; +use apollo_compiler::executable::SelectionSet; +use apollo_compiler::validation::Valid; +use apollo_compiler::validation::WithErrors; +use apollo_compiler::ExecutableDocument; +use apollo_compiler::Node; +use serde_json_bytes::ByteString; +use serde_json_bytes::Map; + +use super::fetch::SubgraphOperation; +use super::rewrites::DataKeyRenamer; +use super::rewrites::DataRewrite; +use crate::json_ext::Path; +use crate::json_ext::PathElement; +use crate::json_ext::Value; +use crate::json_ext::ValueExt; +use crate::spec::Schema; + +#[derive(Debug)] +pub(crate) struct ContextualArguments { + pub(crate) arguments: HashSet, // a set of all argument names that will be passed to the subgraph. This is the unmodified name from the query plan + pub(crate) count: usize, // the number of different sets of arguments that exist. This will either be 1 or the number of entities +} + +pub(crate) struct SubgraphContext<'a> { + pub(crate) data: &'a Value, + pub(crate) schema: &'a Schema, + pub(crate) context_rewrites: &'a Vec, + pub(crate) named_args: Vec>, +} + +// context_path is a non-standard relative path which may navigate up the tree +// from the current position. This is indicated with a ".." PathElement::Key +// note that the return value is an absolute path that may be used anywhere +fn merge_context_path( + current_dir: &Path, + context_path: &Path, +) -> Result { + let mut i = 0; + let mut j = current_dir.len(); + // iterate over the context_path(i), every time we encounter a '..', we want + // to go up one level in the current_dir(j) + while i < context_path.len() { + match &context_path.0.get(i) { + Some(PathElement::Key(e, _)) => { + let mut found = false; + if e == ".." { + while !found { + if j == 0 { + return Err(ContextBatchingError::InvalidRelativePath); + } + j -= 1; + + if let Some(PathElement::Key(_, _)) = current_dir.0.get(j) { + found = true; + } + } + i += 1; + } else { + break; + } + } + _ => break, + } + } + + let mut return_path: Vec = current_dir.iter().take(j).cloned().collect(); + + context_path.iter().skip(i).for_each(|e| { + return_path.push(e.clone()); + }); + Ok(Path(return_path.into_iter().collect())) +} + +impl<'a> SubgraphContext<'a> { + pub(crate) fn new( + data: &'a Value, + schema: &'a Schema, + context_rewrites: &'a Option>, + ) -> Option> { + if let Some(rewrites) = context_rewrites { + if !rewrites.is_empty() { + return Some(SubgraphContext { + data, + schema, + context_rewrites: rewrites, + named_args: Vec::new(), + }); + } + } + None + } + + // For each of the rewrites, start collecting data for the data at path. + // Once we find a Value for a given variable, skip additional rewrites that + // reference the same variable + pub(crate) fn execute_on_path(&mut self, path: &Path) { + let mut found_rewrites: HashSet = HashSet::new(); + let hash_map: HashMap = self + .context_rewrites + .iter() + .filter_map(|rewrite| { + match rewrite { + DataRewrite::KeyRenamer(item) => { + if !found_rewrites.contains(item.rename_key_to.as_str()) { + let wrapped_data_path = merge_context_path(path, &item.path); + if let Ok(data_path) = wrapped_data_path { + let val = self.data.get_path(self.schema, &data_path); + + if let Ok(v) = val { + // add to found + found_rewrites.insert(item.rename_key_to.clone().to_string()); + // TODO: not great + let mut new_value = v.clone(); + if let Some(values) = new_value.as_array_mut() { + for v in values { + let data_rewrite = DataRewrite::KeyRenamer({ + DataKeyRenamer { + path: data_path.clone(), + rename_key_to: item.rename_key_to.clone(), + } + }); + data_rewrite.maybe_apply(self.schema, v); + } + } else { + let data_rewrite = DataRewrite::KeyRenamer({ + DataKeyRenamer { + path: data_path.clone(), + rename_key_to: item.rename_key_to.clone(), + } + }); + data_rewrite.maybe_apply(self.schema, &mut new_value); + } + return Some((item.rename_key_to.to_string(), new_value)); + } + } + } + None + } + DataRewrite::ValueSetter(_) => None, + } + }) + .collect(); + self.named_args.push(hash_map); + } + + // Once all a value has been extracted for every variable, go ahead and add all + // variables to the variables map. Additionally, return a ContextualArguments structure if + // values of variables are entity dependent + pub(crate) fn add_variables_and_get_args( + &self, + variables: &mut Map, + ) -> Option { + let (extended_vars, contextual_args) = if let Some(first_map) = self.named_args.first() { + if self.named_args.iter().all(|map| map == first_map) { + ( + first_map + .iter() + .map(|(k, v)| (k.as_str().into(), v.clone())) + .collect(), + None, + ) + } else { + let mut hash_map: HashMap = HashMap::new(); + let arg_names: HashSet<_> = first_map.keys().cloned().collect(); + for (index, item) in self.named_args.iter().enumerate() { + // append _ to each of the arguments and push all the values into hash_map + hash_map.extend(item.iter().map(|(k, v)| { + let mut new_named_param = k.clone(); + new_named_param.push_str(&format!("_{}", index)); + (new_named_param, v.clone()) + })); + } + ( + hash_map, + Some(ContextualArguments { + arguments: arg_names, + count: self.named_args.len(), + }), + ) + } + } else { + (HashMap::new(), None) + }; + + variables.extend( + extended_vars + .iter() + .map(|(key, value)| (key.as_str().into(), value.clone())), + ); + + contextual_args + } +} + +// Take the existing subgraph operation and rewrite it to use aliasing. This will occur in the case +// where we are collecting entites and different entities may have different variables passed to the resolver. +pub(crate) fn build_operation_with_aliasing( + subgraph_operation: &SubgraphOperation, + contextual_arguments: &ContextualArguments, + subgraph_schema: &Valid, +) -> Result, ContextBatchingError> { + let ContextualArguments { arguments, count } = contextual_arguments; + let parsed_document = subgraph_operation.as_parsed(subgraph_schema); + + let mut ed = ExecutableDocument::new(); + + // for every operation in the document, go ahead and transform even though it's likely that only one exists + if let Ok(document) = parsed_document { + if let Some(anonymous_op) = &document.anonymous_operation { + let mut cloned = anonymous_op.clone(); + transform_operation(&mut cloned, arguments, count)?; + ed.insert_operation(cloned); + } + + for (_, op) in &document.named_operations { + let mut cloned = op.clone(); + transform_operation(&mut cloned, arguments, count)?; + ed.insert_operation(cloned); + } + + return ed + .validate(subgraph_schema) + .map_err(ContextBatchingError::InvalidDocumentGenerated); + } + Err(ContextBatchingError::NoSelectionSet) +} + +fn transform_operation( + operation: &mut Node, + arguments: &HashSet, + count: &usize, +) -> Result<(), ContextBatchingError> { + let mut selections: Vec = vec![]; + let mut new_variables: Vec> = vec![]; + operation.variables.iter().for_each(|v| { + if arguments.contains(v.name.as_str()) { + for i in 0..*count { + new_variables.push(Node::new(VariableDefinition { + name: Name::new_unchecked(format!("{}_{}", v.name.as_str(), i).into()), + ty: v.ty.clone(), + default_value: v.default_value.clone(), + directives: v.directives.clone(), + })); + } + } else { + new_variables.push(v.clone()); + } + }); + + // there should only be one selection that is a field selection that we're going to rename, but let's count to be sure + // and error if that's not the case + // also it's possible that there could be an inline fragment, so if that's the case, just add those to the new selections once + let mut field_selection: Option> = None; + for selection in &operation.selection_set.selections { + match selection { + Selection::Field(f) => { + if field_selection.is_some() { + // if we get here, there is more than one field selection, which should not be the case + // at the top level of a _entities selection set + return Err(ContextBatchingError::UnexpectedSelection); + } + field_selection = Some(f.clone()); + } + _ => { + // again, if we get here, something is wrong. _entities selection sets should have just one field selection + return Err(ContextBatchingError::UnexpectedSelection); + } + } + } + + let field_selection = field_selection.ok_or(ContextBatchingError::UnexpectedSelection)?; + + for i in 0..*count { + // If we are aliasing, we know that there is only one selection in the top level SelectionSet + // it is a field selection for _entities, so it's ok to reach in and give it an alias + let mut cloned = field_selection.clone(); + let cfs = cloned.make_mut(); + cfs.alias = Some(Name::new_unchecked(format!("_{}", i).into())); + + transform_field_arguments(&mut cfs.arguments, arguments, i); + transform_selection_set(&mut cfs.selection_set, arguments, i); + selections.push(Selection::Field(cloned)); + } + let operation = operation.make_mut(); + operation.variables = new_variables; + operation.selection_set = SelectionSet { + ty: operation.selection_set.ty.clone(), + selections, + }; + Ok(()) +} + +// This function will take the selection set (which has been cloned from the original) +// and transform it so that all contextual variables in the selection set will be appended with a _ +// to match the index in the alias that it is +fn transform_selection_set( + selection_set: &mut SelectionSet, + arguments: &HashSet, + index: usize, +) { + selection_set + .selections + .iter_mut() + .for_each(|selection| match selection { + executable::Selection::Field(node) => { + let node = node.make_mut(); + transform_field_arguments(&mut node.arguments, arguments, index); + transform_selection_set(&mut node.selection_set, arguments, index); + } + executable::Selection::InlineFragment(node) => { + let node = node.make_mut(); + transform_selection_set(&mut node.selection_set, arguments, index); + } + _ => (), + }); +} + +// transforms the variable name on the field argment +fn transform_field_arguments( + arguments_in_selection: &mut [Node], + arguments: &HashSet, + index: usize, +) { + arguments_in_selection.iter_mut().for_each(|arg| { + let arg = arg.make_mut(); + if let Some(v) = arg.value.as_variable() { + if arguments.contains(v.as_str()) { + arg.value = Node::new(ast::Value::Variable(Name::new_unchecked( + format!("{}_{}", v.as_str(), index).into(), + ))); + } + } + }); +} + +#[derive(Debug)] +pub(crate) enum ContextBatchingError { + NoSelectionSet, + InvalidDocumentGenerated(WithErrors), + InvalidRelativePath, + UnexpectedSelection, +} + +#[cfg(test)] +mod subgraph_context_unit_tests { + use super::*; + + #[test] + fn test_merge_context_path() { + let current_dir: Path = serde_json::from_str(r#"["t","u"]"#).unwrap(); + let relative_path: Path = serde_json::from_str(r#"["..","... on T","prop"]"#).unwrap(); + let expected = r#"["t","... on T","prop"]"#; + + let result = merge_context_path(¤t_dir, &relative_path).unwrap(); + assert_eq!(expected, serde_json::to_string(&result).unwrap(),); + } + + #[test] + fn test_merge_context_path_invalid() { + let current_dir: Path = serde_json::from_str(r#"["t","u"]"#).unwrap(); + let relative_path: Path = + serde_json::from_str(r#"["..","..","..","... on T","prop"]"#).unwrap(); + + let result = merge_context_path(¤t_dir, &relative_path); + match result { + Ok(_) => panic!("Expected an error, but got Ok"), + Err(e) => match e { + ContextBatchingError::InvalidRelativePath => (), + _ => panic!("Expected InvalidRelativePath, but got a different error"), + }, + } + } + + #[test] + fn test_transform_selection_set() { + let type_name = executable::Name::new("Hello").unwrap(); + let field_name = executable::Name::new("f").unwrap(); + let field_definition = ast::FieldDefinition { + description: None, + name: field_name.clone(), + arguments: vec![Node::new(ast::InputValueDefinition { + description: None, + name: executable::Name::new("param").unwrap(), + ty: Node::new(ast::Type::Named( + executable::Name::new("ParamType").unwrap(), + )), + default_value: None, + directives: ast::DirectiveList(vec![]), + })], + ty: ast::Type::Named(executable::Name::new("FieldType").unwrap()), + directives: ast::DirectiveList(vec![]), + }; + let mut selection_set = SelectionSet::new(type_name); + let field = executable::Field::new( + executable::Name::new("f").unwrap(), + Node::new(field_definition), + ) + .with_argument( + executable::Name::new("param").unwrap(), + Node::new(ast::Value::Variable( + executable::Name::new("variable").unwrap(), + )), + ); + + selection_set.push(Selection::Field(Node::new(field))); + + // before modifications + assert_eq!( + "{ f(param: $variable) }", + selection_set.serialize().no_indent().to_string() + ); + + let mut hash_set = HashSet::new(); + + // create a hash set that will miss completely. transform has no effect + hash_set.insert("one".to_string()); + hash_set.insert("two".to_string()); + hash_set.insert("param".to_string()); + let mut clone = selection_set.clone(); + transform_selection_set(&mut clone, &hash_set, 7); + assert_eq!( + "{ f(param: $variable) }", + clone.serialize().no_indent().to_string() + ); + + // add variable that will hit and cause a rewrite + hash_set.insert("variable".to_string()); + let mut clone = selection_set.clone(); + transform_selection_set(&mut clone, &hash_set, 7); + assert_eq!( + "{ f(param: $variable_7) }", + clone.serialize().no_indent().to_string() + ); + + // add_alias = true will add a "_3:" alias + let clone = selection_set.clone(); + let mut operation = Node::new(executable::Operation { + operation_type: executable::OperationType::Query, + name: None, + variables: vec![], + directives: ast::DirectiveList(vec![]), + selection_set: clone, + }); + let count = 3; + transform_operation(&mut operation, &hash_set, &count).unwrap(); + assert_eq!( + "{ _0: f(param: $variable_0) _1: f(param: $variable_1) _2: f(param: $variable_2) }", + operation.serialize().no_indent().to_string() + ); + } +} diff --git a/apollo-router/src/query_planner/subscription.rs b/apollo-router/src/query_planner/subscription.rs index caf90730e6..6a327fdc60 100644 --- a/apollo-router/src/query_planner/subscription.rs +++ b/apollo-router/src/query_planner/subscription.rs @@ -208,6 +208,7 @@ impl SubscriptionNode { parameters.supergraph_request, parameters.schema, &self.input_rewrites, + &None, ) { Some(variables) => variables, None => { diff --git a/apollo-router/src/query_planner/tests.rs b/apollo-router/src/query_planner/tests.rs index 3431fda2b5..15f2d3b421 100644 --- a/apollo-router/src/query_planner/tests.rs +++ b/apollo-router/src/query_planner/tests.rs @@ -116,6 +116,7 @@ async fn mock_subgraph_service_withf_panics_should_be_reported_as_service_closed &sf, &Default::default(), &Arc::new(Schema::parse_test(test_schema!(), &Default::default()).unwrap()), + &Default::default(), sender, None, &None, @@ -177,6 +178,7 @@ async fn fetch_includes_operation_name() { &sf, &Default::default(), &Arc::new(Schema::parse_test(test_schema!(), &Default::default()).unwrap()), + &Default::default(), sender, None, &None, @@ -235,6 +237,7 @@ async fn fetch_makes_post_requests() { &sf, &Default::default(), &Arc::new(Schema::parse_test(test_schema!(), &Default::default()).unwrap()), + &Default::default(), sender, None, &None, @@ -266,6 +269,7 @@ async fn defer() { id: Some("fetch1".into()), input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: Default::default(), authorization: Default::default(), }))), @@ -311,6 +315,7 @@ async fn defer() { id: Some("fetch2".into()), input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: Default::default(), authorization: Default::default(), })), @@ -385,6 +390,7 @@ async fn defer() { &sf, &Default::default(), &schema, + &Default::default(), sender, None, &None, @@ -493,6 +499,7 @@ async fn defer_if_condition() { .unwrap(), ), &schema, + &Default::default(), sender, None, &None, @@ -515,6 +522,7 @@ async fn defer_if_condition() { &service_factory, &Default::default(), &schema, + &Default::default(), default_sender, None, &None, @@ -546,6 +554,7 @@ async fn defer_if_condition() { .unwrap(), ), &schema, + &Default::default(), sender, None, &None, @@ -667,6 +676,7 @@ async fn dependent_mutations() { &sf, &Default::default(), &Arc::new(Schema::parse_test(schema, &Default::default()).unwrap()), + &Default::default(), sender, None, &None, @@ -1799,6 +1809,7 @@ fn broken_plan_does_not_panic() { id: Some("fetch1".into()), input_rewrites: None, output_rewrites: None, + context_rewrites: None, schema_aware_hash: Default::default(), authorization: Default::default(), }), diff --git a/apollo-router/src/services/execution/service.rs b/apollo-router/src/services/execution/service.rs index ddd9f96ca6..fbde3de97a 100644 --- a/apollo-router/src/services/execution/service.rs +++ b/apollo-router/src/services/execution/service.rs @@ -57,6 +57,7 @@ use crate::spec::Schema; #[derive(Clone)] pub(crate) struct ExecutionService { pub(crate) schema: Arc, + pub(crate) subgraph_schemas: Arc>>>, pub(crate) subgraph_service_factory: Arc, /// Subscription config if enabled subscription_config: Option, @@ -148,6 +149,7 @@ impl ExecutionService { &self.subgraph_service_factory, &Arc::new(req.supergraph_request), &self.schema, + &self.subgraph_schemas, sender, subscription_handle.clone(), &self.subscription_config, @@ -618,6 +620,7 @@ impl ServiceFactory for ExecutionServiceFactory { schema: self.schema.clone(), subgraph_service_factory: self.subgraph_service_factory.clone(), subscription_config: subscription_plugin_conf, + subgraph_schemas: self.subgraph_schemas.clone(), } .boxed(), |acc, (_, e)| e.execution_service(acc), diff --git a/apollo-router/src/uplink/license_enforcement.rs b/apollo-router/src/uplink/license_enforcement.rs index 50b6c410c6..ecc671cfad 100644 --- a/apollo-router/src/uplink/license_enforcement.rs +++ b/apollo-router/src/uplink/license_enforcement.rs @@ -408,6 +408,19 @@ impl LicenseEnforcementReport { }], }, }, + SchemaRestriction::Spec { + name: "context".to_string(), + spec_url: "https://specs.apollo.dev/context".to_string(), + version_req: semver::VersionReq { + comparators: vec![semver::Comparator { + op: semver::Op::Exact, + major: 0, + minor: 1.into(), + patch: 0.into(), + pre: semver::Prerelease::EMPTY, + }], + }, + }, SchemaRestriction::Spec { name: "requiresScopes".to_string(), spec_url: "https://specs.apollo.dev/requiresScopes".to_string(), @@ -436,6 +449,21 @@ impl LicenseEnforcementReport { }, explanation: "The `overrideLabel` argument on the join spec's @field directive is restricted to Enterprise users. This argument exists in your supergraph as a result of using the `@override` directive with the `label` argument in one or more of your subgraphs.".to_string() }, + SchemaRestriction::DirectiveArgument { + name: "field".to_string(), + argument: "contextArguments".to_string(), + spec_url: "https://specs.apollo.dev/join".to_string(), + version_req: semver::VersionReq { + comparators: vec![semver::Comparator { + op: semver::Op::GreaterEq, + major: 0, + minor: 5.into(), + patch: 0.into(), + pre: semver::Prerelease::EMPTY, + }], + }, + explanation: "The `contextArguments` argument on the join spec's @field directive is restricted to Enterprise users. This argument exists in your supergraph as a result of using the `@fromContext` directive in one or more of your subgraphs.".to_string() + }, ] } } @@ -785,6 +813,20 @@ mod test { assert_snapshot!(report.to_string()); } + #[test] + fn set_context() { + let report = check( + include_str!("testdata/oss.router.yaml"), + include_str!("testdata/set_context.graphql"), + ); + + assert!( + !report.restricted_schema_in_use.is_empty(), + "should have found restricted features" + ); + assert_snapshot!(report.to_string()); + } + #[test] fn progressive_override_with_renamed_join_spec() { let report = check( diff --git a/apollo-router/src/uplink/snapshots/apollo_router__uplink__license_enforcement__test__set_context.snap b/apollo-router/src/uplink/snapshots/apollo_router__uplink__license_enforcement__test__set_context.snap new file mode 100644 index 0000000000..79ea2bbce0 --- /dev/null +++ b/apollo-router/src/uplink/snapshots/apollo_router__uplink__license_enforcement__test__set_context.snap @@ -0,0 +1,12 @@ +--- +source: apollo-router/src/uplink/license_enforcement.rs +expression: report.to_string() +--- +Schema features: +* @context + https://specs.apollo.dev/context/v0.1 + +* @join__field.contextArguments + https://specs.apollo.dev/join/v0.5 + +The `contextArguments` argument on the join spec's @field directive is restricted to Enterprise users. This argument exists in your supergraph as a result of using the `@fromContext` directive in one or more of your subgraphs. diff --git a/apollo-router/src/uplink/testdata/set_context.graphql b/apollo-router/src/uplink/testdata/set_context.graphql new file mode 100644 index 0000000000..16b9ba0019 --- /dev/null +++ b/apollo-router/src/uplink/testdata/set_context.graphql @@ -0,0 +1,165 @@ +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) + @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) { + query: Query +} + +directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION + +directive @context__fromContext(field: String) on ARGUMENT_DEFINITION + +directive @join__directive( + graphs: [join__Graph!] + name: String! + args: join__DirectiveArguments +) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field( + graph: join__Graph + requires: join__FieldSet + provides: join__FieldSet + type: String + external: Boolean + override: String + usedOverridden: Boolean + overrideLabel: String + contextArguments: [join__ContextArgument!] +) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements( + graph: join__Graph! + interface: String! +) repeatable on OBJECT | INTERFACE + +directive @join__type( + graph: join__Graph! + key: join__FieldSet + extension: Boolean! = false + resolvable: Boolean! = true + isInterfaceObject: Boolean! = false +) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__unionMember( + graph: join__Graph! + member: String! +) repeatable on UNION + +directive @link( + url: String + as: String + for: link__Purpose + import: [link__Import] +) repeatable on SCHEMA + +scalar context__context + +input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! +} + +scalar join__DirectiveArguments + +scalar join__FieldSet + +scalar join__FieldValue + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://Subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://Subgraph2") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + t: T! @join__field(graph: SUBGRAPH1) + tList: [T]! @join__field(graph: SUBGRAPH1) + a: Int! @join__field(graph: SUBGRAPH2) + k: K! @join__field(graph: SUBGRAPH1) +} + +union K + @join__type(graph: SUBGRAPH1) + @join__unionMember(graph: SUBGRAPH1, member: "A") + @join__unionMember(graph: SUBGRAPH1, member: "B") + @context(name: "Subgraph1__context2") = + A + | B + +type A @join__type(graph: SUBGRAPH1, key: "id") { + id: ID! + v: V! + prop: String! +} + +type B @join__type(graph: SUBGRAPH1, key: "id") { + id: ID! + v: V! + prop: String! +} + +type T + @join__type(graph: SUBGRAPH1, key: "id") + @context(name: "Subgraph1__context") { + id: ID! + u: U! + uList: [U]! + prop: String! +} + +type U + @join__type(graph: SUBGRAPH1, key: "id") + @join__type(graph: SUBGRAPH2, key: "id") { + id: ID! + b: String! @join__field(graph: SUBGRAPH2) + field: Int! + @join__field( + graph: SUBGRAPH1 + contextArguments: [ + { + context: "Subgraph1__context" + name: "a" + type: "String" + selection: "{ prop }" + } + ] + ) +} + +type V + @join__type(graph: SUBGRAPH1, key: "id") + @join__type(graph: SUBGRAPH2, key: "id") { + id: ID! + b: String! @join__field(graph: SUBGRAPH2) + field: Int! + @join__field( + graph: SUBGRAPH1 + contextArguments: [ + { + context: "Subgraph1__context2" + name: "a" + type: "String" + selection: "... on A { prop } ... on B { prop }" + } + ] + ) +} diff --git a/apollo-router/tests/fixtures/set_context/one.json b/apollo-router/tests/fixtures/set_context/one.json new file mode 100644 index 0000000000..e552a0f47b --- /dev/null +++ b/apollo-router/tests/fixtures/set_context/one.json @@ -0,0 +1,414 @@ +{ + "mocks": [ + { + "request": { + "query": "query Query__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query__Subgraph1__0" + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": "prop value", + "id": "1", + "u": { + "__typename": "U", + "id": "1" + } + } + } + } + }, + { + "request": { + "query": "query Query__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query__Subgraph1__0" + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": "prop value", + "id": "1", + "u": { + "__typename": "U", + "id": "1" + } + } + } + } + }, + { + "request": { + "query": "query Query__Subgraph1__0{t{__typename prop id uList{__typename id}}}", + "operationName": "Query__Subgraph1__0" + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": "prop value", + "id": "1", + "uList": [ + { + "__typename": "U", + "id": "1" + }, + { + "__typename": "U", + "id": "2" + }, + { + "__typename": "U", + "id": "3" + } + ] + } + } + } + }, + { + "request": { + "query": "query QueryLL__Subgraph1__0{tList{__typename prop id uList{__typename id}}}", + "operationName": "QueryLL__Subgraph1__0" + }, + "response": { + "data": { + "tList": [ + { + "__typename": "T", + "prop": "prop value 1", + "id": "1", + "uList": [ + { + "__typename": "U", + "id": "3" + } + ] + }, + { + "__typename": "T", + "prop": "prop value 2", + "id": "2", + "uList": [ + { + "__typename": "U", + "id": "4" + } + ] + } + ] + } + } + }, + { + "request": { + "query": "query QueryUnion__Subgraph1__0{k{__typename ...on A{__typename prop v{__typename id}}...on B{__typename prop v{__typename id}}}}", + "operationName": "QueryUnion__Subgraph1__0" + }, + "response": { + "data": { + "k": { + "__typename": "A", + "prop": "prop value 3", + "id": 1, + "v": { + "__typename": "V", + "id": "2" + } + } + } + } + }, + { + "request": { + "query": "query Query__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query__Subgraph1__1", + "variables": { + "contextualArgument_1_0": "prop value", + "representations": [{ "__typename": "U", "id": "1" }] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "1", + "field": 1234 + } + ] + } + } + }, + { + "request": { + "query": "query Query__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query__Subgraph1__1", + "variables": { + "contextualArgument_1_0": "prop value", + "representations": [{ "__typename": "U", "id": "1" }] + } + }, + "response": { + "data": { + "_entities": [ + { + "__typename": "U", + "id": "1", + "field": 1234 + } + ] + } + } + }, + { + "request": { + "query": "query Query__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query__Subgraph1__1", + "variables": { + "contextualArgument_1_0": "prop value", + "representations": [ + { "__typename": "U", "id": "1" }, + { "__typename": "U", "id": "2" }, + { "__typename": "U", "id": "3" } + ] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "1", + "field": 1234 + }, + { + "id": "2", + "field": 2345 + }, + { + "id": "3", + "field": 3456 + } + ] + } + } + }, + { + "request": { + "query": "query QueryLL__Subgraph1__1($representations: [_Any!]!, $contextualArgument_1_0_0: String, $contextualArgument_1_0_1: String) { _0: _entities(representations: $representations) { ... on U { field(a: $contextualArgument_1_0_0) } } _1: _entities(representations: $representations) { ... on U { field(a: $contextualArgument_1_0_1) } } }", + "operationName": "QueryLL__Subgraph1__1", + "variables": { + "contextualArgument_1_0_0": "prop value 1", + "contextualArgument_1_0_1": "prop value 2", + "representations": [ + { "__typename": "U", "id": "3" }, + { "__typename": "U", "id": "4" } + ] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "3", + "field": 3456 + }, + { + "id": "4", + "field": 4567 + } + ] + } + } + }, + { + "request": { + "query": "query QueryLL__Subgraph1__1($representations: [_Any!]!, $contextualArgument_1_0_0: String, $contextualArgument_1_0_1: String) { _0: _entities(representations: $representations) { ... on U { field(a: $contextualArgument_1_0_0) } } _1: _entities(representations: $representations) { ... on U { field(a: $contextualArgument_1_0_1) } } }", + "operationName": "QueryLL__Subgraph1__1", + "variables": { + "contextualArgument_1_0_1": "prop value 2", + "contextualArgument_1_0_0": "prop value 1", + "representations": [ + { "__typename": "U", "id": "3" }, + { "__typename": "U", "id": "4" } + ] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "3", + "field": 3456 + }, + { + "id": "4", + "field": 4567 + } + ] + } + } + }, + { + "request": { + "query": "query QueryUnion__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_1:String){_entities(representations:$representations){...on V{field(a:$contextualArgument_1_1)}}}", + "operationName": "QueryUnion__Subgraph1__1", + "variables": { + "contextualArgument_1_1": "prop value 3", + "representations": [{ "__typename": "V", "id": "2" }] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "3", + "field": 3456 + } + ] + } + } + }, + { + "request": { + "query": "query Query_Null_Param__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query_Null_Param__Subgraph1__0" + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": null, + "id": "1", + "u": { + "__typename": "U", + "id": "1" + } + } + } + } + }, + { + "request": { + "query": "query Query_Null_Param__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query_Null_Param__Subgraph1__1", + "variables": { + "contextualArgument_1_0": null, + "representations": [{ "__typename": "U", "id": "1" }] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "1", + "field": 1234 + } + ] + } + } + }, + { + "request": { + "query": "query Query_type_mismatch__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query_type_mismatch__Subgraph1__0" + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": 7, + "id": "1", + "u": { + "__typename": "U", + "id": "1" + } + } + } + } + }, + { + "request": { + "query": "query Query_type_mismatch__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query_type_mismatch__Subgraph1__1", + "variables": { + "contextualArgument_1_0": 7, + "representations": [{ "__typename": "U", "id": "1" }] + } + }, + "response": { + "data": { + "_entities": [ + { + "id": "1", + "field": 1234 + } + ] + } + } + }, + { + "request": { + "query": "query Query_fetch_failure__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query_fetch_failure__Subgraph1__0" + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": "prop value", + "id": "1", + "u": { + "__typename": "U", + "id": "1" + } + } + } + } + }, + { + "request": { + "query": "query Query_fetch_failure__Subgraph1__2($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query_fetch_failure__Subgraph1__2", + "variables": { + "contextualArgument_1_0": "prop value", + "representations": [{ "__typename": "U", "id": "1" }] + } + }, + "response": { + "data": { + "t": { + "__typename": "T", + "prop": "prop value", + "id": "1", + "u": { + "__typename": "U", + "id": "1" + } + } + } + } + }, + { + "request": { + "query": "query Query_fetch_dependent_failure__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query_fetch_dependent_failure__Subgraph1__0" + }, + "response": { + "response": { + "data": null, + "errors": [{ + "message": "Some error", + "locations": [ + { + "line": 3, + "column": 5 + } + ], + "path": ["t", "u"] + }] + } + } + } + ] +} diff --git a/apollo-router/tests/fixtures/set_context/supergraph.graphql b/apollo-router/tests/fixtures/set_context/supergraph.graphql new file mode 100644 index 0000000000..16b9ba0019 --- /dev/null +++ b/apollo-router/tests/fixtures/set_context/supergraph.graphql @@ -0,0 +1,165 @@ +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) + @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) { + query: Query +} + +directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION + +directive @context__fromContext(field: String) on ARGUMENT_DEFINITION + +directive @join__directive( + graphs: [join__Graph!] + name: String! + args: join__DirectiveArguments +) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field( + graph: join__Graph + requires: join__FieldSet + provides: join__FieldSet + type: String + external: Boolean + override: String + usedOverridden: Boolean + overrideLabel: String + contextArguments: [join__ContextArgument!] +) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements( + graph: join__Graph! + interface: String! +) repeatable on OBJECT | INTERFACE + +directive @join__type( + graph: join__Graph! + key: join__FieldSet + extension: Boolean! = false + resolvable: Boolean! = true + isInterfaceObject: Boolean! = false +) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__unionMember( + graph: join__Graph! + member: String! +) repeatable on UNION + +directive @link( + url: String + as: String + for: link__Purpose + import: [link__Import] +) repeatable on SCHEMA + +scalar context__context + +input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! +} + +scalar join__DirectiveArguments + +scalar join__FieldSet + +scalar join__FieldValue + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://Subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://Subgraph2") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + t: T! @join__field(graph: SUBGRAPH1) + tList: [T]! @join__field(graph: SUBGRAPH1) + a: Int! @join__field(graph: SUBGRAPH2) + k: K! @join__field(graph: SUBGRAPH1) +} + +union K + @join__type(graph: SUBGRAPH1) + @join__unionMember(graph: SUBGRAPH1, member: "A") + @join__unionMember(graph: SUBGRAPH1, member: "B") + @context(name: "Subgraph1__context2") = + A + | B + +type A @join__type(graph: SUBGRAPH1, key: "id") { + id: ID! + v: V! + prop: String! +} + +type B @join__type(graph: SUBGRAPH1, key: "id") { + id: ID! + v: V! + prop: String! +} + +type T + @join__type(graph: SUBGRAPH1, key: "id") + @context(name: "Subgraph1__context") { + id: ID! + u: U! + uList: [U]! + prop: String! +} + +type U + @join__type(graph: SUBGRAPH1, key: "id") + @join__type(graph: SUBGRAPH2, key: "id") { + id: ID! + b: String! @join__field(graph: SUBGRAPH2) + field: Int! + @join__field( + graph: SUBGRAPH1 + contextArguments: [ + { + context: "Subgraph1__context" + name: "a" + type: "String" + selection: "{ prop }" + } + ] + ) +} + +type V + @join__type(graph: SUBGRAPH1, key: "id") + @join__type(graph: SUBGRAPH2, key: "id") { + id: ID! + b: String! @join__field(graph: SUBGRAPH2) + field: Int! + @join__field( + graph: SUBGRAPH1 + contextArguments: [ + { + context: "Subgraph1__context2" + name: "a" + type: "String" + selection: "... on A { prop } ... on B { prop }" + } + ] + ) +} diff --git a/apollo-router/tests/fixtures/set_context/two.json b/apollo-router/tests/fixtures/set_context/two.json new file mode 100644 index 0000000000..6ab18d052b --- /dev/null +++ b/apollo-router/tests/fixtures/set_context/two.json @@ -0,0 +1,43 @@ +{ + "mocks": [ + { + "request": { + "query": "query Query__two__2($representations:[_Any!]!){_entities(representations:$representations){...on U{k}}}", + "operationName": "Query__two__2", + "variables": { "representations": [{ "__typename": "U", "id": "1" }] } + }, + "response": { + "data": { + "_entities": [ + { + "k": "k value" + } + ] + } + } + }, + { + "request": { + "query": "query Query_fetch_failure__Subgraph2__1($representations:[_Any!]!){_entities(representations:$representations){...on U{b}}}", + "operationName": "Query_fetch_failure__Subgraph2__1", + "variables": { + "representations": [{ "__typename": "U", "id": "1" }] + } + }, + "response": { + "data": null, + "errors": [{ + "message": "Some error", + "locations": [ + { + "line": 3, + "column": 5 + } + ], + "path": ["t", "u"] + } + ] + } + } + ] +} diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 4b3aa46d78..29d932c911 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -28,7 +28,7 @@ async fn query_planner() -> Result<(), BoxError> { // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. // 4. Run this test and yank the updated cache key from the redis logs. - let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:9c26cb1f820a78848ba3d5d3295c16aa971368c5295422fd33cc19d4a6006a9c"; + let known_cache_key = "plan:0:v2.8.0-alpha.1:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:9c26cb1f820a78848ba3d5d3295c16aa971368c5295422fd33cc19d4a6006a9c"; let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); let client = RedisClient::new(config, None, None, None); @@ -902,7 +902,7 @@ async fn connection_failure_blocks_startup() { async fn query_planner_redis_update_query_fragments() { test_redis_query_plan_config_update( include_str!("fixtures/query_planner_redis_config_update_query_fragments.router.yaml"), - "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:ae8b525534cb7446a34715fc80edd41d4d29aa65c5f39f9237d4ed8459e3fe82", + "plan:0:v2.8.0-alpha.1:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:ae8b525534cb7446a34715fc80edd41d4d29aa65c5f39f9237d4ed8459e3fe82", ) .await; } @@ -921,7 +921,7 @@ async fn query_planner_redis_update_planner_mode() { async fn query_planner_redis_update_introspection() { test_redis_query_plan_config_update( include_str!("fixtures/query_planner_redis_config_update_introspection.router.yaml"), - "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c", + "plan:0:v2.8.0-alpha.1:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c", ) .await; } @@ -930,7 +930,7 @@ async fn query_planner_redis_update_introspection() { async fn query_planner_redis_update_defer() { test_redis_query_plan_config_update( include_str!("fixtures/query_planner_redis_config_update_defer.router.yaml"), - "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8a17c5b196af5e3a18d24596424e9849d198f456dd48297b852a5f2ca847169b", + "plan:0:v2.8.0-alpha.1:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8a17c5b196af5e3a18d24596424e9849d198f456dd48297b852a5f2ca847169b", ) .await; } @@ -941,7 +941,7 @@ async fn query_planner_redis_update_type_conditional_fetching() { include_str!( "fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml" ), - "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:275f78612ed3d45cdf6bf328ef83e368b5a44393bd8c944d4a7d694aed61f017", + "plan:0:v2.8.0-alpha.1:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:275f78612ed3d45cdf6bf328ef83e368b5a44393bd8c944d4a7d694aed61f017", ) .await; } @@ -952,7 +952,7 @@ async fn query_planner_redis_update_reuse_query_fragments() { include_str!( "fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml" ), - "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:15fbb62c94e8da6ea78f28a6eb86a615dcaf27ff6fd0748fac4eb614b0b17662", + "plan:0:v2.8.0-alpha.1:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:15fbb62c94e8da6ea78f28a6eb86a615dcaf27ff6fd0748fac4eb614b0b17662", ) .await; } @@ -972,7 +972,7 @@ async fn test_redis_query_plan_config_update(updated_config: &str, new_cache_key router.assert_started().await; router.clear_redis_cache().await; - let starting_key = "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c"; + let starting_key = "plan:0:v2.8.0-alpha.1:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c"; router.execute_default_query().await; router.assert_redis_cache_contains(starting_key, None).await; router.update_config(updated_config).await; diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap index 4714fe2240..f90305be82 100644 --- a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap +++ b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap @@ -12,6 +12,7 @@ expression: query_plan "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "121b9859eba2d8fa6dde0a54b6e3781274cf69f7ffb0af912e92c01c6bfff6ca", "authorization": { "is_authenticated": false, diff --git a/apollo-router/tests/set_context.rs b/apollo-router/tests/set_context.rs new file mode 100644 index 0000000000..bc5f44d2da --- /dev/null +++ b/apollo-router/tests/set_context.rs @@ -0,0 +1,325 @@ +//! +//! Please ensure that any tests added to this file use the tokio multi-threaded test executor. +//! + +use apollo_router::graphql::Request; +use apollo_router::graphql::Response; +use apollo_router::plugin::test::MockSubgraph; +use apollo_router::services::supergraph; +use apollo_router::MockedSubgraphs; +use apollo_router::TestHarness; +use serde::Deserialize; +use serde_json::json; +use tower::ServiceExt; + +#[derive(Deserialize)] +struct SubgraphMock { + mocks: Vec, +} + +#[derive(Deserialize)] +struct RequestAndResponse { + request: Request, + response: Response, +} + +macro_rules! snap +{ + ($result:ident) => { + insta::with_settings!({sort_maps => true}, { + insta::assert_json_snapshot!($result); + }); + } +} + +async fn run_single_request(query: &str, mocks: &[(&'static str, &'static str)]) -> Response { + let harness = setup_from_mocks( + json! {{ + "experimental_type_conditioned_fetching": true, + // will make debugging easier + "plugins": { + "experimental.expose_query_plan": true + }, + "include_subgraph_errors": { + "all": true + } + }}, + mocks, + ); + let supergraph_service = harness.build_supergraph().await.unwrap(); + let request = supergraph::Request::fake_builder() + .query(query.to_string()) + .header("Apollo-Expose-Query-Plan", "true") + .variables(Default::default()) + .build() + .expect("expecting valid request"); + + supergraph_service + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap() +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context() { + static QUERY: &str = r#" + query Query { + t { + __typename + id + u { + __typename + field + } + } + }"#; + + let response = run_single_request( + QUERY, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_no_typenames() { + static QUERY_NO_TYPENAMES: &str = r#" + query Query { + t { + id + u { + field + } + } + }"#; + + let response = run_single_request( + QUERY_NO_TYPENAMES, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_list() { + static QUERY_WITH_LIST: &str = r#" + query Query { + t { + id + uList { + field + } + } + }"#; + + let response = run_single_request( + QUERY_WITH_LIST, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_list_of_lists() { + static QUERY_WITH_LIST_OF_LISTS: &str = r#" + query QueryLL { + tList { + id + uList { + field + } + } + }"#; + + let response = run_single_request( + QUERY_WITH_LIST_OF_LISTS, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_union() { + static QUERY_WITH_UNION: &str = r#" + query QueryUnion { + k { + ... on A { + v { + field + } + } + ... on B { + v { + field + } + } + } + }"#; + + let response = run_single_request( + QUERY_WITH_UNION, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_with_null() { + static QUERY: &str = r#" + query Query_Null_Param { + t { + id + u { + field + } + } + }"#; + + let response = run_single_request( + QUERY, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + insta::assert_json_snapshot!(response); +} + +// this test returns the contextual value with a different than expected type +// this currently works, but perhaps should do type valdiation in the future to reject +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_type_mismatch() { + static QUERY: &str = r#" + query Query_type_mismatch { + t { + id + u { + field + } + } + }"#; + + let response = run_single_request( + QUERY, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +// fetch from unrelated (to context) subgraph fails +// validates that the error propagation is correct +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_unrelated_fetch_failure() { + static QUERY: &str = r#" + query Query_fetch_failure { + t { + id + u { + field + b + } + } + }"#; + + let response = run_single_request( + QUERY, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +// subgraph fetch fails where context depends on results of fetch. +// validates that no fetch will get called that passes context +#[tokio::test(flavor = "multi_thread")] +async fn test_set_context_dependent_fetch_failure() { + static QUERY: &str = r#" + query Query_fetch_dependent_failure { + t { + id + u { + field + } + } + }"#; + + let response = run_single_request( + QUERY, + &[ + ("Subgraph1", include_str!("fixtures/set_context/one.json")), + ("Subgraph2", include_str!("fixtures/set_context/two.json")), + ], + ) + .await; + + snap!(response); +} + +fn setup_from_mocks( + configuration: serde_json::Value, + mocks: &[(&'static str, &'static str)], +) -> TestHarness<'static> { + let mut mocked_subgraphs = MockedSubgraphs::default(); + + for (name, m) in mocks { + let subgraph_mock: SubgraphMock = serde_json::from_str(m).unwrap(); + + let mut builder = MockSubgraph::builder(); + + for mock in subgraph_mock.mocks { + builder = builder.with_json( + serde_json::to_value(mock.request).unwrap(), + serde_json::to_value(mock.response).unwrap(), + ); + } + + mocked_subgraphs.insert(name, builder.build()); + } + + let schema = include_str!("fixtures/set_context/supergraph.graphql"); + TestHarness::builder() + .try_log_level("info") + .configuration_json(configuration) + .unwrap() + .schema(schema) + .extra_plugin(mocked_subgraphs) +} diff --git a/apollo-router/tests/snapshots/set_context__set_context.snap b/apollo-router/tests/snapshots/set_context__set_context.snap new file mode 100644 index 0000000000..2e11680753 --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context.snap @@ -0,0 +1,101 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "t": { + "__typename": "T", + "id": "1", + "u": { + "__typename": "U", + "field": 1234 + } + } + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationKind": "query", + "operationName": "Query__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "d7cb2d1809789d49360ca0a60570555f83855f00547675f366915c9d9d90fef9", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "Query__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "66b954f39aead8436321c671eb71e56ce15bbe0c7b82f06b2f8f70473ce1cb6e", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "t", + "u" + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n u {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap b/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap new file mode 100644 index 0000000000..703d8f9c59 --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap @@ -0,0 +1,98 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": null, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_fetch_dependent_failure__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationKind": "query", + "operationName": "Query_fetch_dependent_failure__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "595c36c322602fefc4658fc0070973b51800c2d2debafae5571a7c9811d80745", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_fetch_dependent_failure__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "Query_fetch_dependent_failure__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "37bef7ad43bb477cdec4dfc02446bd2e11a6919dc14ab90e266af85fefde4abd", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "t", + "u" + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n u {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + }, + "valueCompletion": [ + { + "message": "Cannot return null for non-nullable field Query.t", + "path": [] + } + ] + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_list.snap b/apollo-router/tests/snapshots/set_context__set_context_list.snap new file mode 100644 index 0000000000..095326167e --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_list.snap @@ -0,0 +1,108 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "t": { + "id": "1", + "uList": [ + { + "field": 1234 + }, + { + "field": 2345 + }, + { + "field": 3456 + } + ] + } + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query__Subgraph1__0{t{__typename prop id uList{__typename id}}}", + "operationKind": "query", + "operationName": "Query__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "4f746b9319e3ca4f234269464b6815eb97782f2ffe36774b998e7fb78f30abef", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "Query__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "66b954f39aead8436321c671eb71e56ce15bbe0c7b82f06b2f8f70473ce1cb6e", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "t", + "uList", + "@" + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n uList {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".t.uList.@\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap b/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap new file mode 100644 index 0000000000..e7fbee2a8b --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap @@ -0,0 +1,113 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "tList": [ + { + "id": "1", + "uList": [ + { + "field": 3456 + } + ] + }, + { + "id": "2", + "uList": [ + { + "field": 4567 + } + ] + } + ] + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query QueryLL__Subgraph1__0{tList{__typename prop id uList{__typename id}}}", + "operationKind": "query", + "operationName": "QueryLL__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "babf88ea82c1330e535966572a55b03a2934097cd1cf905303b86ae7c197ccaf", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query QueryLL__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "QueryLL__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "a9b24549250c12e38c398c32e9218134fab000be3b934ebc6bb38ea096343646", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "tList", + "@", + "uList", + "@" + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n tList {\n __typename\n prop\n id\n uList {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".tList.@.uList.@\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap b/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap new file mode 100644 index 0000000000..8eaa5b0202 --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap @@ -0,0 +1,99 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "t": { + "id": "1", + "u": { + "field": 1234 + } + } + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationKind": "query", + "operationName": "Query__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "d7cb2d1809789d49360ca0a60570555f83855f00547675f366915c9d9d90fef9", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "Query__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "66b954f39aead8436321c671eb71e56ce15bbe0c7b82f06b2f8f70473ce1cb6e", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "t", + "u" + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n u {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap b/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap new file mode 100644 index 0000000000..1df052723e --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap @@ -0,0 +1,99 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "t": { + "id": "1", + "u": { + "field": 1234 + } + } + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_type_mismatch__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationKind": "query", + "operationName": "Query_type_mismatch__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "7eae890e61f5ae512e112f5260abe0de3504041c92dbcc7aae0891c9bdf2222b", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_type_mismatch__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "Query_type_mismatch__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "d8ea99348ab32931371c85c09565cfb728d2e48cf017201cd79cb9ef860eb9c2", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "t", + "u" + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n u {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_union.snap b/apollo-router/tests/snapshots/set_context__set_context_union.snap new file mode 100644 index 0000000000..e382988a8b --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_union.snap @@ -0,0 +1,157 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "k": { + "v": { + "field": 3456 + } + } + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query QueryUnion__Subgraph1__0{k{__typename ...on A{__typename prop v{__typename id}}...on B{__typename prop v{__typename id}}}}", + "operationKind": "query", + "operationName": "QueryUnion__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "b9124cd1daa6e8347175ffe2108670a31c73cbc983e7812ee39f415235541005", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Parallel", + "nodes": [ + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on A", + "prop" + ], + "renameKeyTo": "contextualArgument_1_1" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query QueryUnion__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_1:String){_entities(representations:$representations){...on V{field(a:$contextualArgument_1_1)}}}", + "operationKind": "query", + "operationName": "QueryUnion__Subgraph1__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "V" + } + ], + "schemaAwareHash": "c50ca82d402a330c1b35a6d76332094c40b00d6dec6f6b2a9b0a32ced68f4e95", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_1" + ] + }, + "path": [ + "", + "k|[A]", + "v" + ] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on B", + "prop" + ], + "renameKeyTo": "contextualArgument_1_1" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query QueryUnion__Subgraph1__2($representations:[_Any!]!$contextualArgument_1_1:String){_entities(representations:$representations){...on V{field(a:$contextualArgument_1_1)}}}", + "operationKind": "query", + "operationName": "QueryUnion__Subgraph1__2", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "V" + } + ], + "schemaAwareHash": "ec99886497fee9b4f13565e19cadb13ae85c83de93acb53f298944b7a29e630e", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_1" + ] + }, + "path": [ + "", + "k|[B]", + "v" + ] + } + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n k {\n __typename\n ... on A {\n __typename\n prop\n v {\n __typename\n id\n }\n }\n ... on B {\n __typename\n prop\n v {\n __typename\n id\n }\n }\n }\n }\n },\n Parallel {\n Flatten(path: \".k|[A].v\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on V {\n __typename\n id\n }\n } =>\n {\n ... on V {\n field(a: $contextualArgument_1_1)\n }\n }\n },\n },\n Flatten(path: \".k|[B].v\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on V {\n __typename\n id\n }\n } =>\n {\n ... on V {\n field(a: $contextualArgument_1_1)\n }\n }\n },\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap b/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap new file mode 100644 index 0000000000..605fd4570a --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap @@ -0,0 +1,170 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": null, + "errors": [ + { + "message": "Some error", + "path": [ + "t", + "u" + ] + } + ], + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_fetch_failure__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationKind": "query", + "operationName": "Query_fetch_failure__Subgraph1__0", + "outputRewrites": null, + "schemaAwareHash": "1813ba1c272be0201096b4c4c963a07638e4f4b4ac1b97e0d90d634f2fcbac11", + "serviceName": "Subgraph1", + "variableUsages": [] + }, + { + "kind": "Parallel", + "nodes": [ + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": null, + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_fetch_failure__Subgraph2__1($representations:[_Any!]!){_entities(representations:$representations){...on U{b}}}", + "operationKind": "query", + "operationName": "Query_fetch_failure__Subgraph2__1", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "1fdff97ad7facf07690c3e75e3dc7f1b11ff509268ef999250912a728e7a94c9", + "serviceName": "Subgraph2", + "variableUsages": [] + }, + "path": [ + "", + "t", + "u" + ] + }, + { + "kind": "Flatten", + "node": { + "authorization": { + "is_authenticated": false, + "policies": [], + "scopes": [] + }, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "id": null, + "inputRewrites": null, + "kind": "Fetch", + "operation": "query Query_fetch_failure__Subgraph1__2($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationKind": "query", + "operationName": "Query_fetch_failure__Subgraph1__2", + "outputRewrites": null, + "requires": [ + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ], + "typeCondition": "U" + } + ], + "schemaAwareHash": "c9c571eac5df81ff34e5e228934d029ed322640c97ab6ad061cbee3cd81040dc", + "serviceName": "Subgraph1", + "variableUsages": [ + "contextualArgument_1_0" + ] + }, + "path": [ + "", + "t", + "u" + ] + } + ] + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n u {\n __typename\n id\n }\n }\n }\n },\n Parallel {\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph2\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n b\n }\n }\n },\n },\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n },\n}" + }, + "valueCompletion": [ + { + "message": "Cannot return null for non-nullable field U.field", + "path": [ + "t", + "u" + ] + }, + { + "message": "Cannot return null for non-nullable field T.u", + "path": [ + "t", + "u" + ] + }, + { + "message": "Cannot return null for non-nullable field T!.t", + "path": [ + "t" + ] + } + ] + } +} diff --git a/apollo-router/tests/snapshots/set_context__set_context_with_null.snap b/apollo-router/tests/snapshots/set_context__set_context_with_null.snap new file mode 100644 index 0000000000..1e361f0a83 --- /dev/null +++ b/apollo-router/tests/snapshots/set_context__set_context_with_null.snap @@ -0,0 +1,99 @@ +--- +source: apollo-router/tests/set_context.rs +expression: response +--- +{ + "data": { + "t": { + "id": "1", + "u": { + "field": 1234 + } + } + }, + "extensions": { + "apolloQueryPlan": { + "object": { + "kind": "QueryPlan", + "node": { + "kind": "Sequence", + "nodes": [ + { + "kind": "Fetch", + "serviceName": "Subgraph1", + "variableUsages": [], + "operation": "query Query_Null_Param__Subgraph1__0{t{__typename prop id u{__typename id}}}", + "operationName": "Query_Null_Param__Subgraph1__0", + "operationKind": "query", + "id": null, + "inputRewrites": null, + "outputRewrites": null, + "contextRewrites": null, + "schemaAwareHash": "19bd66a3ecc2d9495dffce2279774de3275cb027254289bb61b0c1937a7738b4", + "authorization": { + "is_authenticated": false, + "scopes": [], + "policies": [] + } + }, + { + "kind": "Flatten", + "path": [ + "", + "t", + "u" + ], + "node": { + "kind": "Fetch", + "serviceName": "Subgraph1", + "requires": [ + { + "kind": "InlineFragment", + "typeCondition": "U", + "selections": [ + { + "kind": "Field", + "name": "__typename" + }, + { + "kind": "Field", + "name": "id" + } + ] + } + ], + "variableUsages": [ + "contextualArgument_1_0" + ], + "operation": "query Query_Null_Param__Subgraph1__1($representations:[_Any!]!$contextualArgument_1_0:String){_entities(representations:$representations){...on U{field(a:$contextualArgument_1_0)}}}", + "operationName": "Query_Null_Param__Subgraph1__1", + "operationKind": "query", + "id": null, + "inputRewrites": null, + "outputRewrites": null, + "contextRewrites": [ + { + "kind": "KeyRenamer", + "path": [ + "..", + "... on T", + "prop" + ], + "renameKeyTo": "contextualArgument_1_0" + } + ], + "schemaAwareHash": "010ba25ca76f881bd9f0d5e338f9c07829d4d00e183828b6577d593aea0cf21e", + "authorization": { + "is_authenticated": false, + "scopes": [], + "policies": [] + } + } + } + ] + } + }, + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n prop\n id\n u {\n __typename\n id\n }\n }\n }\n },\n Flatten(path: \".t.u\") {\n Fetch(service: \"Subgraph1\") {\n {\n ... on U {\n __typename\n id\n }\n } =>\n {\n ... on U {\n field(a: $contextualArgument_1_0)\n }\n }\n },\n },\n },\n}" + } + } +} diff --git a/apollo-router/tests/snapshots/type_conditions__type_conditions_disabled.snap b/apollo-router/tests/snapshots/type_conditions__type_conditions_disabled.snap index e1b3c3bba7..84b137aa01 100644 --- a/apollo-router/tests/snapshots/type_conditions__type_conditions_disabled.snap +++ b/apollo-router/tests/snapshots/type_conditions__type_conditions_disabled.snap @@ -78,6 +78,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "0144f144d271437ed45f9d20706be86ffbf1e124d77c7add3db17d4a1498ce97", "authorization": { "is_authenticated": false, @@ -135,6 +136,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "23759b36e5149924c757a8b9586adec2c0f6be04ecdf2c3c3ea277446daa690b", "authorization": { "is_authenticated": false, diff --git a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled.snap b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled.snap index 49e1fef4bc..e41aeefee5 100644 --- a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled.snap +++ b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled.snap @@ -78,6 +78,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "0144f144d271437ed45f9d20706be86ffbf1e124d77c7add3db17d4a1498ce97", "authorization": { "is_authenticated": false, @@ -139,6 +140,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "23759b36e5149924c757a8b9586adec2c0f6be04ecdf2c3c3ea277446daa690b", "authorization": { "is_authenticated": false, @@ -198,6 +200,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "8ee58ad8b4823bcbda9126d2565e1cb04bf91ff250b1098476a1d7614a870121", "authorization": { "is_authenticated": false, diff --git a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_generate_query_fragments.snap b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_generate_query_fragments.snap index b04c2208ec..d92517b39d 100644 --- a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_generate_query_fragments.snap +++ b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_generate_query_fragments.snap @@ -78,6 +78,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "844dc4e409cdca1334abe37c347bd4e330123078dd7e65bda8dbb57ea5bdf59c", "authorization": { "is_authenticated": false, @@ -139,6 +140,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "ad82ce0af279c6a012d6b349ff823ba1467902223312aed1cdfc494ec3100b3e", "authorization": { "is_authenticated": false, @@ -198,6 +200,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "7c267302cf4a44a4463820237830155ab50be32c8860371d8a5c8ca905476360", "authorization": { "is_authenticated": false, diff --git a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list.snap b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list.snap index 45697537d8..acffc62599 100644 --- a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list.snap +++ b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list.snap @@ -140,6 +140,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "1343b4972ec8be54afe990c69711ce790992a814f9654e34e2ee2b25e4097e45", "authorization": { "is_authenticated": false, @@ -202,6 +203,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "23759b36e5149924c757a8b9586adec2c0f6be04ecdf2c3c3ea277446daa690b", "authorization": { "is_authenticated": false, @@ -262,6 +264,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "8ee58ad8b4823bcbda9126d2565e1cb04bf91ff250b1098476a1d7614a870121", "authorization": { "is_authenticated": false, diff --git a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list_of_list.snap b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list_of_list.snap index 528a658fe6..2b8feaafc3 100644 --- a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list_of_list.snap +++ b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_list_of_list_of_list.snap @@ -144,6 +144,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "3698f4e74ead34f43a949e1e8459850337a1a07245f8ed627b9203904b4cfff4", "authorization": { "is_authenticated": false, @@ -207,6 +208,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "23759b36e5149924c757a8b9586adec2c0f6be04ecdf2c3c3ea277446daa690b", "authorization": { "is_authenticated": false, @@ -268,6 +270,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "8ee58ad8b4823bcbda9126d2565e1cb04bf91ff250b1098476a1d7614a870121", "authorization": { "is_authenticated": false, diff --git a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_shouldnt_make_article_fetch.snap b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_shouldnt_make_article_fetch.snap index 38ef6bc51a..5020d447b4 100644 --- a/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_shouldnt_make_article_fetch.snap +++ b/apollo-router/tests/snapshots/type_conditions__type_conditions_enabled_shouldnt_make_article_fetch.snap @@ -53,6 +53,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "0144f144d271437ed45f9d20706be86ffbf1e124d77c7add3db17d4a1498ce97", "authorization": { "is_authenticated": false, @@ -114,6 +115,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "23759b36e5149924c757a8b9586adec2c0f6be04ecdf2c3c3ea277446daa690b", "authorization": { "is_authenticated": false, @@ -173,6 +175,7 @@ expression: response "id": null, "inputRewrites": null, "outputRewrites": null, + "contextRewrites": null, "schemaAwareHash": "8ee58ad8b4823bcbda9126d2565e1cb04bf91ff250b1098476a1d7614a870121", "authorization": { "is_authenticated": false, diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 8151da867a..da27ae250f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -20,7 +20,7 @@ reqwest = { workspace = true, features = ["json", "blocking"] } serde_json.workspace = true tokio.workspace = true # note: this dependency should _always_ be pinned, prefix the version with an `=` -router-bridge = "=0.5.21+v2.7.5" +router-bridge = "=0.5.24+v2.8.0-alpha.1" [dev-dependencies] anyhow = "1"