Introduce ability to pre-parse Request's query
This commit is contained in:
parent
9318c0731d
commit
a78b1415b9
|
@ -91,8 +91,8 @@ pub async fn receive_batch_json(body: impl AsyncRead) -> Result<BatchRequest, Pa
|
||||||
body.read_to_end(&mut data)
|
body.read_to_end(&mut data)
|
||||||
.await
|
.await
|
||||||
.map_err(ParseRequestError::Io)?;
|
.map_err(ParseRequestError::Io)?;
|
||||||
Ok(serde_json::from_slice::<BatchRequest>(&data)
|
serde_json::from_slice::<BatchRequest>(&data)
|
||||||
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))?)
|
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive a GraphQL request from a body as CBOR.
|
/// Receive a GraphQL request from a body as CBOR.
|
||||||
|
|
|
@ -365,6 +365,7 @@ impl std::str::FromStr for Protocols {
|
||||||
/// A websocket message received from the client
|
/// A websocket message received from the client
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
|
#[allow(clippy::large_enum_variant)] //Request is at fault
|
||||||
pub enum ClientMessage {
|
pub enum ClientMessage {
|
||||||
/// A new connection
|
/// A new connection
|
||||||
ConnectionInit {
|
ConnectionInit {
|
||||||
|
|
|
@ -4,7 +4,9 @@ use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
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.
|
/// GraphQL request.
|
||||||
///
|
///
|
||||||
|
@ -42,6 +44,9 @@ pub struct Request {
|
||||||
/// Disable introspection queries for this request.
|
/// Disable introspection queries for this request.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub disable_introspection: bool,
|
pub disable_introspection: bool,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) parsed_query: Option<ExecutableDocument>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
|
@ -55,6 +60,7 @@ impl Request {
|
||||||
data: Data::default(),
|
data: Data::default(),
|
||||||
extensions: Default::default(),
|
extensions: Default::default(),
|
||||||
disable_introspection: false,
|
disable_introspection: false,
|
||||||
|
parsed_query: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +93,24 @@ impl Request {
|
||||||
self
|
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.
|
/// Set a variable to an upload value.
|
||||||
///
|
///
|
||||||
/// `var_path` is a dot-separated path to the item that begins with `variables`, for example
|
/// `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:** <https://www.apollographql.com/blog/batching-client-graphql-queries-a685f5bcd41b/>
|
/// **Reference:** <https://www.apollographql.com/blog/batching-client-graphql-queries-a685f5bcd41b/>
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
#[allow(clippy::large_enum_variant)] //Request is at fault
|
||||||
pub enum BatchRequest {
|
pub enum BatchRequest {
|
||||||
/// Single query
|
/// Single query
|
||||||
Single(Request),
|
Single(Request),
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use core::future::Future;
|
||||||
|
use core::pin::Pin;
|
||||||
|
use core::task;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -10,7 +13,9 @@ use crate::context::{Data, QueryEnvInner};
|
||||||
use crate::custom_directive::CustomDirectiveFactory;
|
use crate::custom_directive::CustomDirectiveFactory;
|
||||||
use crate::extensions::{ExtensionFactory, Extensions};
|
use crate::extensions::{ExtensionFactory, Extensions};
|
||||||
use crate::model::__DirectiveLocation;
|
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::parser::{parse_query, Positioned};
|
||||||
use crate::registry::{MetaDirective, MetaInputValue, Registry};
|
use crate::registry::{MetaDirective, MetaInputValue, Registry};
|
||||||
use crate::resolver_utils::{resolve_container, resolve_container_serial};
|
use crate::resolver_utils::{resolve_container, resolve_container_serial};
|
||||||
|
@ -22,6 +27,26 @@ use crate::{
|
||||||
QueryEnv, Request, Response, ServerError, SubscriptionType, Variables, ID,
|
QueryEnv, Request, Response, ServerError, SubscriptionType, Variables, ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct ParseQueryFut<'a> {
|
||||||
|
query: &'a str,
|
||||||
|
parsed: Option<ExecutableDocument>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Future for ParseQueryFut<'a> {
|
||||||
|
type Output = Result<ExecutableDocument, ServerError>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn poll(self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll<Self::Output> {
|
||||||
|
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
|
/// Schema builder
|
||||||
pub struct SchemaBuilder<Query, Mutation, Subscription> {
|
pub struct SchemaBuilder<Query, Mutation, Subscription> {
|
||||||
validation_mode: ValidationMode,
|
validation_mode: ValidationMode,
|
||||||
|
@ -427,10 +452,13 @@ where
|
||||||
let query_data = Arc::new(std::mem::take(&mut request.data));
|
let query_data = Arc::new(std::mem::take(&mut request.data));
|
||||||
extensions.attach_query_data(query_data.clone());
|
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 mut document = {
|
||||||
let query = &request.query;
|
let query = &request.query;
|
||||||
let fut_parse = async { parse_query(&query).map_err(Into::<ServerError>::into) };
|
let fut_parse = ParseQueryFut {
|
||||||
|
query,
|
||||||
|
parsed: request.parsed_query.take(),
|
||||||
|
};
|
||||||
futures_util::pin_mut!(fut_parse);
|
futures_util::pin_mut!(fut_parse);
|
||||||
extensions
|
extensions
|
||||||
.parse_query(&query, &request.variables, &mut fut_parse)
|
.parse_query(&query, &request.variables, &mut fut_parse)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user