Merge branch 'drop_rustc_serialization' of https://github.com/TheServerAsterisk/juniper into TheServerAsterisk-drop_rustc_serialization

# Conflicts:
#	examples/server.rs
This commit is contained in:
Magnus Hallin 2017-06-14 08:51:49 +02:00
commit 7041efeda9
8 changed files with 344 additions and 212 deletions

View file

@ -7,8 +7,8 @@ rust:
- nightly
# The two most recent stable releases before "stable"
- 1.13.0
- 1.14.0
- 1.15.0
- 1.16.0
matrix:
allow_failures:

View file

@ -20,15 +20,18 @@ name = "server"
required-features = ["iron-handlers", "expose-test-schema"]
[features]
default = ["rustc-serialize"]
default = ["serde", "serde_json"]
nightly = []
iron-handlers = ["iron", "rustc-serialize"]
iron-handlers = ["iron", "default", "serde_derive", "urlencoded"]
expose-test-schema = []
[dependencies]
rustc-serialize = { version = "^0.3.19", optional = true }
iron = { version = "^0.5.1", optional = true }
serde = { version = "^0.9.1", optional = true }
serde_json = {version ="^0.9.1", optional = true }
serde_derive = {version="^0.9.1", optional = true }
urlencoded = {version="0.5", optional=true}
[dev-dependencies]
iron = "^0.5.1"

View file

@ -1,7 +1,7 @@
extern crate iron;
extern crate mount;
extern crate logger;
extern crate rustc_serialize;
extern crate serde;
extern crate juniper;
use std::env;

View file

