diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index b7a19c4e3..31f127451 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -18,8 +18,13 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Added `description` field to `ast::Operation`, `ast::Fragment` and `ast::VariableDefinition`. ([#1349], [graphql/graphql-spec#1170]) - Renamed `ast::VariableDefinitions` to `ast::VariablesDefinition`: ([#1353], [graphql/graphql-spec#916]) - Renamed `ast::Operation::variable_definitions` field to `variables_definition`. + - Added `extenstions` field to `http::GraphQLRequest`. ([#1356], [graphql/graphql-spec#976]) + - Added `Ext` type parameter to `http::GraphQLRequest` and `http::GraphQLBatchRequest` defaulting to `Variables`. ([#1356], [graphql/graphql-spec#976]) - Changed `ScalarToken::String` to contain raw quoted and escaped `StringLiteral` (was unquoted but escaped string before). ([#1349]) - Added `LexerError::UnterminatedBlockString` variant. ([#1349]) +- `http::GraphQLRequest`: + - Removed `new()` constructor in favor of constructing it directly. ([#1356]) + - Removed deprecated `operation_name()` method in favor of direct `operation_name` field. ([#1356]) ### Added @@ -72,12 +77,14 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1353]: /../../pull/1353 [#1354]: /../../pull/1354 [#1355]: /../../pull/1355 +[#1356]: /../../pull/1356 [graphql/graphql-spec#525]: https://github.com/graphql/graphql-spec/pull/525 [graphql/graphql-spec#687]: https://github.com/graphql/graphql-spec/issues/687 [graphql/graphql-spec#805]: https://github.com/graphql/graphql-spec/pull/805 [graphql/graphql-spec#825]: https://github.com/graphql/graphql-spec/pull/825 [graphql/graphql-spec#849]: https://github.com/graphql/graphql-spec/pull/849 [graphql/graphql-spec#916]: https://github.com/graphql/graphql-spec/pull/916 +[graphql/graphql-spec#976]: https://github.com/graphql/graphql-spec/pull/976 [graphql/graphql-spec#1040]: https://github.com/graphql/graphql-spec/pull/1040 [graphql/graphql-spec#1142]: https://github.com/graphql/graphql-spec/pull/1142 [graphql/graphql-spec#1170]: https://github.com/graphql/graphql-spec/pull/1170 diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 33a2477d0..42779800a 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -396,7 +396,95 @@ pub trait FromContext { } /// Marker trait for types that can act as context objects for `GraphQL` types. -pub trait Context {} +pub trait Context { + /// Consumes the provided [request `extensions`][0] into this [`Context`]. + /// + /// # Implementation + /// + /// Default implementation does nothing. + /// + /// This method should be implemented if a [`Context`] implementation wants to be populated with + /// [request `extensions`][0]. Since [request `extensions`][0] could be modeled as an arbitrary + /// type, the implementation should downcast to the desired type before use. + /// + /// ```rust + /// # use juniper::{ + /// # Context, DefaultScalarValue, EmptyMutation, EmptySubscription, RootNode, + /// # graphql_object, graphql_value, http::GraphQLRequest, + /// # }; + /// # use serde::{Deserialize, Serialize}; + /// # use serde_json::json; + /// # + /// #[derive(Deserialize, Serialize)] + /// #[serde(rename_all = "camelCase")] + /// struct CustomExtensions { + /// persisted_query: PersistedQueryExtensions, + /// } + /// + /// #[derive(Deserialize, Serialize)] + /// #[serde(rename_all = "camelCase")] + /// struct PersistedQueryExtensions { + /// sha256_hash: Box, + /// } + /// + /// type CustomGraphQLRequest = GraphQLRequest; + /// + /// #[derive(Default)] + /// struct CustomContext { + /// persisted_query_sha256_hash: Option>, + /// } + /// + /// impl Context for CustomContext { + /// fn consume_request_extensions(&mut self, extensions: &T) { + /// use juniper::AnyExt as _; // allows downcasting directly on types without `dyn` + /// + /// if let Some(ext) = extensions.downcast_ref::() { + /// self.persisted_query_sha256_hash = + /// Some(ext.persisted_query.sha256_hash.clone()); + /// } + /// } + /// } + /// + /// struct Query; + /// + /// #[graphql_object] + /// impl Query { + /// fn is_persisted_query(context: &CustomContext) -> bool { + /// context.persisted_query_sha256_hash.is_some() + /// } + /// } + /// # + /// # type Schema = RootNode< + /// # Query, EmptyMutation, EmptySubscription, + /// # >; + /// # + /// # #[tokio::main] + /// # async fn main() { + /// # let request: CustomGraphQLRequest = serde_json::from_value(json!({ + /// # "query": "{ isPersistedQuery }", + /// # "extensions": { + /// # "persistedQuery": { + /// # "sha256Hash": + /// # "c205cf782b5c43c3fc67b5233445b78fbea47b99a0302cf31bda2a8e2162e1e6", + /// # }, + /// # }, + /// # })).unwrap(); + /// # let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + /// # let context = CustomContext::default(); + /// # + /// # assert_eq!( + /// # request.execute(&schema, context).await.into_result(), + /// # Ok((graphql_value!({"isPersistedQuery": true}), vec![])), + /// # ); + /// # } + /// ``` + /// + /// [0]: https://spec.graphql.org/September2025#sel-FANHLBBgBBvC0vW + fn consume_request_extensions(&mut self, extensions: &T) { + _ = extensions; + } + +} impl Context for &C {} diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs index 71f97d581..92230b318 100644 --- a/juniper/src/http/mod.rs +++ b/juniper/src/http/mod.rs @@ -9,6 +9,7 @@ use serde::{ }; use crate::{ + Context, FieldError, GraphQLError, GraphQLSubscriptionType, GraphQLType, GraphQLTypeAsync, RootNode, Value, Variables, ast::InputValue, @@ -24,7 +25,9 @@ use crate::{ /// For GET, you will need to parse the query string and extract "query", /// "operationName", and "variables" manually. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct GraphQLRequest +// Should be specified as top-level, otherwise `serde` infers incorrect `Ext: Default` bound. +#[serde(bound(deserialize = "Ext: Deserialize<'de>"))] +pub struct GraphQLRequest> where S: ScalarValue, { @@ -42,19 +45,18 @@ where serialize = "InputValue: Serialize", ))] pub variables: Option>, + + /// Optional implementation-specific additional information (as per [spec]). + /// + /// [spec]: https://spec.graphql.org/September2025#sel-FANHLBBgBBvC0vW + #[serde(default, skip_serializing_if = "Option::is_none")] + pub extensions: Option, } -impl GraphQLRequest +impl GraphQLRequest where S: ScalarValue, { - // TODO: Remove in 0.17 `juniper` version. - /// Returns the `operation_name` associated with this request. - #[deprecated(since = "0.16.0", note = "Use the direct field access instead.")] - pub fn operation_name(&self) -> Option<&str> { - self.operation_name.as_deref() - } - /// Returns operation [`Variables`] defined withing this request. pub fn variables(&self) -> Variables { self.variables @@ -66,19 +68,6 @@ where .unwrap_or_default() } - /// Construct a new GraphQL request from parts - pub fn new( - query: String, - operation_name: Option, - variables: Option>, - ) -> Self { - Self { - query, - operation_name, - variables, - } - } - /// Execute a GraphQL request synchronously using the specified schema and context /// /// This is a simple wrapper around the `execute_sync` function exposed at the @@ -86,21 +75,22 @@ where pub fn execute_sync( &self, root_node: &RootNode, - context: &QueryT::Context, + mut context: QueryT::Context, ) -> GraphQLResponse where S: ScalarValue, - QueryT: GraphQLType, + QueryT: GraphQLType, MutationT: GraphQLType, SubscriptionT: GraphQLType, + Ext: 'static, { - GraphQLResponse(crate::execute_sync( - &self.query, - self.operation_name.as_deref(), - root_node, - &self.variables(), - context, - )) + let op = self.operation_name.as_deref(); + let vars = &self.variables(); + if let Some(ext) = self.extensions.as_ref() { + context.consume_request_extensions(ext) + } + let res = crate::execute_sync(&self.query, op, root_node, vars, &context); + GraphQLResponse(res) } /// Execute a GraphQL request using the specified schema and context @@ -110,25 +100,129 @@ where pub async fn execute<'a, QueryT, MutationT, SubscriptionT>( &'a self, root_node: &'a RootNode, - context: &'a QueryT::Context, + mut context: QueryT::Context, ) -> GraphQLResponse where - QueryT: GraphQLTypeAsync, - QueryT::TypeInfo: Sync, - QueryT::Context: Sync, - MutationT: GraphQLTypeAsync, - MutationT::TypeInfo: Sync, - SubscriptionT: GraphQLType + Sync, - SubscriptionT::TypeInfo: Sync, S: ScalarValue + Send + Sync, + QueryT: GraphQLTypeAsync, + MutationT: GraphQLTypeAsync, + SubscriptionT: GraphQLType + Sync, + Ext: 'static, { let op = self.operation_name.as_deref(); let vars = &self.variables(); - let res = crate::execute(&self.query, op, root_node, vars, context).await; + if let Some(ext) = self.extensions.as_ref() { + context.consume_request_extensions(ext) + } + let res = crate::execute(&self.query, op, root_node, vars, &context).await; GraphQLResponse(res) } } +/// Simple wrapper around GraphQLRequest to allow the handling of Batch requests. +#[derive(Debug, Deserialize, PartialEq)] +#[serde(untagged)] +#[serde(bound = "GraphQLRequest: Deserialize<'de>")] +pub enum GraphQLBatchRequest> +where + S: ScalarValue, +{ + /// A single operation request. + Single(GraphQLRequest), + + /// A batch operation request. + /// + /// Empty batch is considered as invalid value, so cannot be deserialized. + #[serde(deserialize_with = "deserialize_non_empty_batch")] + Batch(Vec>), +} + +fn deserialize_non_empty_batch<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, + T: Deserialize<'de>, +{ + use de::Error as _; + + let v = Vec::::deserialize(deserializer)?; + if v.is_empty() { + Err(D::Error::invalid_length( + 0, + &"non-empty batch of GraphQL requests", + )) + } else { + Ok(v) + } +} + +impl GraphQLBatchRequest +where + S: ScalarValue, +{ + /// Execute a GraphQL batch request synchronously using the specified schema and context + /// + /// This is a simple wrapper around the `execute_sync` function exposed in GraphQLRequest. + pub fn execute_sync<'a, QueryT, MutationT, SubscriptionT>( + &'a self, + root_node: &'a RootNode, + context: QueryT::Context, + ) -> GraphQLBatchResponse + where + QueryT: GraphQLType, + MutationT: GraphQLType, + SubscriptionT: GraphQLType, + Ext: 'static, + { + match self { + Self::Single(req) => GraphQLBatchResponse::Single(req.execute_sync(root_node, context)), + Self::Batch(reqs) => GraphQLBatchResponse::Batch( + reqs.iter() + .map(|req| req.execute_sync(root_node, context.clone())) + .collect(), + ), + } + } + + /// Executes a GraphQL request using the specified schema and context + /// + /// This is a simple wrapper around the `execute` function exposed in + /// GraphQLRequest + pub async fn execute<'a, QueryT, MutationT, SubscriptionT>( + &'a self, + root_node: &'a RootNode, + context: QueryT::Context, + ) -> GraphQLBatchResponse + where + S: Send + Sync, + QueryT: GraphQLTypeAsync, + MutationT: GraphQLTypeAsync, + SubscriptionT: GraphQLSubscriptionType, + Ext: 'static, + { + match self { + Self::Single(req) => { + let resp = req.execute(root_node, context).await; + GraphQLBatchResponse::Single(resp) + } + Self::Batch(reqs) => { + let resps = futures::future::join_all( + reqs.iter().map(|req| req.execute(root_node, context.clone())), + ) + .await; + GraphQLBatchResponse::Batch(resps) + } + } + } + + /// The operation names of the request. + pub fn operation_names(&self) -> Vec> { + match self { + Self::Single(req) => vec![req.operation_name.as_deref()], + Self::Batch(reqs) => reqs.iter().map(|r| r.operation_name.as_deref()).collect(), + } + } +} + /// Resolve a GraphQL subscription into `Value` using the /// specified schema and context. /// This is a wrapper around the `resolve_into_stream` function exposed at the top @@ -208,8 +302,8 @@ where where S: ser::Serializer, { - match self.0 { - Ok((ref res, ref err)) => { + match &self.0 { + Ok((res, err)) => { let mut map = serializer.serialize_map(None)?; map.serialize_key("data")?; @@ -222,7 +316,7 @@ where map.end() } - Err(ref err) => { + Err(err) => { let mut map = serializer.serialize_map(Some(1))?; map.serialize_key("errors")?; map.serialize_value(err)?; @@ -232,114 +326,6 @@ where } } -/// Simple wrapper around GraphQLRequest to allow the handling of Batch requests. -#[derive(Debug, Deserialize, PartialEq)] -#[serde(untagged)] -#[serde(bound = "InputValue: Deserialize<'de>")] -pub enum GraphQLBatchRequest -where - S: ScalarValue, -{ - /// A single operation request. - Single(GraphQLRequest), - - /// A batch operation request. - /// - /// Empty batch is considered as invalid value, so cannot be deserialized. - #[serde(deserialize_with = "deserialize_non_empty_batch")] - Batch(Vec>), -} - -fn deserialize_non_empty_batch<'de, D, T>(deserializer: D) -> Result, D::Error> -where - D: de::Deserializer<'de>, - T: Deserialize<'de>, -{ - use de::Error as _; - - let v = Vec::::deserialize(deserializer)?; - if v.is_empty() { - Err(D::Error::invalid_length( - 0, - &"non-empty batch of GraphQL requests", - )) - } else { - Ok(v) - } -} - -impl GraphQLBatchRequest -where - S: ScalarValue, -{ - /// Execute a GraphQL batch request synchronously using the specified schema and context - /// - /// This is a simple wrapper around the `execute_sync` function exposed in GraphQLRequest. - pub fn execute_sync<'a, QueryT, MutationT, SubscriptionT>( - &'a self, - root_node: &'a RootNode, - context: &QueryT::Context, - ) -> GraphQLBatchResponse - where - QueryT: GraphQLType, - MutationT: GraphQLType, - SubscriptionT: GraphQLType, - { - match *self { - Self::Single(ref req) => { - GraphQLBatchResponse::Single(req.execute_sync(root_node, context)) - } - Self::Batch(ref reqs) => GraphQLBatchResponse::Batch( - reqs.iter() - .map(|req| req.execute_sync(root_node, context)) - .collect(), - ), - } - } - - /// Executes a GraphQL request using the specified schema and context - /// - /// This is a simple wrapper around the `execute` function exposed in - /// GraphQLRequest - pub async fn execute<'a, QueryT, MutationT, SubscriptionT>( - &'a self, - root_node: &'a RootNode, - context: &'a QueryT::Context, - ) -> GraphQLBatchResponse - where - QueryT: GraphQLTypeAsync, - QueryT::TypeInfo: Sync, - QueryT::Context: Sync, - MutationT: GraphQLTypeAsync, - MutationT::TypeInfo: Sync, - SubscriptionT: GraphQLSubscriptionType, - SubscriptionT::TypeInfo: Sync, - S: Send + Sync, - { - match self { - Self::Single(req) => { - let resp = req.execute(root_node, context).await; - GraphQLBatchResponse::Single(resp) - } - Self::Batch(reqs) => { - let resps = futures::future::join_all( - reqs.iter().map(|req| req.execute(root_node, context)), - ) - .await; - GraphQLBatchResponse::Batch(resps) - } - } - } - - /// The operation names of the request. - pub fn operation_names(&self) -> Vec> { - match self { - Self::Single(req) => vec![req.operation_name.as_deref()], - Self::Batch(reqs) => reqs.iter().map(|r| r.operation_name.as_deref()).collect(), - } - } -} - /// Simple wrapper around the result (GraphQLResponse) from executing a GraphQLBatchRequest /// /// This struct implements Serialize, so you can simply serialize this diff --git a/juniper/src/tests/subscriptions.rs b/juniper/src/tests/subscriptions.rs index 80a23b160..0437f6e94 100644 --- a/juniper/src/tests/subscriptions.rs +++ b/juniper/src/tests/subscriptions.rs @@ -94,7 +94,12 @@ fn create_and_execute( ), Vec>, > { - let request = GraphQLRequest::new(query, None, None); + let request = GraphQLRequest { + query, + operation_name: None, + variables: None, + extensions: None, + }; let root_node = Schema::new(MyQuery, EmptyMutation::new(), MySubscription); diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs index 52527b80d..17580901b 100644 --- a/juniper_actix/src/lib.rs +++ b/juniper_actix/src/lib.rs @@ -36,7 +36,12 @@ where variables, } = get_req; let variables = variables.map(|s| serde_json::from_str(&s).unwrap()); - Self::new(query, operation_name, variables) + Self { + query, + operation_name, + variables, + extensions: None, + } } } @@ -117,9 +122,12 @@ where } "application/graphql" => { let body = String::from_request(&req, &mut payload.into_inner()).await?; - Ok(GraphQLBatchRequest::Single(GraphQLRequest::new( - body, None, None, - ))) + Ok(GraphQLBatchRequest::Single(GraphQLRequest { + query: body, + operation_name: None, + variables: None, + extensions: None, + })) } _ => Err(JsonPayloadError::ContentType), }?; diff --git a/juniper_axum/src/extract.rs b/juniper_axum/src/extract.rs index 6ebf0af54..3780c4777 100644 --- a/juniper_axum/src/extract.rs +++ b/juniper_axum/src/extract.rs @@ -130,9 +130,12 @@ where String::from_request(req, state) .await .map(|body| { - Self(GraphQLBatchRequest::Single(GraphQLRequest::new( - body, None, None, - ))) + Self(GraphQLBatchRequest::Single(GraphQLRequest { + query: body, + operation_name: None, + variables: None, + extensions: None, + })) }) .map_err(|_| (StatusCode::BAD_REQUEST, "Not valid UTF-8 body").into_response()) } @@ -170,11 +173,12 @@ impl TryFrom for GraphQLRequest { operation_name, variables, } = req; - Ok(Self::new( + Ok(Self { query, operation_name, - variables.map(|v| serde_json::from_str(&v)).transpose()?, - )) + variables: variables.map(|v| serde_json::from_str(&v)).transpose()?, + extensions: None, + }) } } @@ -198,11 +202,12 @@ mod juniper_request_tests { .body(Body::empty()) .unwrap_or_else(|e| panic!("cannot build `Request`: {e}")); - let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest::new( - "{ add(a: 2, b: 3) }".into(), - None, - None, - ))); + let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest { + query: "{ add(a: 2, b: 3) }".into(), + operation_name: None, + variables: None, + extensions: None, + })); assert_eq!(do_from_request(req).await, expected); } @@ -219,11 +224,13 @@ mod juniper_request_tests { .body(Body::empty()) .unwrap_or_else(|e| panic!("cannot build `Request`: {e}")); - let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest::new( - "query($id: String!) { human(id: $id) { id, name, appearsIn, homePlanet } }".into(), - None, - Some(graphql_input_value!({"id": "1000"})), - ))); + let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest { + query: "query($id: String!) { human(id: $id) { id, name, appearsIn, homePlanet } }" + .into(), + operation_name: None, + variables: None, + extensions: None, + })); assert_eq!(do_from_request(req).await, expected); } @@ -235,11 +242,12 @@ mod juniper_request_tests { .body(Body::from(r#"{"query": "{ add(a: 2, b: 3) }"}"#)) .unwrap_or_else(|e| panic!("cannot build `Request`: {e}")); - let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest::new( - "{ add(a: 2, b: 3) }".to_string(), - None, - None, - ))); + let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest { + query: "{ add(a: 2, b: 3) }".to_string(), + operation_name: None, + variables: None, + extensions: None, + })); assert_eq!(do_from_request(req).await, expected); } @@ -251,11 +259,12 @@ mod juniper_request_tests { .body(Body::from(r#"{"query": "{ add(a: 2, b: 3) }"}"#)) .unwrap_or_else(|e| panic!("cannot build `Request`: {e}")); - let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest::new( - "{ add(a: 2, b: 3) }".to_string(), - None, - None, - ))); + let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest { + query: "{ add(a: 2, b: 3) }".into(), + operation_name: None, + variables: None, + extensions: None, + })); assert_eq!(do_from_request(req).await, expected); } @@ -267,11 +276,12 @@ mod juniper_request_tests { .body(Body::from(r#"{ add(a: 2, b: 3) }"#)) .unwrap_or_else(|e| panic!("cannot build `Request`: {e}")); - let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest::new( - "{ add(a: 2, b: 3) }".to_string(), - None, - None, - ))); + let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest { + query: "{ add(a: 2, b: 3) }".to_string(), + operation_name: None, + variables: None, + extensions: None, + })); assert_eq!(do_from_request(req).await, expected); } @@ -283,11 +293,12 @@ mod juniper_request_tests { .body(Body::from(r#"{ add(a: 2, b: 3) }"#)) .unwrap_or_else(|e| panic!("cannot build `Request`: {e}")); - let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest::new( - "{ add(a: 2, b: 3) }".to_string(), - None, - None, - ))); + let expected = JuniperRequest(GraphQLBatchRequest::Single(GraphQLRequest { + query: "{ add(a: 2, b: 3) }".to_string(), + operation_name: None, + variables: None, + extensions: None, + })); assert_eq!(do_from_request(req).await, expected); } diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 41d408490..1337ac523 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -138,9 +138,12 @@ where let query = String::from_utf8(chunk.to_bytes().into()).map_err(GraphQLRequestError::BodyUtf8)?; - Ok(GraphQLBatchRequest::Single(GraphQLRequest::new( - query, None, None, - ))) + Ok(GraphQLBatchRequest::Single(GraphQLRequest { + query, + operation_name: None, + variables: None, + extensions: None, + })) } /// Generates a [`Response`] page containing [GraphiQL]. @@ -284,7 +287,12 @@ where } } match query { - Some(query) => Ok(JuniperGraphQLRequest::new(query, operation_name, variables)), + Some(query) => Ok(JuniperGraphQLRequest { + query, + operation_name, + variables, + extensions: None, + }), None => Err(GraphQLRequestError::Invalid( "'query' parameter is missing".into(), )), diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index bdff94bad..9705540a7 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -354,7 +354,12 @@ where match ctx.errors.is_empty() { true => Ok(GraphQLRequest(GraphQLBatchRequest::Single( - http::GraphQLRequest::new(ctx.query.unwrap(), ctx.operation_name, ctx.variables), + http::GraphQLRequest { + query: ctx.query.unwrap(), + operation_name: ctx.operation_name, + variables: ctx.variables, + extensions: None, + }, ))), false => Err(ctx.errors), } @@ -402,7 +407,12 @@ where Err(e) => return Outcome::Error((Status::BadRequest, e.to_string())), } } else { - GraphQLBatchRequest::Single(http::GraphQLRequest::new(body, None, None)) + GraphQLBatchRequest::Single(http::GraphQLRequest { + query: body, + operation_name: None, + variables: None, + extensions: None, + }) })) }) .await @@ -538,9 +548,12 @@ mod fromform_tests { assert!(result.is_ok()); let variables = ::serde_json::from_str::(r#"{"foo":"bar"}"#).unwrap(); - let expected = GraphQLRequest(http::GraphQLBatchRequest::Single( - http::GraphQLRequest::new("test".into(), None, Some(variables)), - )); + let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(http::GraphQLRequest { + query: "test".into(), + operation_name: None, + variables: Some(variables), + extensions: None, + })); assert_eq!(result.unwrap(), expected); } @@ -551,9 +564,12 @@ mod fromform_tests { r#"query=test&variables={"foo":"x%20y%26%3F+z"}"#, )); let variables = ::serde_json::from_str::(r#"{"foo":"x y&? z"}"#).unwrap(); - let expected = GraphQLRequest(http::GraphQLBatchRequest::Single( - http::GraphQLRequest::new("test".into(), None, Some(variables)), - )); + let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(http::GraphQLRequest { + query: "test".into(), + operation_name: None, + variables: Some(variables), + extensions: None, + })); assert_eq!(result.unwrap(), expected); } @@ -566,9 +582,12 @@ mod fromform_tests { assert!(result.is_ok()); - let expected = GraphQLRequest(http::GraphQLBatchRequest::Single( - http::GraphQLRequest::new("%foo bar baz&?".into(), Some("test".into()), None), - )); + let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(http::GraphQLRequest { + query: "%foo bar baz&?".into(), + operation_name: "test".into(), + variables: None, + extensions: None, + })); assert_eq!(result.unwrap(), expected); } diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 35e3e4b2c..0d10b9671 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -305,7 +305,12 @@ where .and_then(async |body: Bytes| { let query = str::from_utf8(body.as_ref()) .map_err(|e| reject::custom(FilterError::NonUtf8Body(e)))?; - let req = GraphQLRequest::new(query.into(), None, None); + let req = GraphQLRequest { + query: query.into(), + operation_name: None, + variables: None, + extensions: None, + }; Ok::, Rejection>(GraphQLBatchRequest::Single(req)) }) } @@ -319,15 +324,18 @@ where warp::get() .and(query::query()) .and_then(async |mut qry: HashMap| { - let req = GraphQLRequest::new( - qry.remove("query") + let req = GraphQLRequest { + query: qry + .remove("query") .ok_or_else(|| reject::custom(FilterError::MissingPathQuery))?, - qry.remove("operation_name"), - qry.remove("variables") + operation_name: qry.remove("operation_name"), + variables: qry + .remove("variables") .map(|vs| serde_json::from_str(&vs)) .transpose() .map_err(|e| reject::custom(FilterError::InvalidPathVariables(e)))?, - ); + extensions: todo!(), + }; Ok::, Rejection>(GraphQLBatchRequest::Single(req)) }) }