From 7f9fd83f07e64f67444bdfdb5a3fa4db373ecdfc Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Mon, 18 Aug 2025 11:37:29 +0300 Subject: [PATCH 1/5] planner service --- bin/gateway/src/main.rs | 13 +++++++++++-- bin/gateway/src/shared_state.rs | 9 ++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/bin/gateway/src/main.rs b/bin/gateway/src/main.rs index 0501ce718..2a238f14f 100644 --- a/bin/gateway/src/main.rs +++ b/bin/gateway/src/main.rs @@ -1,6 +1,7 @@ mod http_utils; mod logger; mod pipeline; +mod planner_service; mod shared_state; use crate::{ @@ -10,12 +11,15 @@ use crate::{ request_id::{RequestIdGenerator, REQUEST_ID_HEADER_NAME}, }, logger::{configure_logging, LoggingFormat}, + planner_service::{ + planner_service_handler, supergraph_schema_handler, supergraph_version_handler, + }, shared_state::GatewaySharedState, }; use axum::{ body::Body, http::Method, - routing::{any_service, get}, + routing::{any_service, get, post}, Router, }; use http::Request; @@ -78,7 +82,8 @@ async fn main() -> Result<(), Box> { ) }); let parsed_schema = parse_schema(&supergraph_sdl); - let gateway_shared_state = GatewaySharedState::new(parsed_schema); + let supergraph_version = env::var("SUPERGRAPH_VERSION").unwrap_or_default(); + let gateway_shared_state = GatewaySharedState::new(parsed_schema, supergraph_version); let pipeline = ServiceBuilder::new() .layer(Extension(gateway_shared_state.clone())) @@ -115,7 +120,11 @@ async fn main() -> Result<(), Box> { let app = Router::new() .route("/graphql", any_service(pipeline)) + .route("/supergraph/version", get(supergraph_version_handler)) + .route("/supergraph/schema", get(supergraph_schema_handler)) + .route("/build-query-plan", post(planner_service_handler)) .route("/health", get(health_check_handler)) + .layer(Extension(gateway_shared_state.clone())) .layer( CorsLayer::new() .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) diff --git a/bin/gateway/src/shared_state.rs b/bin/gateway/src/shared_state.rs index 36a137b25..769f2ea10 100644 --- a/bin/gateway/src/shared_state.rs +++ b/bin/gateway/src/shared_state.rs @@ -25,10 +25,15 @@ pub struct GatewaySharedState { pub validate_cache: Cache>>, pub parse_cache: Cache>>, pub normalize_cache: Cache>, + pub supergraph_version: String, + pub sdl: String, } impl GatewaySharedState { - pub fn new(parsed_supergraph_sdl: Document<'static, String>) -> Arc { + pub fn new( + parsed_supergraph_sdl: Document<'static, String>, + supergraph_version: String, + ) -> Arc { let supergraph_state = SupergraphState::new(&parsed_supergraph_sdl); let planner = Planner::new_from_supergraph(&parsed_supergraph_sdl).expect("failed to create planner"); @@ -52,6 +57,8 @@ impl GatewaySharedState { validate_cache: moka::future::Cache::new(1000), parse_cache: moka::future::Cache::new(1000), normalize_cache: moka::future::Cache::new(1000), + supergraph_version, + sdl: parsed_supergraph_sdl.to_string(), }) } } From b7b40e027f16c7fa1d5d5b5b0fe848dfd5d4523d Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Mon, 18 Aug 2025 11:37:36 +0300 Subject: [PATCH 2/5] Create planner_service.rs --- bin/gateway/src/planner_service.rs | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 bin/gateway/src/planner_service.rs diff --git a/bin/gateway/src/planner_service.rs b/bin/gateway/src/planner_service.rs new file mode 100644 index 000000000..53e69dcae --- /dev/null +++ b/bin/gateway/src/planner_service.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use axum::{response::IntoResponse, Extension, Json}; +use query_planner::utils::parsing::safe_parse_operation; +use serde::Deserialize; +use sonic_rs::json; + +use crate::{pipeline::error::PipelineErrorVariant, shared_state::GatewaySharedState}; + +pub async fn supergraph_version_handler( + state: Extension>, +) -> impl IntoResponse { + json!({ + "version": state.supergraph_version + }) + .to_string() +} + +pub async fn supergraph_schema_handler( + state: Extension>, +) -> impl IntoResponse { + state.sdl.clone() +} + +#[derive(Deserialize)] +pub struct PlannerServiceJsonInput { + #[serde(rename = "operationName")] + pub operation_name: String, + pub query: String, +} + +pub async fn planner_service_handler( + state: Extension>, + body: Json, +) -> impl IntoResponse { + let result = { + let parsed = safe_parse_operation(&body.query) + .map_err(|err| PipelineErrorVariant::FailedToParseOperation(err))?; + }; + + "test" +} From 623e3571e2b9bd744400a294a3e4cd7375b8f2f0 Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Mon, 18 Aug 2025 12:07:14 +0300 Subject: [PATCH 3/5] ok --- bin/gateway/src/planner_service.rs | 76 ++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/bin/gateway/src/planner_service.rs b/bin/gateway/src/planner_service.rs index 53e69dcae..23b52476c 100644 --- a/bin/gateway/src/planner_service.rs +++ b/bin/gateway/src/planner_service.rs @@ -1,7 +1,15 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use axum::{response::IntoResponse, Extension, Json}; -use query_planner::utils::parsing::safe_parse_operation; +use graphql_tools::validation::validate::validate; +use http::{header::CONTENT_TYPE, StatusCode}; +use query_planner::{ + ast::{document::NormalizedDocument, normalization::normalize_operation}, + graph::{PlannerOverrideContext, PERCENTAGE_SCALE_FACTOR}, + planner::plan_nodes::QueryPlan, + utils::parsing::safe_parse_operation, +}; +use rand::Rng; use serde::Deserialize; use sonic_rs::json; @@ -25,7 +33,7 @@ pub async fn supergraph_schema_handler( #[derive(Deserialize)] pub struct PlannerServiceJsonInput { #[serde(rename = "operationName")] - pub operation_name: String, + pub operation_name: Option, pub query: String, } @@ -33,10 +41,62 @@ pub async fn planner_service_handler( state: Extension>, body: Json, ) -> impl IntoResponse { - let result = { - let parsed = safe_parse_operation(&body.query) - .map_err(|err| PipelineErrorVariant::FailedToParseOperation(err))?; - }; + match plan(&body.0, &state).await { + Ok((plan, normalized_document)) => ( + StatusCode::OK, + [(CONTENT_TYPE, "application/json")], + json!({ + "plan": plan, + "normalizedOperation": normalized_document.operation.to_string() + }) + .to_string(), + ), + Err(err) => ( + err.default_status_code(false), + [(CONTENT_TYPE, "application/json")], + json!({ + "error": err.graphql_error_message() + }) + .to_string(), + ), + } +} + +async fn plan( + input: &PlannerServiceJsonInput, + state: &GatewaySharedState, +) -> Result<(QueryPlan, NormalizedDocument), PipelineErrorVariant> { + let parsed_operation = safe_parse_operation(&input.query) + .map_err(|err| PipelineErrorVariant::FailedToParseOperation(err))?; + let consumer_schema_ast = &state.planner.consumer_schema.document; + let validation_errors = validate( + consumer_schema_ast, + &parsed_operation, + &state.validation_plan, + ); + + if validation_errors.len() > 0 { + return Err(PipelineErrorVariant::ValidationErrors(Arc::new( + validation_errors, + ))); + } + + let normalized_operation = normalize_operation( + &state.planner.supergraph, + &parsed_operation, + input.operation_name.as_deref(), + ) + .map_err(|err| PipelineErrorVariant::NormalizationError(err))?; + + let request_override_context = PlannerOverrideContext::new( + HashSet::new(), + rand::rng().random_range(0..=(100 * PERCENTAGE_SCALE_FACTOR)), + ); + + let plan = state + .planner + .plan_from_normalized_operation(&normalized_operation.operation, request_override_context) + .map_err(|err| PipelineErrorVariant::PlannerError(err))?; - "test" + Ok((plan, normalized_operation)) } From ab3bdcf8ddec11011a625ccbb528cda3c1153cf2 Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Mon, 18 Aug 2025 12:10:33 +0300 Subject: [PATCH 4/5] fix --- bin/gateway/src/planner_service.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/gateway/src/planner_service.rs b/bin/gateway/src/planner_service.rs index 23b52476c..1dc5dad0a 100644 --- a/bin/gateway/src/planner_service.rs +++ b/bin/gateway/src/planner_service.rs @@ -67,7 +67,7 @@ async fn plan( state: &GatewaySharedState, ) -> Result<(QueryPlan, NormalizedDocument), PipelineErrorVariant> { let parsed_operation = safe_parse_operation(&input.query) - .map_err(|err| PipelineErrorVariant::FailedToParseOperation(err))?; + .map_err(PipelineErrorVariant::FailedToParseOperation)?; let consumer_schema_ast = &state.planner.consumer_schema.document; let validation_errors = validate( consumer_schema_ast, @@ -75,7 +75,7 @@ async fn plan( &state.validation_plan, ); - if validation_errors.len() > 0 { + if !validation_errors.is_empty() { return Err(PipelineErrorVariant::ValidationErrors(Arc::new( validation_errors, ))); @@ -86,7 +86,7 @@ async fn plan( &parsed_operation, input.operation_name.as_deref(), ) - .map_err(|err| PipelineErrorVariant::NormalizationError(err))?; + .map_err(PipelineErrorVariant::NormalizationError)?; let request_override_context = PlannerOverrideContext::new( HashSet::new(), @@ -96,7 +96,7 @@ async fn plan( let plan = state .planner .plan_from_normalized_operation(&normalized_operation.operation, request_override_context) - .map_err(|err| PipelineErrorVariant::PlannerError(err))?; + .map_err(PipelineErrorVariant::PlannerError)?; Ok((plan, normalized_operation)) } From 693557adeef104edf276ec5cf7543774bb9ea6bd Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Mon, 18 Aug 2025 12:13:38 +0300 Subject: [PATCH 5/5] fix --- bin/gateway/src/planner_service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/gateway/src/planner_service.rs b/bin/gateway/src/planner_service.rs index 1dc5dad0a..6de2fbeb7 100644 --- a/bin/gateway/src/planner_service.rs +++ b/bin/gateway/src/planner_service.rs @@ -66,8 +66,8 @@ async fn plan( input: &PlannerServiceJsonInput, state: &GatewaySharedState, ) -> Result<(QueryPlan, NormalizedDocument), PipelineErrorVariant> { - let parsed_operation = safe_parse_operation(&input.query) - .map_err(PipelineErrorVariant::FailedToParseOperation)?; + let parsed_operation = + safe_parse_operation(&input.query).map_err(PipelineErrorVariant::FailedToParseOperation)?; let consumer_schema_ast = &state.planner.consumer_schema.document; let validation_errors = validate( consumer_schema_ast,