From 22d61df4d7fb229c4cbc8dbbbef5bcee08ecdb34 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 15 Apr 2022 15:58:09 +0900 Subject: [PATCH 1/2] Introduce ability to pre-parse Request's query --- src/http/mod.rs | 4 ++-- src/http/websocket.rs | 1 + src/request.rs | 27 ++++++++++++++++++++++++++- src/schema.rs | 34 +++++++++++++++++++++++++++++++--- 4 files changed, 60 insertions(+), 6 deletions(-) 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) From 2f0bcaa8ee683185d210b191654db4ec153d1477 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 15 Apr 2022 15:31:07 +0800 Subject: [PATCH 2/2] Remove useless `ParseQueryFut` type --- src/http/mod.rs | 4 ++-- src/schema.rs | 38 ++++++++------------------------------ 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/src/http/mod.rs b/src/http/mod.rs index 2fddf6e9..8adf081e 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -111,6 +111,6 @@ pub async fn receive_batch_cbor(body: impl AsyncRead) -> Result(&data) - .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))?) + serde_cbor::from_slice::(&data) + .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e))) } diff --git a/src/schema.rs b/src/schema.rs index 7fbac23c..9480cfd9 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,6 +1,3 @@ -use core::future::Future; -use core::pin::Pin; -use core::task; use std::any::Any; use std::collections::HashMap; use std::ops::Deref; @@ -13,9 +10,7 @@ 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, ExecutableDocument, OperationType, Selection, SelectionSet, -}; +use crate::parser::types::{Directive, DocumentOperations, OperationType, Selection, SelectionSet}; use crate::parser::{parse_query, Positioned}; use crate::registry::{MetaDirective, MetaInputValue, Registry}; use crate::resolver_utils::{resolve_container, resolve_container_serial}; @@ -27,26 +22,6 @@ 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, @@ -455,13 +430,16 @@ where let mut request = extensions.prepare_request(request).await?; let mut document = { let query = &request.query; - let fut_parse = ParseQueryFut { - query, - parsed: request.parsed_query.take(), + let parsed_doc = request.parsed_query.take(); + let fut_parse = async move { + match parsed_doc { + Some(parsed_doc) => Ok(parsed_doc), + None => parse_query(query).map_err(Into::into), + } }; futures_util::pin_mut!(fut_parse); extensions - .parse_query(&query, &request.variables, &mut fut_parse) + .parse_query(query, &request.variables, &mut fut_parse) .await? };