Skip to content

Rework codegen for GraphQL objects #971

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 40 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1790b14
Bootstrap
tyranron Jul 22, 2021
f845c8e
Impl barebone for objects
tyranron Jul 22, 2021
a7fa5d9
Impl barebone for objects, vol.2
tyranron Jul 23, 2021
81fb09f
Merge branch 'master' into rework-graphql-object-codegen
tyranron Jul 26, 2021
25cedde
Impl barebone for objects, vol.3
tyranron Jul 26, 2021
0f23ea5
Impl barebone for objects, vol.4
tyranron Jul 27, 2021
8c6c556
Impl barebone for objects, vol.5
tyranron Jul 27, 2021
2fdaedd
Impl #[derive(GraphQLObject)], break #[graphql_interface]
tyranron Jul 27, 2021
962d8e6
Fix #[derive(GraphQLObject)] and #[graphql_interface]
tyranron Jul 28, 2021
d0a8c84
Impl #[graphql_object], vol.1
tyranron Jul 28, 2021
0ea37a3
Impl #[graphql_object], vol.2
tyranron Jul 28, 2021
2943379
Impl #[graphql_object], vol.3
tyranron Jul 28, 2021
4cf8d1b
Impl #[graphql_object], vol.4
tyranron Jul 29, 2021
d385ca7
Improve #[graphql_interface] with bounded scalar
tyranron Jul 29, 2021
c67f3ef
Refactor integration tests of issues
tyranron Jul 29, 2021
f5097ac
Refactor new macros code to be consistent
tyranron Jul 29, 2021
9afeb5d
Cover exotic scalars in integration tests for unions
tyranron Jul 29, 2021
727b5da
Impl subscriptions, vol.1
tyranron Aug 2, 2021
4b664d2
Impl subscriptions, vol.2
tyranron Aug 2, 2021
72c00a7
Impl subscriptions, vol.3
tyranron Aug 2, 2021
4156d23
Fix codegen failure tests
tyranron Aug 2, 2021
051c356
Fix book, vol.1
tyranron Aug 2, 2021
95ad9bd
Fix tests
tyranron Aug 2, 2021
4c026e8
Fix type params handling
tyranron Aug 2, 2021
210ed93
Integration tests for objects, vol.1
tyranron Aug 4, 2021
26c1fa9
Integration tests for objects, vol.2
tyranron Aug 5, 2021
1e9d1a7
Integration tests for objects, vol.3
tyranron Aug 6, 2021
5ac943d
Integration tests for objects, vol.4
tyranron Aug 6, 2021
2eede12
Integration tests for objects, vol.5
tyranron Aug 6, 2021
908b388
Integration tests for objects, vol.6
tyranron Aug 9, 2021
0564666
Integration tests for subscriptions, vol.1
tyranron Aug 9, 2021
7b78efd
Integration tests for subscriptions, vol.2
tyranron Aug 10, 2021
510f6ac
Codegen failure tests for objects
tyranron Aug 10, 2021
136b1a9
Codegen failure tests for subscriptions
tyranron Aug 10, 2021
d870ad6
Renew docs, vol.1
tyranron Aug 10, 2021
1fe0de0
Renew docs, vol.2
tyranron Aug 11, 2021
5287c48
Renew docs, vol.3
tyranron Aug 11, 2021
0ea9d27
Renew docs, vol.4
tyranron Aug 11, 2021
d7bff0a
Renew book docs, vol.5
tyranron Aug 11, 2021
9d93eb7
Upd CHANGELOG
tyranron Aug 11, 2021
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
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