Skip to content

Commit 1aa1000

Browse files
authored
Redesign #[graphql_interface] macro (#1009, #1000, #814)
- remove support for `#[graphql_interface(dyn)]` - describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait` - forbid default impls on non-skipped trait methods - support additional nullable arguments on implementer - support returning sub-type on implementer
1 parent c866e09 commit 1aa1000

File tree

82 files changed

+3375
-5935
lines changed

Some content is hidden

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

82 files changed

+3375
-5935
lines changed

docs/book/content/types/interfaces.md

Lines changed: 18 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,14 @@ trait Character {
4545
struct Human {
4646
id: String,
4747
}
48-
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
49-
impl Character for Human {
50-
fn id(&self) -> &str {
51-
&self.id
52-
}
53-
}
5448

5549
#[derive(GraphQLObject)]
5650
#[graphql(impl = CharacterValue)]
5751
struct Droid {
5852
id: String,
5953
}
60-
#[graphql_interface]
61-
impl Character for Droid {
62-
fn id(&self) -> &str {
63-
&self.id
64-
}
65-
}
66-
67-
# fn main() {
68-
let human = Human { id: "human-32".to_owned() };
69-
// Values type for interface has `From` implementations for all its implementers,
70-
// so we don't need to bother with enum variant names.
71-
let character: CharacterValue = human.into();
72-
assert_eq!(character.id(), "human-32");
73-
# }
54+
#
55+
# fn main() {}
7456
```
7557

7658
Also, enum name can be specified explicitly, if desired.
@@ -90,71 +72,11 @@ struct Human {
9072
id: String,
9173
home_planet: String,
9274
}
93-
#[graphql_interface]
94-
impl Character for Human {
95-
fn id(&self) -> &str {
96-
&self.id
97-
}
98-
}
9975
#
10076
# fn main() {}
10177
```
10278

10379

104-
### Trait object values
105-
106-
If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, then it should be specified explicitly in the trait definition.
107-
108-
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.
109-
110-
> __NOTICE__:
111-
> 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.
112-
113-
```rust
114-
# extern crate juniper;
115-
# extern crate tokio;
116-
use juniper::{graphql_interface, GraphQLObject};
117-
118-
// `dyn` argument accepts the name of type alias for the required trait object,
119-
// and macro generates this alias automatically.
120-
#[graphql_interface(dyn = DynCharacter, for = Human)]
121-
trait Character {
122-
async fn id(&self) -> &str; // async fields are supported natively
123-
}
124-
125-
#[derive(GraphQLObject)]
126-
#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait,
127-
struct Human { // so it may be specified explicitly when required
128-
id: String,
129-
}
130-
#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
131-
impl Character for Human {
132-
async fn id(&self) -> &str {
133-
&self.id
134-
}
135-
}
136-
137-
#[derive(GraphQLObject)]
138-
#[graphql(impl = DynCharacter<__S>)]
139-
struct Droid {
140-
id: String,
141-
}
142-
#[graphql_interface]
143-
impl Character for Droid {
144-
async fn id(&self) -> &str {
145-
&self.id
146-
}
147-
}
148-
149-
# #[tokio::main]
150-
# async fn main() {
151-
let human = Human { id: "human-32".to_owned() };
152-
let character: Box<DynCharacter> = Box::new(human);
153-
assert_eq!(character.id().await, "human-32");
154-
# }
155-
```
156-
157-
15880
### Ignoring trait methods
15981

16082
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
@@ -176,12 +98,6 @@ trait Character {
17698
struct Human {
17799
id: String,
178100
}
179-
#[graphql_interface]
180-
impl Character for Human {
181-
fn id(&self) -> &str {
182-
&self.id
183-
}
184-
}
185101
#
186102
# fn main() {}
187103
```
@@ -278,24 +194,6 @@ struct Human {
278194
id: String,
279195
name: String,
280196
}
281-
#[graphql_interface]
282-
impl Character for Human {
283-
fn id(&self, db: &Database) -> Option<&str> {
284-
if db.humans.contains_key(&self.id) {
285-
Some(&self.id)
286-
} else {
287-
None
288-
}
289-
}
290-
291-
fn name(&self, db: &Database) -> Option<&str> {
292-
if db.humans.contains_key(&self.id) {
293-
Some(&self.name)
294-
} else {
295-
None
296-
}
297-
}
298-
}
299197
#
300198
# fn main() {}
301199
```
@@ -309,119 +207,50 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`]
309207

