- rework `rocket_server` example as `simple` - provide example in `GraphQLRequest` API docs - mention GET query format in `GraphQLRequest` API docs and `simple` example Additionally: - fix `operationName` query parameter handling - make `graphiql_source()` and `playground_source()` polymorphic over `subscriptions_endpoint_url` argument - provide examples in `graphiql_source()` and `playground_source()` API docs - move integration HTTP tests to a separate file - test both sync and async `juniper_rocket` in integration HTTP tests - polish `FromForm` unit tests
This commit is contained in:
parent
add0e23c5f
commit
bd8dc582a4
7 changed files with 372 additions and 239 deletions
|
@ -19,4 +19,4 @@ Included in the source is a [small example][example] which sets up a basic Graph
|
||||||
[graphiql]: https://github.com/graphql/graphiql
|
[graphiql]: https://github.com/graphql/graphiql
|
||||||
[rocket]: https://rocket.rs/
|
[rocket]: https://rocket.rs/
|
||||||
[juniper_rocket]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket
|
[juniper_rocket]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket
|
||||||
[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs
|
[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/simple.rs
|
||||||
|
|
|
@ -17,10 +17,15 @@ All user visible changes to `juniper_rocket` crate will be documented in this fi
|
||||||
|
|
||||||
- `AsRef` and `AsMut` implementation for `GraphQLRequest` to its inner type. ([#968], [#930])
|
- `AsRef` and `AsMut` implementation for `GraphQLRequest` to its inner type. ([#968], [#930])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Made `subscriptions_endpoint_url` argument polymorphic in `graphiql_source()` and `playground_source()`. ([#1223])
|
||||||
|
|
||||||
[#930]: /../../issues/930
|
[#930]: /../../issues/930
|
||||||
[#968]: /../../pull/968
|
[#968]: /../../pull/968
|
||||||
[#1205]: /../../pull/1205
|
[#1205]: /../../pull/1205
|
||||||
[#1220]: /../../pull/1220
|
[#1220]: /../../pull/1220
|
||||||
|
[#1223]: /../../pull/1223
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ A basic usage example can also be found in the [API docs][`juniper_rocket`].
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Check [`examples/rocket_server.rs`][1] for example code of a working [`rocket`] server with [GraphQL] handlers.
|
Check [`examples/simple.rs`][1] for example code of a working [`rocket`] server with [GraphQL] handlers.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,4 +43,4 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql
|
||||||
[Juniper Book]: https://graphql-rust.github.io
|
[Juniper Book]: https://graphql-rust.github.io
|
||||||
[Rust]: https://www.rust-lang.org
|
[Rust]: https://www.rust-lang.org
|
||||||
|
|
||||||
[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs
|
[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/simple.rs
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
use juniper::{
|
|
||||||
tests::fixtures::starwars::schema::{Database, Query},
|
|
||||||
EmptyMutation, EmptySubscription, RootNode,
|
|
||||||
};
|
|
||||||
use rocket::{response::content, State};
|
|
||||||
|
|
||||||
type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
|
|
||||||
|
|
||||||
#[rocket::get("/")]
|
|
||||||
fn graphiql() -> content::RawHtml<String> {
|
|
||||||
juniper_rocket::graphiql_source("/graphql", None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::get("/graphql?<request>")]
|
|
||||||
fn get_graphql_handler(
|
|
||||||
context: &State<Database>,
|
|
||||||
request: juniper_rocket::GraphQLRequest,
|
|
||||||
schema: &State<Schema>,
|
|
||||||
) -> juniper_rocket::GraphQLResponse {
|
|
||||||
request.execute_sync(schema, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::post("/graphql", data = "<request>")]
|
|
||||||
fn post_graphql_handler(
|
|
||||||
context: &State<Database>,
|
|
||||||
request: juniper_rocket::GraphQLRequest,
|
|
||||||
schema: &State<Schema>,
|
|
||||||
) -> juniper_rocket::GraphQLResponse {
|
|
||||||
request.execute_sync(schema, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::main]
|
|
||||||
async fn main() {
|
|
||||||
let _ = rocket::build()
|
|
||||||
.manage(Database::new())
|
|
||||||
.manage(Schema::new(
|
|
||||||
Query,
|
|
||||||
EmptyMutation::<Database>::new(),
|
|
||||||
EmptySubscription::<Database>::new(),
|
|
||||||
))
|
|
||||||
.mount(
|
|
||||||
"/",
|
|
||||||
rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
|
|
||||||
)
|
|
||||||
.launch()
|
|
||||||
.await
|
|
||||||
.expect("server to launch");
|
|
||||||
}
|
|
68
juniper_rocket/examples/simple.rs
Normal file
68
juniper_rocket/examples/simple.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use juniper::{
|
||||||
|
tests::fixtures::starwars::schema::{Database, Query},
|
||||||
|
EmptyMutation, EmptySubscription, RootNode,
|
||||||
|
};
|
||||||
|
use rocket::{response::content::RawHtml, routes, State};
|
||||||
|
|
||||||
|
type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
|
||||||
|
|
||||||
|
#[rocket::get("/")]
|
||||||
|
async fn homepage() -> RawHtml<&'static str> {
|
||||||
|
RawHtml(
|
||||||
|
"<html><h1>juniper_rocket/simple example</h1>\
|
||||||
|
<div>visit <a href=\"/graphiql\">GraphiQL</a></div>\
|
||||||
|
<div>visit <a href=\"/playground\">GraphQL Playground</a></div>\
|
||||||
|
</html>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::get("/graphiql")]
|
||||||
|
fn graphiql() -> RawHtml<String> {
|
||||||
|
juniper_rocket::graphiql_source("/graphql", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::get("/playground")]
|
||||||
|
fn playground() -> RawHtml<String> {
|
||||||
|
juniper_rocket::playground_source("/graphql", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET request accepts query parameters like these:
|
||||||
|
// ?query=<urlencoded-graphql-query-string>
|
||||||
|
// &operationName=<optional-name>
|
||||||
|
// &variables=<optional-json-encoded-variables>
|
||||||
|
// See details here: https://graphql.org/learn/serving-over-http#get-request
|
||||||
|
#[rocket::get("/graphql?<request..>")]
|
||||||
|
async fn get_graphql(
|
||||||
|
db: &State<Database>,
|
||||||
|
request: juniper_rocket::GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> juniper_rocket::GraphQLResponse {
|
||||||
|
request.execute(schema, db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::post("/graphql", data = "<request>")]
|
||||||
|
async fn post_graphql(
|
||||||
|
db: &State<Database>,
|
||||||
|
request: juniper_rocket::GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> juniper_rocket::GraphQLResponse {
|
||||||
|
request.execute(schema, db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::main]
|
||||||
|
async fn main() {
|
||||||
|
_ = rocket::build()
|
||||||
|
.manage(Database::new())
|
||||||
|
.manage(Schema::new(
|
||||||
|
Query,
|
||||||
|
EmptyMutation::new(),
|
||||||
|
EmptySubscription::new(),
|
||||||
|
))
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![homepage, graphiql, playground, get_graphql, post_graphql],
|
||||||
|
)
|
||||||
|
.launch()
|
||||||
|
.await
|
||||||
|
.expect("server to launch");
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ use rocket::{
|
||||||
form::{error::ErrorKind, DataField, Error, Errors, FromForm, Options, ValueField},
|
form::{error::ErrorKind, DataField, Error, Errors, FromForm, Options, ValueField},
|
||||||
http::{ContentType, Status},
|
http::{ContentType, Status},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
response::{self, content, Responder, Response},
|
response::{self, content::RawHtml, Responder, Response},
|
||||||
Data, Request,
|
Data, Request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,11 +17,50 @@ use juniper::{
|
||||||
InputValue, RootNode, ScalarValue,
|
InputValue, RootNode, ScalarValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Simple wrapper around an incoming GraphQL request
|
/// Simple wrapper around an incoming GraphQL request.
|
||||||
///
|
///
|
||||||
/// See the `http` module for more information. This type can be constructed
|
/// See the [`http`] module for more information. This type can be constructed automatically from
|
||||||
/// automatically from both GET and POST routes by implementing the `FromForm`
|
/// both GET and POST routes, as implements [`FromForm`] and [`FromData`] traits.
|
||||||
/// and `FromData` traits.
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use juniper::{
|
||||||
|
/// tests::fixtures::starwars::schema::{Database, Query},
|
||||||
|
/// EmptyMutation, EmptySubscription, RootNode,
|
||||||
|
/// };
|
||||||
|
/// use rocket::{routes, State};
|
||||||
|
///
|
||||||
|
/// type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
|
||||||
|
///
|
||||||
|
/// // GET request accepts query parameters like these:
|
||||||
|
/// // ?query=<urlencoded-graphql-query-string>
|
||||||
|
/// // &operationName=<optional-name>
|
||||||
|
/// // &variables=<optional-json-encoded-variables>
|
||||||
|
/// // See details here: https://graphql.org/learn/serving-over-http#get-request
|
||||||
|
/// #[rocket::get("/graphql?<request..>")]
|
||||||
|
/// async fn get_graphql_handler(
|
||||||
|
/// db: &State<Database>,
|
||||||
|
/// request: juniper_rocket::GraphQLRequest,
|
||||||
|
/// schema: &State<Schema>,
|
||||||
|
/// ) -> juniper_rocket::GraphQLResponse {
|
||||||
|
/// request.execute(schema, db).await
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[rocket::post("/graphql", data = "<request>")]
|
||||||
|
/// async fn post_graphql_handler(
|
||||||
|
/// db: &State<Database>,
|
||||||
|
/// request: juniper_rocket::GraphQLRequest,
|
||||||
|
/// schema: &State<Schema>,
|
||||||
|
/// ) -> juniper_rocket::GraphQLResponse {
|
||||||
|
/// request.execute(schema, db).await
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let rocket = rocket::build()
|
||||||
|
/// .manage(Database::new())
|
||||||
|
/// .manage(Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()))
|
||||||
|
/// .mount("/", routes![get_graphql_handler, post_graphql_handler]);
|
||||||
|
/// ```
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct GraphQLRequest<S = DefaultScalarValue>(GraphQLBatchRequest<S>)
|
pub struct GraphQLRequest<S = DefaultScalarValue>(GraphQLBatchRequest<S>)
|
||||||
where
|
where
|
||||||
|
@ -42,25 +81,59 @@ impl<S: ScalarValue> AsMut<GraphQLBatchRequest<S>> for GraphQLRequest<S> {
|
||||||
/// Simple wrapper around the result of executing a GraphQL query
|
/// Simple wrapper around the result of executing a GraphQL query
|
||||||
pub struct GraphQLResponse(pub Status, pub String);
|
pub struct GraphQLResponse(pub Status, pub String);
|
||||||
|
|
||||||
/// Generate an HTML page containing GraphiQL
|
/// Generates a [`RawHtml`] page containing [GraphiQL].
|
||||||
pub fn graphiql_source(
|
///
|
||||||
|
/// This does not handle routing, so you can mount it on any endpoint.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::{response::content::RawHtml, routes};
|
||||||
|
///
|
||||||
|
/// #[rocket::get("/graphiql")]
|
||||||
|
/// fn graphiql() -> RawHtml<String> {
|
||||||
|
/// juniper_rocket::graphiql_source("/graphql", "/subscriptions")
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let rocket = rocket::build().mount("/", routes![graphiql]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [GraphiQL]: https://github.com/graphql/graphiql
|
||||||
|
pub fn graphiql_source<'a>(
|
||||||
graphql_endpoint_url: &str,
|
graphql_endpoint_url: &str,
|
||||||
subscriptions_endpoint_url: Option<&str>,
|
subscriptions_endpoint_url: impl Into<Option<&'a str>>,
|
||||||
) -> content::RawHtml<String> {
|
) -> RawHtml<String> {
|
||||||
content::RawHtml(juniper::http::graphiql::graphiql_source(
|
RawHtml(http::graphiql::graphiql_source(
|
||||||
graphql_endpoint_url,
|
graphql_endpoint_url,
|
||||||
subscriptions_endpoint_url,
|
subscriptions_endpoint_url.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate an HTML page containing GraphQL Playground
|
/// Generates a [`RawHtml`] page containing [GraphQL Playground].
|
||||||
pub fn playground_source(
|
///
|
||||||
|
/// This does not handle routing, so you can mount it on any endpoint.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::{response::content::RawHtml, routes};
|
||||||
|
///
|
||||||
|
/// #[rocket::get("/playground")]
|
||||||
|
/// fn playground() -> RawHtml<String> {
|
||||||
|
/// juniper_rocket::playground_source("/graphql", "/subscriptions")
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let rocket = rocket::build().mount("/", routes![playground]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [GraphQL Playground]: https://github.com/prisma/graphql-playground
|
||||||
|
pub fn playground_source<'a>(
|
||||||
graphql_endpoint_url: &str,
|
graphql_endpoint_url: &str,
|
||||||
subscriptions_endpoint_url: Option<&str>,
|
subscriptions_endpoint_url: impl Into<Option<&'a str>>,
|
||||||
) -> content::RawHtml<String> {
|
) -> RawHtml<String> {
|
||||||
content::RawHtml(juniper::http::playground::playground_source(
|
RawHtml(http::playground::playground_source(
|
||||||
graphql_endpoint_url,
|
graphql_endpoint_url,
|
||||||
subscriptions_endpoint_url,
|
subscriptions_endpoint_url.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +199,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLResponse {
|
impl GraphQLResponse {
|
||||||
/// Constructs an error response outside of the normal execution flow
|
/// Constructs an error response outside of the normal execution flow.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -194,7 +267,7 @@ impl<'f, S: ScalarValue> GraphQLContext<'f, S> {
|
||||||
|
|
||||||
fn operation_name(&mut self, value: String) {
|
fn operation_name(&mut self, value: String) {
|
||||||
if self.operation_name.is_some() {
|
if self.operation_name.is_some() {
|
||||||
let error = Error::from(ErrorKind::Duplicate).with_name("operation_name");
|
let error = Error::from(ErrorKind::Duplicate).with_name("operationName");
|
||||||
|
|
||||||
self.errors.push(error)
|
self.errors.push(error)
|
||||||
} else {
|
} else {
|
||||||
|
@ -243,7 +316,7 @@ where
|
||||||
fn push_value(ctx: &mut Self::Context, field: ValueField<'f>) {
|
fn push_value(ctx: &mut Self::Context, field: ValueField<'f>) {
|
||||||
match field.name.key().map(|key| key.as_str()) {
|
match field.name.key().map(|key| key.as_str()) {
|
||||||
Some("query") => ctx.query(field.value.into()),
|
Some("query") => ctx.query(field.value.into()),
|
||||||
Some("operation_name") => ctx.operation_name(field.value.into()),
|
Some("operation_name" | "operationName") => ctx.operation_name(field.value.into()),
|
||||||
Some("variables") => ctx.variables(field.value.into()),
|
Some("variables") => ctx.variables(field.value.into()),
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
if ctx.opts.strict {
|
if ctx.opts.strict {
|
||||||
|
@ -348,30 +421,39 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for GraphQLResponse {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod fromform_tests {
|
mod fromform_tests {
|
||||||
use super::*;
|
use std::borrow::Cow;
|
||||||
use juniper::InputValue;
|
|
||||||
|
use juniper::{http, InputValue};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
form::{error::ErrorKind, Error, Form, Strict},
|
form::{error::ErrorKind, Error, Errors, Form, Strict},
|
||||||
http::RawStr,
|
http::RawStr,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
|
||||||
|
use super::GraphQLRequest;
|
||||||
|
|
||||||
fn check_error(input: &str, expected_errors: Vec<Error>, strict: bool) {
|
fn check_error(input: &str, expected_errors: Vec<Error>, strict: bool) {
|
||||||
let errors = if strict {
|
let errors = if strict {
|
||||||
let result = Form::<Strict<GraphQLRequest>>::parse_encoded(RawStr::new(input));
|
let res = Form::<Strict<GraphQLRequest>>::parse_encoded(RawStr::new(input));
|
||||||
assert!(result.is_err());
|
|
||||||
result.unwrap_err()
|
assert!(res.is_err(), "result: {:#?}", res.unwrap());
|
||||||
|
|
||||||
|
res.unwrap_err()
|
||||||
} else {
|
} else {
|
||||||
let result = Form::<GraphQLRequest>::parse_encoded(RawStr::new(input));
|
let res = Form::<GraphQLRequest>::parse_encoded(RawStr::new(input));
|
||||||
assert!(result.is_err());
|
|
||||||
result.unwrap_err()
|
assert!(res.is_err(), "result: {:#?}", res.unwrap());
|
||||||
|
|
||||||
|
res.unwrap_err()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(errors.len(), expected_errors.len());
|
assert_eq!(errors.len(), expected_errors.len());
|
||||||
|
|
||||||
for (error, expected) in errors.iter().zip(&expected_errors) {
|
for (error, expected) in errors.iter().zip(&expected_errors) {
|
||||||
match (&error.kind, &expected.kind) {
|
match (&error.kind, &expected.kind) {
|
||||||
(ErrorKind::Unknown, ErrorKind::Unknown) => (),
|
(ErrorKind::Unknown, ErrorKind::Unknown) => (),
|
||||||
(kind_a, kind_b) => assert_eq!(kind_a, kind_b),
|
(kind_a, kind_b) => assert_eq!(kind_a, kind_b),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(error.name, expected.name);
|
assert_eq!(error.name, expected.name);
|
||||||
assert_eq!(error.value, expected.value);
|
assert_eq!(error.value, expected.value);
|
||||||
assert_eq!(error.entity, expected.entity);
|
assert_eq!(error.entity, expected.entity);
|
||||||
|
@ -379,7 +461,7 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty_form() {
|
fn empty_form() {
|
||||||
check_error(
|
check_error(
|
||||||
"",
|
"",
|
||||||
vec![Error::from(ErrorKind::Missing).with_name("query")],
|
vec![Error::from(ErrorKind::Missing).with_name("query")],
|
||||||
|
@ -388,7 +470,7 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_query() {
|
fn no_query() {
|
||||||
check_error(
|
check_error(
|
||||||
"operation_name=foo&variables={}",
|
"operation_name=foo&variables={}",
|
||||||
vec![Error::from(ErrorKind::Missing).with_name("query")],
|
vec![Error::from(ErrorKind::Missing).with_name("query")],
|
||||||
|
@ -397,7 +479,7 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_strict() {
|
fn strict() {
|
||||||
check_error(
|
check_error(
|
||||||
"query=test&foo=bar",
|
"query=test&foo=bar",
|
||||||
vec![Error::from(ErrorKind::Unknown).with_name("foo")],
|
vec![Error::from(ErrorKind::Unknown).with_name("foo")],
|
||||||
|
@ -406,7 +488,7 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_duplicate_query() {
|
fn duplicate_query() {
|
||||||
check_error(
|
check_error(
|
||||||
"query=foo&query=bar",
|
"query=foo&query=bar",
|
||||||
vec![Error::from(ErrorKind::Duplicate).with_name("query")],
|
vec![Error::from(ErrorKind::Duplicate).with_name("query")],
|
||||||
|
@ -415,16 +497,16 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_duplicate_operation_name() {
|
fn duplicate_operation_name() {
|
||||||
check_error(
|
check_error(
|
||||||
"query=test&operation_name=op1&operation_name=op2",
|
"query=test&operationName=op1&operationName=op2",
|
||||||
vec![Error::from(ErrorKind::Duplicate).with_name("operation_name")],
|
vec![Error::from(ErrorKind::Duplicate).with_name("operationName")],
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_duplicate_variables() {
|
fn duplicate_variables() {
|
||||||
check_error(
|
check_error(
|
||||||
"query=test&variables={}&variables={}",
|
"query=test&variables={}&variables={}",
|
||||||
vec![Error::from(ErrorKind::Duplicate).with_name("variables")],
|
vec![Error::from(ErrorKind::Duplicate).with_name("variables")],
|
||||||
|
@ -433,7 +515,7 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_variables_invalid_json() {
|
fn variables_invalid_json() {
|
||||||
check_error(
|
check_error(
|
||||||
"query=test&variables=NOT_JSON",
|
"query=test&variables=NOT_JSON",
|
||||||
vec![Error::from(ErrorKind::Validation(Cow::Owned(
|
vec![Error::from(ErrorKind::Validation(Cow::Owned(
|
||||||
|
@ -445,174 +527,45 @@ mod fromform_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_variables_valid_json() {
|
fn variables_valid_json() {
|
||||||
let result: Result<GraphQLRequest, Errors> =
|
let result: Result<GraphQLRequest, Errors> =
|
||||||
Form::parse_encoded(RawStr::new(r#"query=test&variables={"foo":"bar"}"#));
|
Form::parse_encoded(RawStr::new(r#"query=test&variables={"foo":"bar"}"#));
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"bar"}"#).unwrap();
|
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"bar"}"#).unwrap();
|
||||||
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(
|
||||||
"test".into(),
|
http::GraphQLRequest::new("test".into(), None, Some(variables)),
|
||||||
None,
|
));
|
||||||
Some(variables),
|
|
||||||
)));
|
|
||||||
assert_eq!(result.unwrap(), expected);
|
assert_eq!(result.unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_variables_encoded_json() {
|
fn variables_encoded_json() {
|
||||||
let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
|
let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
|
||||||
r#"query=test&variables={"foo":"x%20y%26%3F+z"}"#,
|
r#"query=test&variables={"foo":"x%20y%26%3F+z"}"#,
|
||||||
));
|
));
|
||||||
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"x y&? z"}"#).unwrap();
|
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"x y&? z"}"#).unwrap();
|
||||||
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(
|
||||||
"test".into(),
|
http::GraphQLRequest::new("test".into(), None, Some(variables)),
|
||||||
None,
|
));
|
||||||
Some(variables),
|
|
||||||
)));
|
|
||||||
assert_eq!(result.unwrap(), expected);
|
assert_eq!(result.unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_url_decode() {
|
fn url_decode() {
|
||||||
let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
|
let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
|
||||||
"query=%25foo%20bar+baz%26%3F&operation_name=test",
|
"query=%25foo%20bar+baz%26%3F&operationName=test",
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
|
||||||
"%foo bar baz&?".into(),
|
let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(
|
||||||
Some("test".into()),
|
http::GraphQLRequest::new("%foo bar baz&?".into(), Some("test".into()), None),
|
||||||
None,
|
));
|
||||||
)));
|
|
||||||
assert_eq!(result.unwrap(), expected);
|
assert_eq!(result.unwrap(), expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use juniper::{
|
|
||||||
http::tests as http_tests,
|
|
||||||
tests::fixtures::starwars::schema::{Database, Query},
|
|
||||||
EmptyMutation, EmptySubscription, RootNode,
|
|
||||||
};
|
|
||||||
use rocket::{
|
|
||||||
self, get,
|
|
||||||
http::ContentType,
|
|
||||||
local::asynchronous::{Client, LocalResponse},
|
|
||||||
post, routes, Build, Rocket, State,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
|
|
||||||
|
|
||||||
#[get("/?<request..>")]
|
|
||||||
fn get_graphql_handler(
|
|
||||||
context: &State<Database>,
|
|
||||||
request: super::GraphQLRequest,
|
|
||||||
schema: &State<Schema>,
|
|
||||||
) -> super::GraphQLResponse {
|
|
||||||
request.execute_sync(schema, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/", data = "<request>")]
|
|
||||||
fn post_graphql_handler(
|
|
||||||
context: &State<Database>,
|
|
||||||
request: super::GraphQLRequest,
|
|
||||||
schema: &State<Schema>,
|
|
||||||
) -> super::GraphQLResponse {
|
|
||||||
request.execute_sync(schema, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestRocketIntegration {
|
|
||||||
client: Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl http_tests::HttpIntegration for TestRocketIntegration {
|
|
||||||
fn get(&self, url: &str) -> http_tests::TestResponse {
|
|
||||||
let req = self.client.get(url);
|
|
||||||
let req = futures::executor::block_on(req.dispatch());
|
|
||||||
futures::executor::block_on(make_test_response(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn post_json(&self, url: &str, body: &str) -> http_tests::TestResponse {
|
|
||||||
let req = self.client.post(url).header(ContentType::JSON).body(body);
|
|
||||||
let req = futures::executor::block_on(req.dispatch());
|
|
||||||
futures::executor::block_on(make_test_response(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn post_graphql(&self, url: &str, body: &str) -> http_tests::TestResponse {
|
|
||||||
let req = self
|
|
||||||
.client
|
|
||||||
.post(url)
|
|
||||||
.header(ContentType::new("application", "graphql"))
|
|
||||||
.body(body);
|
|
||||||
let req = futures::executor::block_on(req.dispatch());
|
|
||||||
futures::executor::block_on(make_test_response(req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_test]
|
|
||||||
async fn test_rocket_integration() {
|
|
||||||
let rocket = make_rocket();
|
|
||||||
let client = Client::untracked(rocket).await.expect("valid rocket");
|
|
||||||
let integration = TestRocketIntegration { client };
|
|
||||||
|
|
||||||
http_tests::run_http_test_suite(&integration);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_test]
|
|
||||||
async fn test_operation_names() {
|
|
||||||
#[post("/", data = "<request>")]
|
|
||||||
fn post_graphql_assert_operation_name_handler(
|
|
||||||
context: &State<Database>,
|
|
||||||
request: super::GraphQLRequest,
|
|
||||||
schema: &State<Schema>,
|
|
||||||
) -> super::GraphQLResponse {
|
|
||||||
assert_eq!(request.operation_names(), vec![Some("TestQuery")]);
|
|
||||||
request.execute_sync(schema, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
let rocket = make_rocket_without_routes()
|
|
||||||
.mount("/", routes![post_graphql_assert_operation_name_handler]);
|
|
||||||
let client = Client::untracked(rocket).await.expect("valid rocket");
|
|
||||||
|
|
||||||
let resp = client
|
|
||||||
.post("/")
|
|
||||||
.header(ContentType::JSON)
|
|
||||||
.body(r#"{"query": "query TestQuery {hero{name}}", "operationName": "TestQuery"}"#)
|
|
||||||
.dispatch()
|
|
||||||
.await;
|
|
||||||
let resp = make_test_response(resp);
|
|
||||||
|
|
||||||
assert_eq!(resp.await.status_code, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_rocket() -> Rocket<Build> {
|
|
||||||
make_rocket_without_routes().mount("/", routes![post_graphql_handler, get_graphql_handler])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_rocket_without_routes() -> Rocket<Build> {
|
|
||||||
Rocket::build().manage(Database::new()).manage(Schema::new(
|
|
||||||
Query,
|
|
||||||
EmptyMutation::<Database>::new(),
|
|
||||||
EmptySubscription::<Database>::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn make_test_response(response: LocalResponse<'_>) -> http_tests::TestResponse {
|
|
||||||
let status_code = response.status().code as i32;
|
|
||||||
let content_type = response
|
|
||||||
.content_type()
|
|
||||||
.expect("No content type header from handler")
|
|
||||||
.to_string();
|
|
||||||
let body = response
|
|
||||||
.into_string()
|
|
||||||
.await
|
|
||||||
.expect("No body returned from GraphQL handler");
|
|
||||||
|
|
||||||
http_tests::TestResponse {
|
|
||||||
status_code,
|
|
||||||
body: Some(body),
|
|
||||||
content_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
155
juniper_rocket/tests/http_test_suite.rs
Normal file
155
juniper_rocket/tests/http_test_suite.rs
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
use futures::executor;
|
||||||
|
use juniper::{
|
||||||
|
http::tests::{run_http_test_suite, HttpIntegration, TestResponse},
|
||||||
|
tests::fixtures::starwars::schema::{Database, Query},
|
||||||
|
EmptyMutation, EmptySubscription, RootNode,
|
||||||
|
};
|
||||||
|
use juniper_rocket::{GraphQLRequest, GraphQLResponse};
|
||||||
|
use rocket::{
|
||||||
|
get,
|
||||||
|
http::ContentType,
|
||||||
|
local::asynchronous::{Client, LocalResponse},
|
||||||
|
post, routes, Build, Rocket, State,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
|
||||||
|
|
||||||
|
fn bootstrap_rocket() -> Rocket<Build> {
|
||||||
|
Rocket::build().manage(Database::new()).manage(Schema::new(
|
||||||
|
Query,
|
||||||
|
EmptyMutation::<Database>::new(),
|
||||||
|
EmptySubscription::<Database>::new(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_rocket() -> Rocket<Build> {
|
||||||
|
#[get("/?<request..>")]
|
||||||
|
async fn get_handler(
|
||||||
|
context: &State<Database>,
|
||||||
|
request: GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> GraphQLResponse {
|
||||||
|
request.execute(schema, context).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/", data = "<request>")]
|
||||||
|
async fn post_handler(
|
||||||
|
context: &State<Database>,
|
||||||
|
request: GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> GraphQLResponse {
|
||||||
|
request.execute(schema, context).await
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap_rocket().mount("/", routes![post_handler, get_handler])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_sync_rocket() -> Rocket<Build> {
|
||||||
|
#[get("/?<request..>")]
|
||||||
|
fn get_handler_sync(
|
||||||
|
context: &State<Database>,
|
||||||
|
request: GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> GraphQLResponse {
|
||||||
|
request.execute_sync(schema, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/", data = "<request>")]
|
||||||
|
fn post_handler_sync(
|
||||||
|
context: &State<Database>,
|
||||||
|
request: GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> GraphQLResponse {
|
||||||
|
request.execute_sync(schema, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap_rocket().mount("/", routes![post_handler_sync, get_handler_sync])
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestRocketIntegration {
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn into_test_response(response: LocalResponse<'_>) -> TestResponse {
|
||||||
|
let status_code = response.status().code as i32;
|
||||||
|
let content_type = response
|
||||||
|
.content_type()
|
||||||
|
.expect("no `Content-Type` header from handler")
|
||||||
|
.to_string();
|
||||||
|
let body = response
|
||||||
|
.into_string()
|
||||||
|
.await
|
||||||
|
.expect("no body returned from GraphQL handler");
|
||||||
|
|
||||||
|
TestResponse {
|
||||||
|
status_code,
|
||||||
|
content_type,
|
||||||
|
body: Some(body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpIntegration for TestRocketIntegration {
|
||||||
|
fn get(&self, url: &str) -> TestResponse {
|
||||||
|
let req = self.client.get(url);
|
||||||
|
let resp = executor::block_on(req.dispatch());
|
||||||
|
executor::block_on(into_test_response(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_json(&self, url: &str, body: &str) -> TestResponse {
|
||||||
|
let req = self.client.post(url).header(ContentType::JSON).body(body);
|
||||||
|
let resp = executor::block_on(req.dispatch());
|
||||||
|
executor::block_on(into_test_response(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_graphql(&self, url: &str, body: &str) -> TestResponse {
|
||||||
|
let req = self
|
||||||
|
.client
|
||||||
|
.post(url)
|
||||||
|
.header(ContentType::new("application", "graphql"))
|
||||||
|
.body(body);
|
||||||
|
let resp = executor::block_on(req.dispatch());
|
||||||
|
executor::block_on(into_test_response(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn test_rocket_integration() {
|
||||||
|
let rocket = make_rocket();
|
||||||
|
let client = Client::untracked(rocket).await.expect("valid rocket");
|
||||||
|
|
||||||
|
run_http_test_suite(&TestRocketIntegration { client });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn test_sync_rocket_integration() {
|
||||||
|
let rocket = make_sync_rocket();
|
||||||
|
let client = Client::untracked(rocket).await.expect("valid rocket");
|
||||||
|
|
||||||
|
run_http_test_suite(&TestRocketIntegration { client });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn test_operation_names() {
|
||||||
|
#[post("/", data = "<request>")]
|
||||||
|
async fn post_graphql_assert_operation_name_handler(
|
||||||
|
context: &State<Database>,
|
||||||
|
request: GraphQLRequest,
|
||||||
|
schema: &State<Schema>,
|
||||||
|
) -> GraphQLResponse {
|
||||||
|
assert_eq!(request.operation_names(), vec![Some("TestQuery")]);
|
||||||
|
request.execute(schema, context).await
|
||||||
|
}
|
||||||
|
|
||||||
|
let rocket = bootstrap_rocket().mount("/", routes![post_graphql_assert_operation_name_handler]);
|
||||||
|
let client = Client::untracked(rocket).await.expect("valid rocket");
|
||||||
|
|
||||||
|
let resp = client
|
||||||
|
.post("/")
|
||||||
|
.header(ContentType::JSON)
|
||||||
|
.body(r#"{"query": "query TestQuery {hero{name}}", "operationName": "TestQuery"}"#)
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
let resp = into_test_response(resp).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.status_code, 200, "response: {resp:#?}");
|
||||||
|
}
|
Loading…
Reference in a new issue