Skip to content

Commit cbf16c5

Browse files
tyranronLegNeato
andauthored
Make interfaces great again! (#682)
* Bootstrap * Upd * Bootstrap macro * Revert stuff * Correct PoC to compile * Bootstrap #[graphql_interface] expansion * Bootstrap #[graphql_interface] meta parsing * Bootstrap #[graphql_interface] very basic code generation [skip ci] * Upd trait code generation and fix keywords usage [skip ci] * Expand trait impls [skip ci] * Tune up objects [skip ci] * Finally! Complies at least... [skip ci] * Parse meta for fields and its arguments [skip ci] - also, refactor and bikeshed new macros code * Impl filling fields meta and bootstrap field resolution [skip ci] * Poking with fields resolution [skip ci] * Solve Rust's teen async HRTB problems [skip ci] * Start parsing trait methods [skip ci] * Finish parsing fields from trait methods [skip ci] * Autodetect trait asyncness and allow to specify it [skip ci] * Allow to autogenerate trait object alias via attribute * Support generics in trait definition and asyncify them correctly * Temporary disable explicit async * Cover arguments and custom names/descriptions in tests * Re-enable tests with explicit async and fix the codegen to satisfy it * Check implementers are registered in schema and vice versa * Check argument camelCases * Test argument defaults, and allow Into coercions for them * Re-enable markers * Re-enable markers and relax Sized requirement on IsInputType/IsOutputType marker traits * Revert 'juniper_actix' fmt * Fix missing marks for object * Fix subscriptions marks * Deduce result type correctly via traits * Final fixes * Fmt * Restore marks checking * Support custom ScalarValue * Cover deprecations with tests * Impl dowcasting via methods * Impl dowcasting via external functions * Support custom context, vol. 1 * Support custom context, vol. 2 * Cover fallible field with test * Impl explicit generic ScalarValue, vol.1 * Impl explicit generic ScalarValue, vol.2 * Allow passing executor into methods * Generating enum, vol.1 * Generating enum, vol.2 * Generating enum, vol.3 * Generating enum, vol.3 * Generating enum, vol.4 * Generating enum, vol.5 * Generating enum, vol.6 * Generating enum, vol.7 * Generating enum, vol.8 * Refactor juniper stuff * Fix juniper tests, vol.1 * Fix juniper tests, vol.2 * Polish 'juniper' crate changes, vol.1 * Polish 'juniper' crate changes, vol.2 * Remove redundant stuf * Polishing 'juniper_codegen', vol.1 * Polishing 'juniper_codegen', vol.2 * Polishing 'juniper_codegen', vol.3 * Polishing 'juniper_codegen', vol.4 * Polishing 'juniper_codegen', vol.5 * Polishing 'juniper_codegen', vol.6 * Polishing 'juniper_codegen', vol.7 * Polishing 'juniper_codegen', vol.8 * Polishing 'juniper_codegen', vol.9 * Fix other crates tests and make Clippy happier * Fix examples * Add codegen failure tests, vol. 1 * Add codegen failure tests, vol. 2 * Add codegen failure tests, vol.3 * Fix codegen failure tests accordingly to latest nightly Rust * Fix codegen when interface has no implementers * Fix warnings in book tests * Describing new interfaces in Book, vol.1 Co-authored-by: Christian Legnitto <[email protected]>
1 parent 1e733cc commit cbf16c5

File tree

108 files changed

+10589
-2432
lines changed

Some content is hidden

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

108 files changed

+10589
-2432
lines changed

docs/book/content/advanced/introspection.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ result can then be converted to JSON for use with tools and libraries such as
3030
[graphql-client](https://github.com/graphql-rust/graphql-client):
3131

3232
```rust
33+
# #![allow(unused_variables)]
3334
# extern crate juniper;
3435
# extern crate serde_json;
3536
use juniper::{EmptyMutation, EmptySubscription, FieldResult, IntrospectionFormat};

docs/book/content/advanced/subscriptions.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ operation returns a [`Future`][Future] with an `Item` value of a `Result<Connect
7878
where [`Connection`][Connection] is a `Stream` of values returned by the operation and [`GraphQLError`][GraphQLError] is the error when the subscription fails.
7979

8080
```rust
81+
# #![allow(dead_code)]
8182
# extern crate futures;
8283
# extern crate juniper;
8384
# extern crate juniper_subscriptions;
@@ -88,8 +89,6 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
8889
# use juniper_subscriptions::Coordinator;
8990
# use futures::{Stream, StreamExt};
9091
# use std::pin::Pin;
91-
# use tokio::runtime::Runtime;
92-
# use tokio::task;
9392
#
9493
# #[derive(Clone)]
9594
# pub struct Database;

docs/book/content/quickstart.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ types to a GraphQL schema. The most important one is the
2626
resolvers, which you will use for the `Query` and `Mutation` roots.
2727

2828
```rust
29+
# #![allow(unused_variables)]
2930
# extern crate juniper;
3031
use juniper::{FieldResult, EmptySubscription};
3132

docs/book/content/schema/schemas_and_mutations.md

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The query root is just a GraphQL object. You define it like any other GraphQL
2222
object in Juniper, most commonly using the `graphql_object` proc macro:
2323

2424
```rust
25+
# #![allow(unused_variables)]
2526
# extern crate juniper;
2627
# use juniper::FieldResult;
2728
# #[derive(juniper::GraphQLObject)] struct User { name: String }
@@ -44,6 +45,7 @@ Mutations are _also_ just GraphQL objects. Each mutation is a single field
4445
that performs some mutating side-effect such as updating a database.
4546

4647
```rust
48+
# #![allow(unused_variables)]
4749
# extern crate juniper;
4850
# use juniper::FieldResult;
4951
# #[derive(juniper::GraphQLObject)] struct User { name: String }

docs/book/content/types/input_objects.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ GraphQL fields. In Juniper, you can define input objects using a custom derive
55
attribute, similar to simple objects and enums:
66

77
```rust
8+
# #![allow(unused_variables)]
89
# extern crate juniper;
910
#[derive(juniper::GraphQLInputObject)]
1011
struct Coordinate {
@@ -33,6 +34,7 @@ Just like the [other](objects/defining_objects.md) [derives](enums.md), you can
3334
and add documentation to both the type and the fields:
3435

3536
```rust
37+
# #![allow(unused_variables)]
3638
# extern crate juniper;
3739
#[derive(juniper::GraphQLInputObject)]
3840
#[graphql(name="Coordinate", description="A position on the globe")]

docs/book/content/types/interfaces.md

+282-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,280 @@
1-
# Interfaces
1+
Interfaces
2+
==========
3+
4+
[GraphQL interfaces][1] map well to interfaces known from common object-oriented languages such as Java or C#, but Rust, unfortunately, has no concept that maps perfectly to them. The nearest analogue of [GraphQL interfaces][1] are Rust traits, and the main difference is that in GraphQL [interface type][1] serves both as an _abstraction_ and a _boxed value (downcastable to concrete implementers)_, while in Rust, a trait is an _abstraction only_ and _to represent such a boxed value a separate type is required_, like enum or trait object, because Rust trait does not represent a type itself, and so can have no values. This difference imposes some unintuitive and non-obvious corner cases when we try to express [GraphQL interfaces][1] in Rust, but on the other hand gives you full control over which type is backing your interface, and how it's resolved.
5+
6+
For implementing [GraphQL interfaces][1] Juniper provides `#[graphql_interface]` macro.
7+
28

3-
GraphQL interfaces map well to interfaces known from common object-oriented
4-
languages such as Java or C#, but Rust has unfortunately not a concept that maps
5-
perfectly to them. Because of this, defining interfaces in Juniper can require a
6-
little bit of boilerplate code, but on the other hand gives you full control
7-
over which type is backing your interface.
89

9-
To highlight a couple of different ways you can implement interfaces in Rust,
10-
let's have a look at the same end-result from a few different implementations:
1110

1211
## Traits
1312

13+
Defining a trait is mandatory for defining a [GraphQL interface][1], because this is the _obvious_ way we describe an _abstraction_ in Rust. All [interface][1] fields are defined as computed ones via trait methods.
14+
15+
```rust
16+
# extern crate juniper;
17+
use juniper::graphql_interface;
18+
19+
#[graphql_interface]
20+
trait Character {
21+
fn id(&self) -> &str;
22+
}
23+
#
24+
# fn main() {}
25+
```
26+
27+
However, to return values of such [interface][1], we should provide its implementers and the Rust type representing a _boxed value of this trait_. The last one can be represented in two flavors: enum and [trait object][2].
28+
29+
30+
### Enum values (default)
31+
32+
By default, Juniper generates an enum representing the values of the defined [GraphQL interface][1], and names it straightforwardly, `{Interface}Value`.
33+
34+
```rust
35+
# extern crate juniper;
36+
use juniper::{graphql_interface, GraphQLObject};
37+
38+
#[graphql_interface(for = Human)] // enumerating all implementers is mandatory
39+
trait Character {
40+
fn id(&self) -> &str;
41+
}
42+
43+
#[derive(GraphQLObject)]
44+
#[graphql(impl = CharacterValue)] // notice enum name, NOT trait name
45+
struct Human {
46+
id: String,
47+
}
48+
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
49+
impl Character for Human {
50+
fn id(&self) -> &str {
51+
&self.id
52+
}
53+
}
54+
#
55+
# fn main() {
56+
let human = Human { id: "human-32".to_owned() };
57+
// Values type for interface has `From` implementations for all its implementers,
58+
// so we don't need to bother with enum variant names.
59+
let character: CharacterValue = human.into();
60+
assert_eq!(character.id(), "human-32");
61+
# }
62+
```
63+
64+
Also, enum name can be specified explicitly, if desired.
65+
66+
```rust
67+
# extern crate juniper;
68+
use juniper::{graphql_interface, GraphQLObject};
69+
70+
#[graphql_interface(enum = CharaterInterface, for = Human)]
71+
trait Character {
72+
fn id(&self) -> &str;
73+
}
74+
75+
#[derive(GraphQLObject)]
76+
#[graphql(impl = CharaterInterface)]
77+
struct Human {
78+
id: String,
79+
home_planet: String,
80+
}
81+
#[graphql_interface]
82+
impl Character for Human {
83+
fn id(&self) -> &str {
84+
&self.id
85+
}
86+
}
87+
#
88+
# fn main() {}
89+
```
90+
91+
92+
### Trait object values
93+
94+
If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, that should be specified explicitly in the trait definition.
95+
96+
Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood.
97+
98+
> __NOTICE__:
99+
> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it.
100+
101+
```rust
102+
# extern crate juniper;
103+
# extern crate tokio;
104+
use juniper::{graphql_interface, GraphQLObject};
105+
106+
// `dyn` argument accepts the name of type alias for the required trait object,
107+
// and macro generates this alias automatically
108+
#[graphql_interface(dyn = DynCharacter, for = Human)]
109+
trait Character {
110+
async fn id(&self) -> &str; // async fields are supported natively
111+
}
112+
113+
#[derive(GraphQLObject)]
114+
#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait,
115+
struct Human { // so it may be specified explicitly when required
116+
id: String,
117+
}
118+
#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
119+
impl Character for Human {
120+
async fn id(&self) -> &str {
121+
&self.id
122+
}
123+
}
124+
#
125+
# #[tokio::main]
126+
# async fn main() {
127+
let human = Human { id: "human-32".to_owned() };
128+
let character: Box<DynCharacter> = Box::new(human);
129+
assert_eq!(character.id().await, "human-32");
130+
# }
131+
```
132+
133+
134+
### Ignoring trait methods
135+
136+
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
137+
138+
```rust
139+
# extern crate juniper;
140+
use juniper::{graphql_interface, GraphQLObject};
141+
142+
#[graphql_interface(for = Human)]
143+
trait Character {
144+
fn id(&self) -> &str;
145+
146+
#[graphql_interface(ignore)] // or `#[graphql_interface(skip)]`, your choice
147+
fn ignored(&self) -> u32 { 0 }
148+
}
149+
150+
#[derive(GraphQLObject)]
151+
#[graphql(impl = CharacterValue)]
152+
struct Human {
153+
id: String,
154+
}
155+
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
156+
impl Character for Human {
157+
fn id(&self) -> &str {
158+
&self.id
159+
}
160+
}
161+
#
162+
# fn main() {}
163+
```
164+
165+
166+
### Custom context
167+
168+
If a context is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
169+
170+
```rust
171+
# extern crate juniper;
172+
# use std::collections::HashMap;
173+
use juniper::{graphql_interface, GraphQLObject};
174+
175+
struct Database {
176+
humans: HashMap<String, Human>,
177+
}
178+
impl juniper::Context for Database {}
179+
180+
#[graphql_interface(for = Human)] // look, ma, context type is inferred! \(^o^)/
181+
trait Character { // while still can be specified via `Context = ...` attribute argument
182+
// If a field argument is named `context` or `ctx`, it's automatically assumed
183+
// as a context argument.
184+
fn id(&self, context: &Database) -> Option<&str>;
185+
186+
// Otherwise, you may mark it explicitly as a context argument.
187+
fn name(&self, #[graphql_interface(context)] db: &Database) -> Option<&str>;
188+
}
189+
190+
#[derive(GraphQLObject)]
191+
#[graphql(impl = CharacterValue, Context = Database)]
192+
struct Human {
193+
id: String,
194+
name: String,
195+
}
196+
#[graphql_interface]
197+
impl Character for Human {
198+
fn id(&self, db: &Database) -> Option<&str> {
199+
if db.humans.contains_key(&self.id) {
200+
Some(&self.id)
201+
} else {
202+
None
203+
}
204+
}
205+
206+
fn name(&self, db: &Database) -> Option<&str> {
207+
if db.humans.contains_key(&self.id) {
208+
Some(&self.name)
209+
} else {
210+
None
211+
}
212+
}
213+
}
214+
#
215+
# fn main() {}
216+
```
217+
218+
219+
### Using executor and explicit generic scalar
220+
221+
If an executor is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
222+
223+
This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`][4] does so.
224+
225+
```rust
226+
# extern crate juniper;
227+
use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
228+
229+
#[graphql_interface(for = Human, Scalar = S)] // notice specifying scalar as existing type parameter
230+
trait Character<S: ScalarValue> {
231+
// If a field argument is named `executor`, it's automatically assumed
232+
// as an executor argument.
233+
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
234+
where
235+
S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
236+
237+
238+
// Otherwise, you may mark it explicitly as an executor argument.
239+
async fn name<'b>(
240+
&'b self,
241+
#[graphql_interface(executor)] another: &Executor<'_, '_, (), S>,
242+
) -> &'b str
243+
where
244+
S: Send + Sync;
245+
}
246+
247+
#[derive(GraphQLObject)]
248+
#[graphql(impl = CharacterValue<__S>)]
249+
struct Human {
250+
id: String,
251+
name: String,
252+
}
253+
#[graphql_interface(Scalar = S)]
254+
impl<S: ScalarValue> Character<S> for Human {
255+
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
256+
where
257+
S: Send + Sync,
258+
{
259+
executor.look_ahead().field_name()
260+
}
261+
262+
async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
263+
where
264+
S: Send + Sync,
265+
{
266+
&self.name
267+
}
268+
}
269+
#
270+
# fn main() {}
271+
```
272+
273+
274+
275+
276+
277+
14278
Traits are maybe the most obvious concept you want to use when building
15279
interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
16280
have to manually specify how to convert a trait into a concrete type. This can
@@ -63,7 +327,7 @@ juniper::graphql_interface!(<'a> &'a dyn Character: () as "Character" where Scal
63327
# fn main() {}
64328
```
65329

66-
The `instance_resolvers` declaration lists all the implementors of the given
330+
The `instance_resolvers` declaration lists all the implementers of the given
67331
interface and how to resolve them.
68332

69333
As you can see, you lose a bit of the point with using traits: you need to list
@@ -213,3 +477,12 @@ juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
213477
214478
# fn main() {}
215479
```
480+
481+
482+
483+
484+
485+
[1]: https://spec.graphql.org/June2018/#sec-Interfaces
486+
[2]: https://doc.rust-lang.org/reference/types/trait-object.html
487+
[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
488+
[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ example from the last chapter, this is how you would define `Person` using the
1010
macro:
1111

1212
```rust
13+
# #![allow(dead_code)]
1314
# extern crate juniper;
14-
15+
#
1516
struct Person {
1617
name: String,
1718
age: i32,

0 commit comments

Comments
 (0)