Add juniper_rocket back to main repo

This is done for a lower maintainance burden and combined testing.
This commit is contained in:
theduke 2017-12-02 15:59:23 +01:00
parent bec5295827
commit b89712a887
6 changed files with 364 additions and 0 deletions

2
juniper_rocket/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target
Cargo.lock

23
juniper_rocket/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "juniper_rocket"
version = "0.1.0"
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
description = "Juniper GraphQL integration with Rocket"
license = "BSD-2-Clause"
documentation = "https://docs.rs/juniper_rocket"
repository = "https://github.com/graphql-rust/juniper_rocket"
[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" , git = "https://github.com/graphql-rust/juniper" }
[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"], git = "https://github.com/graphql-rust/juniper" }

25
juniper_rocket/LICENSE Normal file
View file

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2016, Magnus Hallin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

31
juniper_rocket/README.md Normal file
View file

@ -0,0 +1,31 @@
# juniper_rocket
[![Build Status](https://travis-ci.org/graphql-rust/juniper_rocket.svg?branch=master)](https://travis-ci.org/graphql-rust/juniper_rocket)
[![Build status](https://ci.appveyor.com/api/projects/status/9j9bvj7q05jcxw2v?svg=true)](https://ci.appveyor.com/project/theduke/juniper-rocket)
[![Crates.io](https://img.shields.io/crates/v/juniper_rocket.svg?maxAge=2592000)](https://crates.io/crates/juniper_rocket)
[![Gitter chat](https://badges.gitter.im/juniper-graphql/gitter.png)](https://gitter.im/juniper-graphql)
This repository contains the [Rocket][Rocket] web server integration for [Juniper][Juniper], a [GraphQL][GraphQL]
implementation for Rust.
## Documentation
Once the crate is published, the documentation will be on [docs.rs][documentation].
For now, please consult the example below.
## Examples
Check [examples/rocket_server.rs][example] for example code of a working Rocket server with GraphQL handlers.
## License
This project is under the BSD-2 license.
Check the LICENSE file for details.
[Rocket]: https://rocket.rs
[Juniper]: https://github.com/graphql-rust/juniper
[GraphQL]: http://graphql.org
[documentation]: https://docs.rs/juniper_rocket
[example]: https://github.com/graphql-rust/juniper_rocket/blob/master/examples/rocket_server.rs

View file

@ -0,0 +1,51 @@
#![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<Database>>;
#[get("/")]
fn graphiql() -> content::Html<String> {
juniper_rocket::graphiql_source("/graphql")
}
#[get("/graphql?<request>")]
fn get_graphql_handler(
context: State<Database>,
request: juniper_rocket::GraphQLRequest,
schema: State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&schema, &context)
}
#[post("/graphql", data = "<request>")]
fn post_graphql_handler(
context: State<Database>,
request: juniper_rocket::GraphQLRequest,
schema: State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&schema, &context)
}
fn main() {
rocket::ignite()
.manage(Database::new())
.manage(Schema::new(
Database::new(),
EmptyMutation::<Database>::new(),
))
.mount(
"/",
routes![graphiql, get_graphql_handler, post_graphql_handler],
)
.launch();
}

232
juniper_rocket/src/lib.rs Normal file
View file

@ -0,0 +1,232 @@
#![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<String> {
content::Html(juniper::graphiql::graphiql_source(graphql_endpoint_url))
}
impl GraphQLRequest {
/// Execute an incoming GraphQL query
pub fn execute<CtxT, QueryT, MutationT>(
&self,
root_node: &RootNode<QueryT, MutationT>,
context: &CtxT,
) -> GraphQLResponse
where
QueryT: GraphQLType<Context = CtxT>,
MutationT: GraphQLType<Context = CtxT>,
{
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<Self, String> {
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::<InputValue>(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<Self, Self::Error> {
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<Response<'r>, 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<Database>>;
#[get("/?<request>")]
fn get_graphql_handler(
context: State<Database>,
request: super::GraphQLRequest,
schema: State<Schema>,
) -> super::GraphQLResponse {
request.execute(&schema, &context)
}
#[post("/", data = "<request>")]
fn post_graphql_handler(
context: State<Database>,
request: super::GraphQLRequest,
schema: State<Schema>,
) -> 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::<Database>::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::<Vec<_>>().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,
}
}
*/
}