310208
```rust
311209
# extern crate juniper;
312-
use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
210+
use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue};
313211

314212
#[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter
315213
trait Character<S: ScalarValue> {
316214
// If a field argument is named `executor`, it's automatically assumed
317215
// as an executor argument.
318-
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
319-
where
320-
S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
216+
fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str;
321217

322218
// Otherwise, you may mark it explicitly as an executor argument.
323-
async fn name<'b>(
219+
fn name<'b>(
324220
&'b self,
325221
#[graphql(executor)] another: &Executor<'_, '_, (), S>,
326-
) -> &'b str
327-
where
328-
S: Send + Sync;
222+
) -> &'b str;
223+
224+
fn home_planet(&self) -> &str;
329225
}
330226

331-
#[derive(GraphQLObject)]
332-
#[graphql(impl = CharacterValue<__S>)]
333227
struct Human {
334228
id: String,
335229
name: String,
230+
home_planet: String,
336231
}
337-
#[graphql_interface(scalar = S)]
338-
impl<S: ScalarValue> Character<S> for Human {
339-
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
232+
#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
233+
impl Human {
234+
async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
340235
where
341-
S: Send + Sync,
236+
S: ScalarValue,
342237
{
343238
executor.look_ahead().field_name()
344239
}
345240

346-
async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
347-
where
348-
S: Send + Sync,
349-
{
241+
async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str {
350242
&self.name
351243
}
352-
}
353-
#
354-
# fn main() {}
355-
```
356-
357-
358-
### Downcasting
359-
360-
By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` macro argument is used).
361-
362-
However, if some custom logic is needed to downcast a [GraphQL interface][1] implementer, you may specify either an external function or a trait method to do so.
363-
364-
```rust
365-
# extern crate juniper;
366-
# use std::collections::HashMap;
367-
use juniper::{graphql_interface, GraphQLObject};
368-
369-
struct Database {
370-
droids: HashMap<String, Droid>,
371-
}
372-
impl juniper::Context for Database {}
373-
374-
#[graphql_interface(for = [Human, Droid], context = Database)]
375-
#[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` function
376-
trait Character {
377-
fn id(&self) -> &str;
378-
379-
#[graphql(downcast)] // makes method a downcast to `Human`, not a field
380-
// NOTICE: The method signature may optionally contain `&Database` context argument.
381-
fn as_human(&self) -> Option<&Human> {
382-
None
383-
}
384-
}
385-
386-
#[derive(GraphQLObject)]
387-
#[graphql(impl = CharacterValue, Context = Database)]
388-
struct Human {
389-
id: String,
390-
}
391-
#[graphql_interface]
392-
impl Character for Human {
393-
fn id(&self) -> &str {
394-
&self.id
395-
}
396-
397-
fn as_human(&self) -> Option<&Self> {
398-
Some(self)
399-
}
400-
}
401-
402-
#[derive(GraphQLObject)]
403-
#[graphql(impl = CharacterValue, Context = Database)]
404-
struct Droid {
405-
id: String,
406-
}
407-
#[graphql_interface]
408-
impl Character for Droid {
409-
fn id(&self) -> &str {
410-
&self.id
244+
245+
fn home_planet<'c, S>(&'c self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'c str {
246+
// Executor may not be present on the trait method ^^^^^^^^^^^^^^^^^^^^^^^^
247+
&self.home_planet
411248
}
412249
}
413-
414-
// External downcast function doesn't have to be a method of a type.
415-
// It's only a matter of the function signature to match the requirements.
416-
fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> {
417-
db.droids.get(ch.id())
418-
}
419250
#
420251
# fn main() {}
421252
```
422253

423-
The attribute syntax `#[graphql_interface(on ImplementerType = resolver_fn)]` follows the [GraphQL syntax for downcasting interface implementer](https://spec.graphql.org/June2018/#example-5cc55).
424-
425254

