Skip to content
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

Redesign interfaces (#1000) #1009

Merged
merged 55 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
01d7cb8
WIP
ilslv Dec 20, 2021
205cfcd
WIP
ilslv Dec 20, 2021
928cb9c
WIP (with IsSubtype)
ilslv Dec 20, 2021
3dad162
WIP (It looks like we're onto something)
ilslv Dec 20, 2021
af04486
WIP (return to const assertions)
ilslv Dec 21, 2021
15e527f
It's alive!
ilslv Dec 24, 2021
cb9cad5
Merge branch 'master' into 1000-new-interfaces
ilslv Dec 24, 2021
7a78f9e
Use Field inside object's field resolver
ilslv Dec 24, 2021
5c2087c
Add async object fields
ilslv Dec 24, 2021
e9c6046
Fix
ilslv Dec 24, 2021
0739c3e
Fix Wrapped type for tuple with context
ilslv Dec 27, 2021
263d544
WIP (Make graphql_interface_new attribute work)
ilslv Dec 28, 2021
f4c48a6
WIP (move to the new tests volume 1)
ilslv Dec 28, 2021
a1df387
WIP (move to the new tests volume 2)
ilslv Dec 29, 2021
5040d68
WIP (move to the new tests volume 3)
ilslv Dec 29, 2021
f5533e3
WIP (move to the new tests volume 4)
ilslv Dec 29, 2021
527325c
WIP (move to the new tests volume 5)
ilslv Dec 29, 2021
704f079
WIP (move to the new tests volume 5)
ilslv Dec 29, 2021
b84009e
WIP (refactor)
ilslv Dec 30, 2021
11345f7
WIP (refactor)
ilslv Dec 30, 2021
5e7230f
WIP (moving to readable const asserts)
ilslv Dec 30, 2021
d17ba4f
WIP (finally good const assert message)
ilslv Dec 30, 2021
8cb9b81
WIP (corrections)
ilslv Dec 30, 2021
116cdd9
WIP (pretty field arguments check)
ilslv Jan 3, 2022
7bd8c73
WIP (pretty subtype check)
ilslv Jan 3, 2022
3ebc8fd
WIP (corrections)
ilslv Jan 3, 2022
ce249b0
WIP (FieldMeta trait)
ilslv Jan 3, 2022
d9971a0
WIP (Refactor graphql_interface_new a bit)
ilslv Jan 3, 2022
8a966fb
WIP (Corrections)
ilslv Jan 3, 2022
21f4940
WIP (Prettify non-existent Field assertions)
ilslv Jan 4, 2022
e03901b
WIP (Move to macros::reflection module)
ilslv Jan 4, 2022
3ad8399
WIP (Docs vol. 1)
ilslv Jan 4, 2022
463bbb8
WIP (Docs vol. 2)
ilslv Jan 4, 2022
26ab27e
WIP (Refactoring)
ilslv Jan 4, 2022
cddb252
Merge branch 'master' into 1000-new-interfaces
ilslv Jan 4, 2022
9977746
WIP (Refactoring)
ilslv Jan 4, 2022
f5340a7
WIP (Tests corrections)
ilslv Jan 4, 2022
4c31c13
WIP (More tests vol. 1)
ilslv Jan 4, 2022
7bd09d0
WIP (Move everything to the new graphql_interface macro)
ilslv Jan 5, 2022
b5f552a
WIP (More codegen_fail tests)
ilslv Jan 5, 2022
66819e9
WIP (prettify missing references in `impl` and `for` attributes)
ilslv Jan 5, 2022
3d53dc1
WIP (more tests)
ilslv Jan 5, 2022
732bc13
WIP (Correct docs)
ilslv Jan 6, 2022
81d96ce
WIP (Forbid default trait method impls)
ilslv Jan 6, 2022
94164eb
WIP (Corrections)
ilslv Jan 6, 2022
1345bff
WIP (Corrections)
ilslv Jan 6, 2022
6cb5cef
WIP (Corrections)
ilslv Jan 6, 2022
41a2dcf
WIP (CHANGELOG)
ilslv Jan 6, 2022
eb8c56d
WIP (Correction)
ilslv Jan 6, 2022
f531037
Some corrections [skip ci]
tyranron Jan 11, 2022
a2287dc
Minor tests corrections
ilslv Jan 12, 2022
ceb2cf6
Don't inject `#[async_trait]` in case some trait methods are async
ilslv Jan 12, 2022
d77a1ab
Corrections
ilslv Jan 13, 2022
0cb101d
Corrections
ilslv Jan 14, 2022
a411cd0
Corrections
tyranron Jan 26, 2022
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
219 changes: 18 additions & 201 deletions docs/book/content/types/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,14 @@ trait Character {
struct Human {
id: String,
}
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Droid {
id: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}

# fn main() {
let human = Human { id: "human-32".to_owned() };
// Values type for interface has `From` implementations for all its implementers,
// so we don't need to bother with enum variant names.
let character: CharacterValue = human.into();
assert_eq!(character.id(), "human-32");
# }
#
# fn main() {}
```

Also, enum name can be specified explicitly, if desired.
Expand All @@ -90,71 +72,11 @@ struct Human {
id: String,
home_planet: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```


### Trait object values

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.

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.

> __NOTICE__:
> 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.

```rust
# extern crate juniper;
# extern crate tokio;
use juniper::{graphql_interface, GraphQLObject};

// `dyn` argument accepts the name of type alias for the required trait object,
// and macro generates this alias automatically.
#[graphql_interface(dyn = DynCharacter, for = Human)]
trait Character {
async fn id(&self) -> &str; // async fields are supported natively
}

#[derive(GraphQLObject)]
#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait,
struct Human { // so it may be specified explicitly when required
id: String,
}
#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
impl Character for Human {
async fn id(&self) -> &str {
&self.id
}
}

#[derive(GraphQLObject)]
#[graphql(impl = DynCharacter<__S>)]
struct Droid {
id: String,
}
#[graphql_interface]
impl Character for Droid {
async fn id(&self) -> &str {
&self.id
}
}

# #[tokio::main]
# async fn main() {
let human = Human { id: "human-32".to_owned() };
let character: Box<DynCharacter> = Box::new(human);
assert_eq!(character.id().await, "human-32");
# }
```


### Ignoring trait methods

We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
Expand All @@ -176,12 +98,6 @@ trait Character {
struct Human {
id: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```
Expand Down Expand Up @@ -278,24 +194,6 @@ struct Human {
id: String,
name: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self, db: &Database) -> Option<&str> {
if db.humans.contains_key(&self.id) {
Some(&self.id)
} else {
None
}
}

fn name(&self, db: &Database) -> Option<&str> {
if db.humans.contains_key(&self.id) {
Some(&self.name)
} else {
None
}
}
}
#
# fn main() {}
```
Expand All @@ -309,119 +207,50 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`]

```rust
# extern crate juniper;
use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue};

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

// Otherwise, you may mark it explicitly as an executor argument.
async fn name<'b>(
fn name<'b>(
&'b self,
#[graphql(executor)] another: &Executor<'_, '_, (), S>,
) -> &'b str
where
S: Send + Sync;
) -> &'b str;

fn home_planet(&self) -> &str;
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue<__S>)]
struct Human {
id: String,
name: String,
home_planet: String,
}
#[graphql_interface(scalar = S)]
impl<S: ScalarValue> Character<S> for Human {
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
impl Human {
async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
where
S: Send + Sync,
S: ScalarValue,
{
executor.look_ahead().field_name()
}

async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
where
S: Send + Sync,
{
async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str {
&self.name
}
}
#
# fn main() {}
```


### Downcasting

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).

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.

```rust
# extern crate juniper;
# use std::collections::HashMap;
use juniper::{graphql_interface, GraphQLObject};

struct Database {
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}

#[graphql_interface(for = [Human, Droid], context = Database)]
#[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` function
trait Character {
fn id(&self) -> &str;

#[graphql(downcast)] // makes method a downcast to `Human`, not a field
// NOTICE: The method signature may optionally contain `&Database` context argument.
fn as_human(&self) -> Option<&Human> {
None
}
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Context = Database)]
struct Human {
id: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}

fn as_human(&self) -> Option<&Self> {
Some(self)
}
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Context = Database)]
struct Droid {
id: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id

fn home_planet<'c, S>(&'c self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'c str {
// Executor may not be present on the trait method ^^^^^^^^^^^^^^^^^^^^^^^^
&self.home_planet
}
}

