pub mod graphiql;
use serde::de::Deserialize;
use serde::ser::{self, Serialize, SerializeMap};
use ast::InputValue;
use executor::ExecutionError;
use value::{DefaultScalarValue, ScalarRefValue, ScalarValue};
use {FieldError, GraphQLError, GraphQLType, RootNode, Value, Variables};
#[derive(Deserialize, Clone, Serialize, PartialEq, Debug)]
pub struct GraphQLRequest<S = DefaultScalarValue>
where
S: ScalarValue,
{
query: String,
#[serde(rename = "operationName")]
operation_name: Option<String>,
#[serde(bound(deserialize = "InputValue<S>: Deserialize<'de> + Serialize"))]
variables: Option<InputValue<S>>,
}
impl<S> GraphQLRequest<S>
where
S: ScalarValue,
{
fn operation_name(&self) -> Option<&str> {
self.operation_name.as_ref().map(|oper_name| &**oper_name)
}
fn variables(&self) -> Variables<S> {
self.variables
.as_ref()
.and_then(|iv| {
iv.to_object_value().map(|o| {
o.into_iter()
.map(|(k, v)| (k.to_owned(), v.clone()))
.collect()
})
}).unwrap_or_default()
}
pub fn new(
query: String,
operation_name: Option<String>,
variables: Option<InputValue<S>>,
) -> Self {
GraphQLRequest {
query: query,
operation_name: operation_name,
variables: variables,
}
}
pub fn execute<'a, CtxT, QueryT, MutationT>(
&'a self,
root_node: &'a RootNode<QueryT, MutationT, S>,
context: &CtxT,
) -> GraphQLResponse<'a, S>
where
S: ScalarValue,
QueryT: GraphQLType<S, Context = CtxT>,
MutationT: GraphQLType<S, Context = CtxT>,
for<'b> &'b S: ScalarRefValue<'b>,
{
GraphQLResponse(::execute(
&self.query,
self.operation_name(),
root_node,
&self.variables(),
context,
))
}
}
pub struct GraphQLResponse<'a, S = DefaultScalarValue>(
Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>>,
);
impl<'a, S> GraphQLResponse<'a, S>
where
S: ScalarValue,
{
pub fn error(error: FieldError<S>) -> Self {
GraphQLResponse(Ok((Value::null(), vec![ExecutionError::at_origin(error)])))
}
pub fn is_ok(&self) -> bool {
self.0.is_ok()
}
}
impl<'a, T> Serialize for GraphQLResponse<'a, T>
where
T: Serialize + ScalarValue,
Value<T>: Serialize,
ExecutionError<T>: Serialize,
GraphQLError<'a>: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
match self.0 {
Ok((ref res, ref err)) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_key("data")?;
map.serialize_value(res)?;
if !err.is_empty() {
map.serialize_key("errors")?;
map.serialize_value(err)?;
}
map.end()
}
Err(ref err) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_key("errors")?;
map.serialize_value(err)?;
map.end()
}
}
}
}
#[cfg(any(test, feature = "expose-test-schema"))]
#[allow(missing_docs)]
pub mod tests {
use serde_json;
use serde_json::Value as Json;
#[derive(Debug)]
pub struct TestResponse {
pub status_code: i32,
pub body: Option<String>,
pub content_type: String,
}
pub trait HTTPIntegration {
fn get(&self, url: &str) -> TestResponse;
fn post(&self, url: &str, body: &str) -> TestResponse;
}
#[allow(missing_docs)]
pub fn run_http_test_suite<T: HTTPIntegration>(integration: &T) {
println!("Running HTTP Test suite for integration");
println!(" - test_simple_get");
test_simple_get(integration);
println!(" - test_encoded_get");
test_encoded_get(integration);
println!(" - test_get_with_variables");
test_get_with_variables(integration);
println!(" - test_simple_post");
test_simple_post(integration);
println!(" - test_batched_post");
test_batched_post(integration);
println!(" - test_invalid_json");
test_invalid_json(integration);
println!(" - test_invalid_field");
test_invalid_field(integration);
println!(" - test_duplicate_keys");
test_duplicate_keys(integration);
}
fn unwrap_json_response(response: &TestResponse) -> Json {
serde_json::from_str::<Json>(
response
.body
.as_ref()
.expect("No data returned from request"),
).expect("Could not parse JSON object")
}
fn test_simple_get<T: HTTPIntegration>(integration: &T) {
let response = integration.get("/?query=%7Bhero%7Bname%7D%7D");
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type.as_str(), "application/json");
assert_eq!(
unwrap_json_response(&response),
serde_json::from_str::<Json>(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
.expect("Invalid JSON constant in test")
);
}
fn test_encoded_get<T: HTTPIntegration>(integration: &T) {
let response = integration.get(
"/?query=query%20%7B%20human(id%3A%20%221000%22)%20%7B%20id%2C%20name%2C%20appearsIn%2C%20homePlanet%20%7D%20%7D");
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type.as_str(), "application/json");
assert_eq!(
unwrap_json_response(&response),
serde_json::from_str::<Json>(
r#"{
"data": {
"human": {
"appearsIn": [
"NEW_HOPE",
"EMPIRE",
"JEDI"
],
"homePlanet": "Tatooine",
"name": "Luke Skywalker",
"id": "1000"
}
}
}"#
).expect("Invalid JSON constant in test")
);
}
fn test_get_with_variables<T: HTTPIntegration>(integration: &T) {
let response = integration.get(
"/?query=query(%24id%3A%20String!)%20%7B%20human(id%3A%20%24id)%20%7B%20id%2C%20name%2C%20appearsIn%2C%20homePlanet%20%7D%20%7D&variables=%7B%20%22id%22%3A%20%221000%22%20%7D");
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type, "application/json");
assert_eq!(
unwrap_json_response(&response),
serde_json::from_str::<Json>(
r#"{
"data": {
"human": {
"appearsIn": [
"NEW_HOPE",
"EMPIRE",
"JEDI"
],
"homePlanet": "Tatooine",
"name": "Luke Skywalker",
"id": "1000"
}
}
}"#
).expect("Invalid JSON constant in test")
);
}
fn test_simple_post<T: HTTPIntegration>(integration: &T) {
let response = integration.post("/", r#"{"query": "{hero{name}}"}"#);
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type, "application/json");
assert_eq!(
unwrap_json_response(&response),
serde_json::from_str::<Json>(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
.expect("Invalid JSON constant in test")
);
}
fn test_batched_post<T: HTTPIntegration>(integration: &T) {
let response = integration.post(
"/",
r#"[{"query": "{hero{name}}"}, {"query": "{hero{name}}"}]"#,
);
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type, "application/json");
assert_eq!(
unwrap_json_response(&response),
serde_json::from_str::<Json>(
r#"[{"data": {"hero": {"name": "R2-D2"}}}, {"data": {"hero": {"name": "R2-D2"}}}]"#
).expect("Invalid JSON constant in test")
);
}
fn test_invalid_json<T: HTTPIntegration>(integration: &T) {
let response = integration.get("/?query=blah");
assert_eq!(response.status_code, 400);
let response = integration.post("/", r#"blah"#);
assert_eq!(response.status_code, 400);
}
fn test_invalid_field<T: HTTPIntegration>(integration: &T) {
let response = integration.get("/?query=%7Bhero%blah%7D%7D");
assert_eq!(response.status_code, 400);
let response = integration.post("/", r#"{"query": "{hero{blah}}"}"#);
assert_eq!(response.status_code, 400);
}
fn test_duplicate_keys<T: HTTPIntegration>(integration: &T) {
let response = integration.get("/?query=%7B%22query%22%3A%20%22%7Bhero%7Bname%7D%7D%22%2C%20%22query%22%3A%20%22%7Bhero%7Bname%7D%7D%22%7D");
assert_eq!(response.status_code, 400);
let response = integration.post("/", r#"
{"query": "{hero{name}}", "query": "{hero{name}}"}
"#);
assert_eq!(response.status_code, 400);
}
}