@ -1,17 +1,23 @@
//! Optional handlers for the Iron framework. Requires the `iron-handlers` feature enabled.
use iron::prelude::*;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::status;
use iron::method;
use iron::url::Url;
use urlencoded::{UrlEncodedQuery, UrlDecodingError};
use std::collections::BTreeMap;
use std::io::Read;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::error::Error;
use std::fmt;
use std::boxed::Box;
use rustc_serialize::json::{ToJson, Json};
use serde_json;
use serde_json::error::Error as SerdeError;
use ::{InputValue, GraphQLType, RootNode, Variables, execute};
use ::{InputValue, GraphQLType, RootNode, execute};
use super::serde::{WrappedGraphQLResult, GraphQlQuery};
/// Handler that executes GraphQL queries in the given schema
///
@ -38,6 +44,53 @@ pub struct GraphiQLHandler {
graphql_url: String,
}
/// Get queries are allowed to repeat the same key more than once.
fn check_for_repeat_keys(params: &Vec<String>) -> Result<(), IronError> {
if params.len() > 1 {
let error = IronError::new(
Box::new(GraphQlIronError::IO(IoError::new(ErrorKind::InvalidData,
"Was able parse a query string \
but a duplicate uri key was \
found."))),
(status::BadRequest, "Duplicate uri key was found."));
Err(error)
}
else {
Ok(())
}
}
fn parse_url_param(param: Option<Vec<String>>) -> Result<Option<String>, IronError> {
if let Some(values) = param {
check_for_repeat_keys(&values)?;
Ok(Some(values[0].to_owned()))
}
else {
Ok(None)
}
}
fn parse_variable_param(param: Option<Vec<String>>) -> Result<Option<InputValue>, IronError> {
if let Some(values) = param {
check_for_repeat_keys(&values)?;
match serde_json::from_str::<InputValue>(values[0].as_ref()) {
Ok(input_values) => {
Ok(Some(input_values))
}
Err(err) => {
Err(IronError::new(
Box::new(GraphQlIronError::Serde(err)),
(status::BadRequest, "No JSON object was decoded.")))
}
}
}
else {
Ok(None)
}
}
impl<'a, CtxFactory, Query, Mutation, CtxT>
GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
where CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
@ -59,81 +112,62 @@ impl<'a, CtxFactory, Query, Mutation, CtxT>
}
fn handle_get(&self, req: &mut Request) -> IronResult<Response> {
let url: Url = req.url.clone().into();
let mut query = None;
let variables = Variables::new();
for (k, v) in url.query_pairs() {
if k == "query" {
query = Some(v.into_owned());
}
}
let query = iexpect!(query);
self.execute(req, &query, &variables)
}
fn handle_post(&self, req: &mut Request) -> IronResult<Response> {
let json_data = itry!(Json::from_reader(&mut req.body));
let json_obj = match json_data {
Json::Object(o) => o,
_ => return Ok(Response::with((status::BadRequest, "No JSON object was decoded"))),
};
let mut query = None;
let mut variables = Variables::new();
for (k, v) in json_obj {
if k == "query" {
query = v.as_string().map(|s| s.to_owned());
}
else if k == "variables" {
variables = InputValue::from_json(v).to_object_value()
.map(|o| o.into_iter().map(|(k, v)| (k.to_owned(), v.clone())).collect())
.unwrap_or_default();
}
}
let query = iexpect!(query);
self.execute(req, &query, &variables)
}
fn execute(&self, req: &mut Request, query: &str, variables: &Variables) -> IronResult<Response> {
let context = (self.context_factory)(req);
let result = execute(query, None, &self.root_node, variables, &context);
let content_type = "application/json".parse::<Mime>().unwrap();
let mut map = BTreeMap::new();
match result {
Ok((result, errors)) => {
map.insert("data".to_owned(), result.to_json());
if !errors.is_empty() {
map.insert("errors".to_owned(), errors.to_json());
fn handle_get(&self, req: &mut Request) -> IronResult<GraphQlQuery> {
match req.get_mut::<UrlEncodedQuery>() {
Ok(ref mut query_string) => {
let input_query = parse_url_param(query_string.remove("query").to_owned())?;
if let Some(query) = input_query {
let operation_name =
parse_url_param(query_string.remove("operationName"))?;
let input_variables =
parse_variable_param(query_string.remove("variables"))?;
Ok(GraphQlQuery::new(query,operation_name,input_variables))
} else {
Err(IronError::new(
Box::new(GraphQlIronError::IO(IoError::new(ErrorKind::InvalidData,
"No query key was found in \
the Get request."))),
(status::BadRequest, "No query was provided.")))
}
let data = Json::Object(map);
let json = data.pretty();
Ok(Response::with((content_type, status::Ok, json.to_string())))
}
Err(err) => {
map.insert("errors".to_owned(), err.to_json());
let data = Json::Object(map);
let json = data.pretty();
Ok(Response::with((content_type, status::BadRequest, json.to_string())))
Err(IronError::new(
Box::new(GraphQlIronError::Url(err)),
(status::BadRequest, "No JSON object was decoded.")))
}
}
}
fn handle_post(&self, req: &mut Request) -> IronResult<GraphQlQuery> {
let mut request_payload = String::new();
itry!(req.body.read_to_string(&mut request_payload));
let graphql_query = serde_json::from_str::<GraphQlQuery>(request_payload.as_str()).map_err(|err|{
IronError::new(
Box::new(GraphQlIronError::Serde(err)),
(status::BadRequest, "No JSON object was decoded."))
});
graphql_query
}
fn respond(&self, req: &mut Request, graphql: GraphQlQuery) -> IronResult<Response> {
let context = (self.context_factory)(req);
let variables = graphql.variables();
let result = execute(graphql.query(),
graphql.operation_name(),
&self.root_node,
&variables,
&context);
let content_type = "application/json".parse::<Mime>().unwrap();
if result.is_ok() {
let response = WrappedGraphQLResult::new(result);
let json = serde_json::to_string_pretty(&response).unwrap();
Ok(Response::with((content_type, status::Ok, json)))
} else {
let response = WrappedGraphQLResult::new(result);
let json = serde_json::to_string_pretty(&response).unwrap();
Ok(Response::with((content_type, status::BadRequest, json)))
}
}
}
impl GraphiQLHandler {
@ -156,10 +190,16 @@ impl<'a, CtxFactory, Query, Mutation, CtxT>
Query: GraphQLType<Context=CtxT> + Send + Sync + 'static,
Mutation: GraphQLType<Context=CtxT> + Send + Sync + 'static, 'a: 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
fn handle(&self, mut req: &mut Request) -> IronResult<Response> {
match req.method {
method::Get => self.handle_get(req),
method::Post => self.handle_post(req),
method::Get => {
let graphql_query = self.handle_get(&mut req)?;
self.respond(&mut req, graphql_query)
}
method::Post => {
let graphql_query = self.handle_post(&mut req)?;
self.respond(&mut req, graphql_query)
},
_ => Ok(Response::with((status::MethodNotAllowed)))
}
}
@ -179,7 +219,6 @@ impl Handler for GraphiQLHandler {
}
</style>
"#;
let fetcher_source = r#"
<script>
function graphQLFetcher(params) {
@ -236,11 +275,62 @@ impl Handler for GraphiQLHandler {
}
}
/// A general error allowing the developer to see the underlying issue.
#[derive(Debug)]
pub enum GraphQlIronError {
///Captures any errors that were caused by Serde.
Serde(SerdeError),
/// Captures any error related the IO.
IO(IoError),
/// Captures any error related to Url Decoding,
Url(UrlDecodingError)
}
impl fmt::Display for GraphQlIronError {
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GraphQlIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f),
GraphQlIronError::IO(ref err) => fmt::Display::fmt(err, &mut f),
GraphQlIronError::Url(ref err) => fmt::Display::fmt(err, &mut f),
}
}
}
impl Error for GraphQlIronError {
fn description(&self) -> &str {
match *self {
GraphQlIronError::Serde(ref err) => {
err.description()
},
GraphQlIronError::IO(ref err) => {
err.description()
}
GraphQlIronError::Url(ref err) => {
err.description()
}
}
}
fn cause(&self) -> Option<&Error> {
match *self {
GraphQlIronError::Serde(ref err) => {
err.cause()
}
GraphQlIronError::IO(ref err) => {
err.cause()
}
GraphQlIronError::Url(ref err) => {
err.cause()
}
}
}
}
#[cfg(test)]
mod tests {
use rustc_serialize::json::Json;
use serde_json::Value as Json;
use serde_json;
use iron::prelude::*;
use iron::status;
use iron::headers;
@ -267,7 +357,7 @@ mod tests {
fn unwrap_json_response(resp: Response) -> Json {
let result = response::extract_body_to_string(resp);
Json::from_str(&result).expect("Could not parse JSON object")
serde_json::from_str::<Json>(&result).expect("Could not parse JSON object")
}
#[test]
@ -286,10 +376,77 @@ mod tests {
assert_eq!(
json,
Json::from_str(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
serde_json::from_str::<Json>(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
.expect("Invalid JSON constant in test"));
}
#[test]
fn test_encoded_get() {
let response = request::get(
"http://localhost:3000/?query=query%20{%20%20%20human(id:%20\"1000\")%20{%20%20%20%20%20id,%20%20%20%20%20name,%20%20%20%20%20appearsIn,%20%20%20%20%20homePlanet%20%20%20}%20}",
Headers::new(),
&make_handler())
.expect("Unexpected IronError");
assert_eq!(response.status, Some(status::Ok));
assert_eq!(response.headers.get::<headers::ContentType>(),
Some(&headers::ContentType::json()));
let json = unwrap_json_response(response);
assert_eq!(
json,
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"));
}
#[test]
fn test_get_with_variables() {
let response = request::get(
"http://localhost:3000/?query=query($id:%20String!)%20{%20%20%20human(id:%20$id)%20{%20%20%20%20%20id,%20%20%20%20%20name,%20%20%20%20%20appearsIn,%20%20%20%20%20homePlanet%20%20%20}%20}&variables={%20%20%20\"id\":%20%20\"1000\"%20}",
Headers::new(),
&make_handler())
.expect("Unexpected IronError");
assert_eq!(response.status, Some(status::Ok));
assert_eq!(response.headers.get::<headers::ContentType>(),
Some(&headers::ContentType::json()));
let json = unwrap_json_response(response);
assert_eq!(
json,
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"));
}
#[test]
fn test_simple_post() {
let response = request::post(
@ -307,7 +464,7 @@ mod tests {
assert_eq!(
json,
Json::from_str(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
serde_json::from_str::<Json>(r#"{"data": {"hero": {"name": "R2-D2"}}}"#)
.expect("Invalid JSON constant in test"));
}

View file

@ -1,3 +1,2 @@
#[cfg(feature="iron-handlers")] pub mod iron_handlers;
#[cfg(feature="rustc-serialize")] pub mod rustc_serialize;
#[cfg(feature="serde")] pub mod serde;
pub mod serde;

View file

@ -1,114 +0,0 @@
use rustc_serialize::json::{ToJson, Json};
use ::{GraphQLError, Value};
use ast::InputValue;
use executor::ExecutionError;
use parser::{ParseError, Spanning, SourcePosition};
use validation::RuleError;
fn parse_error_to_json(err: &Spanning<ParseError>) -> Json {
Json::Array(vec![
Json::Object(vec![
("message".to_owned(), format!("{}", err.item).to_json()),
("locations".to_owned(), vec![
Json::Object(vec![
("line".to_owned(), (err.start.line() + 1).to_json()),
("column".to_owned(), (err.start.column() + 1).to_json())
].into_iter().collect()),
].to_json()),
].into_iter().collect()),
])
}
impl ToJson for ExecutionError {
fn to_json(&self) -> Json {
Json::Object(vec![
("message".to_owned(), self.message().to_json()),
("locations".to_owned(), vec![self.location().clone()].to_json()),
("path".to_owned(), self.path().to_json()),
].into_iter().collect())
}
}
impl<'a> ToJson for GraphQLError<'a> {
fn to_json(&self) -> Json {
match *self {
GraphQLError::ParseError(ref err) => parse_error_to_json(err),
GraphQLError::ValidationError(ref errs) => errs.to_json(),
GraphQLError::MultipleOperationsProvided => Json::String(
"Must provide operation name if query contains multiple operations".to_owned()),
GraphQLError::NoOperationProvided => Json::String(
"Must provide an operation".to_owned()),
GraphQLError::UnknownOperationName => Json::String(
"Unknown operation".to_owned()),
}
}
}
impl ToJson for InputValue {
fn to_json(&self) -> Json {
match *self {
InputValue::Null | InputValue::Variable(_) => Json::Null,
InputValue::Int(i) => Json::I64(i),
InputValue::Float(f) => Json::F64(f),
InputValue::String(ref s) | InputValue::Enum(ref s) => Json::String(s.clone()),
InputValue::Boolean(b) => Json::Boolean(b),
InputValue::List(ref l) => Json::Array(l.iter().map(|x| x.item.to_json()).collect()),
InputValue::Object(ref o) => Json::Object(o.iter().map(|&(ref k, ref v)| (k.item.clone(), v.item.to_json())).collect()),
}
}
}
impl InputValue {
/// Convert a `Json` structure into an `InputValue`.
///
/// This consumes the JSON instance.
///
/// Notes:
/// * No enums or variables will be produced by this method.
/// * All lists and objects will be unlocated
pub fn from_json(json: Json) -> InputValue {
match json {
Json::I64(i) => InputValue::int(i),
Json::U64(u) => InputValue::float(u as f64),
Json::F64(f) => InputValue::float(f),
Json::String(s) => InputValue::string(s),
Json::Boolean(b) => InputValue::boolean(b),
Json::Array(a) => InputValue::list(a.into_iter().map(InputValue::from_json).collect()),
Json::Object(o) => InputValue::object(o.into_iter().map(|(k, v)| (k, InputValue::from_json(v))).collect()),
Json::Null => InputValue::null(),
}
}
}
impl ToJson for RuleError {
fn to_json(&self) -> Json {
Json::Object(vec![
("message".to_owned(), self.message().to_json()),
("locations".to_owned(), self.locations().to_json()),
].into_iter().collect())
}
}
impl ToJson for SourcePosition {
fn to_json(&self) -> Json {
Json::Object(vec![
("line".to_owned(), (self.line() + 1).to_json()),
("column".to_owned(), (self.column() + 1).to_json()),
].into_iter().collect())
}
}
impl ToJson for Value {
fn to_json(&self) -> Json {
match *self {
Value::Null => Json::Null,
Value::Int(i) => Json::I64(i),
Value::Float(f) => Json::F64(f),
Value::String(ref s) => Json::String(s.clone()),
Value::Boolean(b) => Json::Boolean(b),
Value::List(ref l) => Json::Array(l.iter().map(|x| x.to_json()).collect()),
Value::Object(ref o) => Json::Object(o.iter().map(|(k,v)| (k.clone(), v.to_json())).collect()),
}
}
}

View file

@ -1,14 +1,17 @@
use serde::{de, ser};
use serde::ser::SerializeMap;
use std::collections::HashMap;
use std::fmt;
use std::collections::HashMap;
use ::{GraphQLError, Value};
#[cfg(feature="iron-handlers")]
use ::Variables;
use ast::InputValue;
use executor::ExecutionError;
use parser::{ParseError, Spanning, SourcePosition};
use validation::RuleError;
impl ser::Serialize for ExecutionError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: ser::Serializer,
@ -213,3 +216,87 @@ impl ser::Serialize for Value {
}
}
}
/// The expected structure of the decoded JSON Document for either Post or Get requests.
#[cfg(feature="iron-handlers")]
#[derive(Deserialize)]
pub struct GraphQlQuery {
query: String,
#[serde(rename = "operationName")]
operation_name: Option<String>,
variables: Option<InputValue>
}
#[cfg(feature="iron-handlers")]
impl GraphQlQuery {
pub fn new(query: String,
operation_name: Option<String>,
variables: Option<InputValue>
) -> GraphQlQuery {
GraphQlQuery {
query: query,
operation_name: operation_name,
variables: variables
}
}
pub fn query(&self) -> &str {
self.query.as_str()
}
pub fn operation_name(&self) -> Option<&str> {
self.operation_name.as_ref().map(|oper_name| &**oper_name)
}
pub fn variables(&self) -> Variables {
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()
}
}
#[cfg(feature="iron-handlers")]
pub struct WrappedGraphQLResult<'a>(Result<(Value, Vec<ExecutionError>), GraphQLError<'a>>);
#[cfg(feature="iron-handlers")]
impl<'a> WrappedGraphQLResult<'a> {
pub fn new(result: Result<(Value, Vec<ExecutionError>),
GraphQLError<'a>>
) -> WrappedGraphQLResult<'a> {
WrappedGraphQLResult(result)
}
}
#[cfg(feature="iron-handlers")]
impl<'a> ser::Serialize for WrappedGraphQLResult<'a> {
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 = try!(serializer.serialize_map(None));
try!(map.serialize_key("data"));
try!(map.serialize_value(res));
if !err.is_empty() {
try!(map.serialize_key("errors"));
try!(map.serialize_value(err));
}
map.end()
},
Err(ref err) => {
let mut map = try!(serializer.serialize_map(Some(1)));
try!(map.serialize_key("errors"));
try!(map.serialize_value(err));
map.end()
},
}
}
}

View file

@ -14,7 +14,7 @@ schema, as well as an optional integration into the [Iron framework][2]. It
tries to keep the number of dynamic operations to a minimum, and give you as the
schema developer the control of the query execution path.
Juniper only depends on `rustc-serialize` by default, making it lightweight and
Juniper only depends on `serde` and `serde_json` by default, making it lightweight and
easy to drop into any project. Through the `iron-handlers` feature, it also
depends on Iron.
@ -185,13 +185,13 @@ built-in [GraphiQL][6] handler included.
#![cfg_attr(feature="nightly", feature(test))]
#![warn(missing_docs)]
#[cfg(feature="rustc-serialize")] extern crate rustc_serialize;
#[cfg(feature="serde")] extern crate serde;
#[cfg(feature="nightly")] extern crate test;
#[cfg(feature="iron-handlers")] #[macro_use(itry, iexpect)] extern crate iron;
#[cfg(feature="iron-handlers")] #[macro_use(itry)] extern crate iron;
#[cfg(feature="iron-handlers")] extern crate urlencoded;
#[cfg(test)] extern crate iron_test;
extern crate serde;
extern crate serde_json;
#[cfg(feature="iron-handlers")] #[macro_use] extern crate serde_derive;
#[macro_use] mod macros;
mod ast;