From 2ca9baa441d80c5b97e5a10a49fd79bdb6242493 Mon Sep 17 00:00:00 2001 From: theduke Date: Mon, 7 Aug 2017 08:12:18 +0200 Subject: [PATCH] Extracted iron/rocket crates and updated README. Those now live in separate repos. --- Cargo.toml | 2 - README.md | 4 +- appveyor.yml | 3 - juniper/appveyor.yml | 64 ---- juniper_iron/Cargo.toml | 26 -- juniper_iron/examples/iron_server.rs | 43 --- juniper_iron/src/lib.rs | 397 ----------------------- juniper_rocket/Cargo.toml | 24 -- juniper_rocket/Makefile.toml | 41 --- juniper_rocket/examples/rocket-server.rs | 51 --- juniper_rocket/src/lib.rs | 232 ------------- 11 files changed, 2 insertions(+), 885 deletions(-) delete mode 100644 juniper/appveyor.yml delete mode 100644 juniper_iron/Cargo.toml delete mode 100644 juniper_iron/examples/iron_server.rs delete mode 100644 juniper_iron/src/lib.rs delete mode 100644 juniper_rocket/Cargo.toml delete mode 100644 juniper_rocket/Makefile.toml delete mode 100644 juniper_rocket/examples/rocket-server.rs delete mode 100644 juniper_rocket/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index d6e64937..cc833d78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,4 @@ members = [ "juniper", "juniper_codegen", "juniper_tests", - "juniper_iron", - "juniper_rocket", ] diff --git a/README.md b/README.md index 6faecd79..8b51df88 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ feature flag: ```toml [dependencies] juniper = { version = "0.8.1" } -juniper_iron = { git = "https://github.com/mhallin/juniper" } +juniper_iron = { git = "https://github.com/graphql-rust/juniper_iron" } ``` @@ -45,7 +45,7 @@ If you want Rocket integration, you need to depend on the `juniper_rocket` crate ```toml [dependencies] juniper = { version = "0.8.1" } -juniper_rocket = { git = "https://github.com/mhallin/juniper" } +juniper_rocket = { git = "https://github.com/graphql-rustl/juniper_rocket" } ``` ## Building schemas diff --git a/appveyor.yml b/appveyor.yml index 0fc876d2..534939a6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,7 +51,4 @@ build: false test_script: - cd juniper && cargo build --verbose && cargo test --verbose && cd .. - cd juniper_codegen && cargo build --verbose && cargo test --verbose && cd .. - - cd juniper_iron && cargo build --verbose && cargo test --verbose && cd .. - cd juniper_tests && cargo build --verbose && cargo test --verbose && cd .. - - IF NOT %TARGET% == %TARGET:msvc=% ( IF %CHANNEL% == "nightly" ( cd juniper_rocket && cargo test --verbose && cargo build --verbose && cd .. ) ) - diff --git a/juniper/appveyor.yml b/juniper/appveyor.yml deleted file mode 100644 index 768a0355..00000000 --- a/juniper/appveyor.yml +++ /dev/null @@ -1,64 +0,0 @@ -# Build and test on stable, beta, and nightly on Windows. -# -# Copied general structure from https://github.com/japaric/rust-everywhere/blob/master/appveyor.yml - -environment: - matrix: - # Stable channel - - TARGET: i686-pc-windows-gnu - CHANNEL: stable - - TARGET: i686-pc-windows-msvc - CHANNEL: stable - - TARGET: x86_64-pc-windows-gnu - CHANNEL: stable - - TARGET: x86_64-pc-windows-msvc - CHANNEL: stable - # Beta channel - - TARGET: i686-pc-windows-gnu - CHANNEL: beta - - TARGET: i686-pc-windows-msvc - CHANNEL: beta - - TARGET: x86_64-pc-windows-gnu - CHANNEL: beta - - TARGET: x86_64-pc-windows-msvc - CHANNEL: beta - # Nightly channel - - TARGET: i686-pc-windows-gnu - CHANNEL: nightly - - TARGET: i686-pc-windows-msvc - CHANNEL: nightly - - TARGET: x86_64-pc-windows-gnu - CHANNEL: nightly - - TARGET: x86_64-pc-windows-msvc - CHANNEL: nightly - -# Install Rust and Cargo -# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) -install: - - curl -sSf -o rustup-init.exe https://win.rustup.rs - - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y - - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - - rustc -Vv - - cargo -V - - -# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents -# the "directory does not contain a project or solution file" error. -# source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113 -build: false - -test_script: - # Build library standalone, with Iron, and with Rocket integrations enabled, respectively - - cargo build --verbose - - cargo build --verbose --features iron-handlers - - IF "%CHANNEL%"=="nightly" (cargo build --verbose --features rocket-handlers) - - # Build example binaries; first Iron, then Rocket examples - - cargo build --verbose --example server --features "iron-handlers expose-test-schema" - - IF "%CHANNEL%"=="nightly" (cargo build --verbose --example rocket-server --features "rocket-handlers expose-test-schema") - - # Run all tests for the base library and available integrations - - set TEST_FEATURES=iron-handlers expose-test-schema - - IF "%CHANNEL%"=="nightly" (set TEST_FEATURES=%TEST_FEATURES% rocket-handlers rocket/testing) - - - cargo test --verbose --features "%TEST_FEATURES%" diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml deleted file mode 100644 index 8ca5dde8..00000000 --- a/juniper_iron/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "juniper_iron" -version = "0.1.0" -authors = ["Magnus Hallin "] -description = "Iron integration for juniper" -license = "BSD-2-Clause" -documentation = "https://docs.rs/juniper_iron" -repository = "https://github.com/mhallin/juniper" - -[dependencies] -serde = { version = "^1.0.2" } -serde_json = { version = "^1.0.2" } -urlencoded = { version = "^0.5.0" } -iron = "^0.5.1" -juniper = { version = "0.8.1", path = "../juniper" } - -[dev-dependencies] -iron-test = "^0.5.0" -router = "^0.5.0" -mount = "^0.3.0" -logger = "^0.3.0" -juniper = { version = "0.8.1", path = "../juniper" , features = ["expose-test-schema", "serde_json"] } - -[badges] -travis-ci = { repository = "mhallin/juniper" } -appveyor = { repository = "mhallin/juniper" } diff --git a/juniper_iron/examples/iron_server.rs b/juniper_iron/examples/iron_server.rs deleted file mode 100644 index 9fa4f0ab..00000000 --- a/juniper_iron/examples/iron_server.rs +++ /dev/null @@ -1,43 +0,0 @@ -extern crate iron; -extern crate mount; -extern crate logger; -extern crate serde; -extern crate juniper; -extern crate juniper_iron; - -use std::env; - -use mount::Mount; -use logger::Logger; -use iron::prelude::*; -use juniper::EmptyMutation; -use juniper_iron::{GraphQLHandler, GraphiQLHandler}; -use juniper::tests::model::Database; - -fn context_factory(_: &mut Request) -> Database { - Database::new() -} - -fn main() { - let mut mount = Mount::new(); - - let graphql_endpoint = GraphQLHandler::new( - context_factory, - Database::new(), - EmptyMutation::::new(), - ); - let graphiql_endpoint = GraphiQLHandler::new("/graphql"); - - mount.mount("/", graphiql_endpoint); - mount.mount("/graphql", graphql_endpoint); - - let (logger_before, logger_after) = Logger::new(None); - - let mut chain = Chain::new(mount); - chain.link_before(logger_before); - chain.link_after(logger_after); - - let host = env::var("LISTEN").unwrap_or("0.0.0.0:8080".to_owned()); - println!("GraphQL server started on {}", host); - Iron::new(chain).http(host.as_str()).unwrap(); -} diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs deleted file mode 100644 index 661042ca..00000000 --- a/juniper_iron/src/lib.rs +++ /dev/null @@ -1,397 +0,0 @@ -/*! - -[Juniper][1] handlers for the [Iron][2] framework. - -## Integrating with Iron - - - -For example, continuing from the schema created above and using Iron to expose -the schema on an HTTP endpoint supporting both GET and POST requests: - -```rust,no_run -extern crate iron; -# #[macro_use] extern crate juniper; -# extern crate juniper_iron; -# use std::collections::HashMap; - -use iron::prelude::*; -use juniper_iron::GraphQLHandler; -use juniper::{Context, EmptyMutation}; - -# use juniper::FieldResult; -# -# struct User { id: String, name: String, friend_ids: Vec } -# struct QueryRoot; -# struct Database { users: HashMap } -# -# graphql_object!(User: Database |&self| { -# field id() -> FieldResult<&String> { -# Ok(&self.id) -# } -# -# field name() -> FieldResult<&String> { -# Ok(&self.name) -# } -# -# field friends(&executor) -> FieldResult> { -# Ok(self.friend_ids.iter() -# .filter_map(|id| executor.context().users.get(id)) -# .collect()) -# } -# }); -# -# graphql_object!(QueryRoot: Database |&self| { -# field user(&executor, id: String) -> FieldResult> { -# Ok(executor.context().users.get(&id)) -# } -# }); - -// This function is executed for every request. Here, we would realistically -// provide a database connection or similar. For this example, we'll be -// creating the database from scratch. -fn context_factory(_: &mut Request) -> Database { - Database { - users: vec![ - ( "1000".to_owned(), User { - id: "1000".to_owned(), name: "Robin".to_owned(), - friend_ids: vec!["1001".to_owned()] } ), - ( "1001".to_owned(), User { - id: "1001".to_owned(), name: "Max".to_owned(), - friend_ids: vec!["1000".to_owned()] } ), - ].into_iter().collect() - } -} - -impl Context for Database {} - -fn main() { - // GraphQLHandler takes a context factory function, the root object, - // and the mutation object. If we don't have any mutations to expose, we - // can use the empty tuple () to indicate absence. - let graphql_endpoint = GraphQLHandler::new( - context_factory, QueryRoot, EmptyMutation::::new()); - - // Start serving the schema at the root on port 8080. - Iron::new(graphql_endpoint).http("localhost:8080").unwrap(); -} - -``` - -See the [iron_server.rs][5] -example for more information on how to use these handlers. - -See the the [`GraphQLHandler`][3] documentation for more information on what request methods are -supported. -There's also a built-in [GraphiQL][4] handler included. - -[1]: https://github.com/mhallin/Juniper -[2]: http://ironframework.io -[3]: ./struct.GraphQLHandler.html -[4]: https://github.com/graphql/graphiql -[5]: https://github.com/mhallin/juniper/blob/master/juniper_iron/examples/iron_server.rs - -*/ - -extern crate serde_json; -extern crate juniper; -extern crate urlencoded; -#[macro_use] -extern crate iron; -#[cfg(test)] -extern crate iron_test; - -use iron::prelude::*; -use iron::middleware::Handler; -use iron::mime::Mime; -use iron::status; -use iron::method; -use urlencoded::{UrlDecodingError, UrlEncodedQuery}; - -use std::io::Read; -use std::error::Error; -use std::fmt; - -use serde_json::error::Error as SerdeError; - -use juniper::{GraphQLType, InputValue, RootNode}; -use juniper::http; - -/// Handler that executes GraphQL queries in the given schema -/// -/// The handler responds to GET requests and POST requests only. In GET -/// requests, the query should be supplied in the `query` URL parameter, e.g. -/// `http://localhost:3000/graphql?query={hero{name}}`. -/// -/// POST requests support both queries and variables. POST a JSON document to -/// this endpoint containing the field `"query"` and optionally `"variables"`. -/// The variables should be a JSON object containing the variable to value -/// mapping. -pub struct GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT> -where - CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static, - CtxT: 'static, - Query: GraphQLType + Send + Sync + 'static, - Mutation: GraphQLType + Send + Sync + 'static, -{ - context_factory: CtxFactory, - root_node: RootNode<'a, Query, Mutation>, -} - -/// Handler that renders GraphiQL - a graphical query editor interface -pub struct GraphiQLHandler { - graphql_url: String, -} - - -fn get_single_value(mut values: Vec) -> IronResult { - if values.len() == 1 { - Ok(values.remove(0)) - } else { - Err( - GraphQLIronError::InvalidData("Duplicate URL query parameter").into(), - ) - } -} - -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(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) - } -} - - -impl<'a, CtxFactory, Query, Mutation, CtxT> GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT> -where - CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static, - CtxT: 'static, - Query: GraphQLType + Send + Sync + 'static, - Mutation: GraphQLType + Send + Sync + 'static, -{ - /// Build a new GraphQL handler - /// - /// The context factory will receive the Iron request object and is - /// expected to construct a context object for the given schema. This can - /// be used to construct e.g. database connections or similar data that - /// the schema needs to execute the query. - pub fn new(context_factory: CtxFactory, query: Query, mutation: Mutation) -> Self { - GraphQLHandler { - context_factory: context_factory, - root_node: RootNode::new(query, mutation), - } - } - - - 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 { - let mut request_payload = String::new(); - itry!(req.body.read_to_string(&mut request_payload)); - - Ok(serde_json::from_str::( - request_payload.as_str(), - ).map_err(|err| GraphQLIronError::Serde(err))?) - } - - 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 { - /// Build a new GraphiQL handler targeting the specified URL. - /// - /// The provided URL should point to the URL of the attached `GraphQLHandler`. It can be - /// relative, so a common value could be `"/graphql"`. - pub fn new(graphql_url: &str) -> GraphiQLHandler { - GraphiQLHandler { - graphql_url: graphql_url.to_owned(), - } - } -} - -impl<'a, CtxFactory, Query, Mutation, CtxT> Handler - for GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT> -where - CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static, - CtxT: 'static, - Query: GraphQLType + Send + Sync + 'static, - Mutation: GraphQLType + Send + Sync + 'static, - 'a: 'static, -{ - fn handle(&self, mut req: &mut Request) -> IronResult { - 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) - } -} - -impl Handler for GraphiQLHandler { - fn handle(&self, _: &mut Request) -> IronResult { - let content_type = "text/html".parse::().unwrap(); - - Ok(Response::with(( - content_type, - status::Ok, - juniper::graphiql::graphiql_source(&self.graphql_url), - ))) - } -} - -#[derive(Debug)] -enum GraphQLIronError { - Serde(SerdeError), - Url(UrlDecodingError), - InvalidData(&'static str), -} - -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::Url(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::InvalidData(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::Url(ref err) => err.description(), - GraphQLIronError::InvalidData(ref err) => err, - } - } - - 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)) - } -} - -#[cfg(test)] -mod tests { - use iron::prelude::*; - use iron_test::{request, response}; - use iron::{Handler, Headers}; - - use juniper::tests::model::Database; - use http::tests as http_tests; - use juniper::EmptyMutation; - - use super::GraphQLHandler; - - struct TestIronIntegration; - - impl http_tests::HTTPIntegration for TestIronIntegration { - fn get(&self, url: &str) -> http_tests::TestResponse { - make_test_response(request::get( - &("http://localhost:3000".to_owned() + url), - Headers::new(), - &make_handler(), - )) - } - - fn post(&self, url: &str, body: &str) -> http_tests::TestResponse { - make_test_response(request::post( - &("http://localhost:3000".to_owned() + url), - Headers::new(), - body, - &make_handler(), - )) - } - } - - #[test] - fn test_iron_integration() { - let integration = TestIronIntegration; - - http_tests::run_http_test_suite(&integration); - } - - fn context_factory(_: &mut Request) -> Database { - Database::new() - } - - fn make_test_response(response: IronResult) -> http_tests::TestResponse { - let response = response.expect("Error response from GraphQL handler"); - let status_code = response - .status - .expect("No status code returned from handler") - .to_u16() as i32; - let content_type = String::from_utf8( - response - .headers - .get_raw("content-type") - .expect("No content type header from handler")[0] - .clone(), - ).expect("Content-type header invalid UTF-8"); - let body = response::extract_body_to_string(response); - - http_tests::TestResponse { - status_code: status_code, - body: Some(body), - content_type: content_type, - } - } - - fn make_handler() -> Box { - Box::new(GraphQLHandler::new( - context_factory, - Database::new(), - EmptyMutation::::new(), - )) - } -} diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml deleted file mode 100644 index 67b73e43..00000000 --- a/juniper_rocket/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "juniper_rocket" -version = "0.0.1" -authors = ["Magnus Hallin "] -description = "Juniper GraphQL integration with Rocket" -license = "BSD-2-Clause" -documentation = "https://docs.rs/juniper_rocket" -repository = "https://github.com/mhallin/juniper" - -[dependencies] -serde = { version = "^1.0.8" } -serde_derive = {version="^1.0.8" } -serde_json = { version = "^1.0.2" } -rocket = { version = "^0.3.0" } -rocket_codegen = { version = "^0.3.0" } -juniper = { version = "0.8.1", path = "../juniper" } -juniper_codegen = { version = "0.8.1", path = "../juniper_codegen" } - -[badges] -travis-ci = { repository = "mhallin/juniper" } -appveyor = { repository = "mhallin/juniper" } - -[dev-dependencies] -juniper = { version = "0.8.1", path = "../juniper", features=["expose-test-schema", "serde_json"] } diff --git a/juniper_rocket/Makefile.toml b/juniper_rocket/Makefile.toml deleted file mode 100644 index d01df41f..00000000 --- a/juniper_rocket/Makefile.toml +++ /dev/null @@ -1,41 +0,0 @@ -[tasks.build-verbose] -command = "cargo" -args = ["build", "--verbose"] -condition_script = [ -''' -if [ "$CARGO_MAKE_RUST_CHANNEL" = "nightly" ]; then - exit 0 -else - exit 1 -fi -''' -] - -[tasks.build-verbose.windows] -command = "cargo" -args = ["build", "--verbose"] -condition_script = [ - '''IF "%CARGO_MAKE_RUST_CHANNEL%"=="nightly" (exit 0) ELSE (exit 1)''' -] - -[tasks.test-verbose] -command = "cargo" -args = ["test", "--verbose"] -dependencies = ["build-verbose"] -condition_script = [ -''' -if [ "$CARGO_MAKE_RUST_CHANNEL" = "nightly" ]; then - exit 0 -else - exit 1 -fi -''' -] - -[tasks.test-verbose.windows] -command = "cargo" -args = ["test", "--verbose"] -dependencies = ["build-verbose"] -condition_script = [ - '''IF "%CARGO_MAKE_RUST_CHANNEL%"=="nightly" (exit 0) ELSE (exit 1)''' -] diff --git a/juniper_rocket/examples/rocket-server.rs b/juniper_rocket/examples/rocket-server.rs deleted file mode 100644 index 82f42ea2..00000000 --- a/juniper_rocket/examples/rocket-server.rs +++ /dev/null @@ -1,51 +0,0 @@ -#![feature(plugin)] -#![plugin(rocket_codegen)] - -extern crate rocket; -extern crate juniper; -extern crate juniper_rocket; - -use rocket::response::content; -use rocket::State; - -use juniper::tests::model::Database; -use juniper::{EmptyMutation, RootNode}; - -type Schema = RootNode<'static, Database, EmptyMutation>; - -#[get("/")] -fn graphiql() -> content::Html { - juniper_rocket::graphiql_source("/graphql") -} - -#[get("/graphql?")] -fn get_graphql_handler( - context: State, - request: juniper_rocket::GraphQLRequest, - schema: State, -) -> juniper_rocket::GraphQLResponse { - request.execute(&schema, &context) -} - -#[post("/graphql", data = "")] -fn post_graphql_handler( - context: State, - request: juniper_rocket::GraphQLRequest, - schema: State, -) -> juniper_rocket::GraphQLResponse { - request.execute(&schema, &context) -} - -fn main() { - rocket::ignite() - .manage(Database::new()) - .manage(Schema::new( - Database::new(), - EmptyMutation::::new(), - )) - .mount( - "/", - routes![graphiql, get_graphql_handler, post_graphql_handler], - ) - .launch(); -} diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs deleted file mode 100644 index 262fcd12..00000000 --- a/juniper_rocket/src/lib.rs +++ /dev/null @@ -1,232 +0,0 @@ -#![feature(plugin)] -#![plugin(rocket_codegen)] - -extern crate juniper; -extern crate serde_json; -extern crate rocket; - -use std::io::{Cursor, Read}; -use std::error::Error; - -use rocket::Request; -use rocket::request::{FormItems, FromForm}; -use rocket::data::{FromData, Outcome as FromDataOutcome}; -use rocket::response::{content, Responder, Response}; -use rocket::http::{ContentType, Status}; -use rocket::Data; -use rocket::Outcome::{Failure, Forward, Success}; - -use juniper::InputValue; -use juniper::http; - -use juniper::GraphQLType; -use juniper::RootNode; - -/// Simple wrapper around an incoming GraphQL request -/// -/// See the `http` module for more information. This type can be constructed -/// automatically from both GET and POST routes by implementing the `FromForm` -/// and `FromData` traits. -pub struct GraphQLRequest(http::GraphQLRequest); - -/// Simple wrapper around the result of executing a GraphQL query -pub struct GraphQLResponse(Status, String); - -/// Generate an HTML page containing GraphiQL -pub fn graphiql_source(graphql_endpoint_url: &str) -> content::Html { - content::Html(juniper::graphiql::graphiql_source(graphql_endpoint_url)) -} - -impl GraphQLRequest { - /// Execute an incoming GraphQL query - pub fn execute( - &self, - root_node: &RootNode, - context: &CtxT, - ) -> GraphQLResponse - where - QueryT: GraphQLType, - MutationT: GraphQLType, - { - let response = self.0.execute(root_node, context); - let status = if response.is_ok() { - Status::Ok - } else { - Status::BadRequest - }; - let json = serde_json::to_string_pretty(&response).unwrap(); - - GraphQLResponse(status, json) - } -} - -impl<'f> FromForm<'f> for GraphQLRequest { - type Error = String; - - fn from_form(form_items: &mut FormItems<'f>, strict: bool) -> Result { - let mut query = None; - let mut operation_name = None; - let mut variables = None; - - for (key, value) in form_items { - match key.as_str() { - "query" => if query.is_some() { - return Err("Query parameter must not occur more than once".to_owned()); - } else { - query = Some(value.as_str().to_string()); - }, - "operation_name" => if operation_name.is_some() { - return Err( - "Operation name parameter must not occur more than once".to_owned(), - ); - } else { - operation_name = Some(value.as_str().to_string()); - }, - "variables" => if variables.is_some() { - return Err( - "Variables parameter must not occur more than once".to_owned(), - ); - } else { - variables = Some(serde_json::from_str::(value.as_str()) - .map_err(|err| err.description().to_owned())?); - }, - _ => if strict { - return Err(format!("Prohibited extra field '{}'", key).to_owned()); - }, - } - } - - if let Some(query) = query { - Ok(GraphQLRequest( - http::GraphQLRequest::new(query, operation_name, variables), - )) - } else { - Err("Query parameter missing".to_owned()) - } - } -} - -impl FromData for GraphQLRequest { - type Error = String; - - fn from_data(request: &Request, data: Data) -> FromDataOutcome { - if !request.content_type().map_or(false, |ct| ct.is_json()) { - return Forward(data); - } - - let mut body = String::new(); - if let Err(e) = data.open().read_to_string(&mut body) { - return Failure((Status::InternalServerError, format!("{:?}", e))); - } - - match serde_json::from_str(&body) { - Ok(value) => Success(GraphQLRequest(value)), - Err(failure) => return Failure((Status::BadRequest, format!("{}", failure))), - } - } -} - -impl<'r> Responder<'r> for GraphQLResponse { - fn respond_to(self, _: &Request) -> Result, Status> { - let GraphQLResponse(status, body) = self; - - Ok( - Response::build() - .header(ContentType::new("application", "json")) - .status(status) - .sized_body(Cursor::new(body)) - .finalize(), - ) - } -} - -#[cfg(test)] -mod tests { - - use rocket; - use rocket::Rocket; - use rocket::http::{ContentType, Method}; - use rocket::State; - - use juniper::RootNode; - use juniper::tests::model::Database; - use juniper::http::tests as http_tests; - use juniper::EmptyMutation; - - type Schema = RootNode<'static, Database, EmptyMutation>; - - - #[get("/?")] - fn get_graphql_handler( - context: State, - request: super::GraphQLRequest, - schema: State, - ) -> super::GraphQLResponse { - request.execute(&schema, &context) - } - - #[post("/", data = "")] - fn post_graphql_handler( - context: State, - request: super::GraphQLRequest, - schema: State, - ) -> super::GraphQLResponse { - request.execute(&schema, &context) - } - - struct TestRocketIntegration { - rocket: Rocket, - } - - /* - - impl http_tests::HTTPIntegration for TestRocketIntegration - { - fn get(&self, url: &str) -> http_tests::TestResponse { - make_test_response(&self.rocket, MockRequest::new( - Method::Get, - url)) - } - - fn post(&self, url: &str, body: &str) -> http_tests::TestResponse { - make_test_response( - &self.rocket, - MockRequest::new( - Method::Post, - url, - ).header(ContentType::JSON).body(body)) - } - } - - #[test] - fn test_rocket_integration() { - let integration = TestRocketIntegration { - rocket: make_rocket(), - }; - - http_tests::run_http_test_suite(&integration); - } - - fn make_rocket() -> Rocket { - rocket::ignite() - .manage(Database::new()) - .manage(Schema::new(Database::new(), EmptyMutation::::new())) - .mount("/", routes![post_graphql_handler, get_graphql_handler]) - } - - fn make_test_response<'r>(rocket: &'r Rocket, mut request: MockRequest<'r>) -> http_tests::TestResponse { - let mut response = request.dispatch_with(&rocket); - let status_code = response.status().code as i32; - let content_type = response.header_values("content-type").collect::>().into_iter().next() - .expect("No content type header from handler").to_owned(); - let body = response.body().expect("No body returned from GraphQL handler").into_string(); - - http_tests::TestResponse { - status_code: status_code, - body: body, - content_type: content_type, - } - } - - */ -}