From 7ca2737e69a164604c03238bf0c5f882c3b31260 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Thu, 13 Sep 2018 14:43:49 -0700 Subject: [PATCH 1/6] Fix skeptic and enable tests in Travis --- .travis.yml | 2 +- _skeptic/Cargo.toml | 6 ++---- docs/quickstart.md | 5 +---- docs/servers/iron.md | 22 +++++++++++----------- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 11ed16e..8ca3ad4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_install: - rustup update script: - # - yarn test + - yarn test - yarn build - touch _book/.nojekyll diff --git a/_skeptic/Cargo.toml b/_skeptic/Cargo.toml index 2cbd8da..47b6c13 100644 --- a/_skeptic/Cargo.toml +++ b/_skeptic/Cargo.toml @@ -11,12 +11,10 @@ juniper_iron = { git = "https://github.com/graphql-rust/juniper" } iron = "^0.5.0" mount = "^0.3.0" -skeptic = "0.12" +skeptic = "0.13" [build-dependencies] -skeptic = "0.12" +skeptic = "0.13" [patch.crates-io] juniper_codegen = { git = "https://github.com/graphql-rust/juniper" } - - diff --git a/docs/quickstart.md b/docs/quickstart.md index f75fd4c..789b11b 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -41,9 +41,6 @@ use juniper::{FieldResult}; # fn insert_human(&self, human: &NewHuman) -> FieldResult { Err("")? } # } - -use juniper::{FieldResult}; - #[derive(GraphQLEnum)] enum Episode { NewHope, @@ -180,7 +177,7 @@ fn main() { // Ensure the value matches. assert_eq!( - res.as_object_value().unwrap()["favoriteEpisode"].as_string_value().unwrap(), + res.as_object_value().unwrap().get_field_value("favoriteEpisode").unwrap().as_string_value().unwrap(), "NEW_HOPE", ); } diff --git a/docs/servers/iron.md b/docs/servers/iron.md index 665c3d8..447c149 100644 --- a/docs/servers/iron.md +++ b/docs/servers/iron.md @@ -8,10 +8,11 @@ channel. Juniper's Iron integration is contained in the `juniper_iron` crate: !FILENAME Cargo.toml + ```toml [dependencies] -juniper = "0.9.0" -juniper_iron = "0.1.0" +juniper = "0.10" +juniper_iron = "0.2.0" ``` Included in the source is a [small @@ -40,15 +41,15 @@ use iron::prelude::*; use juniper::EmptyMutation; use juniper_iron::GraphQLHandler; -fn context_factory(_: &mut Request) -> () { - () +fn context_factory(_: &mut Request) -> Result<(), iron::IronError> { + Ok(()) } struct Root; graphql_object!(Root: () |&self| { field foo() -> String { - "Bar".to_owned() + "Bar".to_owned() } }); @@ -90,10 +91,10 @@ struct Context { impl juniper::Context for Context {} -fn context_factory(req: &mut Request) -> Context { - Context { +fn context_factory(req: &mut Request) -> Result { + Ok(Context { remote_addr: req.remote_addr - } + }) } struct Root; @@ -119,7 +120,6 @@ graphql_object!(Root: Context |&self| { FIXME: Show how the `persistent` crate works with contexts using e.g. `r2d2`. - -[Iron]: http://ironframework.io -[GraphiQL]: https://github.com/graphql/graphiql +[iron]: http://ironframework.io +[graphiql]: https://github.com/graphql/graphiql [mount]: https://github.com/iron/mount From 8141f0d45d8a4186a6666e49ea44ac2a711c883e Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Thu, 13 Sep 2018 14:56:21 -0700 Subject: [PATCH 2/6] Use `IronResult` --- docs/servers/iron.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/servers/iron.md b/docs/servers/iron.md index 447c149..00a0179 100644 --- a/docs/servers/iron.md +++ b/docs/servers/iron.md @@ -41,7 +41,7 @@ use iron::prelude::*; use juniper::EmptyMutation; use juniper_iron::GraphQLHandler; -fn context_factory(_: &mut Request) -> Result<(), iron::IronError> { +fn context_factory(_: &mut iron::request::Request) -> IronResult<()> { Ok(()) } @@ -91,7 +91,7 @@ struct Context { impl juniper::Context for Context {} -fn context_factory(req: &mut Request) -> Result { +fn context_factory(req: &mut Request) -> IronResult { Ok(Context { remote_addr: req.remote_addr }) From d4fb9385341ab9c034df3136c4c722dd44699701 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Thu, 13 Sep 2018 14:56:45 -0700 Subject: [PATCH 3/6] Switch to travis Rust images Rather than using a node image and installing rust, we will use the rust images and install node. This makes it easy to test on stable/beta/nightly. --- .travis.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ca3ad4..730995f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,21 @@ -language: node_js -node_js: - - "8" +language: rust +rust: + - stable + - beta + - nightly env: global: - secure: "vaPicq7s2hHBZwtG5eZ1wSmlIYog8FBZ7OilJs6cXQ0fyP5FqGFdc+VG+FSNbEDqPct/v5ojrfbwhQWZVjzgyMZ+Ikrpd9QD0T2Ie4f5yHh2schpphiog7pAfRG1A56/JsGq7aZr76DsICYUGeU4d8BzjaeFC2ozoo5tE9NXpp5ENLFNuErYGwMcQ0vlLTrK2miyuDn18HasHeT5pmxZT1qN5KjxzqChTvEFbH9pQsVKv+dVQiWVifYt4beOfSxaZJmCyBJHv2MjUOyWmYPtqikVxz4dkTbS/Cyx9dK3u2AgrH2Trrl0RFa5VKQUA+06v9NC+oH8NJj72aw44JdryVTchfQw3VF27H/2xfeg3WJX87/1J1oWvCBBtFWU5UwWapXq4Tz7UjT75H7unmlnc11hwmgMklpqMpD52om8n/GLMY2wkS5/dPJpLbYWt6OnBCPtHdP2EO59Wxg1YJ73PZdsrC81z3t8c4SSUXCmzUCG7P8UrSjpBl0g3yXTtR1/fvvSU1qQLFIDN8ib4tl8KGEgbX1ipJkkgCExriuZ58wOPqOdioqNMfWxyGszqxALsL1qxcET8ZtVOzIRCGuVptV0cUujxUwM9LJBqWq4MqPVO9+98FtX6xZvMM5gUM2dq4gWI45KK/VcNEkgihoSKUyVR2OaW5sTs6d28OejOXs=" before_install: - - curl https://sh.rustup.rs -sSf | sh -s -- -y - - export PATH=$HOME/.cargo/bin:$PATH - - rustup update + # Install node. + - curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - + - sudo apt-get install -y nodejs + # Install yarn. + - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - + - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + - sudo apt-get update && sudo apt-get install -y yarn script: - yarn test From e379c11ed19a944adb930d442b575606947aac45 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Thu, 13 Sep 2018 15:13:27 -0700 Subject: [PATCH 4/6] Ignore Iron skeptic tests These are failing to build and I don't know why: ``` error[E0277]: expected a `std::ops::Fn<(&mut iron::request::Request<'_, '_>,)>` closure, found `juniper_iron::GraphQLHandler<'_, for<'r, 's, 't0> fn(&'r mut iron::Request<'s, 't0>) -> std::result::Result<(), iron::IronError> {context_factory}, Root, juniper::EmptyMutation<()>, ()>` --> /var/folders/f3/7xdpkjk508z7b90jfqg1dn2m0000gn/T/rust-skeptic.vXzza3OIPuba/test.rs:34:11 | 34 | mount.mount("/graphql", graphql_endpoint); | ^^^^^ expected an `Fn<(&mut iron::request::Request<'_, '_>,)>` closure, found `juniper_iron::GraphQLHandler<'_, for<'r, 's, 't0> fn(&'r mut iron::Request<'s, 't0>) -> std::result::Result<(), iron::IronError> {context_factory}, Root, juniper::EmptyMutation<()>, ()>` | = help: the trait `for<'r, 's, 't0> std::ops::Fn<(&'r mut iron::request::Request<'s, 't0>,)>` is not implemented for `juniper_iron::GraphQLHandler<'_, for<'r, 's, 't0> fn(&'r mut iron::Request<'s, 't0>) -> std::result::Result<(), iron::IronError> {context_factory}, Root, juniper::EmptyMutation<()>, ()>` = note: required because of the requirements on the impl of `iron::middleware::Handler` for `juniper_iron::GraphQLHandler<'_, for<'r, 's, 't0> fn(&'r mut iron::Request<'s, 't0>) -> std::result::Result<(), iron::IronError> {context_factory}, Root, juniper::EmptyMutation<()>, ()>` error[E0277]: expected a `std::ops::Fn<(&mut iron::Request<'_, '_>,)>` closure, found `mount::Mount` --> /var/folders/f3/7xdpkjk508z7b90jfqg1dn2m0000gn/T/rust-skeptic.vXzza3OIPuba/test.rs:36:17 | 36 | let chain = Chain::new(mount); | ^^^^^^^^^^ expected an `Fn<(&mut iron::Request<'_, '_>,)>` closure, found `mount::Mount` | = help: the trait `for<'r, 's, 't0> std::ops::Fn<(&'r mut iron::Request<'s, 't0>,)>` is not implemented for `mount::Mount` = note: required because of the requirements on the impl of `iron::Handler` for `mount::Mount` = note: required by `iron::Chain::new` error: aborting due to 2 previous errors ``` --- docs/servers/iron.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/servers/iron.md b/docs/servers/iron.md index 00a0179..9290498 100644 --- a/docs/servers/iron.md +++ b/docs/servers/iron.md @@ -30,7 +30,7 @@ set up other global data that the schema might require. In this example, we won't use any global data so we just return an empty value. -```rust +```rust,ignore #[macro_use] extern crate juniper; extern crate juniper_iron; extern crate iron; @@ -41,7 +41,7 @@ use iron::prelude::*; use juniper::EmptyMutation; use juniper_iron::GraphQLHandler; -fn context_factory(_: &mut iron::request::Request) -> IronResult<()> { +fn context_factory(_: &mut Request) -> IronResult<()> { Ok(()) } @@ -78,7 +78,7 @@ If you want to access e.g. the source IP address of the request from a field resolver, you need to pass this data using Juniper's [context feature](context.md). -```rust +```rust,ignore # #[macro_use] extern crate juniper; # extern crate juniper_iron; # extern crate iron; From da3d21bad8d32c9623aa6538f42def4aa3f6deae Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Thu, 13 Sep 2018 15:23:07 -0700 Subject: [PATCH 5/6] Install js dependencies --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 730995f..4fb7172 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,9 @@ before_install: - sudo apt-get update && sudo apt-get install -y yarn script: - - yarn test + - yarn - yarn build + - yarn test - touch _book/.nojekyll branches: From 5a0d960d7fb54c47cf90ac8b26c0c1daf8e0b365 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Thu, 13 Sep 2018 14:00:41 -0700 Subject: [PATCH 6/6] Add `IntoFieldError` to error docs Part of https://github.com/graphql-rust/graphql-rust.github.io/issues/8. --- docs/types/objects/error_handling.md | 124 ++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/docs/types/objects/error_handling.md b/docs/types/objects/error_handling.md index 40a63f9..d415b33 100644 --- a/docs/types/objects/error_handling.md +++ b/docs/types/objects/error_handling.md @@ -17,6 +17,7 @@ use juniper::FieldResult; use std::path::PathBuf; use std::fs::File; use std::io::Read; +use std::str; struct Example { filename: PathBuf, @@ -29,6 +30,15 @@ graphql_object!(Example: () |&self| { file.read_to_string(&mut contents)?; Ok(contents) } + field foo() -> FieldResult> { + // Some invalid bytes. + let invalid = vec![128, 223]; + + match str::from_utf8(&invalid) { + Ok(s) => Ok(Some(s.to_string())), + Err(e) => Err(e)?, + } + } }); # fn main() {} @@ -41,6 +51,118 @@ there - those errors are automatically converted into `FieldError`. When a field returns an error, the field's result is replaced by `null`, an additional `errors` object is created at the top level of the response, and the -execution is resumed. If an error is returned from a non-null field, such as the +execution is resumed. For example, with the previous example and the following +query: + +```graphql +{ + example { + contents + foo + } +} +``` + +If `str::from_utf8` resulted in a `std::str::Utf8Error`, the following would be +returned: + +!FILENAME Response for nullable field with error + +```js +{ + "data": { + "example": { + contents: "", + foo: null, + } + }, + "errors": [ + "message": "invalid utf-8 sequence of 2 bytes from index 0", + "locations": [{ "line": 2, "column": 4 }]) + ] +} +``` + +If an error is returned from a non-null field, such as the example above, the `null` value is propagated up to the first nullable parent field, or the root `data` object if there are no nullable fields. + +For example, with the following query: + +```graphql +{ + example { + contents + } +} +``` + +If `File::open()` above resulted in `std::io::ErrorKind::PermissionDenied`, the +following would be returned: + +!FILENAME Response for non-null field with error and no nullable parent + +```js +{ + "errors": [ + "message": "Permission denied (os error 13)", + "locations": [{ "line": 2, "column": 4 }]) + ] +} +``` + +## Structured errors + +Sometimes it is desirable to return additional structured error information +to clients. This can be accomplished by implementing [`IntoFieldError`](https://docs.rs/juniper/latest/juniper/trait.IntoFieldError.html): + +```rust +# #[macro_use] extern crate juniper; +use juniper::{FieldError, IntoFieldError}; + +enum CustomError { + WhateverNotSet, +} + +impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + match self { + CustomError::WhateverNotSet => FieldError::new( + "Whatever does not exist", + graphql_value!({ + "type": "NO_WHATEVER" + }), + ), + } + } +} + +struct Example { + whatever: Option, +} + +graphql_object!(Example: () |&self| { + field whatever() -> Result { + if let Some(value) = self.whatever { + return Ok(value); + } + Err(CustomError::WhateverNotSet) + } +}); + +# fn main() {} +``` + +The specified structured error information is included in the [`extensions`](https://facebook.github.io/graphql/June2018/#sec-Errors) key: + +```js +{ + "errors": [ + "message": "Whatever does not exist", + "locations": [{ "line": 2, "column": 4 }]), + "extensions": { + "type": "NO_WHATEVER" + } + ] +} +```