Add support for parse request from query string #1085
This commit is contained in:
parent
73f6b0b136
commit
053c1178c2
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# [4.0.14] 2022-09-25
|
||||||
|
|
||||||
|
# Add support for parse request from query string. [#1085](https://github.com/async-graphql/async-graphql/issues/1085)
|
||||||
|
|
||||||
# [4.0.13] 2022-09-09
|
# [4.0.13] 2022-09-09
|
||||||
|
|
||||||
- Compare to expected schema [#1048](https://github.com/async-graphql/async-graphql/pull/1048)
|
- Compare to expected schema [#1048](https://github.com/async-graphql/async-graphql/pull/1048)
|
||||||
|
|
|
@ -54,6 +54,7 @@ static_assertions = "1.1.0"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
thiserror = "1.0.24"
|
thiserror = "1.0.24"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
serde_urlencoded = "0.7.0"
|
||||||
|
|
||||||
# Feature optional dependencies
|
# Feature optional dependencies
|
||||||
bson = { version = "2.4.0", optional = true, features = [
|
bson = { version = "2.4.0", optional = true, features = [
|
||||||
|
|
|
@ -23,7 +23,6 @@ futures-channel = "0.3.13"
|
||||||
futures-util = { version = "0.3.0", default-features = false }
|
futures-util = { version = "0.3.0", default-features = false }
|
||||||
serde_cbor = { version = "0.11.2", optional = true }
|
serde_cbor = { version = "0.11.2", optional = true }
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
serde_urlencoded = "0.7.0"
|
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -75,7 +75,8 @@ impl FromRequest for GraphQLBatchRequest {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if req.method() == Method::GET {
|
if req.method() == Method::GET {
|
||||||
let res = serde_urlencoded::from_str(req.query_string());
|
let res = async_graphql::http::parse_query_string(req.query_string())
|
||||||
|
.map_err(|err| io::Error::new(ErrorKind::Other, err));
|
||||||
Box::pin(async move { Ok(Self(async_graphql::BatchRequest::Single(res?))) })
|
Box::pin(async move { Ok(Self(async_graphql::BatchRequest::Single(res?))) })
|
||||||
} else if req.method() == Method::POST {
|
} else if req.method() == Method::POST {
|
||||||
let content_type = req
|
let content_type = req
|
||||||
|
|
|
@ -20,6 +20,5 @@ bytes = "1.0.1"
|
||||||
futures-util = "0.3.0"
|
futures-util = "0.3.0"
|
||||||
http-body = "0.4.2"
|
http-body = "0.4.2"
|
||||||
serde_json = "1.0.66"
|
serde_json = "1.0.66"
|
||||||
serde_urlencoded = "0.7.0"
|
|
||||||
tokio-util = { version = "0.7.1", features = ["io", "compat"] }
|
tokio-util = { version = "0.7.1", features = ["io", "compat"] }
|
||||||
tower-service = "0.3"
|
tower-service = "0.3"
|
||||||
|
|
|
@ -107,7 +107,7 @@ where
|
||||||
|
|
||||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
if let (&Method::GET, uri) = (req.method(), req.uri()) {
|
if let (&Method::GET, uri) = (req.method(), req.uri()) {
|
||||||
let res = serde_urlencoded::from_str(uri.query().unwrap_or_default()).map_err(|err| {
|
let res = async_graphql::http::parse_query_string(uri.query().unwrap_or_default()).map_err(|err| {
|
||||||
ParseRequestError::Io(std::io::Error::new(
|
ParseRequestError::Io(std::io::Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
format!("failed to parse graphql request from uri query: {}", err),
|
format!("failed to parse graphql request from uri query: {}", err),
|
||||||
|
|
|
@ -3,7 +3,6 @@ use poem::{
|
||||||
async_trait,
|
async_trait,
|
||||||
error::BadRequest,
|
error::BadRequest,
|
||||||
http::{header, Method},
|
http::{header, Method},
|
||||||
web::Query,
|
|
||||||
FromRequest, Request, RequestBody, Result,
|
FromRequest, Request, RequestBody, Result,
|
||||||
};
|
};
|
||||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||||
|
@ -68,7 +67,9 @@ pub struct GraphQLBatchRequest(pub async_graphql::BatchRequest);
|
||||||
impl<'a> FromRequest<'a> for GraphQLBatchRequest {
|
impl<'a> FromRequest<'a> for GraphQLBatchRequest {
|
||||||
async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result<Self> {
|
async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result<Self> {
|
||||||
if req.method() == Method::GET {
|
if req.method() == Method::GET {
|
||||||
let req = Query::from_request(req, body).await?.0;
|
let req =
|
||||||
|
async_graphql::http::parse_query_string(req.uri().query().unwrap_or_default())
|
||||||
|
.map_err(BadRequest)?;
|
||||||
Ok(Self(async_graphql::BatchRequest::Single(req)))
|
Ok(Self(async_graphql::BatchRequest::Single(req)))
|
||||||
} else {
|
} else {
|
||||||
let content_type = req
|
let content_type = req
|
||||||
|
|
|
@ -132,7 +132,9 @@ pub async fn receive_batch_request_opts<State: Clone + Send + Sync + 'static>(
|
||||||
opts: MultipartOptions,
|
opts: MultipartOptions,
|
||||||
) -> tide::Result<async_graphql::BatchRequest> {
|
) -> tide::Result<async_graphql::BatchRequest> {
|
||||||
if request.method() == Method::Get {
|
if request.method() == Method::Get {
|
||||||
request.query::<async_graphql::Request>().map(Into::into)
|
async_graphql::http::parse_query_string(request.url().query().unwrap_or_default())
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(|err| tide::Error::new(StatusCode::BadRequest, err))
|
||||||
} else if request.method() == Method::Post {
|
} else if request.method() == Method::Post {
|
||||||
let body = request.take_body();
|
let body = request.take_body();
|
||||||
let content_type = request
|
let content_type = request
|
||||||
|
|
|
@ -35,7 +35,13 @@ where
|
||||||
Subscription: SubscriptionType + 'static,
|
Subscription: SubscriptionType + 'static,
|
||||||
{
|
{
|
||||||
warp::any()
|
warp::any()
|
||||||
.and(warp::get().and(warp::query()).map(BatchRequest::Single))
|
.and(warp::get().and(warp::filters::query::raw()).and_then(
|
||||||
|
|query_string: String| async move {
|
||||||
|
async_graphql::http::parse_query_string(&query_string)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(|e| warp::reject::custom(GraphQLBadRequest(e)))
|
||||||
|
},
|
||||||
|
))
|
||||||
.or(warp::post()
|
.or(warp::post()
|
||||||
.and(warp::header::optional::<String>("content-type"))
|
.and(warp::header::optional::<String>("content-type"))
|
||||||
.and(warp::body::stream())
|
.and(warp::body::stream())
|
||||||
|
|
|
@ -6,18 +6,59 @@ mod multipart;
|
||||||
mod playground_source;
|
mod playground_source;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
use futures_util::io::{AsyncRead, AsyncReadExt};
|
use futures_util::io::{AsyncRead, AsyncReadExt};
|
||||||
pub use graphiql_source::graphiql_source;
|
pub use graphiql_source::graphiql_source;
|
||||||
pub use graphiql_v2_source::GraphiQLSource;
|
pub use graphiql_v2_source::GraphiQLSource;
|
||||||
use mime;
|
use mime;
|
||||||
pub use multipart::MultipartOptions;
|
pub use multipart::MultipartOptions;
|
||||||
pub use playground_source::{playground_source, GraphQLPlaygroundConfig};
|
pub use playground_source::{playground_source, GraphQLPlaygroundConfig};
|
||||||
|
use serde::Deserialize;
|
||||||
pub use websocket::{
|
pub use websocket::{
|
||||||
ClientMessage, Protocols as WebSocketProtocols, WebSocket, WsMessage, ALL_WEBSOCKET_PROTOCOLS,
|
ClientMessage, Protocols as WebSocketProtocols, WebSocket, WsMessage, ALL_WEBSOCKET_PROTOCOLS,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{BatchRequest, ParseRequestError, Request};
|
use crate::{BatchRequest, ParseRequestError, Request};
|
||||||
|
|
||||||
|
/// Parse a GraphQL request from a query string.
|
||||||
|
pub fn parse_query_string(input: &str) -> Result<Request, ParseRequestError> {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct RequestSerde {
|
||||||
|
#[serde(default)]
|
||||||
|
pub query: String,
|
||||||
|
pub operation_name: Option<String>,
|
||||||
|
pub variables: Option<String>,
|
||||||
|
pub extensions: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let request: RequestSerde = serde_urlencoded::from_str(input)
|
||||||
|
.map_err(|err| std::io::Error::new(ErrorKind::Other, err))?;
|
||||||
|
let variables = request
|
||||||
|
.variables
|
||||||
|
.map(|data| serde_json::from_str(&data))
|
||||||
|
.transpose()
|
||||||
|
.map_err(|err| {
|
||||||
|
std::io::Error::new(ErrorKind::Other, format!("invalid variables: {}", err))
|
||||||
|
})?
|
||||||
|
.unwrap_or_default();
|
||||||
|
let extensions = request
|
||||||
|
.extensions
|
||||||
|
.map(|data| serde_json::from_str(&data))
|
||||||
|
.transpose()
|
||||||
|
.map_err(|err| {
|
||||||
|
std::io::Error::new(ErrorKind::Other, format!("invalid extensions: {}", err))
|
||||||
|
})?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(Request {
|
||||||
|
operation_name: request.operation_name,
|
||||||
|
variables,
|
||||||
|
extensions,
|
||||||
|
..Request::new(request.query)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Receive a GraphQL request from a content type and body.
|
/// Receive a GraphQL request from a content type and body.
|
||||||
pub async fn receive_body(
|
pub async fn receive_body(
|
||||||
content_type: Option<impl AsRef<str>>,
|
content_type: Option<impl AsRef<str>>,
|
||||||
|
@ -116,3 +157,33 @@ pub async fn receive_batch_cbor(body: impl AsyncRead) -> Result<BatchRequest, Pa
|
||||||
serde_cbor::from_slice::<BatchRequest>(&data)
|
serde_cbor::from_slice::<BatchRequest>(&data)
|
||||||
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
|
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{value, Variables};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_query_string() {
|
||||||
|
let request = parse_query_string("variables=%7B%7D&extensions=%7B%22persistedQuery%22%3A%7B%22sha256Hash%22%3A%22cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3%22%2C%22version%22%3A1%7D%7D").unwrap();
|
||||||
|
assert_eq!(request.query.as_str(), "");
|
||||||
|
assert_eq!(request.variables, Variables::default());
|
||||||
|
assert_eq!(request.extensions, {
|
||||||
|
let mut extensions = HashMap::new();
|
||||||
|
extensions.insert("persistedQuery".to_string(), value!({
|
||||||
|
"sha256Hash": "cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3",
|
||||||
|
"version": 1,
|
||||||
|
}));
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
let request = parse_query_string("query={a}&variables=%7B%22a%22%3A10%7D").unwrap();
|
||||||
|
assert_eq!(request.query.as_str(), "{a}");
|
||||||
|
assert_eq!(
|
||||||
|
request.variables,
|
||||||
|
Variables::from_value(value!({ "a" : 10 }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use crate::{ConstValue, Name};
|
use crate::{ConstValue, Name};
|
||||||
|
|
||||||
/// Variables of a query.
|
/// Variables of a query.
|
||||||
#[derive(Debug, Clone, Default, Serialize)]
|
#[derive(Debug, Clone, Default, Serialize, Eq, PartialEq)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Variables(BTreeMap<Name, ConstValue>);
|
pub struct Variables(BTreeMap<Name, ConstValue>);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue