diff --git a/changelog.d/graphql_endpoint_toggle.enhancement.md b/changelog.d/graphql_endpoint_toggle.enhancement.md new file mode 100644 index 0000000000000..0336ae4f61f48 --- /dev/null +++ b/changelog.d/graphql_endpoint_toggle.enhancement.md @@ -0,0 +1,3 @@ +Added a boolean `graphql` field to the api configuration to allow disabling the graphql endpoint. + +Note that the `playground` endpoint will now only be enabled if the `graphql` endpoint is also enabled. diff --git a/src/api/server.rs b/src/api/server.rs index 97711185270dc..5b2f5abe71aa6 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -18,7 +18,7 @@ use warp::{filters::BoxedFilter, http::Response, ws::Ws, Filter, Reply}; use super::{handler, schema, ShutdownTx}; use crate::{ - config, + config::{self, api}, http::build_http_trace_layer, internal_events::{SocketBindError, SocketMode}, topology, @@ -38,7 +38,7 @@ impl Server { running: Arc, handle: &Handle, ) -> crate::Result { - let routes = make_routes(config.api.playground, watch_rx, running); + let routes = make_routes(config.api, watch_rx, running); let (_shutdown, rx) = oneshot::channel(); // warp uses `tokio::spawn` and so needs us to enter the runtime context. @@ -96,7 +96,7 @@ impl Server { } fn make_routes( - playground: bool, + api: api::Options, watch_tx: topology::WatchRx, running: Arc, ) -> BoxedFilter<(impl Reply,)> { @@ -141,16 +141,22 @@ fn make_routes( // Handle GraphQL queries. Headers will first be parsed to determine whether the query is // a subscription and if so, an attempt will be made to upgrade the connection to WebSockets. // All other queries will fall back to the default HTTP handler. - let graphql_handler = warp::path("graphql").and(graphql_subscription_handler.or( - async_graphql_warp::graphql(schema::build_schema().finish()).and_then( - |(schema, request): (Schema<_, _, _>, Request)| async move { - Ok::<_, Infallible>(GraphQLResponse::from(schema.execute(request).await)) - }, - ), - )); + let graphql_handler = if api.graphql { + warp::path("graphql") + .and(graphql_subscription_handler.or( + async_graphql_warp::graphql(schema::build_schema().finish()).and_then( + |(schema, request): (Schema<_, _, _>, Request)| async move { + Ok::<_, Infallible>(GraphQLResponse::from(schema.execute(request).await)) + }, + ), + )) + .boxed() + } else { + not_found_graphql.boxed() + }; // Provide a playground for executing GraphQL queries/mutations/subscriptions. - let graphql_playground = if playground { + let graphql_playground = if api.playground && api.graphql { warp::path("playground") .map(move || { Response::builder() diff --git a/src/config/api.rs b/src/config/api.rs index 2da33bc134152..c9ed3802d9bef 100644 --- a/src/config/api.rs +++ b/src/config/api.rs @@ -19,6 +19,10 @@ pub struct Options { /// Whether or not to expose the GraphQL playground on the API endpoint. #[serde(default = "default_playground")] pub playground: bool, + + /// Whether or not the GraphQL endpoint is enabled + #[serde(default = "default_graphql", skip_serializing_if = "is_true")] + pub graphql: bool, } impl Default for Options { @@ -27,10 +31,17 @@ impl Default for Options { enabled: default_enabled(), playground: default_playground(), address: default_address(), + graphql: default_graphql(), } } } +// serde passes struct fields as reference +#[allow(clippy::trivially_copy_pass_by_ref)] +const fn is_true(value: &bool) -> bool { + *value +} + const fn default_enabled() -> bool { false } @@ -53,6 +64,10 @@ const fn default_playground() -> bool { true } +const fn default_graphql() -> bool { + true +} + impl Options { pub fn merge(&mut self, other: Self) -> Result<(), String> { // Merge options @@ -78,6 +93,7 @@ impl Options { address, enabled: self.enabled | other.enabled, playground: self.playground & other.playground, + graphql: self.graphql & other.graphql, }; *self = options; @@ -91,6 +107,7 @@ fn bool_merge() { enabled: true, address: None, playground: false, + graphql: false, }; a.merge(Options::default()).unwrap(); @@ -101,6 +118,7 @@ fn bool_merge() { enabled: true, address: default_address(), playground: false, + graphql: false } ); } @@ -112,6 +130,7 @@ fn bind_merge() { enabled: true, address: Some(address), playground: true, + graphql: true, }; a.merge(Options::default()).unwrap(); @@ -122,6 +141,7 @@ fn bind_merge() { enabled: true, address: Some(address), playground: true, + graphql: true, } ); } diff --git a/website/cue/reference/api.cue b/website/cue/reference/api.cue index e5e5a75db2559..47fc8a29109c7 100644 --- a/website/cue/reference/api.cue +++ b/website/cue/reference/api.cue @@ -44,7 +44,18 @@ api: { description: """ Whether the [GraphQL Playground](\(urls.graphql_playground)) is enabled for the API. The Playground is accessible via the `/playground` endpoint - of the address set using the `bind` parameter. + of the address set using the `bind` parameter. Note that the `playground` + endpoint will only be enabled if the `graphql` endpoint is also enabled. + """ + } + graphql: { + common: true + required: false + type: bool: default: true + description: """ + Whether the endpoint for receiving and processing GraphQL queries is + enabled for the API. The endpoint is accessible via the `/graphql` + endpoint of the address set using the `bind` parameter. """ } }