Skip to content

🔬 Make interfaces great again! #682

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 89 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
0909a0d
Bootstrap
tyranron Jun 14, 2020
c4d1eb0
Upd
tyranron Jun 16, 2020
0702689
Merge branch 'master' into async-interfaces
tyranron Jun 29, 2020
7789312
Bootstrap macro
tyranron Jun 29, 2020
cbb168b
Revert stuff
tyranron Jun 30, 2020
873f682
Merge branch 'master' into async-interfaces
tyranron Jun 30, 2020
1698f11
Correct PoC to compile
tyranron Jul 1, 2020
1e7a369
Bootstrap #[graphql_interface] expansion
tyranron Jul 1, 2020
8758f21
Bootstrap #[graphql_interface] meta parsing
tyranron Jul 1, 2020
335f693
Bootstrap #[graphql_interface] very basic code generation [skip ci]
tyranron Jul 6, 2020
60d19ff
Upd trait code generation and fix keywords usage [skip ci]
tyranron Jul 7, 2020
e3fed20
Expand trait impls [skip ci]
tyranron Jul 7, 2020
1a32282
Tune up objects [skip ci]
tyranron Jul 7, 2020
cda7f31
Finally! Complies at least... [skip ci]
tyranron Jul 7, 2020
09dadd8
Merge branch 'master' into async-interfaces
tyranron Aug 17, 2020
dcd4ab5
Merge branch 'master' into async-interfaces
tyranron Aug 18, 2020
264138a
Parse meta for fields and its arguments [skip ci]
tyranron Aug 18, 2020
b059686
Impl filling fields meta and bootstrap field resolution [skip ci]
tyranron Aug 19, 2020
4fb13a9
Poking with fields resolution [skip ci]
tyranron Aug 20, 2020
d49b9a5
Solve Rust's teen async HRTB problems [skip ci]
tyranron Aug 21, 2020
118e428
Start parsing trait methods [skip ci]
tyranron Aug 21, 2020
c39b0b2
Finish parsing fields from trait methods [skip ci]
tyranron Aug 25, 2020
06c9c4d
Autodetect trait asyncness and allow to specify it [skip ci]
tyranron Aug 25, 2020
4c17bd1
Allow to autogenerate trait object alias via attribute
tyranron Aug 26, 2020
e758667
Support generics in trait definition and asyncify them correctly
tyranron Aug 28, 2020
c16dc00
Temporary disable explicit async
tyranron Aug 28, 2020
33d9877
Cover arguments and custom names/descriptions in tests
tyranron Aug 28, 2020
469412a
Merge branch 'master' into async-interfaces
LegNeato Aug 31, 2020
1185f46
Re-enable tests with explicit async and fix the codegen to satisfy it
tyranron Aug 31, 2020
3f385f7
Check implementers are registered in schema and vice versa
tyranron Aug 31, 2020
23b5204
Check argument camelCases
tyranron Aug 31, 2020
92870ad
Test argument defaults, and allow Into coercions for them
tyranron Aug 31, 2020
fefcd0c
Re-enable markers
tyranron Sep 1, 2020
337693e
Re-enable markers and relax Sized requirement on IsInputType/IsOutput…
tyranron Sep 1, 2020
190618f
Revert 'juniper_actix' fmt
tyranron Sep 1, 2020
63faed5
Fix missing marks for object
tyranron Sep 1, 2020
fe5a278
Fix subscriptions marks
tyranron Sep 2, 2020
0ac2701
Deduce result type correctly via traits
tyranron Sep 2, 2020
8f10cce
Final fixes
tyranron Sep 2, 2020
6a0240a
Fmt
tyranron Sep 2, 2020
5ff57f6
Merge branch 're-enable-markers' into async-interfaces
tyranron Sep 2, 2020
871f91e
Restore marks checking
tyranron Sep 2, 2020
4f88410
Support custom ScalarValue
tyranron Sep 2, 2020
f2110be
Cover deprecations with tests
tyranron Sep 2, 2020
434aaf9
Merge branch 'master' into async-interfaces
tyranron Sep 2, 2020
b2041a1
Impl dowcasting via methods
tyranron Sep 4, 2020
ea79393
Impl dowcasting via external functions
tyranron Sep 4, 2020
236ede4
Support custom context, vol. 1
tyranron Sep 7, 2020
ed5f0e5
Support custom context, vol. 2
tyranron Sep 8, 2020
4a59c67
Cover fallible field with test
tyranron Sep 8, 2020
d8ea7c9
Impl explicit generic ScalarValue, vol.1
tyranron Sep 8, 2020
8bcd899
Impl explicit generic ScalarValue, vol.2
tyranron Sep 9, 2020
a237715
Allow passing executor into methods
tyranron Sep 10, 2020
eff3558
Merge branch 'master' into async-interfaces
tyranron Sep 11, 2020
a615c21
Generating enum, vol.1
tyranron Sep 14, 2020
a1ce976
Generating enum, vol.2
tyranron Sep 16, 2020
f494ea1
Generating enum, vol.3
tyranron Sep 17, 2020
cbf8116
Generating enum, vol.3
tyranron Sep 17, 2020
e475fd2
Generating enum, vol.4
tyranron Sep 20, 2020
7f54ba7
Generating enum, vol.5
tyranron Sep 21, 2020
3a99794
Generating enum, vol.6
tyranron Sep 22, 2020
5bc9f2b
Generating enum, vol.7
tyranron Sep 22, 2020
5dfe250
Generating enum, vol.8
tyranron Sep 23, 2020
d50258e
Refactor juniper stuff
tyranron Sep 23, 2020
038fd67
Fix juniper tests, vol.1
tyranron Sep 24, 2020
f64a0bc
Fix juniper tests, vol.2
tyranron Sep 25, 2020
f860ab8
Polish 'juniper' crate changes, vol.1
tyranron Sep 25, 2020
991507f
Merge branch 'master' into async-interfaces
tyranron Sep 25, 2020
8753a6e
Polish 'juniper' crate changes, vol.2
tyranron Sep 25, 2020
2112a83
Remove redundant stuf
tyranron Sep 25, 2020
a83202e
Polishing 'juniper_codegen', vol.1
tyranron Sep 25, 2020
084619c
Polishing 'juniper_codegen', vol.2
tyranron Sep 28, 2020
ab52be5
Polishing 'juniper_codegen', vol.3
tyranron Sep 28, 2020
d703926
Polishing 'juniper_codegen', vol.4
tyranron Sep 28, 2020
85ee3bd
Polishing 'juniper_codegen', vol.5
tyranron Sep 29, 2020
427d1cc
Polishing 'juniper_codegen', vol.6
tyranron Sep 29, 2020
ad046de
Polishing 'juniper_codegen', vol.7
tyranron Sep 29, 2020
c45f290
Polishing 'juniper_codegen', vol.8
tyranron Sep 30, 2020
f75df50
Polishing 'juniper_codegen', vol.9
tyranron Sep 30, 2020
49c8d91
Fix other crates tests and make Clippy happier
tyranron Sep 30, 2020
6ce1259
Fix examples
tyranron Sep 30, 2020
357bc2a
Add codegen failure tests, vol. 1
tyranron Sep 30, 2020
f5f72a3
Add codegen failure tests, vol. 2
tyranron Oct 1, 2020
9cd6321
Add codegen failure tests, vol.3
tyranron Oct 1, 2020
8563dbc
Fix codegen failure tests accordingly to latest nightly Rust
tyranron Oct 1, 2020
8919df9
Fix codegen when interface has no implementers
tyranron Oct 5, 2020
3b1e270
Fix warnings in book tests
tyranron Oct 5, 2020
bfa6a4c
Describing new interfaces in Book, vol.1
tyranron Oct 5, 2020
34b5bd4
Merge branch 'master' into async-interfaces
LegNeato Oct 6, 2020
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
1 change: 1 addition & 0 deletions docs/book/content/advanced/introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ result can then be converted to JSON for use with tools and libraries such as
[graphql-client](https://github.com/graphql-rust/graphql-client):

```rust
# #![allow(unused_variables)]
# extern crate juniper;
# extern crate serde_json;
use juniper::{EmptyMutation, EmptySubscription, FieldResult, IntrospectionFormat};
Expand Down
3 changes: 1 addition & 2 deletions docs/book/content/advanced/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ operation returns a [`Future`][Future] with an `Item` value of a `Result<Connect
where [`Connection`][Connection] is a `Stream` of values returned by the operation and [`GraphQLError`][GraphQLError] is the error when the subscription fails.

```rust
# #![allow(dead_code)]
# extern crate futures;
# extern crate juniper;
# extern crate juniper_subscriptions;
Expand All @@ -88,8 +89,6 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
# use juniper_subscriptions::Coordinator;
# use futures::{Stream, StreamExt};
# use std::pin::Pin;
# use tokio::runtime::Runtime;
# use tokio::task;
#
# #[derive(Clone)]
# pub struct Database;
Expand Down
1 change: 1 addition & 0 deletions docs/book/content/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ types to a GraphQL schema. The most important one is the
resolvers, which you will use for the `Query` and `Mutation` roots.

```rust
# #![allow(unused_variables)]
# extern crate juniper;
use juniper::{FieldResult, EmptySubscription};

Expand Down
2 changes: 2 additions & 0 deletions docs/book/content/schema/schemas_and_mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The query root is just a GraphQL object. You define it like any other GraphQL
object in Juniper, most commonly using the `graphql_object` proc macro:

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

```rust
# #![allow(unused_variables)]
# extern crate juniper;
# use juniper::FieldResult;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
Expand Down
2 changes: 2 additions & 0 deletions docs/book/content/types/input_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ GraphQL fields. In Juniper, you can define input objects using a custom derive
attribute, similar to simple objects and enums:

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

```rust
# #![allow(unused_variables)]
# extern crate juniper;
#[derive(juniper::GraphQLInputObject)]
#[graphql(name="Coordinate", description="A position on the globe")]
Expand Down
291 changes: 282 additions & 9 deletions docs/book/content/types/interfaces.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,280 @@
# Interfaces
Interfaces
==========

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

For implementing [GraphQL interfaces][1] Juniper provides `#[graphql_interface]` macro.


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

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

## Traits

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.

```rust
# extern crate juniper;
use juniper::graphql_interface;

#[graphql_interface]
trait Character {
fn id(&self) -> &str;
}
#
# fn main() {}
```

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


### Enum values (default)

By default, Juniper generates an enum representing the values of the defined [GraphQL interface][1], and names it straightforwardly, `{Interface}Value`.

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

#[graphql_interface(for = Human)] // enumerating all implementers is mandatory
trait Character {
fn id(&self) -> &str;
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)] // notice enum name, NOT trait name
struct Human {
id: String,
}
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
impl Character for Human {
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");
# }
```

Also, enum name can be specified explicitly, if desired.

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

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

#[derive(GraphQLObject)]
#[graphql(impl = CharaterInterface)]
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, that 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
}
}
#
# #[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.

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

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

#[graphql_interface(ignore)] // or `#[graphql_interface(skip)]`, your choice
fn ignored(&self) -> u32 { 0 }
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Human {
id: String,
}
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```


### Custom context

If a context is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.

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

struct Database {
humans: HashMap<String, Human>,
}
impl juniper::Context for Database {}

#[graphql_interface(for = Human)] // look, ma, context type is inferred! \(^o^)/
trait Character { // while still can be specified via `Context = ...` attribute argument
// If a field argument is named `context` or `ctx`, it's automatically assumed
// as a context argument.
fn id(&self, context: &Database) -> Option<&str>;

// Otherwise, you may mark it explicitly as a context argument.
fn name(&self, #[graphql_interface(context)] db: &Database) -> Option<&str>;
}

#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Context = Database)]
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() {}
```


### Using executor and explicit generic scalar

If an executor is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.

This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`][4] does so.

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

#[graphql_interface(for = Human, Scalar = S)] // notice specifying scalar 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 ¯\_(ツ)_/¯


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

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

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






Traits are maybe the most obvious concept you want to use when building
interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
have to manually specify how to convert a trait into a concrete type. This can
Expand Down Expand Up @@ -63,7 +327,7 @@ juniper::graphql_interface!(<'a> &'a dyn Character: () as "Character" where Scal
# fn main() {}
```

The `instance_resolvers` declaration lists all the implementors of the given
The `instance_resolvers` declaration lists all the implementers of the given
interface and how to resolve them.

As you can see, you lose a bit of the point with using traits: you need to list
Expand Down Expand Up @@ -213,3 +477,12 @@ juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {

# fn main() {}
```





[1]: https://spec.graphql.org/June2018/#sec-Interfaces
[2]: https://doc.rust-lang.org/reference/types/trait-object.html
[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html
3 changes: 2 additions & 1 deletion docs/book/content/types/objects/complex_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ example from the last chapter, this is how you would define `Person` using the
macro:

```rust
# #![allow(dead_code)]
# extern crate juniper;

#
struct Person {
name: String,
age: i32,
Expand Down
Loading