diff --git a/.travis.yml b/.travis.yml index 11ed16e..4fb7172 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,26 @@ -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 + - yarn - yarn build + - yarn test - touch _book/.nojekyll branches: 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..9290498 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 @@ -29,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; @@ -40,15 +41,15 @@ use iron::prelude::*; use juniper::EmptyMutation; use juniper_iron::GraphQLHandler; -fn context_factory(_: &mut Request) -> () { - () +fn context_factory(_: &mut Request) -> IronResult<()> { + Ok(()) } struct Root; graphql_object!(Root: () |&self| { field foo() -> String { - "Bar".to_owned() + "Bar".to_owned() } }); @@ -77,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; @@ -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) -> IronResult { + 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 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" + } + ] +} +```