Skip to content

Commit

Permalink
Add experimental support for Rust-based schema introspection (#5948)
Browse files Browse the repository at this point in the history
Also drive-by fixes related to:
* Multiple operations in one document
* Non-default root operation names
* All field selections behind `skip(if: true)` or `include(if: false)`
* Missing metrics in some error cases
* Avoid schema/operation re-parsing in record-and-replay plugin

Co-authored-by: Iryna Shestak <[email protected]>
Co-authored-by: Geoffroy Couprie <[email protected]>
  • Loading branch information
3 people authored Sep 4, 2024
1 parent cc92669 commit 2e51d80
Show file tree
Hide file tree
Showing 30 changed files with 1,388 additions and 290 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"

[[package]]
name = "apollo-compiler"
version = "1.0.0-beta.20"
version = "1.0.0-beta.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07961541ebb5c85cc02ea0f08357e31b30537674bbca818884f1fc658fa99116"
checksum = "9496debc2b28a7da94aa6329b653fae37e0f0ec44da93d82a8d5d2a6a82abe0e"
dependencies = [
"ahash",
"apollo-parser",
Expand Down Expand Up @@ -449,9 +449,9 @@ dependencies = [

[[package]]
name = "apollo-smith"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ef0a8fba05f32a14d03eb3ff74f556cecca820012d5846770b839c75332b38"
checksum = "de03c56d7feec663e7f9e981cf4570db68a0901de8c4667f5b5d20321b88af6e"
dependencies = [
"apollo-compiler",
"apollo-parser",
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ debug = 1
# Dependencies used in more than one place are specified here in order to keep versions in sync:
# https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table
[workspace.dependencies]
apollo-compiler = "=1.0.0-beta.20"
apollo-compiler = "=1.0.0-beta.21"
apollo-parser = "0.8.0"
apollo-smith = "0.10.0"
apollo-smith = "0.11.0"
async-trait = "0.1.77"
hex = { version = "0.4.3", features = ["serde"] }
http = "0.2.11"
Expand All @@ -75,4 +75,4 @@ serde_json_bytes = { version = "0.2.4", features = ["preserve_order"] }
sha1 = "0.10.6"
tempfile = "3.10.1"
tokio = { version = "1.36.0", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
25 changes: 25 additions & 0 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ pub struct Configuration {
#[serde(default)]
pub(crate) experimental_query_planner_mode: QueryPlannerMode,

/// Set the GraphQL schema introspection implementation to use.
#[serde(default)]
pub(crate) experimental_introspection_mode: IntrospectionMode,

/// Plugin configuration
#[serde(default)]
pub(crate) plugins: UserPlugins,
Expand Down Expand Up @@ -226,6 +230,21 @@ pub(crate) enum QueryPlannerMode {
BothBestEffort,
}

/// Which implementation of GraphQL schema introspection to use, if enabled
#[derive(Copy, Clone, PartialEq, Eq, Default, Derivative, Serialize, Deserialize, JsonSchema)]
#[derivative(Debug)]
#[serde(rename_all = "lowercase")]
pub(crate) enum IntrospectionMode {
/// Use the new Rust-based implementation.
New,
/// Use the old JavaScript-based implementation.
#[default]
Legacy,
/// Use Rust-based and Javascript-based implementations side by side,
/// logging warnings if the implementations disagree.
Both,
}

impl<'de> serde::Deserialize<'de> for Configuration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand All @@ -252,6 +271,7 @@ impl<'de> serde::Deserialize<'de> for Configuration {
batching: Batching,
experimental_type_conditioned_fetching: bool,
experimental_query_planner_mode: QueryPlannerMode,
experimental_introspection_mode: IntrospectionMode,
}
let mut ad_hoc: AdHocConfiguration = serde::Deserialize::deserialize(deserializer)?;

Expand Down Expand Up @@ -279,6 +299,7 @@ impl<'de> serde::Deserialize<'de> for Configuration {
experimental_chaos: ad_hoc.experimental_chaos,
experimental_type_conditioned_fetching: ad_hoc.experimental_type_conditioned_fetching,
experimental_query_planner_mode: ad_hoc.experimental_query_planner_mode,
experimental_introspection_mode: ad_hoc.experimental_introspection_mode,
plugins: ad_hoc.plugins,
apollo_plugins: ad_hoc.apollo_plugins,
batching: ad_hoc.batching,
Expand Down Expand Up @@ -325,6 +346,7 @@ impl Configuration {
experimental_type_conditioned_fetching: Option<bool>,
batching: Option<Batching>,
experimental_query_planner_mode: Option<QueryPlannerMode>,
experimental_introspection_mode: Option<IntrospectionMode>,
) -> Result<Self, ConfigurationError> {
let notify = Self::notify(&apollo_plugins)?;

Expand All @@ -340,6 +362,7 @@ impl Configuration {
limits: operation_limits.unwrap_or_default(),
experimental_chaos: chaos.unwrap_or_default(),
experimental_query_planner_mode: experimental_query_planner_mode.unwrap_or_default(),
experimental_introspection_mode: experimental_introspection_mode.unwrap_or_default(),
plugins: UserPlugins {
plugins: Some(plugins),
},
Expand Down Expand Up @@ -442,6 +465,7 @@ impl Configuration {
batching: Option<Batching>,
experimental_type_conditioned_fetching: Option<bool>,
experimental_query_planner_mode: Option<QueryPlannerMode>,
experimental_introspection_mode: Option<IntrospectionMode>,
) -> Result<Self, ConfigurationError> {
let configuration = Self {
validated_yaml: Default::default(),
Expand All @@ -453,6 +477,7 @@ impl Configuration {
limits: operation_limits.unwrap_or_default(),
experimental_chaos: chaos.unwrap_or_default(),
experimental_query_planner_mode: experimental_query_planner_mode.unwrap_or_default(),
experimental_introspection_mode: experimental_introspection_mode.unwrap_or_default(),
plugins: UserPlugins {
plugins: Some(plugins),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3568,6 +3568,32 @@ expression: "&schema"
},
"type": "object"
},
"IntrospectionMode": {
"description": "Which implementation of GraphQL schema introspection to use, if enabled",
"oneOf": [
{
"description": "Use the new Rust-based implementation.",
"enum": [
"new"
],
"type": "string"
},
{
"description": "Use the old JavaScript-based implementation.",
"enum": [
"legacy"
],
"type": "string"
},
{
"description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.",
"enum": [
"both"
],
"type": "string"
}
]
},
"InvalidationEndpointConfig": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -8289,6 +8315,10 @@ expression: "&schema"
"$ref": "#/definitions/Chaos",
"description": "#/definitions/Chaos"
},
"experimental_introspection_mode": {
"$ref": "#/definitions/IntrospectionMode",
"description": "#/definitions/IntrospectionMode"
},
"experimental_query_planner_mode": {
"$ref": "#/definitions/QueryPlannerMode",
"description": "#/definitions/QueryPlannerMode"
Expand Down
30 changes: 30 additions & 0 deletions apollo-router/src/graphql/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use serde_json_bytes::Map;

use crate::error::Error;
use crate::error::FetchError;
use crate::graphql::IntoGraphQLErrors;
use crate::json_ext::Object;
use crate::json_ext::Path;
use crate::json_ext::Value;
Expand Down Expand Up @@ -244,6 +245,35 @@ impl IncrementalResponse {
}
}

impl From<apollo_compiler::execution::Response> for Response {
fn from(response: apollo_compiler::execution::Response) -> Response {
let apollo_compiler::execution::Response {
errors,
data,
extensions,
} = response;
Self {
errors: errors.into_graphql_errors().unwrap(),
data: match data {
apollo_compiler::execution::ResponseData::Object(map) => {
Some(serde_json_bytes::Value::Object(map))
}
apollo_compiler::execution::ResponseData::Null => {
Some(serde_json_bytes::Value::Null)
}
apollo_compiler::execution::ResponseData::Absent => None,
},
extensions,
label: None,
path: None,
has_next: None,
subscribed: None,
created_at: None,
incremental: Vec::new(),
}
}
}

#[cfg(test)]
mod tests {
use router_bridge::planner::Location;
Expand Down
22 changes: 1 addition & 21 deletions apollo-router/src/plugins/authentication/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,7 @@ async fn it_rejects_when_there_is_no_auth_header() {
.unwrap();

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.build()
.unwrap();
let request_with_appropriate_name = supergraph::Request::canned_builder().build().unwrap();

// ...And call our service stack with it
let mut service_response = test_harness
Expand Down Expand Up @@ -214,7 +211,6 @@ async fn it_rejects_when_auth_prefix_is_missing() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, "invalid")
.build()
.unwrap();
Expand Down Expand Up @@ -251,7 +247,6 @@ async fn it_rejects_when_auth_prefix_has_no_jwt() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, "Bearer")
.build()
.unwrap();
Expand Down Expand Up @@ -288,7 +283,6 @@ async fn it_rejects_when_auth_prefix_has_invalid_format_jwt() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, "Bearer header.payload")
.build()
.unwrap();
Expand Down Expand Up @@ -327,7 +321,6 @@ async fn it_rejects_when_auth_prefix_has_correct_format_but_invalid_jwt() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::AUTHORIZATION,
"Bearer header.payload.signature",
Expand Down Expand Up @@ -367,7 +360,6 @@ async fn it_rejects_when_auth_prefix_has_correct_format_and_invalid_jwt() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::AUTHORIZATION,
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleTEifQ.eyJleHAiOjEwMDAwMDAwMDAwLCJhbm90aGVyIGNsYWltIjoidGhpcyBpcyBhbm90aGVyIGNsYWltIn0.4GrmfxuUST96cs0YUC0DfLAG218m7vn8fO_ENfXnu5B",
Expand Down Expand Up @@ -407,7 +399,6 @@ async fn it_accepts_when_auth_prefix_has_correct_format_and_valid_jwt() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::AUTHORIZATION,
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleTEifQ.eyJleHAiOjEwMDAwMDAwMDAwLCJhbm90aGVyIGNsYWltIjoidGhpcyBpcyBhbm90aGVyIGNsYWltIn0.4GrmfxuUST96cs0YUC0DfLAG218m7vn8fO_ENfXnu5A",
Expand Down Expand Up @@ -445,7 +436,6 @@ async fn it_accepts_when_auth_prefix_does_not_match_config_and_is_ignored() {
let test_harness = build_a_test_harness(None, None, false, true).await;
// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, "Basic dXNlcjpwYXNzd29yZA==")
.build()
.unwrap();
Expand Down Expand Up @@ -481,7 +471,6 @@ async fn it_accepts_when_auth_prefix_has_correct_format_multiple_jwks_and_valid_

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::AUTHORIZATION,
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleTEifQ.eyJleHAiOjEwMDAwMDAwMDAwLCJhbm90aGVyIGNsYWltIjoidGhpcyBpcyBhbm90aGVyIGNsYWltIn0.4GrmfxuUST96cs0YUC0DfLAG218m7vn8fO_ENfXnu5A",
Expand Down Expand Up @@ -521,7 +510,6 @@ async fn it_accepts_when_auth_prefix_has_correct_format_and_valid_jwt_custom_aut

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
"SOMETHING",
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleTEifQ.eyJleHAiOjEwMDAwMDAwMDAwLCJhbm90aGVyIGNsYWltIjoidGhpcyBpcyBhbm90aGVyIGNsYWltIn0.4GrmfxuUST96cs0YUC0DfLAG218m7vn8fO_ENfXnu5A",
Expand Down Expand Up @@ -561,7 +549,6 @@ async fn it_accepts_when_auth_prefix_has_correct_format_and_valid_jwt_custom_pre

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::AUTHORIZATION,
"SOMETHING eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleTEifQ.eyJleHAiOjEwMDAwMDAwMDAwLCJhbm90aGVyIGNsYWltIjoidGhpcyBpcyBhbm90aGVyIGNsYWltIn0.4GrmfxuUST96cs0YUC0DfLAG218m7vn8fO_ENfXnu5A",
Expand Down Expand Up @@ -600,7 +587,6 @@ async fn it_accepts_when_no_auth_prefix_and_valid_jwt_custom_prefix() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::AUTHORIZATION,
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleTEifQ.eyJleHAiOjEwMDAwMDAwMDAwLCJhbm90aGVyIGNsYWltIjoidGhpcyBpcyBhbm90aGVyIGNsYWltIn0.4GrmfxuUST96cs0YUC0DfLAG218m7vn8fO_ENfXnu5A",
Expand Down Expand Up @@ -702,7 +688,6 @@ async fn it_extracts_the_token_from_cookies() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(
http::header::COOKIE,
format!("a= b; c = d HttpOnly; authz = {token}; e = f"),
Expand Down Expand Up @@ -799,7 +784,6 @@ async fn it_supports_multiple_sources() {

// Let's create a request with our operation name
let request_with_appropriate_name = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header("Authz2", format!("Bear {token}"))
.build()
.unwrap();
Expand Down Expand Up @@ -1001,7 +985,6 @@ async fn issuer_check() {
.unwrap();

let request = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.build()
.unwrap();
Expand Down Expand Up @@ -1039,7 +1022,6 @@ async fn issuer_check() {
.unwrap();

let request = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.build()
.unwrap();
Expand Down Expand Up @@ -1076,7 +1058,6 @@ async fn issuer_check() {
.unwrap();

let request = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.build()
.unwrap();
Expand Down Expand Up @@ -1108,7 +1089,6 @@ async fn issuer_check() {
.unwrap();

let request = supergraph::Request::canned_builder()
.operation_name("me".to_string())
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.build()
.unwrap();
Expand Down
Loading

0 comments on commit 2e51d80

Please sign in to comment.