async-graphql/src/http/mod.rs

293 lines
8.7 KiB
Rust
Raw Normal View History

2020-03-26 03:34:28 +00:00
//! A helper module that supports HTTP
2020-03-05 00:39:56 +00:00
mod graphiql_source;
2020-04-14 01:53:17 +00:00
mod into_query_builder;
2020-05-21 03:36:44 +00:00
mod multipart_stream;
2020-03-05 00:39:56 +00:00
mod playground_source;
2020-04-14 01:53:17 +00:00
mod stream_body;
2020-03-05 00:39:56 +00:00
use itertools::Itertools;
2020-03-05 00:39:56 +00:00
pub use graphiql_source::graphiql_source;
2020-05-21 03:36:44 +00:00
pub use multipart_stream::multipart_stream;
2020-03-05 00:39:56 +00:00
pub use playground_source::playground_source;
2020-04-14 01:53:17 +00:00
pub use stream_body::StreamBody;
2020-03-05 00:39:56 +00:00
2020-04-21 02:14:14 +00:00
use crate::query::{IntoQueryBuilder, IntoQueryBuilderOpts};
use crate::{
Error, ParseRequestError, Pos, QueryBuilder, QueryError, QueryResponse, Result, Variables,
};
2020-03-08 12:35:36 +00:00
use serde::ser::{SerializeMap, SerializeSeq};
2020-03-05 00:39:56 +00:00
use serde::{Serialize, Serializer};
/// Deserializable GraphQL Request object
2020-03-05 00:39:56 +00:00
#[derive(Deserialize, Clone, PartialEq, Debug)]
pub struct GQLRequest {
2020-03-20 03:56:08 +00:00
/// Query source
2020-03-05 00:39:56 +00:00
pub query: String,
2020-03-20 03:56:08 +00:00
/// Operation name for this query
2020-03-05 00:39:56 +00:00
#[serde(rename = "operationName")]
pub operation_name: Option<String>,
2020-03-20 03:56:08 +00:00
/// Variables for this query
2020-03-05 00:39:56 +00:00
pub variables: Option<serde_json::Value>,
}
#[async_trait::async_trait]
impl IntoQueryBuilder for GQLRequest {
2020-04-21 02:14:14 +00:00
async fn into_query_builder_opts(
self,
_opts: &IntoQueryBuilderOpts,
) -> std::result::Result<QueryBuilder, ParseRequestError> {
2020-04-14 01:53:17 +00:00
let mut builder = QueryBuilder::new(self.query);
2020-04-01 08:53:49 +00:00
if let Some(operation_name) = self.operation_name {
builder = builder.operator_name(operation_name);
2020-03-14 03:46:20 +00:00
}
2020-04-01 08:53:49 +00:00
if let Some(variables) = self.variables {
if let Ok(variables) = Variables::parse_from_json(variables) {
builder = builder.variables(variables);
}
}
Ok(builder)
2020-03-05 00:39:56 +00:00
}
}
/// Serializable GraphQL Response object
pub struct GQLResponse(pub Result<QueryResponse>);
2020-03-05 00:39:56 +00:00
impl Serialize for GQLResponse {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match &self.0 {
Ok(res) => {
let mut map = serializer.serialize_map(None)?;
if let Some(label) = &res.label {
map.serialize_key("label")?;
map.serialize_value(label)?;
}
2020-05-20 00:18:28 +00:00
if let Some(path) = &res.path {
map.serialize_key("path")?;
map.serialize_value(path)?;
}
2020-03-05 00:39:56 +00:00
map.serialize_key("data")?;
2020-03-26 03:34:28 +00:00
map.serialize_value(&res.data)?;
if res.extensions.is_some() {
map.serialize_key("extensions")?;
map.serialize_value(&res.extensions)?;
}
2020-03-05 00:39:56 +00:00
map.end()
}
Err(err) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_key("errors")?;
2020-03-08 12:35:36 +00:00
map.serialize_value(&GQLError(err))?;
2020-03-05 00:39:56 +00:00
map.end()
}
}
}
}
2020-03-20 03:56:08 +00:00
/// Serializable error type
pub struct GQLError<'a>(pub &'a Error);
2020-03-05 00:39:56 +00:00
impl<'a> Serialize for GQLError<'a> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.0 {
Error::Parse(err) => {
let mut seq = serializer.serialize_seq(Some(1))?;
seq.serialize_element(&serde_json::json! ({
"message": err.message,
"locations": [{"line": err.pos.line, "column": err.pos.column}]
}))?;
seq.end()
}
Error::Query { pos, path, err } => {
let mut seq = serializer.serialize_seq(Some(1))?;
if let QueryError::FieldError {
err,
extended_error,
} = err
{
let mut map = serde_json::Map::new();
map.insert("message".to_string(), err.to_string().into());
map.insert(
"locations".to_string(),
serde_json::json!([{"line": pos.line, "column": pos.column}]),
);
if let Some(path) = path {
map.insert("path".to_string(), path.clone());
}
if let Some(obj @ serde_json::Value::Object(_)) = extended_error {
map.insert("extensions".to_string(), obj.clone());
}
2020-04-02 04:57:53 +00:00
seq.serialize_element(&serde_json::Value::Object(map))?;
} else {
seq.serialize_element(&serde_json::json!({
"message": err.to_string(),
"locations": [{"line": pos.line, "column": pos.column}]
}))?;
}
seq.end()
}
Error::Rule { errors } => {
let mut seq = serializer.serialize_seq(Some(1))?;
for error in errors {
seq.serialize_element(&serde_json::json!({
"message": error.message,
"locations": error.locations.iter().map(|pos| serde_json::json!({"line": pos.line, "column": pos.column})).collect_vec(),
}))?;
}
seq.end()
}
2020-03-05 00:39:56 +00:00
}
2020-03-08 12:35:36 +00:00
}
}
struct GQLErrorPos<'a>(&'a Pos);
impl<'a> Serialize for GQLErrorPos<'a> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("line", &self.0.line)?;
map.serialize_entry("column", &self.0.column)?;
map.end()
}
}
2020-03-05 00:39:56 +00:00
#[cfg(test)]
mod tests {
use super::*;
use crate::Pos;
2020-03-05 00:39:56 +00:00
use serde_json::json;
#[test]
fn test_request() {
let request: GQLRequest = serde_json::from_value(json! ({
"query": "{ a b c }"
}))
.unwrap();
assert!(request.variables.is_none());
assert!(request.operation_name.is_none());
assert_eq!(request.query, "{ a b c }");
}
#[test]
fn test_request_with_operation_name() {
let request: GQLRequest = serde_json::from_value(json! ({
"query": "{ a b c }",
"operationName": "a"
}))
.unwrap();
assert!(request.variables.is_none());
assert_eq!(request.operation_name.as_deref(), Some("a"));
assert_eq!(request.query, "{ a b c }");
}
#[test]
fn test_request_with_variables() {
let request: GQLRequest = serde_json::from_value(json! ({
"query": "{ a b c }",
"variables": {
"v1": 100,
"v2": [1, 2, 3],
"v3": "str",
}
}))
.unwrap();
assert_eq!(
request.variables,
Some(json!({
"v1": 100,
"v2": [1, 2, 3],
"v3": "str",
}))
);
assert!(request.operation_name.is_none());
assert_eq!(request.query, "{ a b c }");
}
#[test]
fn test_response_data() {
let resp = GQLResponse(Ok(QueryResponse {
label: None,
path: None,
2020-03-26 03:34:28 +00:00
data: json!({"ok": true}),
extensions: None,
2020-04-14 01:53:17 +00:00
cache_control: Default::default(),
2020-03-26 03:34:28 +00:00
}));
2020-03-05 00:39:56 +00:00
assert_eq!(
serde_json::to_value(resp).unwrap(),
json! ({
"data": {
"ok": true,
}
})
);
}
#[test]
fn test_field_error_with_extension() {
let err = Error::Query {
pos: Pos {
line: 10,
column: 20,
},
path: None,
err: QueryError::FieldError {
2020-04-02 04:57:53 +00:00
err: "MyErrorMessage".to_owned(),
extended_error: Some(json!({
"code": "MY_TEST_CODE"
})),
},
};
let resp = GQLResponse(Err(err.into()));
assert_eq!(
serde_json::to_value(resp).unwrap(),
json!({
"errors": [{
"message":"MyErrorMessage",
"extensions": {
"code": "MY_TEST_CODE"
},
"locations": [{"line": 10, "column": 20}]
2020-03-05 00:39:56 +00:00
}]
})
);
}
#[test]
fn test_response_error_with_pos() {
let resp = GQLResponse(Err(Error::Query {
pos: Pos {
2020-03-05 00:39:56 +00:00
line: 10,
column: 20,
},
path: None,
err: QueryError::NotSupported,
}));
2020-03-05 00:39:56 +00:00
assert_eq!(
serde_json::to_value(resp).unwrap(),
json!({
"errors": [{
"message":"Not supported.",
2020-03-05 00:39:56 +00:00
"locations": [
{"line": 10, "column": 20}
]
}]
})
);
}
}