(book) impl_object refactor

This commit is contained in:
Christoph Herzog 2019-05-07 10:56:06 +02:00
parent bf50c3eb86
commit 693405afa5
No known key found for this signature in database
GPG key ID: DAFF71D48B493238
14 changed files with 164 additions and 111 deletions

View file

@ -30,9 +30,6 @@ result can then be converted to JSON for use with tools and libraries such as
[graphql-client](https://github.com/graphql-rust/graphql-client):
```rust
# // Only needed due to 2018 edition because the macro is not accessible.
# extern crate juniper;
# extern crate serde_json;
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
// Define our schema.
@ -47,11 +44,14 @@ impl juniper::Context for Context {}
struct Query;
juniper::graphql_object!(Query: Context |&self| {
field example(&executor, id: String) -> FieldResult<Example> {
#[juniper::impl_object(
Context = Context,
)]
impl Query {
fn example(id: String) -> FieldResult<Example> {
unimplemented!()
}
});
}
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;

View file

@ -23,21 +23,22 @@ enum SignUpResult {
Error(Vec<ValidationError>),
}
juniper::graphql_object!(SignUpResult: () |&self| {
field user() -> Option<&User> {
#[juniper::impl_object]
impl SignUpResult {
fn user(&self) -> Option<&User> {
match *self {
SignUpResult::Ok(ref user) => Some(user),
SignUpResult::Error(_) => None,
}
}
field error() -> Option<&Vec<ValidationError>> {
fn error(&self) -> Option<&Vec<ValidationError>> {
match *self {
SignUpResult::Ok(_) => None,
SignUpResult::Error(ref errors) => Some(errors)
}
}
});
}
# fn main() {}
```

View file

@ -25,25 +25,31 @@ struct ValidationError {
# #[allow(dead_code)]
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
juniper::graphql_object!(MutationResult<User>: () as "UserResult" |&self| {
field user() -> Option<&User> {
#[juniper::impl_object(
name = "UserResult",
)]
impl MutationResult<User> {
fn user(&self) -> Option<&User> {
self.0.as_ref().ok()
}
field error() -> Option<&Vec<ValidationError>> {
fn error(&self) -> Option<&Vec<ValidationError>> {
self.0.as_ref().err()
}
});
}
juniper::graphql_object!(MutationResult<ForumPost>: () as "ForumPostResult" |&self| {
field forum_post() -> Option<&ForumPost> {
#[juniper::impl_object(
name = "ForumPostResult",
)]
impl MutationResult<ForumPost> {
fn forum_post(&self) -> Option<&ForumPost> {
self.0.as_ref().ok()
}
field error() -> Option<&Vec<ValidationError>> {
fn error(&self) -> Option<&Vec<ValidationError>> {
self.0.as_ref().err()
}
});
}
# fn main() {}
```

View file

@ -20,7 +20,7 @@ naturally map to GraphQL features, such as `Option<T>`, `Vec<T>`, `Box<T>`,
For more advanced mappings, Juniper provides multiple macros to map your Rust
types to a GraphQL schema. The most important one is the
[graphql_object!][jp_obj_macro] macro that is used for declaring an object with
[impl_object][jp_impl_object] procedural macro that is used for declaring an object with
resolvers, which you will use for the `Query` and `Mutation` roots.
```rust
@ -60,7 +60,7 @@ struct NewHuman {
}
// Now, we create our root Query and Mutation types with resolvers by using the
// graphql_object! macro.
// impl_object macro.
// Objects can have contexts that allow accessing shared state like a database
// pool.
@ -74,17 +74,23 @@ impl juniper::Context for Context {}
struct Query;
juniper::graphql_object!(Query: Context |&self| {
#[juniper::impl_object(
// Here we specify the context type for the object.
// We need to do this in every type that
// needs access to the context.
Context = Context,
)]
impl Query {
field apiVersion() -> &str {
fn apiVersion() -> &str {
"1.0"
}
// Arguments to resolvers can either be simple types or input objects.
// The executor is a special (optional) argument that allows accessing the context.
field human(&executor, id: String) -> FieldResult<Human> {
// Get the context from the executor.
let context = executor.context();
// To gain access to the context, we specify a argument
// that is a reference to the Context type.
// Juniper automatically injects the correct context here.
fn human(context: &Context, id: String) -> FieldResult<Human> {
// Get a db connection.
let connection = context.pool.get_connection()?;
// Execute a db query.
@ -93,18 +99,23 @@ juniper::graphql_object!(Query: Context |&self| {
// Return the result.
Ok(human)
}
});
}
// Now, we do the same for our Mutation type.
struct Mutation;
juniper::graphql_object!(Mutation: Context |&self| {
#[juniper::impl_object(
Context = Context,
)]
impl Mutation {
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human> {
let db = executor.context().pool.get_connection()?;
let human: Human = db.insert_human(&new_human)?;
Ok(human)
}
});
}
// A root schema consists of a query and a mutation.
// Request queries can be executed against a RootNode.
@ -130,6 +141,7 @@ You can invoke `juniper::execute` directly to run a GraphQL query:
# #[macro_use] extern crate juniper;
use juniper::{FieldResult, Variables, EmptyMutation};
#[derive(juniper::GraphQLEnum, Clone, Copy)]
enum Episode {
NewHope,
@ -137,18 +149,23 @@ enum Episode {
Jedi,
}
struct Query;
juniper::graphql_object!(Query: Ctx |&self| {
field favoriteEpisode(&executor) -> FieldResult<Episode> {
// Use the special &executor argument to fetch our fav episode.
Ok(executor.context().0)
}
});
// Arbitrary context data.
struct Ctx(Episode);
impl juniper::Context for Ctx {}
struct Query;
#[juniper::impl_object(
Context = Ctx,
)]
impl Query {
fn favoriteEpisode(context: &Ctx) -> FieldResult<Episode> {
Ok(context.0)
}
}
// A root schema consists of a query and a mutation.
// Request queries can be executed against a RootNode.
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
@ -181,4 +198,4 @@ fn main() {
[rocket]: servers/rocket.md
[iron]: servers/iron.md
[tutorial]: ./tutorial.html
[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.graphql_object.html
[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.impl_object.html

View file

@ -20,19 +20,20 @@ object somewhere but never references it, it will not be exposed in a schema.
## The query root
The query root is just a GraphQL object. You define it like any other GraphQL
object in Juniper, most commonly using the `graphql_object!` macro:
object in Juniper, most commonly using the `impl_object` proc macro:
```rust
# use juniper::FieldResult;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
struct Root;
juniper::graphql_object!(Root: () |&self| {
field userWithUsername(username: String) -> FieldResult<Option<User>> {
#[juniper::impl_object]
impl Root {
fn userWithUsername(username: String) -> FieldResult<Option<User>> {
// Look up user in database...
# unimplemented!()
}
});
}
# fn main() { }
```
@ -47,12 +48,13 @@ usually performs some mutating side-effect, such as updating a database.
# #[derive(juniper::GraphQLObject)] struct User { name: String }
struct Mutations;
juniper::graphql_object!(Mutations: () |&self| {
field signUpUser(name: String, email: String) -> FieldResult<User> {
#[juniper::impl_object]
impl Mutations {
fn signUpUser(name: String, email: String) -> FieldResult<User> {
// Validate inputs and save user in database...
# unimplemented!()
}
});
}
# fn main() { }
```

View file

@ -47,11 +47,12 @@ fn context_factory(_: &mut Request) -> IronResult<()> {
struct Root;
juniper::graphql_object!(Root: () |&self| {
field foo() -> String {
#[juniper::impl_object]
impl Root {
fn foo() -> String {
"Bar".to_owned()
}
});
}
# #[allow(unreachable_code, unused_variables)]
fn main() {
@ -98,13 +99,14 @@ fn context_factory(req: &mut Request) -> IronResult<Context> {
struct Root;
juniper::graphql_object!(Root: Context |&self| {
field my_addr(&executor) -> String {
let context = executor.context();
#[juniper::impl_object(
Context = Context,
)]
impl Root {
field my_addr(context: &Context) -> String {
format!("Hello, you're coming from {}", context.remote_addr)
}
});
}
# fn main() {
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
@ -115,10 +117,6 @@ juniper::graphql_object!(Root: Context |&self| {
# }
```
## Accessing global data
FIXME: Show how the `persistent` crate works with contexts using e.g. `r2d2`.
[iron]: http://ironframework.io
[graphiql]: https://github.com/graphql/graphiql
[mount]: https://github.com/iron/mount

View file

@ -14,12 +14,14 @@ struct Coordinate {
struct Root;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
juniper::graphql_object!(Root: () |&self| {
field users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
#[juniper::impl_object]
impl Root {
fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
// Send coordinate to database
// ...
# unimplemented!()
}
});
}
# fn main() {}
```
@ -43,12 +45,14 @@ struct WorldCoordinate {
struct Root;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
juniper::graphql_object!(Root: () |&self| {
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
#[juniper::impl_object]
impl Root {
fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
// Send coordinate to database
// ...
# unimplemented!()
}
});
}
# fn main() {}
```

View file

@ -49,7 +49,7 @@ impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}
juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S>|&self| {
juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
field id() -> &str { self.id() }
instance_resolvers: |_| {
@ -79,14 +79,14 @@ we'll use two hashmaps, but this could be two tables and some SQL calls instead:
```rust
# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,

View file

@ -2,8 +2,8 @@
If you've got a struct that can't be mapped directly to GraphQL, that contains
computed fields or circular structures, you have to use a more powerful tool:
the `graphql_object!` macro. This macro lets you define GraphQL objects similar
to how you define methods in a Rust `impl` block for a type. Continuing with the
the `impl_object` procedural macro. This macro lets you define GraphQL object
fields in a Rust `impl` block for a type. Continuing with the
example from the last chapter, this is how you would define `Person` using the
macro:
@ -14,15 +14,16 @@ struct Person {
age: i32,
}
juniper::graphql_object!(Person: () |&self| {
field name() -> &str {
#[juniper::impl_object]
impl Person {
fn name(&self) -> &str {
self.name.as_str()
}
field age() -> i32 {
fn age(&self) -> i32 {
self.age
}
});
}
# fn main() { }
```
@ -42,12 +43,13 @@ struct House {
inhabitants: Vec<Person>,
}
juniper::graphql_object!(House: () |&self| {
#[juniper::impl_object]
impl House {
// Creates the field inhabitantWithName(name), returning a nullable person
field inhabitant_with_name(name: String) -> Option<&Person> {
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
self.inhabitants.iter().find(|p| p.name == name)
}
});
}
# fn main() {}
```
@ -68,15 +70,19 @@ struct Person {
website_url: String,
}
juniper::graphql_object!(Person: () as "PersonObject" |&self| {
field name() -> &str {
#[juniper::impl_object(
// With this attribtue you can change the public GraphQL name of the type.
name = "PersonObject",
)]
impl Person {
fn name(&self) -> &str {
self.name.as_str()
}
field websiteURL() -> &str {
fn websiteURL(&self) -> &str {
self.website_url.as_str()
}
});
}
# fn main() { }
```
@ -90,4 +96,4 @@ GraphQL fields expose more features than Rust's standard method syntax gives us:
* Per-argument descriptions
These, and more features, are described more thorougly in [the reference
documentation](https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html).
documentation](https://docs.rs/juniper/latest/juniper/macro.impl_object.html).

View file

@ -154,7 +154,7 @@ attribute:
struct Person {
name: String,
age: i32,
#[graphql(deprecation="Please use the name field instead")]
#[graphql(deprecated = "Please use the name field instead")]
first_name: String,
}

View file

@ -25,14 +25,16 @@ struct Example {
filename: PathBuf,
}
juniper::graphql_object!(Example: () |&self| {
field contents() -> FieldResult<String> {
#[juniper::impl_object]
impl Example {
fn contents() -> FieldResult<String> {
let mut file = File::open(&self.filename)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
field foo() -> FieldResult<Option<String>> {
fn foo() -> FieldResult<Option<String>> {
// Some invalid bytes.
let invalid = vec![128, 223];
@ -41,7 +43,7 @@ juniper::graphql_object!(Example: () |&self| {
Err(e) => Err(e)?,
}
}
});
}
# fn main() {}
```
@ -141,14 +143,15 @@ struct Example {
whatever: Option<bool>,
}
juniper::graphql_object!(Example: () |&self| {
field whatever() -> Result<bool, CustomError> {
#[juniper::impl_object]
impl Example {
fn whatever() -> Result<bool, CustomError> {
if let Some(value) = self.whatever {
return Ok(value);
}
Err(CustomError::WhateverNotSet)
}
});
}
# fn main() {}
```

View file

@ -31,42 +31,57 @@ We would like a `friends` field on `User` that returns a list of `User` objects.
In order to write such a field though, the database must be queried.
To solve this, we mark the `Database` as a valid context type and assign it to
the user object. Then, we use the special `&executor` argument to access the
current context object:
the user object.
To gain access to the context, we need to specify an argument with the same
type as the specified `Context` for the type:
```rust
# use std::collections::HashMap;
extern crate juniper;
// This struct represents our context.
struct Database {
users: HashMap<i32, User>,
}
// Mark the Database as a valid context type for Juniper
impl juniper::Context for Database {}
struct User {
id: i32,
name: String,
friend_ids: Vec<i32>,
}
// 1. Mark the Database as a valid context type for Juniper
impl juniper::Context for Database {}
// 2. Assign Database as the context type for User
juniper::graphql_object!(User: Database |&self| {
// 3. Use the special executor argument
field friends(&executor) -> Vec<&User> {
// 4. Use the executor to access the context object
let database = executor.context();
// Assign Database as the context type for User
#[juniper::impl_object(
Context = Database,
)]
impl User {
// 3. Inject the context by specifying an argument
// with the context type.
// Note:
// - the type must be a reference
// - the name of the argument SHOULD be context
fn friends(&self, context: &Database) -> Vec<&User> {
// 5. Use the database to lookup users
self.friend_ids.iter()
.map(|id| database.users.get(id).expect("Could not find user with ID"))
.map(|id| context.users.get(id).expect("Could not find user with ID"))
.collect()
}
field name() -> &str { self.name.as_str() }
field id() -> i32 { self.id }
});
fn name(&self) -> &str {
self.name.as_str()
}
fn id(&self) -> i32 {
self.id
}
}
# fn main() { }
```

View file

@ -42,7 +42,7 @@ impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}
juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
instance_resolvers: |_| {
// The left hand side indicates the concrete type T, the right hand
// side should be an expression returning Option<T>
@ -61,14 +61,14 @@ FIXME: This example does not compile at the moment
```rust
# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,
@ -108,14 +108,14 @@ juniper::graphql_union!(<'a> &'a Character: Database as "Character" where Scalar
```rust
# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,

View file

@ -6,13 +6,14 @@ edition = "2018"
build = "build.rs"
[dependencies]
juniper = { version = "0.11", path = "../../../juniper" }
juniper_iron = { version = "0.3", path = "../../../juniper_iron" }
juniper = { path = "../../../juniper" }
juniper_iron = { path = "../../../juniper_iron" }
iron = "^0.5.0"
mount = "^0.3.0"
skeptic = "0.13"
serde_json = "1.0.39"
[build-dependencies]
skeptic = "0.13"