Skip to content

Commit

Permalink
Use apollo-federation to generate the API schema (#4059)
Browse files Browse the repository at this point in the history
Experimental option to test API schema generation using the
apollo-federation crate

Co-authored-by: Iryna Shestak <[email protected]>
  • Loading branch information
Geal and lrlna authored Nov 3, 2023
1 parent e5a2108 commit 89ea712
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 2 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,22 @@ dependencies = [
"thiserror",
]

[[package]]
name = "apollo-federation"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d08d88375b1cb778d4b663877fa5d2ba482180283d2071f4de75e697627d60"
dependencies = [
"apollo-compiler 1.0.0-beta.4",
"indexmap 2.0.2",
"lazy_static",
"salsa",
"strum",
"strum_macros",
"thiserror",
"url",
]

[[package]]
name = "apollo-parser"
version = "0.6.3"
Expand Down Expand Up @@ -272,6 +288,7 @@ dependencies = [
"access-json",
"anyhow",
"apollo-compiler 1.0.0-beta.4",
"apollo-federation",
"arc-swap",
"askama",
"async-compression",
Expand Down
1 change: 1 addition & 0 deletions apollo-router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ askama = "0.12.1"
access-json = "0.1.0"
anyhow = "1.0.75"
apollo-compiler = "=1.0.0-beta.4"
apollo-federation = "0.0.1"
arc-swap = "1.6.0"
async-compression = { version = "0.4.4", features = [
"tokio",
Expand Down
24 changes: 24 additions & 0 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ pub struct Configuration {
#[serde(default)]
pub(crate) experimental_graphql_validation_mode: GraphQLValidationMode,

/// Set the API schema generation implementation to use.
#[serde(default)]
pub(crate) experimental_api_schema_generation_mode: ApiSchemaMode,

/// Plugin configuration
#[serde(default)]
pub(crate) plugins: UserPlugins,
Expand Down Expand Up @@ -210,6 +214,21 @@ pub(crate) enum GraphQLValidationMode {
Both,
}

/// GraphQL validation modes.
#[derive(Clone, PartialEq, Eq, Default, Derivative, Serialize, Deserialize, JsonSchema)]
#[derivative(Debug)]
#[serde(rename_all = "lowercase")]
pub(crate) enum ApiSchemaMode {
/// 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 Down Expand Up @@ -292,6 +311,7 @@ impl Configuration {
chaos: Option<Chaos>,
uplink: Option<UplinkConfig>,
graphql_validation_mode: Option<GraphQLValidationMode>,
experimental_api_schema_generation_mode: Option<ApiSchemaMode>,
experimental_batching: Option<Batching>,
) -> Result<Self, ConfigurationError> {
#[cfg(not(test))]
Expand Down Expand Up @@ -319,6 +339,7 @@ impl Configuration {
limits: operation_limits.unwrap_or_default(),
experimental_chaos: chaos.unwrap_or_default(),
experimental_graphql_validation_mode: graphql_validation_mode.unwrap_or_default(),
experimental_api_schema_generation_mode: experimental_api_schema_generation_mode.unwrap_or_default(),
plugins: UserPlugins {
plugins: Some(plugins),
},
Expand Down Expand Up @@ -367,6 +388,7 @@ impl Configuration {
uplink: Option<UplinkConfig>,
graphql_validation_mode: Option<GraphQLValidationMode>,
experimental_batching: Option<Batching>,
experimental_api_schema_generation_mode: Option<ApiSchemaMode>,
) -> Result<Self, ConfigurationError> {
let configuration = Self {
validated_yaml: Default::default(),
Expand All @@ -378,6 +400,8 @@ impl Configuration {
limits: operation_limits.unwrap_or_default(),
experimental_chaos: chaos.unwrap_or_default(),
experimental_graphql_validation_mode: graphql_validation_mode.unwrap_or_default(),
experimental_api_schema_generation_mode: experimental_api_schema_generation_mode
.unwrap_or_default(),
plugins: UserPlugins {
plugins: Some(plugins),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,33 @@ expression: "&schema"
},
"additionalProperties": false
},
"experimental_api_schema_generation_mode": {
"description": "Set the API schema generation implementation to use.",
"default": "legacy",
"oneOf": [
{
"description": "Use the new Rust-based implementation.",
"type": "string",
"enum": [
"new"
]
},
{
"description": "Use the old JavaScript-based implementation.",
"type": "string",
"enum": [
"legacy"
]
},
{
"description": "Use Rust-based and Javascript-based implementations side by side, logging warnings if the implementations disagree.",
"type": "string",
"enum": [
"both"
]
}
]
},
"experimental_batching": {
"description": "Batching configuration.",
"default": {
Expand Down
59 changes: 57 additions & 2 deletions apollo-router/src/query_planner/bridge_query_planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Write;
use std::sync::Arc;
use std::time::Instant;

Expand Down Expand Up @@ -122,8 +123,62 @@ impl BridgeQueryPlanner {

let planner = Arc::new(planner);

let api_schema = planner.api_schema().await?;
let api_schema = Schema::parse(&api_schema.schema, &configuration)?;
let api_schema_string = match configuration.experimental_api_schema_generation_mode {
crate::configuration::ApiSchemaMode::Legacy => {
let api_schema = planner.api_schema().await?;
api_schema.schema
}
crate::configuration::ApiSchemaMode::New => schema.create_api_schema(),

crate::configuration::ApiSchemaMode::Both => {
let api_schema = planner.api_schema().await?;
let new_api_schema = schema.create_api_schema();

if api_schema.schema != new_api_schema {
tracing::warn!(
monotonic_counter.apollo.router.api_schema = 1u64,
generation.result = "failed",
"API schema generation mismatch: apollo-federation and router-bridge write different schema"
);

let differences = diff::lines(&api_schema.schema, &new_api_schema);
let mut output = String::new();
for diff_line in differences {
match diff_line {
diff::Result::Left(l) => {
let trimmed = l.trim();
if !trimmed.starts_with('#') && !trimmed.is_empty() {
writeln!(&mut output, "-{l}").expect("write will never fail");
} else {
writeln!(&mut output, " {l}").expect("write will never fail");
}
}
diff::Result::Both(l, _) => {
writeln!(&mut output, " {l}").expect("write will never fail");
}
diff::Result::Right(r) => {
let trimmed = r.trim();
if trimmed != "---" && !trimmed.is_empty() {
writeln!(&mut output, "+{r}").expect("write will never fail");
}
}
}
}
tracing::debug!(
"different API schema between apollo-federation and router-bridge:\n{}",
output
);
} else {
tracing::warn!(
monotonic_counter.apollo.router.api_schema = 1u64,
generation.result = VALIDATION_MATCH,
);
}
api_schema.schema
}
};
let api_schema = Schema::parse(&api_schema_string, &configuration)?;

let schema = Arc::new(schema.with_api_schema(api_schema));
let introspection = if configuration.supergraph.introspection {
Some(Arc::new(Introspection::new(planner.clone()).await))
Expand Down
6 changes: 6 additions & 0 deletions apollo-router/src/spec/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ impl Schema {
})
}

pub(crate) fn create_api_schema(&self) -> String {
apollo_federation::Supergraph::from(self.definitions.clone())
.to_api_schema()
.to_string()
}

pub(crate) fn with_api_schema(mut self, api_schema: Schema) -> Self {
self.api_schema = Some(Box::new(api_schema));
self
Expand Down

0 comments on commit 89ea712

Please sign in to comment.