Skip to content

Commit a3fda73

Browse files
authored
Rework codegen for GraphQL objects and subscriptions (#971, #421)
- 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
1 parent 39d1e43 commit a3fda73

File tree

165 files changed

+11684
-9101
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

165 files changed

+11684
-9101
lines changed

docs/book/content/advanced/implicit_and_explicit_null.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ impl Into<UserPatch> for UserPatchInput {
9999
struct Context {
100100
session: Session,
101101
}
102+
impl juniper::Context for Context {}
102103

103104
struct Mutation;
104105

105-
#[juniper::graphql_object(Context=Context)]
106+
#[juniper::graphql_object(context = Context)]
106107
impl Mutation {
107108
fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> {
108109
ctx.session.patch_user(patch.into())?;

docs/book/content/advanced/subscriptions.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ This example shows a subscription operation that returns two events, the strings
2525
sequentially:
2626

2727
```rust
28-
# extern crate futures;
29-
# extern crate juniper;
30-
# extern crate juniper_subscriptions;
31-
# extern crate tokio;
3228
# use juniper::{graphql_object, graphql_subscription, FieldError};
3329
# use futures::Stream;
3430
# use std::pin::Pin;
@@ -40,7 +36,7 @@ sequentially:
4036
# pub struct Query;
4137
# #[graphql_object(context = Database)]
4238
# impl Query {
43-
# fn hello_world() -> &str {
39+
# fn hello_world() -> &'static str {
4440
# "Hello World!"
4541
# }
4642
# }
@@ -110,7 +106,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
110106
#
111107
# #[graphql_object(context = Database)]
112108
# impl Query {
113-
# fn hello_world() -> &str {
109+
# fn hello_world() -> &'static str {
114110
# "Hello World!"
115111
# }
116112
# }

docs/book/content/quickstart.md

+6-9
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch
66

77
## Installation
88

9-
!FILENAME Cargo.toml
10-
119
```toml
1210
[dependencies]
13-
juniper = { git = "https://github.com/graphql-rust/juniper" }
11+
juniper = "0.15"
1412
```
1513

1614
## Schema example
@@ -89,7 +87,7 @@ struct Query;
8987
context = Context,
9088
)]
9189
impl Query {
92-
fn apiVersion() -> &str {
90+
fn apiVersion() -> &'static str {
9391
"1.0"
9492
}
9593

@@ -114,14 +112,13 @@ struct Mutation;
114112

115113
#[graphql_object(
116114
context = Context,
117-
118115
// If we need to use `ScalarValue` parametrization explicitly somewhere
119-
// in the object definition (like here in `FieldResult`), we should
116+
// in the object definition (like here in `FieldResult`), we could
120117
// declare an explicit type parameter for that, and specify it.
121-
scalar = S,
118+
scalar = S: ScalarValue + Display,
122119
)]
123-
impl<S: ScalarValue + Display> Mutation {
124-
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
120+
impl Mutation {
121+
fn createHuman<S: ScalarValue + Display>(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
125122
let db = context.pool.get_connection().map_err(|e| e.map_scalar_value())?;
126123
let human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?;
127124
Ok(human)

docs/book/content/types/interfaces.md

+15
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,21 @@ trait Character {
232232
# fn main() {}
233233
```
234234

235+
Renaming policies for all [GraphQL interface][1] fields and arguments are supported as well:
236+
```rust
237+
# #![allow(deprecated)]
238+
# extern crate juniper;
239+
use juniper::graphql_interface;
240+
241+
#[graphql_interface(rename_all = "none")] // disables any renaming
242+
trait Character {
243+
// Now exposed as `my_id` and `my_num` in the schema
244+
fn my_id(&self, my_num: i32) -> &str;
245+
}
246+
#
247+
# fn main() {}
248+
```
249+
235250

236251
### Custom context
237252

docs/book/content/types/objects/complex_fields.md

+61-33
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
If you've got a struct that can't be mapped directly to GraphQL, that contains
44
computed fields or circular structures, you have to use a more powerful tool:
55
the `#[graphql_object]` procedural macro. This macro lets you define GraphQL object
6-
fields in a Rust `impl` block for a type. Note that only GraphQL fields
7-
can be specified in this `impl` block. If you want to define normal methods on the struct,
8-
you have to do so in a separate, normal `impl` block. Continuing with the
6+
fields in a Rust `impl` block for a type. Note, that GraphQL fields are defined in
7+
this `impl` block by default. If you want to define normal methods on the struct,
8+
you have to do so either in a separate "normal" `impl` block, or mark them with
9+
`#[graphql(ignore)]` attribute to be omitted by the macro. Continuing with the
910
example from the last chapter, this is how you would define `Person` using the
1011
macro:
1112

@@ -28,12 +29,15 @@ impl Person {
2829
fn age(&self) -> i32 {
2930
self.age
3031
}
32+
33+
#[graphql(ignore)]
34+
pub fn hidden_from_graphql(&self) {
35+
// [...]
36+
}
3137
}
3238

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

47-
4851
```rust
4952
# extern crate juniper;
5053
# use juniper::{graphql_object, GraphQLObject};
@@ -61,7 +64,7 @@ struct House {
6164

6265
#[graphql_object]
6366
impl House {
64-
// Creates the field inhabitantWithName(name), returning a nullable person
67+
// Creates the field `inhabitantWithName(name)`, returning a nullable `Person`.
6568
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
6669
self.inhabitants.iter().find(|p| p.name == name)
6770
}
@@ -127,15 +130,29 @@ impl Person {
127130
# fn main() { }
128131
```
129132

133+
Or provide a different renaming policy on a `impl` block for all its fields:
134+
```rust
135+
# extern crate juniper;
136+
# use juniper::graphql_object;
137+
struct Person;
138+
139+
#[graphql_object(rename_all = "none")] // disables any renaming
140+
impl Person {
141+
// Now exposed as `renamed_field` in the schema
142+
fn renamed_field() -> bool {
143+
true
144+
}
145+
}
146+
#
147+
# fn main() {}
148+
```
149+
130150
## Customizing arguments
131151

132152
Method field arguments can also be customized.
133153

134154
They can have custom descriptions and default values.
135155

136-
**Note**: The syntax for this is currently a little awkward.
137-
This will become better once the [Rust RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented.
138-
139156
```rust
140157
# extern crate juniper;
141158
# use juniper::graphql_object;
@@ -144,35 +161,46 @@ struct Person {}
144161

145162
#[graphql_object]
146163
impl Person {
147-
#[graphql(
148-
arguments(
149-
arg1(
150-
// Set a default value which will be injected if not present.
151-
// The default can be any valid Rust expression, including a function call, etc.
152-
default = true,
153-
// Set a description.
154-
description = "The first argument..."
155-
),
156-
arg2(
157-
default = 0,
158-
)
159-
)
160-
)]
161-
fn field1(&self, arg1: bool, arg2: i32) -> String {
164+
fn field1(
165+
&self,
166+
#[graphql(
167+
// Arguments can also be renamed if required.
168+
name = "arg",
169+
// Set a default value which will be injected if not present.
170+
// The default can be any valid Rust expression, including a function call, etc.
171+
default = true,
172+
// Set a description.
173+
description = "The first argument..."
174+
)]
175+
arg1: bool,
176+
// If default expression is not specified then `Default::default()` value is used.
177+
#[graphql(default)]
178+
arg2: i32,
179+
) -> String {
162180
format!("{} {}", arg1, arg2)
163181
}
164182
}
165183
#
166184
# fn main() { }
167185
```
168186

169-
## More features
187+
Provide a different renaming policy on a `impl` block also implies for arguments:
188+
```rust
189+
# extern crate juniper;
190+
# use juniper::graphql_object;
191+
struct Person;
170192

171-
GraphQL fields expose more features than Rust's standard method syntax gives us:
193+
#[graphql_object(rename_all = "none")] // disables any renaming
194+
impl Person {
195+
// Now exposed as `my_arg` in the schema
196+
fn field(my_arg: bool) -> bool {
197+
my_arg
198+
}
199+
}
200+
#
201+
# fn main() {}
202+
```
172203

173-
* Per-field description and deprecation messages
174-
* Per-argument default values
175-
* Per-argument descriptions
204+
## More features
176205

177-
These, and more features, are described more thoroughly in [the reference
178-
documentation](https://docs.rs/juniper/latest/juniper/macro.object.html).
206+
These, and more features, are described more thoroughly in [the reference documentation](https://docs.rs/juniper/latest/juniper/attr.graphql_object.html).

docs/book/content/types/objects/defining_objects.md

+20-5
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,22 @@ struct Person {
152152
name: String,
153153
age: i32,
154154
#[graphql(name = "websiteURL")]
155-
website_url: Option<String>, // Now exposed as websiteURL in the schema
155+
website_url: Option<String>, // now exposed as `websiteURL` in the schema
156+
}
157+
#
158+
# fn main() {}
159+
```
160+
161+
Or provide a different renaming policy on a struct for all its fields:
162+
```rust
163+
# extern crate juniper;
164+
# use juniper::GraphQLObject;
165+
#[derive(GraphQLObject)]
166+
#[graphql(rename_all = "none")] // disables any renaming
167+
struct Person {
168+
name: String,
169+
age: i32,
170+
website_url: Option<String>, // now exposed as `website_url` in the schema
156171
}
157172
#
158173
# fn main() {}
@@ -181,9 +196,9 @@ The `name`, `description`, and `deprecation` arguments can of course be
181196
combined. Some restrictions from the GraphQL spec still applies though; you can
182197
only deprecate object fields and enum values.
183198

184-
## Skipping fields
199+
## Ignoring fields
185200

186-
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)]`:
201+
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)]`:
187202

188203
```rust
189204
# extern crate juniper;
@@ -192,9 +207,9 @@ By default all fields in a `GraphQLObject` are included in the generated GraphQL
192207
struct Person {
193208
name: String,
194209
age: i32,
195-
#[graphql(skip)]
210+
#[graphql(ignore)]
196211
# #[allow(dead_code)]
197-
password_hash: String, // This cannot be queried or modified from GraphQL
212+
password_hash: String, // cannot be queried or modified from GraphQL
198213
}
199214
#
200215
# fn main() {}

docs/book/content/types/objects/error_handling.md

+13-18
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ it will bubble up to the surrounding framework and hopefully be dealt with
1717
there.
1818

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

2322
```rust
2423
# extern crate juniper;
@@ -36,21 +35,18 @@ struct Example {
3635

3736
#[graphql_object]
3837
impl Example {
39-
fn contents() -> FieldResult<String> {
38+
fn contents(&self) -> FieldResult<String> {
4039
let mut file = File::open(&self.filename)?;
4140
let mut contents = String::new();
4241
file.read_to_string(&mut contents)?;
4342
Ok(contents)
4443
}
4544

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

50-
match str::from_utf8(&invalid) {
51-
Ok(s) => Ok(Some(s.to_string())),
52-
Err(e) => Err(e)?,
53-
}
49+
Ok(Some(str::from_utf8(&invalid)?.to_string()))
5450
}
5551
}
5652
#
@@ -66,7 +62,6 @@ there - those errors are automatically converted into `FieldError`.
6662

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

69-
7065
When a field returns an error, the field's result is replaced by `null`, an
7166
additional `errors` object is created at the top level of the response, and the
7267
execution is resumed. For example, with the previous example and the following
@@ -86,12 +81,12 @@ returned:
8681

8782
!FILENAME Response for nullable field with error
8883

89-
```js
84+
```json
9085
{
9186
"data": {
9287
"example": {
9388
contents: "<Contents of the file>",
94-
foo: null,
89+
foo: null
9590
}
9691
},
9792
"errors": [
@@ -120,7 +115,7 @@ following would be returned:
120115

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

123-
```js
118+
```json
124119
{
125120
"errors": [
126121
"message": "Permission denied (os error 13)",
@@ -162,11 +157,11 @@ struct Example {
162157

163158
#[graphql_object]
164159
impl Example {
165-
fn whatever() -> Result<bool, CustomError> {
166-
if let Some(value) = self.whatever {
167-
return Ok(value);
168-
}
169-
Err(CustomError::WhateverNotSet)
160+
fn whatever(&self) -> Result<bool, CustomError> {
161+
if let Some(value) = self.whatever {
162+
return Ok(value);
163+
}
164+
Err(CustomError::WhateverNotSet)
170165
}
171166
}
172167
#

0 commit comments

Comments
 (0)