diff --git a/src/graphiql.rs b/src/graphiql.rs new file mode 100644 index 00000000..75faf9ac --- /dev/null +++ b/src/graphiql.rs @@ -0,0 +1,64 @@ +pub fn graphiql_source(graphql_endpoint_url: &str) -> String { + let stylesheet_source = r#" + + "#; + let fetcher_source = r#" + + "#; + + format!(r#" + + + + GraphQL + {stylesheet_source} + + + +
+ + + + + + + {fetcher_source} + + +"#, + graphql_url = graphql_endpoint_url, + stylesheet_source = stylesheet_source, + fetcher_source = fetcher_source) + +} \ No newline at end of file diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 00000000..62c8b167 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,92 @@ +use serde::ser; +use serde::ser::SerializeMap; + +use ::{GraphQLError, Value, Variables, GraphQLType, RootNode}; +use ast::InputValue; +use executor::ExecutionError; + +/// The expected structure of the decoded JSON Document for either Post or Get requests. +#[derive(Deserialize)] +pub struct GraphQLRequest { + query: String, + #[serde(rename = "operationName")] + operation_name: Option, + variables: Option +} + +impl GraphQLRequest { + fn operation_name(&self) -> Option<&str> { + self.operation_name.as_ref().map(|oper_name| &**oper_name) + } + + 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() + } + + pub fn new(query: String, operation_name: Option, variables: Option) -> GraphQLRequest { + GraphQLRequest { + query: query, + operation_name: operation_name, + variables: variables, + } + } + + pub fn execute<'a, CtxT, QueryT, MutationT>( + &'a self, + root_node: &RootNode, + context: &CtxT, + ) + -> GraphQLResponse<'a> + where QueryT: GraphQLType, + MutationT: GraphQLType, + { + GraphQLResponse(::execute( + &self.query, + self.operation_name(), + root_node, + &self.variables(), + context, + )) + } +} + + +pub struct GraphQLResponse<'a>(Result<(Value, Vec), GraphQLError<'a>>); + +impl<'a> GraphQLResponse<'a> { + pub fn is_ok(&self) -> bool { + self.0.is_ok() + } +} + +impl<'a> ser::Serialize for GraphQLResponse<'a> { + fn serialize(&self, serializer: S) -> Result + 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() + }, + } + } +} diff --git a/src/integrations/iron_handlers.rs b/src/integrations/iron_handlers.rs index 494cfbd8..72ff6ed1 100644 --- a/src/integrations/iron_handlers.rs +++ b/src/integrations/iron_handlers.rs @@ -7,17 +7,14 @@ use iron::method; use urlencoded::{UrlEncodedQuery, UrlDecodingError}; 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 serde_json; use serde_json::error::Error as SerdeError; -use ::{InputValue, GraphQLType, RootNode, execute}; -use super::serde::{WrappedGraphQLResult, GraphQLQuery}; +use ::{InputValue, GraphQLType, RootNode}; +use ::http; /// Handler that executes GraphQL queries in the given schema /// @@ -45,45 +42,29 @@ pub struct GraphiQLHandler { } -/// 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) +fn get_single_value(mut values: Vec) -> IronResult { + if values.len() == 1 { + Ok(values.remove(0)) } else { - Ok(()) + Err(GraphQLIronError::InvalidData("Duplicate URL query parameter").into()) } } -fn parse_url_param(param: Option>) -> Result, IronError> { - if let Some(values) = param { - check_for_repeat_keys(&values)?; - Ok(Some(values[0].to_owned())) +fn parse_url_param(params: Option>) -> IronResult> { + if let Some(values) = params { + get_single_value(values).map(Some) } 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."))) - } - } +fn parse_variable_param(params: Option>) -> IronResult> { + if let Some(values) = params { + Ok(serde_json::from_str::(get_single_value(values)?.as_ref()) + .map(Some) + .map_err(GraphQLIronError::Serde)?) } else { Ok(None) @@ -112,62 +93,36 @@ impl<'a, CtxFactory, Query, Mutation, CtxT> } - 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."))) - } - } - Err(err) => { - Err(IronError::new( - Box::new(GraphQlIronError::Url(err)), - (status::BadRequest, "No JSON object was decoded."))) - } - } + fn handle_get(&self, req: &mut Request) -> IronResult { + let url_query_string = req.get_mut::() + .map_err(|e| GraphQLIronError::Url(e))?; + + let input_query = parse_url_param(url_query_string.remove("query"))? + .ok_or_else(|| GraphQLIronError::InvalidData("No query provided"))?; + let operation_name = parse_url_param(url_query_string.remove("operationName"))?; + let variables = parse_variable_param(url_query_string.remove("variables"))?; + + Ok(http::GraphQLRequest::new(input_query, operation_name, variables)) } - fn handle_post(&self, req: &mut Request) -> IronResult { + 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 + + Ok(serde_json::from_str::(request_payload.as_str()) + .map_err(|err| GraphQLIronError::Serde(err))?) } - 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))) - } - } + fn execute(&self, context: &CtxT, request: http::GraphQLRequest) -> IronResult { + let response = request.execute( + &self.root_node, + context, + ); + let content_type = "application/json".parse::().unwrap(); + let json = serde_json::to_string_pretty(&response).unwrap(); + let status = if response.is_ok() { status::Ok } else { status::BadRequest }; + Ok(Response::with((content_type, status, json))) + } } impl GraphiQLHandler { @@ -191,17 +146,15 @@ impl<'a, CtxFactory, Query, Mutation, CtxT> Mutation: GraphQLType + Send + Sync + 'static, 'a: 'static, { fn handle(&self, mut req: &mut Request) -> IronResult { - match req.method { - 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))) - } + let context = (self.context_factory)(req); + + let graphql_request = match req.method { + method::Get => self.handle_get(&mut req)?, + method::Post => self.handle_post(&mut req)?, + _ => return Ok(Response::with((status::MethodNotAllowed))) + }; + + self.execute(&context, graphql_request) } } @@ -209,121 +162,54 @@ impl Handler for GraphiQLHandler { fn handle(&self, _: &mut Request) -> IronResult { let content_type = "text/html".parse::().unwrap(); - let stylesheet_source = r#" - - "#; - let fetcher_source = r#" - - "#; - - let source = format!(r#" - - - - GraphQL - {stylesheet_source} - - - -
- - - - - - - {fetcher_source} - - -"#, - graphql_url = self.graphql_url, - stylesheet_source = stylesheet_source, - fetcher_source = fetcher_source); - - Ok(Response::with((content_type, status::Ok, source))) + Ok(Response::with(( + content_type, + status::Ok, + ::graphiql::graphiql_source(&self.graphql_url), + ))) } } -/// A general error allowing the developer to see the underlying issue. #[derive(Debug)] -pub enum GraphQlIronError { - ///Captures any errors that were caused by Serde. +enum GraphQLIronError { Serde(SerdeError), - /// Captures any error related the IO. - IO(IoError), - /// Captures any error related to Url Decoding, - Url(UrlDecodingError) + Url(UrlDecodingError), + InvalidData(&'static str), } -impl fmt::Display for GraphQlIronError { +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), + GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f), + GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f), + GraphQLIronError::InvalidData(ref err) => fmt::Display::fmt(err, &mut f), } } } -impl Error for GraphQlIronError { +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() - } + GraphQLIronError::Serde(ref err) => err.description(), + GraphQLIronError::Url(ref err) => err.description(), + GraphQLIronError::InvalidData(ref err) => err, } - } + } - 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() - } - } - } + fn cause(&self) -> Option<&Error> { + match *self { + GraphQLIronError::Serde(ref err) => Some(err), + GraphQLIronError::Url(ref err) => Some(err), + GraphQLIronError::InvalidData(_) => None, + } + } +} + +impl From for IronError { + fn from(err: GraphQLIronError) -> IronError { + let message = format!("{}", err); + IronError::new(err, (status::BadRequest, message)) + } } diff --git a/src/integrations/serde.rs b/src/integrations/serde.rs index c9ddfb7a..e73b3fa4 100644 --- a/src/integrations/serde.rs +++ b/src/integrations/serde.rs @@ -3,7 +3,7 @@ use serde::ser::SerializeMap; use std::fmt; use std::collections::HashMap; -use ::{GraphQLError, Value, Variables}; +use ::{GraphQLError, Value}; use ast::InputValue; use executor::ExecutionError; use parser::{ParseError, Spanning, SourcePosition}; @@ -223,81 +223,3 @@ impl ser::Serialize for Value { } } } - -/// The expected structure of the decoded JSON Document for either Post or Get requests. -#[derive(Deserialize)] -pub struct GraphQLQuery { - query: String, - #[serde(rename = "operationName")] - operation_name: Option, - variables: Option -} - -impl GraphQLQuery { - pub fn new(query: String, - operation_name: Option, - variables: Option - ) -> 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() - } - -} - - -pub struct WrappedGraphQLResult<'a>(Result<(Value, Vec), GraphQLError<'a>>); - -impl<'a> WrappedGraphQLResult<'a> { - pub fn new(result: Result<(Value, Vec), - GraphQLError<'a>> - ) -> WrappedGraphQLResult<'a> { - WrappedGraphQLResult(result) - } -} - -impl<'a> ser::Serialize for WrappedGraphQLResult<'a> { - fn serialize(&self, serializer: S) -> Result - 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() - }, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 4ae89a93..c32a7bcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,8 @@ mod schema; mod validation; mod executor; mod integrations; +pub mod graphiql; +pub mod http; #[macro_use] mod result_ext; #[cfg(all(test, not(feature="expose-test-schema")))] mod tests;