Skip to content

Commit

Permalink
Rework codegen for GraphQL objects and subscriptions (#971, #421)
Browse files Browse the repository at this point in the history
- preserve and reuse defined impl blocks in #[graphql_object] and #[graphql_subscription] macros expansion
- allow renaming `ScalarValue` type parameter in expanded code via `scalar = S: ScalarValue` syntax

Additionally:
- rename `rename` attribute's argument to `rename_all`
- support `rename_all` in #[graphql_interface] macro
  • Loading branch information
tyranron authored Aug 11, 2021
1 parent 39d1e43 commit a3fda73
Show file tree
Hide file tree
Showing 165 changed files with 11,684 additions and 9,101 deletions.
3 changes: 2 additions & 1 deletion docs/book/content/advanced/implicit_and_explicit_null.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ impl Into<UserPatch> for UserPatchInput {
struct Context {
session: Session,
}
impl juniper::Context for Context {}

struct Mutation;

#[juniper::graphql_object(Context=Context)]
#[juniper::graphql_object(context = Context)]
impl Mutation {
fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> {
ctx.session.patch_user(patch.into())?;
Expand Down
8 changes: 2 additions & 6 deletions docs/book/content/advanced/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ This example shows a subscription operation that returns two events, the strings
sequentially:

```rust
# extern crate futures;
# extern crate juniper;
# extern crate juniper_subscriptions;
# extern crate tokio;
# use juniper::{graphql_object, graphql_subscription, FieldError};
# use futures::Stream;
# use std::pin::Pin;
Expand All @@ -40,7 +36,7 @@ sequentially:
# pub struct Query;
# #[graphql_object(context = Database)]
# impl Query {
# fn hello_world() -> &str {
# fn hello_world() -> &'static str {
# "Hello World!"
# }
# }
Expand Down Expand Up @@ -110,7 +106,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
#
# #[graphql_object(context = Database)]
# impl Query {
# fn hello_world() -> &str {
# fn hello_world() -> &'static str {
# "Hello World!"
# }
# }
Expand Down
15 changes: 6 additions & 9 deletions docs/book/content/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch

## Installation

!FILENAME Cargo.toml

```toml
[dependencies]
juniper = { git = "https://github.com/graphql-rust/juniper" }
juniper = "0.15"
```

## Schema example
Expand Down Expand Up @@ -89,7 +87,7 @@ struct Query;
context = Context,
)]
impl Query {
fn apiVersion() -> &str {
fn apiVersion() -> &'static str {
"1.0"
}

Expand All @@ -114,14 +112,13 @@ struct Mutation;

#[graphql_object(
context = Context,

// If we need to use `ScalarValue` parametrization explicitly somewhere
// in the object definition (like here in `FieldResult`), we should
// in the object definition (like here in `FieldResult`), we could
// declare an explicit type parameter for that, and specify it.
scalar = S,
scalar = S: ScalarValue + Display,
)]
impl<S: ScalarValue + Display> Mutation {
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
impl Mutation {
fn createHuman<S: ScalarValue + Display>(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
let db = context.pool.get_connection().map_err(|e| e.map_scalar_value())?;
let human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?;
Ok(human)
Expand Down
15 changes: 15 additions & 0 deletions docs/book/content/types/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,21 @@ trait Character {
# fn main() {}
```

Renaming policies for all [GraphQL interface][1] fields and arguments are supported as well:
```rust
# #![allow(deprecated)]
# extern crate juniper;
use juniper::graphql_interface;

#[graphql_interface(rename_all = "none")] // disables any renaming
trait Character {
// Now exposed as `my_id` and `my_num` in the schema
fn my_id(&self, my_num: i32) -> &str;
}
#
# fn main() {}
```


### Custom context

Expand Down
94 changes: 61 additions & 33 deletions docs/book/content/types/objects/complex_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
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]` procedural macro. This macro lets you define GraphQL object
fields in a Rust `impl` block for a type. Note that only GraphQL fields
can be specified in this `impl` block. If you want to define normal methods on the struct,
you have to do so in a separate, normal `impl` block. Continuing with the
fields in a Rust `impl` block for a type. Note, that GraphQL fields are defined in
this `impl` block by default. If you want to define normal methods on the struct,
you have to do so either in a separate "normal" `impl` block, or mark them with
`#[graphql(ignore)]` attribute to be omitted by the macro. Continuing with the
example from the last chapter, this is how you would define `Person` using the
macro:

Expand All @@ -28,12 +29,15 @@ impl Person {
fn age(&self) -> i32 {
self.age
}

#[graphql(ignore)]
pub fn hidden_from_graphql(&self) {
// [...]
}
}

// Note that this syntax generates an implementation of the GraphQLType trait,
// the base impl of your struct can still be written like usual:
impl Person {
pub fn hidden_from_graphql(&self) {
pub fn hidden_from_graphql2(&self) {
// [...]
}
}
Expand All @@ -44,7 +48,6 @@ impl Person {
While this is a bit more verbose, it lets you write any kind of function in the
field resolver. With this syntax, fields can also take arguments:


```rust
# extern crate juniper;
# use juniper::{graphql_object, GraphQLObject};
Expand All @@ -61,7 +64,7 @@ struct House {

#[graphql_object]
impl House {
// Creates the field inhabitantWithName(name), returning a nullable person
// Creates the field `inhabitantWithName(name)`, returning a nullable `Person`.
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
self.inhabitants.iter().find(|p| p.name == name)
}
Expand Down Expand Up @@ -127,15 +130,29 @@ impl Person {
# fn main() { }
```

Or provide a different renaming policy on a `impl` block for all its fields:
```rust
# extern crate juniper;
# use juniper::graphql_object;
struct Person;

#[graphql_object(rename_all = "none")] // disables any renaming
impl Person {
// Now exposed as `renamed_field` in the schema
fn renamed_field() -> bool {
true
}
}
#
# fn main() {}
```

## Customizing arguments

Method field arguments can also be customized.

They can have custom descriptions and default values.

**Note**: The syntax for this is currently a little awkward.
This will become better once the [Rust RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented.

```rust
# extern crate juniper;
# use juniper::graphql_object;
Expand All @@ -144,35 +161,46 @@ struct Person {}

#[graphql_object]
impl Person {
#[graphql(
arguments(
arg1(
// Set a default value which will be injected if not present.
// The default can be any valid Rust expression, including a function call, etc.
default = true,
// Set a description.
description = "The first argument..."
),
arg2(
default = 0,
)
)
)]
fn field1(&self, arg1: bool, arg2: i32) -> String {
fn field1(
&self,
#[graphql(
// Arguments can also be renamed if required.
name = "arg",
// Set a default value which will be injected if not present.
// The default can be any valid Rust expression, including a function call, etc.
default = true,
// Set a description.
description = "The first argument..."
)]
arg1: bool,
// If default expression is not specified then `Default::default()` value is used.
#[graphql(default)]
arg2: i32,
) -> String {
format!("{} {}", arg1, arg2)
}
}
#
# fn main() { }
```

## More features
Provide a different renaming policy on a `impl` block also implies for arguments:
```rust
# extern crate juniper;
# use juniper::graphql_object;
struct Person;

GraphQL fields expose more features than Rust's standard method syntax gives us:
#[graphql_object(rename_all = "none")] // disables any renaming
impl Person {
// Now exposed as `my_arg` in the schema
fn field(my_arg: bool) -> bool {
my_arg
}
}
#
# fn main() {}
```

* Per-field description and deprecation messages
* Per-argument default values
* Per-argument descriptions
## More features

These, and more features, are described more thoroughly in [the reference
documentation](https://docs.rs/juniper/latest/juniper/macro.object.html).
These, and more features, are described more thoroughly in [the reference documentation](https://docs.rs/juniper/latest/juniper/attr.graphql_object.html).
25 changes: 20 additions & 5 deletions docs/book/content/types/objects/defining_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,22 @@ struct Person {
name: String,
age: i32,
#[graphql(name = "websiteURL")]
website_url: Option<String>, // Now exposed as websiteURL in the schema
website_url: Option<String>, // now exposed as `websiteURL` in the schema
}
#
# fn main() {}
```

Or provide a different renaming policy on a struct for all its fields:
```rust
# extern crate juniper;
# use juniper::GraphQLObject;
#[derive(GraphQLObject)]
#[graphql(rename_all = "none")] // disables any renaming
struct Person {
name: String,
age: i32,
website_url: Option<String>, // now exposed as `website_url` in the schema
}
#
# fn main() {}
Expand Down Expand Up @@ -181,9 +196,9 @@ The `name`, `description`, and `deprecation` arguments can of course be
combined. Some restrictions from the GraphQL spec still applies though; you can
only deprecate object fields and enum values.

## Skipping fields
## Ignoring fields

By default all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(skip)]`:
By default, all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(ignore)]`:

```rust
# extern crate juniper;
Expand All @@ -192,9 +207,9 @@ By default all fields in a `GraphQLObject` are included in the generated GraphQL
struct Person {
name: String,
age: i32,
#[graphql(skip)]
#[graphql(ignore)]
# #[allow(dead_code)]
password_hash: String, // This cannot be queried or modified from GraphQL
password_hash: String, // cannot be queried or modified from GraphQL
}
#
# fn main() {}
Expand Down
31 changes: 13 additions & 18 deletions docs/book/content/types/objects/error_handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ it will bubble up to the surrounding framework and hopefully be dealt with
there.

For recoverable errors, Juniper works well with the built-in `Result` type, you
can use the `?` operator or the `try!` macro and things will generally just work
as you expect them to:
can use the `?` operator and things will generally just work as you expect them to:

```rust
# extern crate juniper;
Expand All @@ -36,21 +35,18 @@ struct Example {

#[graphql_object]
impl Example {
fn contents() -> FieldResult<String> {
fn contents(&self) -> FieldResult<String> {
let mut file = File::open(&self.filename)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}

fn foo() -> FieldResult<Option<String>> {
// Some invalid bytes.
let invalid = vec![128, 223];
// Some invalid bytes.
let invalid = vec![128, 223];

match str::from_utf8(&invalid) {
Ok(s) => Ok(Some(s.to_string())),
Err(e) => Err(e)?,
}
Ok(Some(str::from_utf8(&invalid)?.to_string()))
}
}
#
Expand All @@ -66,7 +62,6 @@ there - those errors are automatically converted into `FieldError`.

Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability).


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. For example, with the previous example and the following
Expand All @@ -86,12 +81,12 @@ returned:

!FILENAME Response for nullable field with error

```js
```json
{
"data": {
"example": {
contents: "<Contents of the file>",
foo: null,
foo: null
}
},
"errors": [
Expand Down Expand Up @@ -120,7 +115,7 @@ following would be returned:

!FILENAME Response for non-null field with error and no nullable parent

```js
```json
{
"errors": [
"message": "Permission denied (os error 13)",
Expand Down Expand Up @@ -162,11 +157,11 @@ struct Example {

#[graphql_object]
impl Example {
fn whatever() -> Result<bool, CustomError> {
if let Some(value) = self.whatever {
return Ok(value);
}
Err(CustomError::WhateverNotSet)
fn whatever(&self) -> Result<bool, CustomError> {
if let Some(value) = self.whatever {
return Ok(value);
}
Err(CustomError::WhateverNotSet)
}
}
#
Expand Down
Loading

0 comments on commit a3fda73

Please sign in to comment.