diff --git a/src/http/mod.rs b/src/http/mod.rs index c97e02bf..2fddf6e9 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -91,8 +91,8 @@ pub async fn receive_batch_json(body: impl AsyncRead) -> Result(&data) - .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))?) + serde_json::from_slice::(&data) + .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e))) } /// Receive a GraphQL request from a body as CBOR. diff --git a/src/http/websocket.rs b/src/http/websocket.rs index e8ad9ffd..1448a96a 100644 --- a/src/http/websocket.rs +++ b/src/http/websocket.rs @@ -365,6 +365,7 @@ impl std::str::FromStr for Protocols { /// A websocket message received from the client #[derive(Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] +#[allow(clippy::large_enum_variant)] //Request is at fault pub enum ClientMessage { /// A new connection ConnectionInit { diff --git a/src/request.rs b/src/request.rs index 9132e7af..7e24d82f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -4,7 +4,9 @@ use std::fmt::{self, Debug, Formatter}; use serde::{Deserialize, Deserializer, Serialize}; -use crate::{Data, ParseRequestError, UploadValue, Value, Variables}; +use crate::parser::parse_query; +use crate::parser::types::ExecutableDocument; +use crate::{Data, ParseRequestError, ServerError, UploadValue, Value, Variables}; /// GraphQL request. /// @@ -42,6 +44,9 @@ pub struct Request { /// Disable introspection queries for this request. #[serde(skip)] pub disable_introspection: bool, + + #[serde(skip)] + pub(crate) parsed_query: Option, } impl Request { @@ -55,6 +60,7 @@ impl Request { data: Data::default(), extensions: Default::default(), disable_introspection: false, + parsed_query: None, } } @@ -87,6 +93,24 @@ impl Request { self } + #[inline] + /// Performs parsing of query ahead of execution. + /// + /// This effectively allows to inspect query information, before passing request to schema for + /// execution as long as query is valid. + pub fn parsed_query(&mut self) -> Result<&ExecutableDocument, ServerError> { + if self.parsed_query.is_none() { + match parse_query(&self.query) { + Ok(parsed) => self.parsed_query = Some(parsed), + Err(error) => return Err(error.into()), + } + } + + //forbid_unsafe effectively bans optimize away else branch here so use unwrap + //but this unwrap never panics + Ok(self.parsed_query.as_ref().unwrap()) + } + /// Set a variable to an upload value. /// /// `var_path` is a dot-separated path to the item that begins with `variables`, for example @@ -141,6 +165,7 @@ impl Debug for Request { /// **Reference:** #[derive(Debug, Deserialize)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] //Request is at fault pub enum BatchRequest { /// Single query Single(Request), diff --git a/src/schema.rs b/src/schema.rs index bfdd0920..7fbac23c 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,3 +1,6 @@ +use core::future::Future; +use core::pin::Pin; +use core::task; use std::any::Any; use std::collections::HashMap; use std::ops::Deref; @@ -10,7 +13,9 @@ use crate::context::{Data, QueryEnvInner}; use crate::custom_directive::CustomDirectiveFactory; use crate::extensions::{ExtensionFactory, Extensions}; use crate::model::__DirectiveLocation; -use crate::parser::types::{Directive, DocumentOperations, OperationType, Selection, SelectionSet}; +use crate::parser::types::{ + Directive, DocumentOperations, ExecutableDocument, OperationType, Selection, SelectionSet, +}; use crate::parser::{parse_query, Positioned}; use crate::registry::{MetaDirective, MetaInputValue, Registry}; use crate::resolver_utils::{resolve_container, resolve_container_serial}; @@ -22,6 +27,26 @@ use crate::{ QueryEnv, Request, Response, ServerError, SubscriptionType, Variables, ID, }; +pub struct ParseQueryFut<'a> { + query: &'a str, + parsed: Option, +} + +impl<'a> Future for ParseQueryFut<'a> { + type Output = Result; + + #[inline(always)] + fn poll(self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll { + let this = Pin::into_inner(self); + let result = match this.parsed.take() { + Some(document) => Ok(document), + None => parse_query(this.query).map_err(Into::into), + }; + + task::Poll::Ready(result) + } +} + /// Schema builder pub struct SchemaBuilder { validation_mode: ValidationMode, @@ -427,10 +452,13 @@ where let query_data = Arc::new(std::mem::take(&mut request.data)); extensions.attach_query_data(query_data.clone()); - let request = extensions.prepare_request(request).await?; + let mut request = extensions.prepare_request(request).await?; let mut document = { let query = &request.query; - let fut_parse = async { parse_query(&query).map_err(Into::::into) }; + let fut_parse = ParseQueryFut { + query, + parsed: request.parsed_query.take(), + }; futures_util::pin_mut!(fut_parse); extensions .parse_query(&query, &request.variables, &mut fut_parse)