Add juniper_rocket back to main repo
This is done for a lower maintainance burden and combined testing.
This commit is contained in:
parent
bec5295827
commit
b89712a887
6 changed files with 364 additions and 0 deletions
2
juniper_rocket/.gitignore
vendored
Normal file
2
juniper_rocket/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
Cargo.lock
|
23
juniper_rocket/Cargo.toml
Normal file
23
juniper_rocket/Cargo.toml
Normal 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
25
juniper_rocket/LICENSE
Normal 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
31
juniper_rocket/README.md
Normal 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
|
51
juniper_rocket/examples/rocket_server.rs
Normal file
51
juniper_rocket/examples/rocket_server.rs
Normal 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
232
juniper_rocket/src/lib.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
Loading…
Reference in a new issue