// External downcast function doesn't have to be a method of a type.
// It's only a matter of the function signature to match the requirements.
fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> {
db.droids.get(ch.id())
}
#
# fn main() {}
```

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




Expand All @@ -445,25 +274,13 @@ struct Human {
id: String,
home_planet: String,
}
#[graphql_interface(scalar = DefaultScalarValue)]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
struct Droid {
id: String,
primary_function: String,
}
#[graphql_interface(scalar = DefaultScalarValue)]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```
Expand Down
2 changes: 1 addition & 1 deletion examples/actix_subscriptions/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use actix_web::{

use juniper::{
graphql_object, graphql_subscription, graphql_value,
tests::fixtures::starwars::schema::{Character as _, Database, Query},
tests::fixtures::starwars::schema::{Database, Query},
EmptyMutation, FieldError, RootNode,
};
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use juniper::{graphql_interface, graphql_object};

pub struct ObjA {
id: String,
}

#[graphql_object(impl = CharacterValue)]
impl ObjA {
fn id(&self, is_present: bool) -> &str {
is_present.then(|| self.id.as_str()).unwrap_or("missing")
}
}

#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str;
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/additional_non_nullable_argument.rs:14:1
|
14 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
use juniper::{graphql_interface, GraphQLObject};

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
use juniper::graphql_interface;

#[graphql_interface]
impl Character for ObjA {}

#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self, __num: i32) -> &str {
"funA"
}
fn id(&self, __num: i32) -> &str;
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
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.
--> fail/interface/argument_double_underscored.rs:14:18
|
14 | fn id(&self, __num: i32) -> &str {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/argument_double_underscored.rs:4:18
--> fail/interface/argument_double_underscored.rs:5:18
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope

error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/argument_double_underscored.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope
5 | fn id(&self, __num: i32) -> &str;
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema
Loading