426255

427256

@@ -445,25 +274,13 @@ struct Human {
445274
id: String,
446275
home_planet: String,
447276
}
448-
#[graphql_interface(scalar = DefaultScalarValue)]
449-
impl Character for Human {
450-
fn id(&self) -> &str {
451-
&self.id
452-
}
453-
}
454277

455278
#[derive(GraphQLObject)]
456279
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
457280
struct Droid {
458281
id: String,
459282
primary_function: String,
460283
}
461-
#[graphql_interface(scalar = DefaultScalarValue)]
462-
impl Character for Droid {
463-
fn id(&self) -> &str {
464-
&self.id
465-
}
466-
}
467284
#
468285
# fn main() {}
469286
```

examples/actix_subscriptions/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use actix_web::{
1010

1111
use juniper::{
1212
graphql_object, graphql_subscription, graphql_value,
13-
tests::fixtures::starwars::schema::{Character as _, Database, Query},
13+
tests::fixtures::starwars::schema::{Database, Query},
1414
EmptyMutation, FieldError, RootNode,
1515
};
1616
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use juniper::{graphql_interface, graphql_object};
2+
3+
pub struct ObjA {
4+
id: String,
5+
}
6+
7+
#[graphql_object(impl = CharacterValue)]
8+
impl ObjA {
9+
fn id(&self, is_present: bool) -> &str {
10+
is_present.then(|| self.id.as_str()).unwrap_or("missing")
11+
}
12+
}
13+
14+
#[graphql_interface(for = ObjA)]
15+
trait Character {
16+
fn id(&self) -> &str;
17+
}
18+
19+
fn main() {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
error[E0080]: evaluation of constant value failed
2+
--> fail/interface/additional_non_nullable_argument.rs:14:1
3+
|
4+
14 | #[graphql_interface(for = ObjA)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
use juniper::{graphql_interface, GraphQLObject};
2-
3-
#[derive(GraphQLObject)]
4-
#[graphql(impl = CharacterValue)]
5-
pub struct ObjA {
6-
test: String,
7-
}
1+
use juniper::graphql_interface;
82

93
#[graphql_interface]
10-
impl Character for ObjA {}
11-
12-
#[graphql_interface(for = ObjA)]
134
trait Character {
14-
fn id(&self, __num: i32) -> &str {
15-
"funA"
16-
}
5+
fn id(&self, __num: i32) -> &str;
176
}
187

198
fn main() {}
Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
11
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
2-
--> fail/interface/argument_double_underscored.rs:14:18
3-
|
4-
14 | fn id(&self, __num: i32) -> &str {
5-
| ^^^^^
6-
|
7-
= note: https://spec.graphql.org/June2018/#sec-Schema
8-
9-
error[E0412]: cannot find type `CharacterValue` in this scope
10-
--> fail/interface/argument_double_underscored.rs:4:18
2+
--> fail/interface/argument_double_underscored.rs:5:18
113
|
12-
4 | #[graphql(impl = CharacterValue)]
13-
| ^^^^^^^^^^^^^^ not found in this scope
14-
15-
error[E0405]: cannot find trait `Character` in this scope
16-
--> fail/interface/argument_double_underscored.rs:10:6
17-
|
18-
10 | impl Character for ObjA {}
19-
| ^^^^^^^^^ not found in this scope
4+
5 | fn id(&self, __num: i32) -> &str;
5+
| ^^^^^
6+
|
7+
= note: https://spec.graphql.org/June2018/#sec-Schema

0 commit comments

Comments
 (0)