diff --git a/.travis.yml b/.travis.yml index 67aad4abd..55bd8f511 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/Cargo.toml b/Cargo.toml index da46c251f..9600688a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,15 +16,18 @@ harness = false path = "benches/bench.rs" [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" diff --git a/examples/server.rs b/examples/server.rs index e4ef3cbed..9aa31107e 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,7 +1,7 @@ extern crate iron; extern crate mount; extern crate logger; -extern crate rustc_serialize; +extern crate serde; #[macro_use] extern crate juniper; use std::env; diff --git a/src/integrations/iron_handlers.rs b/src/integrations/iron_handlers.rs index 1101aefdd..87f9776a8 100644 --- a/src/integrations/iron_handlers.rs +++ b/src/integrations/iron_handlers.rs @@ -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) -> 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>) -> Result, 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>) -> Result, IronError> { + if let Some(values) = param { + check_for_repeat_keys(&values)?; + match serde_json::from_str::(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 { - 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 { - 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 { - let context = (self.context_factory)(req); - let result = execute(query, None, &self.root_node, variables, &context); - - let content_type = "application/json".parse::().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 { + match req.get_mut::() { + 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 { + let mut request_payload = String::new(); + itry!(req.body.read_to_string(&mut request_payload)); + let graphql_query = serde_json::from_str::(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 { + 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::().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 + Send + Sync + 'static, Mutation: GraphQLType + Send + Sync + 'static, 'a: 'static, { - fn handle(&self, req: &mut Request) -> IronResult { + fn handle(&self, mut req: &mut Request) -> IronResult { 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 { } "#; - let fetcher_source = r#"