Skip to content

Graphql object proc macro #333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ rust:
- beta
- nightly

# TODO: re-enable once new versions are released.
# Prevent accidentally breaking older Rust versions
- 1.32.0
- 1.31.0
# - 1.33.0

matrix:
include:
Expand Down
9 changes: 5 additions & 4 deletions _build/azure-pipelines-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ jobs:
rustup_toolchain: beta
nightly:
rustup_toolchain: nightly
minimum_supported_version_plus_one:
rustup_toolchain: 1.32.0
minimum_supported_version:
rustup_toolchain: 1.31.0
# TODO: re-enable once new versions are released.
# minimum_supported_version_plus_one:
# rustup_toolchain: 1.32.0
#minimum_supported_version:
# rustup_toolchain: 1.33.0
steps:
- ${{ if ne(parameters.name, 'Windows') }}:
# Linux and macOS.
Expand Down
12 changes: 6 additions & 6 deletions docs/book/content/advanced/introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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::object(
Context = Context,
)]
impl Query {
fn example(id: String) -> FieldResult<Example> {
unimplemented!()
}
});
}

type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;

Expand Down
9 changes: 5 additions & 4 deletions docs/book/content/advanced/non_struct_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,22 @@ enum SignUpResult {
Error(Vec<ValidationError>),
}

juniper::graphql_object!(SignUpResult: () |&self| {
field user() -> Option<&User> {
#[juniper::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() {}
```
Expand Down
22 changes: 14 additions & 8 deletions docs/book/content/advanced/objects_and_generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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::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::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() {}
```
Expand Down
57 changes: 37 additions & 20 deletions docs/book/content/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
[object][jp_object] procedural macro that is used for declaring an object with
resolvers, which you will use for the `Query` and `Mutation` roots.

```rust
Expand Down Expand Up @@ -60,7 +60,7 @@ struct NewHuman {
}

// Now, we create our root Query and Mutation types with resolvers by using the
// graphql_object! macro.
// object macro.
// Objects can have contexts that allow accessing shared state like a database
// pool.

Expand All @@ -74,17 +74,23 @@ impl juniper::Context for Context {}

struct Query;

juniper::graphql_object!(Query: Context |&self| {
#[juniper::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.
Expand All @@ -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::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.
Expand All @@ -130,24 +141,30 @@ 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,
Empire,
Jedi,
}

// Arbitrary context data.
struct Ctx(Episode);

impl juniper::Context for Ctx {}

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)
#[juniper::object(
Context = Ctx,
)]
impl Query {
fn favoriteEpisode(context: &Ctx) -> FieldResult<Episode> {
Ok(context.0)
}
});
}

// Arbitrary context data.
struct Ctx(Episode);

// A root schema consists of a query and a mutation.
// Request queries can be executed against a RootNode.
Expand Down Expand Up @@ -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.object.html
16 changes: 9 additions & 7 deletions docs/book/content/schema/schemas_and_mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `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::object]
impl Root {
fn userWithUsername(username: String) -> FieldResult<Option<User>> {
// Look up user in database...
# unimplemented!()
}
});
}

# fn main() { }
```
Expand All @@ -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::object]
impl Mutations {
fn signUpUser(name: String, email: String) -> FieldResult<User> {
// Validate inputs and save user in database...
# unimplemented!()
}
});
}

# fn main() { }
```
22 changes: 10 additions & 12 deletions docs/book/content/servers/iron.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ fn context_factory(_: &mut Request) -> IronResult<()> {

struct Root;

juniper::graphql_object!(Root: () |&self| {
field foo() -> String {
#[juniper::object]
impl Root {
fn foo() -> String {
"Bar".to_owned()
}
});
}

# #[allow(unreachable_code, unused_variables)]
fn main() {
Expand Down Expand Up @@ -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::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(
Expand All @@ -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
16 changes: 10 additions & 6 deletions docs/book/content/types/input_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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::object]
impl Root {
fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
// Send coordinate to database
// ...
# unimplemented!()
}
});
}

# fn main() {}
```
Expand All @@ -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::object]
impl Root {
fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
// Send coordinate to database
// ...
# unimplemented!()
}
});
}

# fn main() {}
```
6 changes: 3 additions & 3 deletions docs/book/content/types/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: |_| {
Expand Down Expand Up @@ -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,
Expand Down
Loading