Codegen reimplementation for GraphQL unions (#666)

- reimplement #[derive(GraphQLUnion)] macro to support:
    - both structs and enums
    - generics in type definition
    - multiple #[graphql] attributes
    - external resolver functions
- remove From trait impls generation for enum variants

- reimplement #[graphql_union] macro to support:
    - traits
    - generics in trait definition
    - multiple attributes
    - external resolver functions
    - GraphQLType implemetation for a raw trait object
    - GraphQLTypeAsync implemetation (#549)

- add marker::GraphQLUnion trait

- rewrite "2.5 Unions" section in Book (Juniper user documentation)

- rewrite `codegen` and `codegen_fail` integration tests for GraphQL unions

Additionally:
- re-export `futures` crate in `juniper` for convenient reuse in the generated code without requiring library user to provide `futures` crate by himself (#663)
- use unit type () as default context for EmptyMutation and EmptySubscriptions
- relax Sized trait bound on some GraphQLType and GraphQLTypeAsync definitions, implementations and usages
This commit is contained in:
Kai Ren 2020-06-04 11:19:01 +03:00 committed by GitHub
parent 31d08888e4
commit ddc1488195
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 5693 additions and 1567 deletions

View file

@ -1,36 +1,268 @@
# Unions
Unions
======
From a server's point of view, GraphQL unions are similar to interfaces: the
only exception is that they don't contain fields on their own.
From the server's point of view, [GraphQL unions][1] are similar to interfaces - the only exception is that they don't contain fields on their own.
In Juniper, the `graphql_union!` has identical syntax to the
[interface macro](interfaces.md), but does not support defining
fields. Therefore, the same considerations about using traits,
placeholder types, or enums still apply to unions. For simple
situations, Juniper provides `#[derive(GraphQLUnion)]` for enums.
For implementing [GraphQL unions][1] Juniper provides:
- `#[derive(GraphQLUnion)]` macro for enums and structs.
- `#[graphql_union]` for traits.
If we look at the same examples as in the interfaces chapter, we see the
similarities and the tradeoffs:
## Traits
### Downcasting via accessor methods
## Enums
Most of the time, we just need a trivial and straightforward Rust enum to represent a [GraphQL union][1].
```rust
#[derive(juniper::GraphQLObject)]
# #![allow(dead_code)]
use derive_more::From;
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
#[derive(From, GraphQLUnion)]
enum Character {
Human(Human),
Droid(Droid),
}
#
# fn main() {}
```
### Ignoring enum variants
In some rare situations we may want to omit exposing an enum variant in the GraphQL schema.
As an example, let's consider the situation where we need to bind some type parameter `T` for doing interesting type-level stuff in our resolvers. To achieve this we need to have `PhantomData<T>`, but we don't want it exposed in the GraphQL schema.
> __WARNING__:
> It's the _library user's responsibility_ to ensure that ignored enum variant is _never_ returned from resolvers, otherwise resolving the GraphQL query will __panic at runtime__.
```rust
# use std::marker::PhantomData;
use derive_more::From;
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
#[derive(From, GraphQLUnion)]
enum Character<S> {
Human(Human),
Droid(Droid),
#[from(ignore)]
#[graphql(ignore)] // or `#[graphql(skip)]`, your choice
_State(PhantomData<S>),
}
#
# fn main() {}
```
### External resolver functions
If some custom logic is needed to resolve a [GraphQL union][1] variant, you may specify an external function to do so:
```rust
# #![allow(dead_code)]
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Droid {
id: String,
primary_function: String,
}
pub struct CustomContext {
droid: Droid,
}
impl juniper::Context for CustomContext {}
#[derive(GraphQLUnion)]
#[graphql(Context = CustomContext)]
enum Character {
Human(Human),
#[graphql(with = Character::droid_from_context)]
Droid(Droid),
}
impl Character {
// NOTICE: The function signature must contain `&self` and `&Context`,
// and return `Option<&VariantType>`.
fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> {
Some(&ctx.droid)
}
}
#
# fn main() {}
```
With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/June2018/#example-f8163).
```rust
# #![allow(dead_code)]
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Droid {
id: String,
primary_function: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Ewok {
id: String,
is_funny: bool,
}
pub struct CustomContext {
ewok: Ewok,
}
impl juniper::Context for CustomContext {}
#[derive(GraphQLUnion)]
#[graphql(Context = CustomContext)]
#[graphql(on Ewok = Character::ewok_from_context)]
enum Character {
Human(Human),
Droid(Droid),
#[graphql(ignore)] // or `#[graphql(skip)]`, your choice
Ewok,
}
impl Character {
fn ewok_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Ewok> {
if let Self::Ewok = self {
Some(&ctx.ewok)
} else {
None
}
}
}
#
# fn main() {}
```
## Structs
Using Rust structs as [GraphQL unions][1] is very similar to using enums, with the nuance that specifying an external resolver function is the only way to declare a [GraphQL union][1] variant.
```rust
# use std::collections::HashMap;
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap<String, Human>,
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
#[derive(GraphQLUnion)]
#[graphql(
Context = Database,
on Human = Character::get_human,
on Droid = Character::get_droid,
)]
struct Character {
id: String,
}
impl Character {
fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human>{
ctx.humans.get(&self.id)
}
fn get_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid>{
ctx.droids.get(&self.id)
}
}
#
# fn main() {}
```
## Traits
To use a Rust trait definition as a [GraphQL union][1] you need to use the `#[graphql_union]` macro. [Rust doesn't allow derive macros on traits](https://doc.rust-lang.org/stable/reference/procedural-macros.html#derive-macros), so using `#[derive(GraphQLUnion)]` on traits doesn't work.
> __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](https://doc.rust-lang.org/stable/reference/types/trait-object.html) to specify a [GraphQL union][1] behind it.
```rust
use juniper::{graphql_union, GraphQLObject};
#[derive(GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
#[graphql_union]
trait Character {
// Downcast methods, each concrete class will need to implement one of these
// NOTICE: The method signature must contain `&self` and return `Option<&VariantType>`.
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
}
@ -42,34 +274,28 @@ impl Character for Human {
impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}
#[juniper::graphql_union]
impl<'a> GraphQLUnion for &'a dyn Character {
fn resolve(&self) {
match self {
Human => self.as_human(),
Droid => self.as_droid(),
}
}
}
#
# fn main() {}
```
### Using an extra database lookup
FIXME: This example does not compile at the moment
### Custom context
If a context is required in a trait method to resolve a [GraphQL union][1] variant, specify it as an argument.
```rust
# #![allow(unused_variables)]
# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
use juniper::{graphql_union, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
id: String,
@ -80,10 +306,107 @@ struct Database {
humans: HashMap<String, Human>,
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
#[graphql_union(Context = Database)]
trait Character {
// NOTICE: The method signature may optionally contain `&Context`.
fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> { None }
fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> { None }
}
impl Character for Human {
fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
ctx.humans.get(&self.id)
}
}
impl Character for Droid {
fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> {
ctx.droids.get(&self.id)
}
}
#
# fn main() {}
```
### Ignoring trait methods
As with enums, we may want to omit some trait methods to be assumed as [GraphQL union][1] variants and ignore them.
```rust
use juniper::{graphql_union, GraphQLObject};
#[derive(GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
#[graphql_union]
trait Character {
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
#[graphql_union(ignore)] // or `#[graphql_union(skip)]`, your choice
fn id(&self) -> &str;
}
impl Character for Human {
fn as_human(&self) -> Option<&Human> { Some(&self) }
fn id(&self) -> &str { self.id.as_str() }
}
impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
fn id(&self) -> &str { self.id.as_str() }
}
#
# fn main() {}
```
### External resolver functions
Similarly to enums and structs, it's not mandatory to use trait methods as [GraphQL union][1] variant resolvers. Instead, custom functions may be specified:
```rust
# use std::collections::HashMap;
use juniper::{graphql_union, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap<String, Human>,
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
#[graphql_union(Context = Database)]
#[graphql_union(
on Human = DynCharacter::get_human,
on Droid = get_droid,
)]
trait Character {
#[graphql_union(ignore)] // or `#[graphql_union(skip)]`, your choice
fn id(&self) -> &str;
}
@ -95,125 +418,61 @@ impl Character for Droid {
fn id(&self) -> &str { self.id.as_str() }
}
// The trait object is always `Send` and `Sync`.
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
#[juniper::graphql_union(
Context = Database
)]
impl<'a> GraphQLUnion for &'a dyn Character {
fn resolve(&self, context: &Database) {
match self {
Human => context.humans.get(self.id()),
Droid => context.droids.get(self.id()),
}
impl<'a> DynCharacter<'a> {
fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
ctx.humans.get(self.id())
}
}
// External resolver 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: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droid> {
ctx.droids.get(ch.id())
}
#
# fn main() {}
```
## Placeholder objects
## `ScalarValue` considerations
By default, `#[derive(GraphQLUnion)]` and `#[graphql_union]` macros generate code, which is generic over a [`ScalarValue`][2] type. This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`][2] type in its implementation. To resolve such problem, a concrete [`ScalarValue`][2] type should be specified:
```rust
# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
# #![allow(dead_code)]
use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
#[derive(GraphQLObject)]
#[graphql(Scalar = DefaultScalarValue)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap<String, Human>,
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
struct Character {
id: String,
}
#[juniper::graphql_union(
Context = Database,
)]
impl GraphQLUnion for Character {
fn resolve(&self, context: &Database) {
match self {
Human => { context.humans.get(&self.id) },
Droid => { context.droids.get(&self.id) },
}
}
}
# fn main() {}
```
## Enums (Impl)
```rust
#[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
# #[allow(dead_code)]
#[derive(GraphQLUnion)]
#[graphql(Scalar = DefaultScalarValue)] // removing this line will fail compilation
enum Character {
Human(Human),
Droid(Droid),
}
#[juniper::graphql_union]
impl Character {
fn resolve(&self) {
match self {
Human => { match *self { Character::Human(ref h) => Some(h), _ => None } },
Droid => { match *self { Character::Droid(ref d) => Some(d), _ => None } },
}
}
}
#
# fn main() {}
```
## Enums (Derive)
This example is similar to `Enums (Impl)`. To successfully use the
derive macro, ensure that each variant of the enum has a different
type. Since each variant is different, the device macro provides
`std::convert::Into<T>` converter for each variant.
```rust
#[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
#[derive(juniper::GraphQLUnion)]
enum Character {
Human(Human),
Droid(Droid),
}
# fn main() {}
```
[1]: https://spec.graphql.org/June2018/#sec-Unions
[2]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html

View file

@ -9,6 +9,8 @@ build = "build.rs"
juniper = { path = "../../../juniper" }
juniper_iron = { path = "../../../juniper_iron" }
juniper_subscriptions = { path = "../../../juniper_subscriptions" }
derive_more = "0.99.7"
futures = "0.3"
tokio = { version = "0.2", features = ["rt-core", "blocking", "stream", "rt-util"] }
iron = "0.5.0"

View file

@ -0,0 +1,6 @@
use juniper::graphql_union;
#[graphql_union]
enum Character {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: #[graphql_union] attribute is applicable to trait definitions only
--> $DIR/attr_wrong_item.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,12 +0,0 @@
#[derive(juniper::GraphQLEnum)]
pub enum Test {
A,
B,
}
#[derive(juniper::GraphQLUnion)]
enum Character {
Test(Test),
}
fn main() {}

View file

@ -1,8 +0,0 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied
--> $DIR/derive_enum_field.rs:7:10
|
7 | #[derive(juniper::GraphQLUnion)]
| ^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<__S>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,4 +0,0 @@
#[derive(juniper::GraphQLUnion)]
enum Character {}
fn main() {}

View file

@ -1,7 +0,0 @@
error: GraphQL union expects at least one field
--> $DIR/derive_no_fields.rs:2:1
|
2 | enum Character {}
| ^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,10 +0,0 @@
error[E0119]: conflicting implementations of trait `std::convert::From<std::string::String>` for type `Character`:
--> $DIR/derive_same_type.rs:1:10
|
1 | #[derive(juniper::GraphQLUnion)]
| ^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `Character`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,6 @@
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
union Character { id: i32 }
fn main() {}

View file

@ -0,0 +1,5 @@
error: GraphQL union can only be derived for enums and structs
--> $DIR/derive_wrong_item.rs:4:1
|
4 | union Character { id: i32 }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,15 @@
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLUnion)]
#[graphql(on Human = resolve_fn1)]
enum Character {
#[graphql(with = resolve_fn2)]
A(Human),
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL union variant `Human` already has external resolver function `resolve_fn1` declared on the enum
--> $DIR/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs:6:15
|
6 | #[graphql(with = resolve_fn2)]
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -0,0 +1,13 @@
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLUnion)]
enum __Character {
A(Human),
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +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 GraphQLs introspection system.
--> $DIR/enum_name_double_underscored.rs:4:6
|
4 | enum __Character {
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,6 @@
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
enum Character {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL union expects at least one union variant
--> $DIR/enum_no_fields.rs:4:1
|
4 | enum Character {}
| ^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -0,0 +1,14 @@
use juniper::{GraphQLEnum, GraphQLUnion};
#[derive(GraphQLEnum)]
pub enum Test {
A,
B,
}
#[derive(GraphQLUnion)]
enum Character {
Test(Test),
}
fn main() {}

View file

@ -0,0 +1,17 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/enum_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied
--> $DIR/enum_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<__S>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,9 @@
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
enum Character {
A(u8),
B(u8),
}
fn main() {}

View file

@ -0,0 +1,10 @@
error: GraphQL union must have a different type for each union variant
--> $DIR/enum_same_type_pretty.rs:4:1
|
4 | / enum Character {
5 | | A(u8),
6 | | B(u8),
7 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,4 +1,6 @@
#[derive(juniper::GraphQLUnion)]
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
enum Character {
A(std::string::String),
B(String),

View file

@ -0,0 +1,10 @@
error[E0119]: conflicting implementations of trait `<Character as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
--> $DIR/enum_same_type_ugly.rs:3:10
|
3 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `std::string::String`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,18 @@
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLUnion)]
enum Character1 {
A { human: Human },
}
#[derive(GraphQLUnion)]
enum Character2 {
A(Human, u8),
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +1,15 @@
error: GraphQL union enum allows only unnamed variants with a single field, e.g. `Some(T)`
--> $DIR/enum_wrong_variant_field.rs:5:5
|
5 | A { human: Human },
| ^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
error: GraphQL union enum allows only unnamed variants with a single field, e.g. `Some(T)`
--> $DIR/enum_wrong_variant_field.rs:10:6
|
10 | A(Human, u8),
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,23 +0,0 @@
#[derive(juniper::GraphQLEnum)]
#[graphql(context = ())]
pub enum Test {
A,
B,
}
enum Character {
Test(Test),
}
#[juniper::graphql_union]
impl Character {
fn resolve(&self) {
match self {
Test => match *self {
Character::Test(ref h) => Some(h),
},
}
}
}
fn main() {}

View file

@ -1,8 +0,0 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/impl_enum_field.rs:12:1
|
12 | #[juniper::graphql_union]
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,10 +0,0 @@
enum Character {}
#[juniper::graphql_union]
impl Character {
fn resolve(&self) {
match self {}
}
}
fn main() {}

View file

@ -1,9 +0,0 @@
error: GraphQL union expects at least one field
--> $DIR/impl_no_fields.rs:5:5
|
5 | / fn resolve(&self) {
6 | | match self {}
7 | | }
| |_____^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,32 +0,0 @@
// NOTICE: This can not be tested. Implementing Into<T> for each
// variant is not possible since we did not created the
// enum. Therefore, it is possible that the enum already has existing
// Into<T> implementations.
#[derive(juniper::GraphQLObject)]
pub struct Test {
test: String,
}
enum Character {
A(Test),
B(Test),
}
#[juniper::graphql_union]
impl Character {
fn resolve(&self) {
match self {
Test => match *self {
Character::A(ref h) => Some(h),
_ => None,
},
Test => match *self {
Character::B(ref h) => Some(h),
_ => None,
},
}
}
}
fn main() {}

View file

@ -0,0 +1,18 @@
use juniper::{GraphQLObject, GraphQLUnion};
#[derive(GraphQLUnion)]
#[graphql(on Human = __Character::a)]
struct __Character;
impl __Character {
fn a(&self, _: &()) -> Option<&Human> {
None
}
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +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 GraphQLs introspection system.
--> $DIR/struct_name_double_underscored.rs:5:8
|
5 | struct __Character;
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,6 @@
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
struct Character;
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL union expects at least one union variant
--> $DIR/struct_no_fields.rs:4:1
|
4 | struct Character;
| ^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -0,0 +1,19 @@
use juniper::{GraphQLEnum, GraphQLUnion};
#[derive(GraphQLEnum)]
pub enum Test {
A,
B,
}
#[derive(GraphQLUnion)]
#[graphql(on Test = Character::a)]
struct Character;
impl Character {
fn a(&self, _: &()) -> Option<&Test> {
None
}
}
fn main() {}

View file

@ -0,0 +1,17 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/struct_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied
--> $DIR/struct_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<__S>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,18 @@
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
#[graphql(on i32 = Character::a)]
#[graphql(on i32 = Character::b)]
struct Character;
impl Character {
fn a(&self, _: &()) -> Option<&i32> {
None
}
fn b(&self, _: &()) -> Option<&i32> {
None
}
}
fn main() {}

View file

@ -0,0 +1,5 @@
error: duplicated attribute
--> $DIR/struct_same_type_pretty.rs:5:14
|
5 | #[graphql(on i32 = Character::b)]
| ^^^

View file

@ -0,0 +1,18 @@
use juniper::GraphQLUnion;
#[derive(GraphQLUnion)]
#[graphql(on String = Character::a)]
#[graphql(on std::string::String = Character::b)]
struct Character;
impl Character {
fn a(&self, _: &()) -> Option<&String> {
None
}
fn b(&self, _: &()) -> Option<&String> {
None
}
}
fn main() {}

View file

@ -0,0 +1,10 @@
error[E0119]: conflicting implementations of trait `<Character as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
--> $DIR/struct_same_type_ugly.rs:3:10
|
3 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `std::string::String`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,35 @@
use juniper::{graphql_union, FromContext, GraphQLObject};
#[graphql_union]
trait Character {
fn a(&self, ctx: &SubContext) -> Option<&Human>;
fn b(&self, ctx: &CustomContext) -> Option<&Droid>;
}
#[derive(GraphQLObject)]
#[graphql(context = CustomContext)]
pub struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(context = CustomContext)]
pub struct Droid {
id: String,
primary_function: String,
}
pub struct CustomContext;
impl juniper::Context for CustomContext {}
pub struct SubContext;
impl juniper::Context for SubContext {}
impl FromContext<CustomContext> for SubContext {
fn from(_: &CustomContext) -> &Self {
&Self
}
}
fn main() {}

View file

@ -0,0 +1,18 @@
error[E0277]: the trait bound `CustomContext: juniper::executor::FromContext<SubContext>` is not satisfied
--> $DIR/trait_fail_infer_context.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `juniper::executor::FromContext<SubContext>` is not implemented for `CustomContext`
|
= note: required by `juniper::executor::FromContext::from`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> $DIR/trait_fail_infer_context.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ expected struct `CustomContext`, found struct `SubContext`
|
= note: expected reference `&CustomContext`
found reference `&SubContext`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,13 @@
use juniper::{graphql_union, GraphQLObject};
#[graphql_union(on Human = some_fn)]
trait Character {
fn a(&self) -> Option<&Human>;
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +1,8 @@
error: GraphQL union trait method `a` conflicts with the external resolver function `some_fn` declared on the trait to resolve the variant type `Human`
--> $DIR/trait_method_conflicts_with_external_resolver_fn.rs:5:5
|
5 | fn a(&self) -> Option<&Human>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
= note: use `#[graphql_union(ignore)]` attribute to ignore this trait method for union variants resolution

View file

@ -0,0 +1,13 @@
use juniper::{graphql_union, GraphQLObject};
#[graphql_union]
trait __Character {
fn a(&self) -> Option<&Human>;
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +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 GraphQLs introspection system.
--> $DIR/trait_name_double_underscored.rs:4:7
|
4 | trait __Character {
| ^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,6 @@
use juniper::graphql_union;
#[graphql_union]
trait Character {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL union expects at least one union variant
--> $DIR/trait_no_fields.rs:4:1
|
4 | trait Character {}
| ^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -0,0 +1,14 @@
use juniper::{graphql_union, GraphQLEnum};
#[derive(GraphQLEnum)]
pub enum Test {
A,
B,
}
#[graphql_union]
trait Character {
fn a(&self) -> Option<&Test>;
}
fn main() {}

View file

@ -0,0 +1,17 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/trait_non_object_variant.rs:9:1
|
9 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied
--> $DIR/trait_non_object_variant.rs:9:1
|
9 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<__S>` is not implemented for `Test`
|
= note: required by `juniper::types::marker::GraphQLObjectType::mark`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,9 @@
use juniper::graphql_union;
#[graphql_union]
trait Character {
fn a(&self) -> Option<&u8>;
fn b(&self) -> Option<&u8>;
}
fn main() {}

View file

@ -0,0 +1,10 @@
error: GraphQL union must have a different type for each union variant
--> $DIR/trait_same_type_pretty.rs:4:1
|
4 | / trait Character {
5 | | fn a(&self) -> Option<&u8>;
6 | | fn b(&self) -> Option<&u8>;
7 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -0,0 +1,9 @@
use juniper::graphql_union;
#[graphql_union]
trait Character {
fn a(&self) -> Option<&String>;
fn b(&self) -> Option<&std::string::String>;
}
fn main() {}

View file

@ -0,0 +1,10 @@
error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
--> $DIR/trait_same_type_ugly.rs:3:1
|
3 | #[graphql_union]
| ^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `std::string::String`
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,14 @@
use juniper::{graphql_union, GraphQLObject};
#[graphql_union]
trait Character {
#[graphql_union(with = something)]
fn a(&self) -> Option<&Human>;
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +1,8 @@
error: GraphQL union cannot use #[graphql_union(with = ...)] attribute on a trait method
--> $DIR/trait_with_attr_on_method.rs:5:21
|
5 | #[graphql_union(with = something)]
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions
= note: instead use #[graphql_union(ignore)] on the method with #[graphql_union(on ... = ...)] on the trait itself

View file

@ -0,0 +1,13 @@
use juniper::{graphql_union, GraphQLObject};
#[graphql_union]
trait Character {
fn a(&self, ctx: &(), rand: u8) -> Option<&Human>;
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL union expects trait method to accept `&self` only and, optionally, `&Context`
--> $DIR/trait_wrong_method_input_args.rs:5:10
|
5 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -0,0 +1,13 @@
use juniper::{graphql_union, GraphQLObject};
#[graphql_union]
trait Character {
fn a(&self) -> &Human;
}
#[derive(GraphQLObject)]
pub struct Human {
id: String,
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL union expects trait method return type to be `Option<&VariantType>` only
--> $DIR/trait_wrong_method_return_type.rs:5:20
|
5 | fn a(&self) -> &Human;
| ^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Unions

View file

@ -1,12 +1,13 @@
[package]
name = "juniper_tests"
version = "0.1.0"
publish = false
edition = "2018"
publish = false
[dependencies]
juniper = { path = "../../juniper" }
derive_more = "0.99.7"
futures = "0.3.1"
juniper = { path = "../../juniper" }
[dev-dependencies]
serde_json = { version = "1" }

View file

@ -1,275 +0,0 @@
// Test for union's derive macro
#[cfg(test)]
use fnv::FnvHashMap;
#[cfg(test)]
use juniper::{
self, execute, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType, RootNode,
Value, Variables,
};
#[derive(juniper::GraphQLObject)]
pub struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
pub struct Droid {
id: String,
primary_function: String,
}
#[derive(juniper::GraphQLUnion)]
#[graphql(description = "A Collection of things")]
pub enum Character {
One(Human),
Two(Droid),
}
// Context Test
pub struct CustomContext {
is_left: bool,
}
impl juniper::Context for CustomContext {}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = CustomContext)]
pub struct HumanContext {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = CustomContext)]
pub struct DroidContext {
id: String,
primary_function: String,
}
/// A Collection of things
#[derive(juniper::GraphQLUnion)]
#[graphql(Context = CustomContext)]
pub enum CharacterContext {
One(HumanContext),
Two(DroidContext),
}
// #[juniper::object] compatibility
pub struct HumanCompat {
id: String,
home_planet: String,
}
#[juniper::graphql_object]
impl HumanCompat {
fn id(&self) -> &String {
&self.id
}
fn home_planet(&self) -> &String {
&self.home_planet
}
}
pub struct DroidCompat {
id: String,
primary_function: String,
}
#[juniper::graphql_object]
impl DroidCompat {
fn id(&self) -> &String {
&self.id
}
fn primary_function(&self) -> &String {
&self.primary_function
}
}
#[derive(juniper::GraphQLUnion)]
#[graphql(Context = CustomContext)]
pub enum DifferentContext {
A(DroidContext),
B(Droid),
}
// NOTICE: this can not compile due to generic implementation of GraphQLType<__S>
// #[derive(juniper::GraphQLUnion)]
// pub enum CharacterCompatFail {
// One(HumanCompat),
// Two(DroidCompat),
// }
/// A Collection of things
#[derive(juniper::GraphQLUnion)]
#[graphql(Scalar = juniper::DefaultScalarValue)]
pub enum CharacterCompat {
One(HumanCompat),
Two(DroidCompat),
}
pub struct Query;
#[juniper::graphql_object(
Context = CustomContext,
)]
impl Query {
fn context(&self, ctx: &CustomContext) -> CharacterContext {
if ctx.is_left {
HumanContext {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}
.into()
} else {
DroidContext {
id: "droid-99".to_string(),
primary_function: "run".to_string(),
}
.into()
}
}
}
#[tokio::test]
async fn test_derived_union_doc_macro() {
assert_eq!(
<Character as GraphQLType<DefaultScalarValue>>::name(&()),
Some("Character")
);
let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
let meta = Character::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("Character"));
assert_eq!(
meta.description(),
Some(&"A Collection of things".to_string())
);
}
#[tokio::test]
async fn test_derived_union_doc_string() {
assert_eq!(
<CharacterContext as GraphQLType<DefaultScalarValue>>::name(&()),
Some("CharacterContext")
);
let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
let meta = CharacterContext::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("CharacterContext"));
assert_eq!(
meta.description(),
Some(&"A Collection of things".to_string())
);
}
#[tokio::test]
async fn test_derived_union_left() {
let doc = r#"
{
context {
... on HumanContext {
humanId: id
homePlanet
}
... on DroidContext {
droidId: id
primaryFunction
}
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<CustomContext>::new(),
EmptySubscription::<CustomContext>::new(),
);
assert_eq!(
execute(
doc,
None,
&schema,
&Variables::new(),
&CustomContext { is_left: true }
)
.await,
Ok((
Value::object(
vec![(
"context",
Value::object(
vec![
("humanId", Value::scalar("human-32".to_string())),
("homePlanet", Value::scalar("earth".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}
#[tokio::test]
async fn test_derived_union_right() {
let doc = r#"
{
context {
... on HumanContext {
humanId: id
homePlanet
}
... on DroidContext {
droidId: id
primaryFunction
}
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<CustomContext>::new(),
EmptySubscription::<CustomContext>::new(),
);
assert_eq!(
execute(
doc,
None,
&schema,
&Variables::new(),
&CustomContext { is_left: false }
)
.await,
Ok((
Value::object(
vec![(
"context",
Value::object(
vec![
("droidId", Value::scalar("droid-99".to_string())),
("primaryFunction", Value::scalar("run".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}

View file

@ -1,44 +0,0 @@
// Trait.
#[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
trait Character {
fn as_human(&self) -> Option<&Human> {
None
}
fn as_droid(&self) -> Option<&Droid> {
None
}
}
impl Character for Human {
fn as_human(&self) -> Option<&Human> {
Some(&self)
}
}
impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> {
Some(&self)
}
}
#[juniper::graphql_union]
impl<'a> GraphQLUnion for &'a dyn Character {
fn resolve(&self) {
match self {
Human => self.as_human(),
Droid => self.as_droid(),
}
}
}

View file

@ -2,8 +2,8 @@ mod derive_enum;
mod derive_input_object;
mod derive_object;
mod derive_object_with_raw_idents;
mod derive_union;
mod impl_object;
mod impl_scalar;
mod impl_union;
mod scalar_value_transparent;
mod union_attr;
mod union_derive;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
## Features
- Support raw identifiers in field and argument names. (#[object] macro)
- Support raw identifiers in field and argument names. (`#[object]` macro)
- Most error types now implement `std::error::Error`:
- `GraphQLError`
@ -29,10 +29,21 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
- Derive macro `GraphQLEnum` supports custom context (see [#621](https://github.com/graphql-rust/juniper/pull/621))
- Reworked `#[derive(GraphQLUnion)]` macro ([#666]):
- Applicable to enums and structs.
- Supports custom resolvers.
- Supports generics.
- Supports multiple `#[graphql]` attributes.
- New `#[graphql_union]` macro ([#666]):
- Applicable to traits.
- Supports custom resolvers.
- Supports generics.
- Supports multiple `#[graphql_union]` attributes.
- Better error messages for all proc macros (see
[#631](https://github.com/graphql-rust/juniper/pull/631)
- Improved lookahead visibility for aliased fields (see [#662](https://github.com/graphql-rust/juniper/pull/631))
- Improved lookahead visibility for aliased fields (see [#662](https://github.com/graphql-rust/juniper/pull/631))
## Breaking Changes
@ -45,10 +56,10 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
- Remove deprecated `ScalarValue` custom derive (renamed to GraphQLScalarValue)
- `graphql_union!` macro removed, replaced by `#[graphql_union]` proc macro
- `graphql_union!` macro removed, replaced by `#[graphql_union]` proc macro and custom resolvers for the `#[derive(GraphQLUnion)]` macro.
- The `#[derive(GraphQLUnion)]` macro doesn't generate `From` impls for enum variants anymore. Consider using the [`derive_more`](https//docs.rs/derive_more) crate directly ([#666]).
- ScalarRefValue trait removed
Trait was not required.
- `ScalarRefValue` trait removed. Trait was not required.
- Changed return type of GraphQLType::resolve to `ExecutionResult`
This was done to unify the return type of all resolver methods
@ -59,7 +70,7 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
add subscription type to `RootNode`,
add subscription endpoint to `playground_source()`
- Putting a scalar type into a string is not allowed anymore, e..g,
- Putting a scalar type into a string is not allowed anymore, e.g.
`#[graphql(scalar = "DefaultScalarValue")]`. Only
`#[derive(GraphQLInputObject)]` supported this syntax. The
refactoring of GraphQLInputObject allowed to drop the support
@ -75,6 +86,8 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
- When using LookAheadMethods to access child selections, children are always found using their alias if it exists rather than their name (see [#662](https://github.com/graphql-rust/juniper/pull/631)). These methods are also deprecated in favour of the new `children` method.
[#666]: https://github.com/graphql-rust/juniper/pull/666
# [[0.14.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.2)
- Fix incorrect validation with non-executed operations [#455](https://github.com/graphql-rust/juniper/issues/455)

View file

@ -43,6 +43,7 @@ futures = "0.3.1"
indexmap = { version = "1.0.0", features = ["serde-1"] }
serde = { version = "1.0.8", features = ["derive"] }
serde_json = { version="1.0.2", optional = true }
static_assertions = "1.1"
url = { version = "2", optional = true }
uuid = { version = "0.8", optional = true }

View file

@ -242,8 +242,9 @@ impl<S> IntoFieldError<S> for FieldError<S> {
}
#[doc(hidden)]
pub trait IntoResolvable<'a, S, T: GraphQLType<S>, C>: Sized
pub trait IntoResolvable<'a, S, T, C>
where
T: GraphQLType<S>,
S: ScalarValue,
{
#[doc(hidden)]
@ -404,7 +405,7 @@ where
pub fn resolve_with_ctx<NewCtxT, T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
where
NewCtxT: FromContext<CtxT>,
T: GraphQLType<S, Context = NewCtxT>,
T: GraphQLType<S, Context = NewCtxT> + ?Sized,
{
self.replaced_context(<NewCtxT as FromContext<CtxT>>::from(self.context))
.resolve(info, value)
@ -413,7 +414,7 @@ where
/// Resolve a single arbitrary value into an `ExecutionResult`
pub fn resolve<T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
where
T: GraphQLType<S, Context = CtxT>,
T: GraphQLType<S, Context = CtxT> + ?Sized,
{
value.resolve(info, self.current_selection_set, self)
}
@ -421,7 +422,7 @@ where
/// Resolve a single arbitrary value into an `ExecutionResult`
pub async fn resolve_async<T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
where
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync + ?Sized,
T::TypeInfo: Send + Sync,
CtxT: Send + Sync,
S: Send + Sync,
@ -468,18 +469,15 @@ where
/// If the field fails to resolve, `null` will be returned.
pub async fn resolve_into_value_async<T>(&self, info: &T::TypeInfo, value: &T) -> Value<S>
where
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync + ?Sized,
T::TypeInfo: Send + Sync,
CtxT: Send + Sync,
S: Send + Sync,
{
match self.resolve_async(info, value).await {
Ok(v) => v,
Err(e) => {
self.push_error(e);
Value::null()
}
}
self.resolve_async(info, value).await.unwrap_or_else(|e| {
self.push_error(e);
Value::null()
})
}
/// Derive a new executor by replacing the context
@ -1103,7 +1101,7 @@ where
/// construct its metadata and store it.
pub fn get_type<T>(&mut self, info: &T::TypeInfo) -> Type<'r>
where
T: GraphQLType<S>,
T: GraphQLType<S> + ?Sized,
{
if let Some(name) = T::name(info) {
let validated_name = name.parse::<Name>().unwrap();
@ -1124,7 +1122,7 @@ where
/// Create a field with the provided name
pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S>
where
T: GraphQLType<S>,
T: GraphQLType<S> + ?Sized,
{
Field {
name: name.to_owned(),
@ -1156,7 +1154,7 @@ where
/// Create an argument with the provided name
pub fn arg<T>(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S>
where
T: GraphQLType<S> + FromInputValue<S>,
T: GraphQLType<S> + FromInputValue<S> + ?Sized,
{
Argument::new(name, self.get_type::<T>(info))
}
@ -1172,7 +1170,7 @@ where
info: &T::TypeInfo,
) -> Argument<'r, S>
where
T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S>,
T: GraphQLType<S> + ToInputValue<S> + FromInputValue<S> + ?Sized,
{
Argument::new(name, self.get_type::<Option<T>>(info)).default_value(value.to_input_value())
}
@ -1188,20 +1186,23 @@ where
/// This expects the type to implement `FromInputValue`.
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
where
T: FromInputValue<S> + GraphQLType<S> + ParseScalarValue<S> + 'r,
T: FromInputValue<S> + GraphQLType<S> + ParseScalarValue<S> + ?Sized + 'r,
{
let name = T::name(info).expect("Scalar types must be named. Implement name()");
ScalarMeta::new::<T>(Cow::Owned(name.to_string()))
}
/// Create a list meta type
pub fn build_list_type<T: GraphQLType<S>>(&mut self, info: &T::TypeInfo) -> ListMeta<'r> {
pub fn build_list_type<T: GraphQLType<S> + ?Sized>(
&mut self,
info: &T::TypeInfo,
) -> ListMeta<'r> {
let of_type = self.get_type::<T>(info);
ListMeta::new(of_type)
}
/// Create a nullable meta type
pub fn build_nullable_type<T: GraphQLType<S>>(
pub fn build_nullable_type<T: GraphQLType<S> + ?Sized>(
&mut self,
info: &T::TypeInfo,
) -> NullableMeta<'r> {
@ -1219,7 +1220,7 @@ where
fields: &[Field<'r, S>],
) -> ObjectMeta<'r, S>
where
T: GraphQLType<S>,
T: GraphQLType<S> + ?Sized,
{
let name = T::name(info).expect("Object types must be named. Implement name()");
@ -1235,7 +1236,7 @@ where
values: &[EnumValue],
) -> EnumMeta<'r, S>
where
T: FromInputValue<S> + GraphQLType<S>,
T: FromInputValue<S> + GraphQLType<S> + ?Sized,
{
let name = T::name(info).expect("Enum types must be named. Implement name()");
@ -1250,7 +1251,7 @@ where
fields: &[Field<'r, S>],
) -> InterfaceMeta<'r, S>
where
T: GraphQLType<S>,
T: GraphQLType<S> + ?Sized,
{
let name = T::name(info).expect("Interface types must be named. Implement name()");
@ -1262,7 +1263,7 @@ where
/// Create a union meta type
pub fn build_union_type<T>(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r>
where
T: GraphQLType<S>,
T: GraphQLType<S> + ?Sized,
{
let name = T::name(info).expect("Union types must be named. Implement name()");
@ -1276,7 +1277,7 @@ where
args: &[Argument<'r, S>],
) -> InputObjectMeta<'r, S>
where
T: FromInputValue<S> + GraphQLType<S>,
T: FromInputValue<S> + GraphQLType<S> + ?Sized,
{
let name = T::name(info).expect("Input object types must be named. Implement name()");

View file

@ -167,6 +167,7 @@ mod union {
value::Value,
};
#[crate::graphql_union_internal]
trait Pet {
fn as_dog(&self) -> Option<&Dog> {
None
@ -176,16 +177,6 @@ mod union {
}
}
#[crate::graphql_union_internal]
impl<'a> GraphQLUnion for &'a dyn Pet {
fn resolve(&self) {
match self {
Dog => self.as_dog(),
Cat => self.as_cat(),
}
}
}
struct Dog {
name: String,
woofs: bool,

View file

@ -93,6 +93,8 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
#![doc(html_root_url = "https://docs.rs/juniper/0.14.2")]
#![warn(missing_docs)]
use std::fmt;
#[doc(hidden)]
pub extern crate serde;
@ -111,6 +113,13 @@ extern crate uuid;
#[cfg(any(test, feature = "bson"))]
extern crate bson;
// These are required by the code generated via the `juniper_codegen` macros.
#[doc(hidden)]
pub use {futures, static_assertions as sa};
#[doc(inline)]
pub use futures::future::BoxFuture;
// Depend on juniper_codegen and re-export everything in it.
// This allows users to just depend on juniper and get the derive
// functionality automatically.
@ -124,7 +133,7 @@ pub use juniper_codegen::{
use juniper_codegen::{
graphql_object_internal, graphql_scalar_internal, graphql_subscription_internal,
graphql_union_internal, GraphQLEnumInternal, GraphQLInputObjectInternal,
GraphQLScalarValueInternal,
GraphQLScalarValueInternal, GraphQLUnionInternal,
};
#[macro_use]
@ -161,7 +170,6 @@ use crate::{
parser::{parse_document_source, ParseError, Spanning},
validation::{validate_input_values, visit_all_rules, ValidatorContext},
};
use std::fmt;
pub use crate::{
ast::{FromInputValue, InputValue, Selection, ToInputValue, Type},
@ -179,7 +187,7 @@ pub use crate::{
types::{
async_await::GraphQLTypeAsync,
base::{Arguments, GraphQLType, TypeKind},
marker,
marker::{self, GraphQLUnion},
scalars::{EmptyMutation, EmptySubscription, ID},
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
},
@ -187,9 +195,6 @@ pub use crate::{
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value},
};
/// A pinned, boxed future that can be polled.
pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a + Send>>;
/// An error that prevented query execution
#[derive(Debug, PartialEq)]
#[allow(missing_docs)]

View file

@ -14,81 +14,70 @@ use std::marker::PhantomData;
use crate::{
ast::InputValue,
graphql_object_internal,
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
value::{DefaultScalarValue, Object, Value},
GraphQLUnionInternal,
};
struct Concrete;
enum CustomName {
Concrete(Concrete),
}
enum WithLifetime<'a> {
Int(PhantomData<&'a i32>),
}
enum WithGenerics<T> {
Generic(T),
}
enum DescriptionFirst {
Concrete(Concrete),
}
struct Root;
#[crate::graphql_object_internal]
#[graphql_object_internal]
impl Concrete {
fn simple() -> i32 {
123
}
}
#[crate::graphql_union_internal(name = "ACustomNamedUnion")]
impl CustomName {
fn resolve(&self) {
match self {
Concrete => match *self {
CustomName::Concrete(ref c) => Some(c),
},
}
}
#[derive(GraphQLUnionInternal)]
#[graphql(name = "ACustomNamedUnion", scalar = DefaultScalarValue)]
enum CustomName {
Concrete(Concrete),
}
#[derive(GraphQLUnionInternal)]
#[graphql(on Concrete = WithLifetime::resolve, scalar = DefaultScalarValue)]
enum WithLifetime<'a> {
#[graphql(ignore)]
Int(PhantomData<&'a i32>),
}
#[crate::graphql_union_internal]
impl<'a> WithLifetime<'a> {
fn resolve(&self) {
match self {
Concrete => match *self {
WithLifetime::Int(_) => Some(&Concrete),
},
fn resolve(&self, _: &()) -> Option<&Concrete> {
if matches!(self, Self::Int(_)) {
Some(&Concrete)
} else {
None
}
}
}
#[crate::graphql_union_internal]
#[derive(GraphQLUnionInternal)]
#[graphql(on Concrete = WithGenerics::resolve, scalar = DefaultScalarValue)]
enum WithGenerics<T> {
#[graphql(ignore)]
Generic(T),
}
impl<T> WithGenerics<T> {
fn resolve(&self) {
match self {
Concrete => match *self {
WithGenerics::Generic(_) => Some(&Concrete),
},
fn resolve(&self, _: &()) -> Option<&Concrete> {
if matches!(self, Self::Generic(_)) {
Some(&Concrete)
} else {
None
}
}
}
#[crate::graphql_union_internal(description = "A description")]
impl DescriptionFirst {
fn resolve(&self) {
match self {
Concrete => match *self {
DescriptionFirst::Concrete(ref c) => Some(c),
},
}
}
#[derive(GraphQLUnionInternal)]
#[graphql(description = "A description", scalar = DefaultScalarValue)]
enum DescriptionFirst {
Concrete(Concrete),
}
struct Root;
// FIXME: make async work
#[crate::graphql_object_internal(noasync)]
impl<'a> Root {

View file

@ -572,8 +572,10 @@ where
S: ScalarValue,
{
/// Build a new input type with the specified name and input fields
pub fn new<T: FromInputValue<S>>(name: Cow<'a, str>, input_fields: &[Argument<'a, S>]) -> Self
where {
pub fn new<T>(name: Cow<'a, str>, input_fields: &[Argument<'a, S>]) -> Self
where
T: FromInputValue<S> + ?Sized,
{
InputObjectMeta {
name,
description: None,

View file

@ -98,7 +98,7 @@ fn resolve_selection_set_into_async<'a, 'e, T, CtxT, S>(
executor: &'e Executor<'e, 'e, CtxT, S>,
) -> BoxFuture<'a, Value<S>>
where
T: GraphQLTypeAsync<S, Context = CtxT>,
T: GraphQLTypeAsync<S, Context = CtxT> + ?Sized,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
@ -129,7 +129,7 @@ pub(crate) async fn resolve_selection_set_into_async_recursive<'a, T, CtxT, S>(
executor: &'a Executor<'a, 'a, CtxT, S>,
) -> Value<S>
where
T: GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
T: GraphQLTypeAsync<S, Context = CtxT> + Send + Sync + ?Sized,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,

View file

@ -230,7 +230,7 @@ impl GraphQLType<DefaultScalarValue> for User
```
*/
pub trait GraphQLType<S = DefaultScalarValue>: Sized
pub trait GraphQLType<S = DefaultScalarValue>
where
S: ScalarValue,
{
@ -355,7 +355,7 @@ pub(crate) fn resolve_selection_set_into<T, CtxT, S>(
result: &mut Object<S>,
) -> bool
where
T: GraphQLType<S, Context = CtxT>,
T: GraphQLType<S, Context = CtxT> + ?Sized,
S: ScalarValue,
{
let meta_type = executor

View file

@ -1,15 +1,11 @@
use crate::{
ast::{FromInputValue, InputValue, Selection, ToInputValue},
executor::ExecutionResult,
executor::{ExecutionResult, Executor, Registry},
schema::meta::MetaType,
types::{async_await::GraphQLTypeAsync, base::GraphQLType},
value::{ScalarValue, Value},
};
use crate::{
executor::{Executor, Registry},
types::base::GraphQLType,
};
impl<S, T, CtxT> GraphQLType<S> for Option<T>
where
S: ScalarValue,
@ -42,6 +38,30 @@ where
}
}
impl<S, T, CtxT> GraphQLTypeAsync<S> for Option<T>
where
T: GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = async move {
let value = match *self {
Some(ref obj) => executor.resolve_into_value_async(info, obj).await,
None => Value::null(),
};
Ok(value)
};
Box::pin(f)
}
}
impl<S, T> FromInputValue<S> for Option<T>
where
T: FromInputValue<S>,
@ -50,10 +70,7 @@ where
fn from_input_value<'a>(v: &'a InputValue<S>) -> Option<Option<T>> {
match v {
&InputValue::Null => Some(None),
v => match v.convert() {
Some(x) => Some(Some(x)),
None => None,
},
v => v.convert().map(Some),
}
}
}
@ -100,6 +117,24 @@ where
}
}
impl<S, T, CtxT> GraphQLTypeAsync<S> for Vec<T>
where
T: GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = resolve_into_list_async(executor, info, self.iter());
Box::pin(f)
}
}
impl<T, S> FromInputValue<S> for Vec<T>
where
T: FromInputValue<S>,
@ -117,13 +152,7 @@ where {
None
}
}
ref other => {
if let Some(e) = other.convert() {
Some(vec![e])
} else {
None
}
}
ref other => other.convert().map(|e| vec![e]),
}
}
}
@ -134,11 +163,11 @@ where
S: ScalarValue,
{
fn to_input_value(&self) -> InputValue<S> {
InputValue::list(self.iter().map(|v| v.to_input_value()).collect())
InputValue::list(self.iter().map(T::to_input_value).collect())
}
}
impl<'a, S, T, CtxT> GraphQLType<S> for &'a [T]
impl<S, T, CtxT> GraphQLType<S> for [T]
where
S: ScalarValue,
T: GraphQLType<S, Context = CtxT>,
@ -167,25 +196,43 @@ where
}
}
impl<S, T, CtxT> GraphQLTypeAsync<S> for [T]
where
T: GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = resolve_into_list_async(executor, info, self.iter());
Box::pin(f)
}
}
impl<'a, T, S> ToInputValue<S> for &'a [T]
where
T: ToInputValue<S>,
S: ScalarValue,
{
fn to_input_value(&self) -> InputValue<S> {
InputValue::list(self.iter().map(|v| v.to_input_value()).collect())
InputValue::list(self.iter().map(T::to_input_value).collect())
}
}
fn resolve_into_list<S, T, I>(
fn resolve_into_list<'t, S, T, I>(
executor: &Executor<T::Context, S>,
info: &T::TypeInfo,
iter: I,
) -> ExecutionResult<S>
where
S: ScalarValue,
I: Iterator<Item = T> + ExactSizeIterator,
T: GraphQLType<S>,
I: Iterator<Item = &'t T> + ExactSizeIterator,
T: GraphQLType<S> + ?Sized + 't,
{
let stop_on_null = executor
.current_type()
@ -195,30 +242,26 @@ where
let mut result = Vec::with_capacity(iter.len());
for o in iter {
match executor.resolve(info, &o) {
Ok(value) => {
if stop_on_null && value.is_null() {
return Ok(value);
} else {
result.push(value)
}
}
Err(e) => return Err(e),
let val = executor.resolve(info, o)?;
if stop_on_null && val.is_null() {
return Ok(val);
} else {
result.push(val)
}
}
Ok(Value::list(result))
}
async fn resolve_into_list_async<'a, S, T, I>(
async fn resolve_into_list_async<'a, 't, S, T, I>(
executor: &'a Executor<'a, 'a, T::Context, S>,
info: &'a T::TypeInfo,
items: I,
) -> ExecutionResult<S>
where
S: ScalarValue + Send + Sync,
I: Iterator<Item = T> + ExactSizeIterator,
T: crate::GraphQLTypeAsync<S>,
I: Iterator<Item = &'t T> + ExactSizeIterator,
T: GraphQLTypeAsync<S> + ?Sized + 't,
T::TypeInfo: Send + Sync,
T::Context: Send + Sync,
{
@ -231,8 +274,7 @@ where
.expect("Current type is not a list type")
.is_non_null();
let iter =
items.map(|item| async move { executor.resolve_into_value_async(info, &item).await });
let iter = items.map(|item| async move { executor.resolve_into_value_async(info, item).await });
let mut futures = FuturesOrdered::from_iter(iter);
let mut values = Vec::with_capacity(futures.len());
@ -245,63 +287,3 @@ where
Ok(Value::list(values))
}
impl<S, T, CtxT> crate::GraphQLTypeAsync<S> for Vec<T>
where
T: crate::GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = resolve_into_list_async(executor, info, self.iter());
Box::pin(f)
}
}
impl<S, T, CtxT> crate::GraphQLTypeAsync<S> for &[T]
where
T: crate::GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = resolve_into_list_async(executor, info, self.iter());
Box::pin(f)
}
}
impl<S, T, CtxT> crate::GraphQLTypeAsync<S> for Option<T>
where
T: crate::GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = async move {
let value = match *self {
Some(ref obj) => executor.resolve_into_value_async(info, obj).await,
None => Value::null(),
};
Ok(value)
};
Box::pin(f)
}
}

View file

@ -23,6 +23,30 @@ pub trait GraphQLObjectType<S: ScalarValue>: GraphQLType<S> {
fn mark() {}
}
/// Maker trait for [GraphQL unions][1].
///
/// This trait extends the [`GraphQLType`] and is only used to mark [union][1]. During compile this
/// addition information is required to prevent unwanted structure compiling. If an object requires
/// this trait instead of the [`GraphQLType`], then it explicitly requires [GraphQL unions][1].
/// Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and [interfaces][6]) are
/// not allowed.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [2]: https://spec.graphql.org/June2018/#sec-Scalars
/// [3]: https://spec.graphql.org/June2018/#sec-Enums
/// [4]: https://spec.graphql.org/June2018/#sec-Objects
/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects
/// [6]: https://spec.graphql.org/June2018/#sec-Interfaces
pub trait GraphQLUnion: GraphQLType {
/// An arbitrary function without meaning.
///
/// May contain compile timed check logic which ensures that types are used correctly according
/// to the [GraphQL specification][1].
///
/// [1]: https://spec.graphql.org/June2018/
fn mark() {}
}
/// Marker trait for types which can be used as output types.
///
/// The GraphQL specification differentiates between input and output

View file

@ -1,17 +1,21 @@
use crate::ast::{FromInputValue, InputValue, Selection, ToInputValue};
use std::{fmt::Debug, sync::Arc};
use crate::{
ast::{FromInputValue, InputValue, Selection, ToInputValue},
executor::{ExecutionResult, Executor, Registry},
schema::meta::MetaType,
types::base::{Arguments, GraphQLType},
types::{
async_await::GraphQLTypeAsync,
base::{Arguments, GraphQLType},
},
value::ScalarValue,
BoxFuture,
};
impl<S, T, CtxT> GraphQLType<S> for Box<T>
where
S: ScalarValue,
T: GraphQLType<S, Context = CtxT>,
T: GraphQLType<S, Context = CtxT> + ?Sized,
{
type Context = CtxT;
type TypeInfo = T::TypeInfo;
@ -57,6 +61,23 @@ where
}
}
impl<S, T, CtxT> crate::GraphQLTypeAsync<S> for Box<T>
where
T: GraphQLTypeAsync<S, Context = CtxT> + ?Sized,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> BoxFuture<'a, ExecutionResult<S>> {
(**self).resolve_async(info, selection_set, executor)
}
}
impl<T, S> FromInputValue<S> for Box<T>
where
S: ScalarValue,
@ -83,7 +104,7 @@ where
impl<'e, S, T, CtxT> GraphQLType<S> for &'e T
where
S: ScalarValue,
T: GraphQLType<S, Context = CtxT>,
T: GraphQLType<S, Context = CtxT> + ?Sized,
{
type Context = CtxT;
type TypeInfo = T::TypeInfo;
@ -129,10 +150,10 @@ where
}
}
impl<'e, S, T> crate::GraphQLTypeAsync<S> for &'e T
impl<'e, S, T> GraphQLTypeAsync<S> for &'e T
where
S: ScalarValue + Send + Sync,
T: crate::GraphQLTypeAsync<S>,
T: GraphQLTypeAsync<S> + ?Sized,
T::TypeInfo: Send + Sync,
T::Context: Send + Sync,
{
@ -142,8 +163,8 @@ where
field_name: &'b str,
arguments: &'b Arguments<S>,
executor: &'b Executor<Self::Context, S>,
) -> crate::BoxFuture<'b, ExecutionResult<S>> {
crate::GraphQLTypeAsync::resolve_field_async(&**self, info, field_name, arguments, executor)
) -> BoxFuture<'b, ExecutionResult<S>> {
GraphQLTypeAsync::resolve_field_async(&**self, info, field_name, arguments, executor)
}
fn resolve_async<'a>(
@ -151,8 +172,8 @@ where
info: &'a Self::TypeInfo,
selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
crate::GraphQLTypeAsync::resolve_async(&**self, info, selection_set, executor)
) -> BoxFuture<'a, ExecutionResult<S>> {
GraphQLTypeAsync::resolve_async(&**self, info, selection_set, executor)
}
}
@ -169,7 +190,7 @@ where
impl<S, T> GraphQLType<S> for Arc<T>
where
S: ScalarValue,
T: GraphQLType<S>,
T: GraphQLType<S> + ?Sized,
{
type Context = T::Context;
type TypeInfo = T::TypeInfo;
@ -215,36 +236,19 @@ where
}
}
impl<S, T, CtxT> crate::GraphQLTypeAsync<S> for Box<T>
impl<'e, S, T> GraphQLTypeAsync<S> for Arc<T>
where
T: crate::GraphQLTypeAsync<S, Context = CtxT>,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
T: GraphQLTypeAsync<S> + ?Sized,
<T as GraphQLType<S>>::TypeInfo: Send + Sync,
<T as GraphQLType<S>>::Context: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, crate::ExecutionResult<S>> {
(**self).resolve_async(info, selection_set, executor)
}
}
impl<'e, S, T> crate::GraphQLTypeAsync<S> for std::sync::Arc<T>
where
S: ScalarValue + Send + Sync,
T: crate::GraphQLTypeAsync<S>,
<T as crate::types::base::GraphQLType<S>>::TypeInfo: Send + Sync,
<T as crate::types::base::GraphQLType<S>>::Context: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, crate::ExecutionResult<S>> {
) -> BoxFuture<'a, ExecutionResult<S>> {
(**self).resolve_async(info, selection_set, executor)
}
}

View file

@ -192,7 +192,7 @@ where
})
}
impl<'a, S> GraphQLType<S> for &'a str
impl<S> GraphQLType<S> for str
where
S: ScalarValue,
{
@ -216,11 +216,11 @@ where
_: Option<&[Selection<S>]>,
_: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
Ok(Value::scalar(String::from(*self)))
Ok(Value::scalar(String::from(self)))
}
}
impl<'e, S> crate::GraphQLTypeAsync<S> for &'e str
impl<S> crate::GraphQLTypeAsync<S> for str
where
S: ScalarValue + Send + Sync,
{
@ -325,11 +325,11 @@ where
/// If you instantiate `RootNode` with this as the mutation, no mutation will be
/// generated for the schema.
#[derive(Debug)]
pub struct EmptyMutation<T> {
pub struct EmptyMutation<T: ?Sized = ()> {
phantom: PhantomData<T>,
}
impl<T> EmptyMutation<T> {
impl<T: ?Sized> EmptyMutation<T> {
/// Construct a new empty mutation
pub fn new() -> EmptyMutation<T> {
EmptyMutation {
@ -339,7 +339,7 @@ impl<T> EmptyMutation<T> {
}
// This is safe because `T` is never used.
unsafe impl<T> Send for EmptyMutation<T> {}
unsafe impl<T: ?Sized> Send for EmptyMutation<T> {}
impl<S, T> GraphQLType<S> for EmptyMutation<T>
where
@ -382,14 +382,14 @@ impl<T> Default for EmptyMutation<T> {
///
/// If you instantiate `RootNode` with this as the subscription,
/// no subscriptions will be generated for the schema.
pub struct EmptySubscription<T> {
pub struct EmptySubscription<T: ?Sized = ()> {
phantom: PhantomData<T>,
}
// This is safe due to never using `T`.
unsafe impl<T> Send for EmptySubscription<T> {}
unsafe impl<T: ?Sized> Send for EmptySubscription<T> {}
impl<T> EmptySubscription<T> {
impl<T: ?Sized> EmptySubscription<T> {
/// Construct a new empty subscription
pub fn new() -> Self {
EmptySubscription {

View file

@ -184,7 +184,7 @@ where
'e: 'fut,
'ref_e: 'fut,
'res: 'fut,
T: GraphQLSubscriptionType<S, Context = CtxT>,
T: GraphQLSubscriptionType<S, Context = CtxT> + ?Sized,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,
@ -203,7 +203,7 @@ async fn resolve_selection_set_into_stream_recursive<'i, 'inf, 'ref_e, 'e, 'res,
executor: &'ref_e Executor<'ref_e, 'e, CtxT, S>,
) -> Value<ValuesStream<'res, S>>
where
T: GraphQLSubscriptionType<S, Context = CtxT> + Send + Sync,
T: GraphQLSubscriptionType<S, Context = CtxT> + Send + Sync + ?Sized,
T::TypeInfo: Send + Sync,
S: ScalarValue + Send + Sync,
CtxT: Send + Sync,

View file

@ -1,6 +1,7 @@
[package]
name = "juniper_codegen"
version = "0.14.2"
edition = "2018"
authors = [
"Magnus Hallin <mhallin@fastmail.com>",
"Christoph Herzog <chris@theduke.at>",
@ -9,20 +10,20 @@ description = "Internal custom derive trait for Juniper GraphQL"
license = "BSD-2-Clause"
documentation = "https://docs.rs/juniper"
repository = "https://github.com/graphql-rust/juniper"
edition = "2018"
[badges]
travis-ci = { repository = "graphql-rust/juniper" }
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.1"
syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] }
quote = "1.0.3"
futures = "0.3.1"
proc-macro-error = "1.0.2"
proc-macro2 = "1.0.1"
quote = "1.0.3"
syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] }
[dev-dependencies]
juniper = { version = "0.14.2", path = "../juniper"}
[badges]
travis-ci = { repository = "graphql-rust/juniper" }
derive_more = "0.99.7"
futures = "0.3.1"
juniper = { version = "0.14.2", path = "../juniper" }

View file

@ -58,7 +58,7 @@ pub fn impl_enum(
let _type = match field.fields {
Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(),
_ => {
error.custom(
error.emit_custom(
field.fields.span(),
"all fields of the enum must be unnamed, e.g., None",
);
@ -145,6 +145,7 @@ pub fn impl_enum(
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
mode: is_internal.into(),
};
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };

View file

@ -145,6 +145,7 @@ pub fn impl_input_object(
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
mode: is_internal.into(),
};
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };

View file

@ -132,6 +132,7 @@ pub fn build_derive_object(
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
mode: is_internal.into(),
};
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };

View file

@ -127,7 +127,7 @@ fn impl_scalar_struct(
executor: &'a #crate_name::Executor<Self::Context, __S>,
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<__S>> {
use #crate_name::GraphQLType;
use futures::future;
use #crate_name::futures::future;
let v = self.resolve(info, selection_set, executor);
Box::pin(future::ready(v))
}

View file

@ -1,182 +0,0 @@
use crate::{
result::{GraphQLScope, UnsupportedAttribute},
util::{self, span_container::SpanContainer},
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields};
pub fn build_derive_union(
ast: syn::DeriveInput,
is_internal: bool,
error: GraphQLScope,
) -> syn::Result<TokenStream> {
let ast_span = ast.span();
let enum_fields = match ast.data {
Data::Enum(data) => data.variants,
_ => return Err(error.custom_error(ast_span, "can only be applied to enums")),
};
// Parse attributes.
let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
let ident = &ast.ident;
let name = attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| ident.unraw().to_string());
let fields = enum_fields
.into_iter()
.filter_map(|field| {
let span = field.span();
let field_attrs = match util::FieldAttributes::from_attrs(
&field.attrs,
util::FieldAttributeParseMode::Object,
) {
Ok(attrs) => attrs,
Err(e) => {
proc_macro_error::emit_error!(e);
return None;
}
};
if let Some(ident) = field_attrs.skip {
error.unsupported_attribute_within(ident.span(), UnsupportedAttribute::Skip);
return None;
}
let variant_name = field.ident;
let name = field_attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| util::to_camel_case(&variant_name.unraw().to_string()));
let resolver_code = quote!(
#ident :: #variant_name
);
let _type = match field.fields {
Fields::Unnamed(inner) => {
let mut iter = inner.unnamed.iter();
let first = match iter.next() {
Some(val) => val,
None => unreachable!(),
};
if iter.next().is_some() {
error.custom(
inner.span(),
"all members must be unnamed with a single element e.g. Some(T)",
);
}
first.ty.clone()
}
_ => {
error.custom(
variant_name.span(),
"only unnamed fields with a single element are allowed, e.g., Some(T)",
);
return None;
}
};
if let Some(description) = field_attrs.description {
error.unsupported_attribute_within(
description.span_ident(),
UnsupportedAttribute::Description,
);
}
if let Some(default) = field_attrs.default {
error.unsupported_attribute_within(
default.span_ident(),
UnsupportedAttribute::Default,
);
}
if name.starts_with("__") {
error.no_double_underscore(if let Some(name) = field_attrs.name {
name.span_ident()
} else {
variant_name.span()
});
}
Some(util::GraphQLTypeDefinitionField {
name,
_type,
args: Vec::new(),
description: None,
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
resolver_code,
is_type_inferred: true,
is_async: false,
default: None,
span,
})
})
.collect::<Vec<_>>();
// Early abort after checking all fields
proc_macro_error::abort_if_dirty();
if !attrs.interfaces.is_empty() {
attrs.interfaces.iter().for_each(|elm| {
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
});
}
if fields.is_empty() {
error.not_empty(ast_span);
}
if name.starts_with("__") && !is_internal {
error.no_double_underscore(if let Some(name) = attrs.name {
name.span_ident()
} else {
ident.span()
});
}
// NOTICE: This is not an optimal implementation. It is possible
// to bypass this check by using a full qualified path instead
// (crate::Test vs Test). Since this requirement is mandatory, the
// `std::convert::Into<T>` implementation is used to enforce this
// requirement. However, due to the bad error message this
// implementation should stay and provide guidance.
let all_variants_different = {
let mut all_types: Vec<_> = fields.iter().map(|field| &field._type).collect();
let before = all_types.len();
all_types.dedup();
before == all_types.len()
};
if !all_variants_different {
error.custom(ident.span(), "each variant must have a different type");
}
// Early abort after GraphQL properties
proc_macro_error::abort_if_dirty();
let definition = util::GraphQLTypeDefiniton {
name,
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
context: attrs.context.map(SpanContainer::into_inner),
scalar: attrs.scalar.map(SpanContainer::into_inner),
description: attrs.description.map(SpanContainer::into_inner),
fields,
generics: ast.generics,
interfaces: None,
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
};
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
Ok(definition.into_union_tokens(juniper_crate_name))
}

View file

@ -0,0 +1,345 @@
//! Code generation for `#[graphql_union]`/`#[graphql_union_internal]` macros.
use std::{mem, ops::Deref as _};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens as _};
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
use crate::{
result::GraphQLScope,
util::{path_eq_single, span_container::SpanContainer, unparenthesize, Mode},
};
use super::{
all_variants_different, emerge_union_variants_from_meta, UnionDefinition, UnionMeta,
UnionVariantDefinition, UnionVariantMeta,
};
/// [`GraphQLScope`] of errors for `#[graphql_union]`/`#[graphql_union_internal]` macros.
const ERR: GraphQLScope = GraphQLScope::UnionAttr;
/// Returns the concrete name of the `proc_macro_attribute` for deriving `GraphQLUnion`
/// implementation depending on the provided `mode`.
fn attr_path(mode: Mode) -> &'static str {
match mode {
Mode::Public => "graphql_union",
Mode::Internal => "graphql_union_internal",
}
}
/// Expands `#[graphql_union]`/`#[graphql_union_internal]` macro into generated code.
pub fn expand(attr_args: TokenStream, body: TokenStream, mode: Mode) -> syn::Result<TokenStream> {
let attr_path = attr_path(mode);
let mut ast = syn::parse2::<syn::ItemTrait>(body).map_err(|_| {
syn::Error::new(
Span::call_site(),
format!(
"#[{}] attribute is applicable to trait definitions only",
attr_path,
),
)
})?;
let mut trait_attrs = Vec::with_capacity(ast.attrs.len() + 1);
trait_attrs.push({
let attr_path = syn::Ident::new(attr_path, Span::call_site());
parse_quote! { #[#attr_path(#attr_args)] }
});
trait_attrs.extend_from_slice(&ast.attrs);
// Remove repeated attributes from the definition, to omit duplicate expansion.
ast.attrs = ast
.attrs
.into_iter()
.filter_map(|attr| {
if path_eq_single(&attr.path, attr_path) {
None
} else {
Some(attr)
}
})
.collect();
let meta = UnionMeta::from_attrs(attr_path, &trait_attrs)?;
let trait_span = ast.span();
let trait_ident = &ast.ident;
let name = meta
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| trait_ident.unraw().to_string());
if matches!(mode, Mode::Public) && name.starts_with("__") {
ERR.no_double_underscore(
meta.name
.as_ref()
.map(SpanContainer::span_ident)
.unwrap_or_else(|| trait_ident.span()),
);
}
let mut variants: Vec<_> = ast
.items
.iter_mut()
.filter_map(|i| match i {
syn::TraitItem::Method(m) => {
parse_variant_from_trait_method(m, trait_ident, &meta, mode)
}
_ => None,
})
.collect();
proc_macro_error::abort_if_dirty();
emerge_union_variants_from_meta(&mut variants, meta.external_resolvers, mode);
if variants.is_empty() {
ERR.emit_custom(trait_span, "expects at least one union variant");
}
if !all_variants_different(&variants) {
ERR.emit_custom(
trait_span,
"must have a different type for each union variant",
);
}
proc_macro_error::abort_if_dirty();
let context = meta
.context
.map(SpanContainer::into_inner)
.or_else(|| variants.iter().find_map(|v| v.context_ty.as_ref()).cloned());
let generated_code = UnionDefinition {
name,
ty: parse_quote! { #trait_ident },
is_trait_object: true,
description: meta.description.map(SpanContainer::into_inner),
context,
scalar: meta.scalar.map(SpanContainer::into_inner),
generics: ast.generics.clone(),
variants,
span: trait_span,
mode,
};
Ok(quote! {
#ast
#generated_code
})
}
/// Parses given Rust trait `method` as [GraphQL union][1] variant.
///
/// On failure returns [`None`] and internally fills up [`proc_macro_error`] with the corresponding
/// errors.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
fn parse_variant_from_trait_method(
method: &mut syn::TraitItemMethod,
trait_ident: &syn::Ident,
trait_meta: &UnionMeta,
mode: Mode,
) -> Option<UnionVariantDefinition> {
let attr_path = attr_path(mode);
let method_attrs = method.attrs.clone();
// Remove repeated attributes from the method, to omit incorrect expansion.
method.attrs = mem::take(&mut method.attrs)
.into_iter()
.filter_map(|attr| {
if path_eq_single(&attr.path, attr_path) {
None
} else {
Some(attr)
}
})
.collect();
let meta = UnionVariantMeta::from_attrs(attr_path, &method_attrs)
.map_err(|e| proc_macro_error::emit_error!(e))
.ok()?;
if let Some(rslvr) = meta.external_resolver {
ERR.custom(
rslvr.span_ident(),
format!(
"cannot use #[{}(with = ...)] attribute on a trait method",
attr_path,
),
)
.note(format!(
"instead use #[{0}(ignore)] on the method with #[{0}(on ... = ...)] on the trait \
itself",
attr_path,
))
.emit()
}
if meta.ignore.is_some() {
return None;
}
let method_span = method.sig.span();
let method_ident = &method.sig.ident;
let ty = parse_trait_method_output_type(&method.sig)
.map_err(|span| {
ERR.emit_custom(
span,
"expects trait method return type to be `Option<&VariantType>` only",
)
})
.ok()?;
let method_context_ty = parse_trait_method_input_args(&method.sig)
.map_err(|span| {
ERR.emit_custom(
span,
"expects trait method to accept `&self` only and, optionally, `&Context`",
)
})
.ok()?;
if let Some(is_async) = &method.sig.asyncness {
ERR.emit_custom(
is_async.span(),
"doesn't support async union variants resolvers yet",
);
return None;
}
let resolver_code = {
if let Some(other) = trait_meta.external_resolvers.get(&ty) {
ERR.custom(
method_span,
format!(
"trait method `{}` conflicts with the external resolver function `{}` declared \
on the trait to resolve the variant type `{}`",
method_ident,
other.to_token_stream(),
ty.to_token_stream(),
),
)
.note(format!(
"use `#[{}(ignore)]` attribute to ignore this trait method for union variants \
resolution",
attr_path,
))
.emit();
}
if method_context_ty.is_some() {
let crate_path = mode.crate_path();
parse_quote! {
#trait_ident::#method_ident(self, #crate_path::FromContext::from(context))
}
} else {
parse_quote! {
#trait_ident::#method_ident(self)
}
}
};
// Doing this may be quite an expensive, because resolving may contain some heavy computation,
// so we're preforming it twice. Unfortunately, we have no other options here, until the
// `juniper::GraphQLType` itself will allow to do it in some cleverer way.
let resolver_check = parse_quote! {
({ #resolver_code } as ::std::option::Option<&#ty>).is_some()
};
Some(UnionVariantDefinition {
ty,
resolver_code,
resolver_check,
enum_path: None,
context_ty: method_context_ty,
span: method_span,
})
}
/// Parses type of [GraphQL union][1] variant from the return type of trait method.
///
/// If return type is invalid, then returns the [`Span`] to display the corresponding error at.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
fn parse_trait_method_output_type(sig: &syn::Signature) -> Result<syn::Type, Span> {
let ret_ty = match &sig.output {
syn::ReturnType::Type(_, ty) => ty.deref(),
_ => return Err(sig.span()),
};
let path = match unparenthesize(ret_ty) {
syn::Type::Path(syn::TypePath { qself: None, path }) => path,
_ => return Err(ret_ty.span()),
};
let (ident, args) = match path.segments.last() {
Some(syn::PathSegment {
ident,
arguments: syn::PathArguments::AngleBracketed(generic),
}) => (ident, &generic.args),
_ => return Err(ret_ty.span()),
};
if ident.unraw() != "Option" {
return Err(ret_ty.span());
}
if args.len() != 1 {
return Err(ret_ty.span());
}
let var_ty = match args.first() {
Some(syn::GenericArgument::Type(inner_ty)) => match unparenthesize(inner_ty) {
syn::Type::Reference(inner_ty) => {
if inner_ty.mutability.is_some() {
return Err(inner_ty.span());
}
unparenthesize(inner_ty.elem.deref()).clone()
}
_ => return Err(ret_ty.span()),
},
_ => return Err(ret_ty.span()),
};
Ok(var_ty)
}
/// Parses trait method input arguments and validates them to be acceptable for resolving into
/// [GraphQL union][1] variant type. Returns type of the context used in input arguments, if any.
///
/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
fn parse_trait_method_input_args(sig: &syn::Signature) -> Result<Option<syn::Type>, Span> {
match sig.receiver() {
Some(syn::FnArg::Receiver(rcv)) => {
if rcv.reference.is_none() || rcv.mutability.is_some() {
return Err(rcv.span());
}
}
_ => return Err(sig.span()),
}
if sig.inputs.len() > 2 {
return Err(sig.inputs.span());
}
let second_arg_ty = match sig.inputs.iter().skip(1).next() {
Some(syn::FnArg::Typed(arg)) => arg.ty.deref(),
None => return Ok(None),
_ => return Err(sig.inputs.span()),
};
match unparenthesize(second_arg_ty) {
syn::Type::Reference(ref_ty) => {
if ref_ty.mutability.is_some() {
return Err(ref_ty.span());
}
Ok(Some(ref_ty.elem.deref().clone()))
}
ty => Err(ty.span()),
}
}

View file

@ -0,0 +1,226 @@
//! Code generation for `#[derive(GraphQLUnion)]`/`#[derive(GraphQLUnionInternal)]` macros.
use proc_macro2::TokenStream;
use proc_macro_error::ResultExt as _;
use quote::{quote, ToTokens};
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields};
use crate::{
result::GraphQLScope,
util::{span_container::SpanContainer, unparenthesize, Mode},
};
use super::{
all_variants_different, emerge_union_variants_from_meta, UnionDefinition, UnionMeta,
UnionVariantDefinition, UnionVariantMeta,
};
/// [`GraphQLScope`] of errors for `#[derive(GraphQLUnion)]`/`#[derive(GraphQLUnionInternal)]`
/// macros.
const ERR: GraphQLScope = GraphQLScope::UnionDerive;
/// Expands `#[derive(GraphQLUnion)]`/`#[derive(GraphQLUnionInternal)]` macro into generated code.
pub fn expand(input: TokenStream, mode: Mode) -> syn::Result<TokenStream> {
let ast = syn::parse2::<syn::DeriveInput>(input).unwrap_or_abort();
match &ast.data {
Data::Enum(_) => expand_enum(ast, mode),
Data::Struct(_) => expand_struct(ast, mode),
_ => Err(ERR.custom_error(ast.span(), "can only be derived for enums and structs")),
}
.map(ToTokens::into_token_stream)
}
/// Expands into generated code a `#[derive(GraphQLUnion)]`/`#[derive(GraphQLUnionInternal)]` macro
/// placed on a Rust enum.
fn expand_enum(ast: syn::DeriveInput, mode: Mode) -> syn::Result<UnionDefinition> {
let meta = UnionMeta::from_attrs("graphql", &ast.attrs)?;
let enum_span = ast.span();
let enum_ident = ast.ident;
let name = meta
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| enum_ident.unraw().to_string());
if matches!(mode, Mode::Public) && name.starts_with("__") {
ERR.no_double_underscore(
meta.name
.as_ref()
.map(SpanContainer::span_ident)
.unwrap_or_else(|| enum_ident.span()),
);
}
let mut variants: Vec<_> = match ast.data {
Data::Enum(data) => data.variants,
_ => unreachable!(),
}
.into_iter()
.filter_map(|var| parse_variant_from_enum_variant(var, &enum_ident, &meta, mode))
.collect();
proc_macro_error::abort_if_dirty();
emerge_union_variants_from_meta(&mut variants, meta.external_resolvers, mode);
if variants.is_empty() {
ERR.emit_custom(enum_span, "expects at least one union variant");
}
if !all_variants_different(&variants) {
ERR.emit_custom(
enum_span,
"must have a different type for each union variant",
);
}
proc_macro_error::abort_if_dirty();
Ok(UnionDefinition {
name,
ty: parse_quote! { #enum_ident },
is_trait_object: false,
description: meta.description.map(SpanContainer::into_inner),
context: meta.context.map(SpanContainer::into_inner),
scalar: meta.scalar.map(SpanContainer::into_inner),
generics: ast.generics,
variants,
span: enum_span,
mode,
})
}
/// Parses given Rust enum `var`iant as [GraphQL union][1] variant.
///
/// On failure returns [`None`] and internally fills up [`proc_macro_error`] with the corresponding
/// errors.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
fn parse_variant_from_enum_variant(
var: syn::Variant,
enum_ident: &syn::Ident,
enum_meta: &UnionMeta,
mode: Mode,
) -> Option<UnionVariantDefinition> {
let meta = UnionVariantMeta::from_attrs("graphql", &var.attrs)
.map_err(|e| proc_macro_error::emit_error!(e))
.ok()?;
if meta.ignore.is_some() {
return None;
}
let var_span = var.span();
let var_ident = var.ident;
let ty = match var.fields {
Fields::Unnamed(fields) => {
let mut iter = fields.unnamed.iter();
let first = iter.next().unwrap();
if iter.next().is_none() {
Ok(unparenthesize(&first.ty).clone())
} else {
Err(fields.span())
}
}
_ => Err(var_ident.span()),
}
.map_err(|span| {
ERR.emit_custom(
span,
"enum allows only unnamed variants with a single field, e.g. `Some(T)`",
)
})
.ok()?;
let enum_path = quote! { #enum_ident::#var_ident };
let resolver_code = if let Some(rslvr) = meta.external_resolver {
if let Some(other) = enum_meta.external_resolvers.get(&ty) {
ERR.emit_custom(
rslvr.span_ident(),
format!(
"variant `{}` already has external resolver function `{}` declared on the enum",
ty.to_token_stream(),
other.to_token_stream(),
),
);
}
let crate_path = mode.crate_path();
let resolver_fn = rslvr.into_inner();
parse_quote! {
#resolver_fn(self, #crate_path::FromContext::from(context))
}
} else {
parse_quote! {
match self { #enum_ident::#var_ident(ref v) => Some(v), _ => None, }
}
};
let resolver_check = parse_quote! {
matches!(self, #enum_path(_))
};
Some(UnionVariantDefinition {
ty,
resolver_code,
resolver_check,
enum_path: Some(enum_path),
context_ty: None,
span: var_span,
})
}
/// Expands into generated code a `#[derive(GraphQLUnion)]`/`#[derive(GraphQLUnionInternal)]` macro
/// placed on a Rust struct.
fn expand_struct(ast: syn::DeriveInput, mode: Mode) -> syn::Result<UnionDefinition> {
let meta = UnionMeta::from_attrs("graphql", &ast.attrs)?;
let struct_span = ast.span();
let struct_ident = ast.ident;
let name = meta
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| struct_ident.unraw().to_string());
if matches!(mode, Mode::Public) && name.starts_with("__") {
ERR.no_double_underscore(
meta.name
.as_ref()
.map(SpanContainer::span_ident)
.unwrap_or_else(|| struct_ident.span()),
);
}
let mut variants = vec![];
emerge_union_variants_from_meta(&mut variants, meta.external_resolvers, mode);
if variants.is_empty() {
ERR.emit_custom(struct_span, "expects at least one union variant");
}
if !all_variants_different(&variants) {
ERR.emit_custom(
struct_span,
"must have a different type for each union variant",
);
}
proc_macro_error::abort_if_dirty();
Ok(UnionDefinition {
name,
ty: parse_quote! { #struct_ident },
is_trait_object: false,
description: meta.description.map(SpanContainer::into_inner),
context: meta.context.map(SpanContainer::into_inner),
scalar: meta.scalar.map(SpanContainer::into_inner),
generics: ast.generics,
variants,
span: struct_span,
mode,
})
}

View file

@ -0,0 +1,700 @@
//! Code generation for [GraphQL union][1].
//!
//! [1]: https://spec.graphql.org/June2018/#sec-Unions
pub mod attr;
pub mod derive;
use std::collections::HashMap;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt as _};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned as _,
};
use crate::util::{
filter_attrs, get_doc_comment, span_container::SpanContainer, Mode, OptionExt as _,
};
/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of
/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related
/// to the `$another` struct (a later one).
///
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
/// By default, [`SpanContainer::span_ident`] is used.
macro_rules! try_merge_opt {
($field:ident: $self:ident, $another:ident => $span:ident) => {{
if let Some(v) = $self.$field {
$another
.$field
.replace(v)
.none_or_else(|dup| dup_attr_err(dup.$span()))?;
}
$another.$field
}};
($field:ident: $self:ident, $another:ident) => {
try_merge_opt!($field: $self, $another => span_ident)
};
}
/// Attempts to merge a [`HashMap`]ed `$field` of a `$self` struct with the same `$field` of
/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error
/// with a [`Span`] related to the `$another` struct (a later one).
///
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
/// By default, [`SpanContainer::span_ident`] is used.
macro_rules! try_merge_hashmap {
($field:ident: $self:ident, $another:ident => $span:ident) => {{
if !$self.$field.is_empty() {
for (ty, rslvr) in $self.$field {
$another
.$field
.insert(ty, rslvr)
.none_or_else(|dup| dup_attr_err(dup.$span()))?;
}
}
$another.$field
}};
($field:ident: $self:ident, $another:ident) => {
try_merge_hashmap!($field: $self, $another => span_ident)
};
}
/// Creates and returns duplication error pointing to the given `span`.
fn dup_attr_err(span: Span) -> syn::Error {
syn::Error::new(span, "duplicated attribute")
}
/// Helper alias for the type of [`UnionMeta::external_resolvers`] field.
type UnionMetaResolvers = HashMap<syn::Type, SpanContainer<syn::ExprPath>>;
/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_union]`) attribute when
/// generating code for [GraphQL union][1] type.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
#[derive(Debug, Default)]
struct UnionMeta {
/// Explicitly specified name of [GraphQL union][1] type.
///
/// If absent, then Rust type name is used by default.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub name: Option<SpanContainer<String>>,
/// Explicitly specified [description][2] of [GraphQL union][1] type.
///
/// If absent, then Rust doc comment is used as [description][2], if any.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [2]: https://spec.graphql.org/June2018/#sec-Descriptions
pub description: Option<SpanContainer<String>>,
/// Explicitly specified type of `juniper::Context` to use for resolving this [GraphQL union][1]
/// type with.
///
/// If absent, then unit type `()` is assumed as type of `juniper::Context`.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub context: Option<SpanContainer<syn::Type>>,
/// Explicitly specified type of `juniper::ScalarValue` to use for resolving this
/// [GraphQL union][1] type with.
///
/// If absent, then generated code will be generic over any `juniper::ScalarValue` type, which,
/// in turn, requires all [union][1] variants to be generic over any `juniper::ScalarValue` type
/// too. That's why this type should be specified only if one of the variants implements
/// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` type.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub scalar: Option<SpanContainer<syn::Type>>,
/// Explicitly specified external resolver functions for [GraphQL union][1] variants.
///
/// If absent, then macro will try to auto-infer all the possible variants from the type
/// declaration, if possible. That's why specifying an external resolver function has sense,
/// when some custom [union][1] variant resolving logic is involved, or variants cannot be
/// inferred.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub external_resolvers: UnionMetaResolvers,
}
impl Parse for UnionMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut output = Self::default();
while !input.is_empty() {
let ident: syn::Ident = input.parse()?;
match ident.to_string().as_str() {
"name" => {
input.parse::<syn::Token![=]>()?;
let name = input.parse::<syn::LitStr>()?;
output
.name
.replace(SpanContainer::new(
ident.span(),
Some(name.span()),
name.value(),
))
.none_or_else(|_| dup_attr_err(ident.span()))?
}
"desc" | "description" => {
input.parse::<syn::Token![=]>()?;
let desc = input.parse::<syn::LitStr>()?;
output
.description
.replace(SpanContainer::new(
ident.span(),
Some(desc.span()),
desc.value(),
))
.none_or_else(|_| dup_attr_err(ident.span()))?
}
"ctx" | "context" | "Context" => {
input.parse::<syn::Token![=]>()?;
let ctx = input.parse::<syn::Type>()?;
output
.context
.replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx))
.none_or_else(|_| dup_attr_err(ident.span()))?
}
"scalar" | "Scalar" | "ScalarValue" => {
input.parse::<syn::Token![=]>()?;
let scl = input.parse::<syn::Type>()?;
output
.scalar
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
.none_or_else(|_| dup_attr_err(ident.span()))?
}
"on" => {
let ty = input.parse::<syn::Type>()?;
input.parse::<syn::Token![=]>()?;
let rslvr = input.parse::<syn::ExprPath>()?;
let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr);
let rslvr_span = rslvr_spanned.span_joined();
output
.external_resolvers
.insert(ty, rslvr_spanned)
.none_or_else(|_| dup_attr_err(rslvr_span))?
}
_ => {
return Err(syn::Error::new(ident.span(), "unknown attribute"));
}
}
if input.lookahead1().peek(syn::Token![,]) {
input.parse::<syn::Token![,]>()?;
}
}
Ok(output)
}
}
impl UnionMeta {
/// Tries to merge two [`UnionMeta`]s into single one, reporting about duplicates, if any.
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
Ok(Self {
name: try_merge_opt!(name: self, another),
description: try_merge_opt!(description: self, another),
context: try_merge_opt!(context: self, another),
scalar: try_merge_opt!(scalar: self, another),
external_resolvers: try_merge_hashmap!(external_resolvers: self, another => span_joined),
})
}
/// Parses [`UnionMeta`] from the given multiple `name`d attributes placed on type definition.
pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
let mut meta = filter_attrs(name, attrs)
.map(|attr| attr.parse_args())
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
if meta.description.is_none() {
meta.description = get_doc_comment(attrs);
}
Ok(meta)
}
}
/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_union]`) attribute when
/// generating code for [GraphQL union][1]'s variant.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
#[derive(Debug, Default)]
struct UnionVariantMeta {
/// Explicitly specified marker for the variant/field being ignored and not included into
/// [GraphQL union][1].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub ignore: Option<SpanContainer<syn::Ident>>,
/// Explicitly specified external resolver function for this [GraphQL union][1] variant.
///
/// If absent, then macro will generate the code which just returns the variant inner value.
/// Usually, specifying an external resolver function has sense, when some custom resolving
/// logic is involved.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub external_resolver: Option<SpanContainer<syn::ExprPath>>,
}
impl Parse for UnionVariantMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut output = Self::default();
while !input.is_empty() {
let ident: syn::Ident = input.parse()?;
match ident.to_string().as_str() {
"ignore" | "skip" => output
.ignore
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
.none_or_else(|_| dup_attr_err(ident.span()))?,
"with" => {
input.parse::<syn::Token![=]>()?;
let rslvr = input.parse::<syn::ExprPath>()?;
output
.external_resolver
.replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr))
.none_or_else(|_| dup_attr_err(ident.span()))?
}
_ => {
return Err(syn::Error::new(ident.span(), "unknown attribute"));
}
}
if input.lookahead1().peek(syn::Token![,]) {
input.parse::<syn::Token![,]>()?;
}
}
Ok(output)
}
}
impl UnionVariantMeta {
/// Tries to merge two [`UnionVariantMeta`]s into single one, reporting about duplicates, if
/// any.
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
Ok(Self {
ignore: try_merge_opt!(ignore: self, another),
external_resolver: try_merge_opt!(external_resolver: self, another),
})
}
/// Parses [`UnionVariantMeta`] from the given multiple `name`d attributes placed on
/// variant/field/method definition.
pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
filter_attrs(name, attrs)
.map(|attr| attr.parse_args())
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
}
}
/// Definition of [GraphQL union][1] variant for code generation.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
struct UnionVariantDefinition {
/// Rust type that this [GraphQL union][1] variant resolves into.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub ty: syn::Type,
/// Rust code for value resolution of this [GraphQL union][1] variant.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub resolver_code: syn::Expr,
/// Rust code for checking whether [GraphQL union][1] should be resolved into this variant.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub resolver_check: syn::Expr,
/// Rust enum variant path that this [GraphQL union][1] variant is associated with.
///
/// It's available only when code generation happens for Rust enums.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub enum_path: Option<TokenStream>,
/// Rust type of `juniper::Context` that this [GraphQL union][1] variant requires for
/// resolution.
///
/// It's available only when code generation happens for Rust traits and a trait method contains
/// context argument.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub context_ty: Option<syn::Type>,
/// [`Span`] that points to the Rust source code which defines this [GraphQL union][1] variant.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub span: Span,
}
/// Definition of [GraphQL union][1] for code generation.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
struct UnionDefinition {
/// Name of this [GraphQL union][1] in GraphQL schema.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub name: String,
/// Rust type that this [GraphQL union][1] is represented with.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub ty: syn::Type,
/// Generics of the Rust type that this [GraphQL union][1] is implemented for.
pub generics: syn::Generics,
/// Indicator whether code should be generated for a trait object, rather than for a regular
/// Rust type.
pub is_trait_object: bool,
/// Description of this [GraphQL union][1] to put into GraphQL schema.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub description: Option<String>,
/// Rust type of `juniper::Context` to generate `juniper::GraphQLType` implementation with
/// for this [GraphQL union][1].
///
/// If [`None`] then generated code will use unit type `()` as `juniper::Context`.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub context: Option<syn::Type>,
/// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` implementation with
/// for this [GraphQL union][1].
///
/// If [`None`] then generated code will be generic over any `juniper::ScalarValue` type, which,
/// in turn, requires all [union][1] variants to be generic over any `juniper::ScalarValue` type
/// too. That's why this type should be specified only if one of the variants implements
/// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` type.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub scalar: Option<syn::Type>,
/// Variants definitions of this [GraphQL union][1].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub variants: Vec<UnionVariantDefinition>,
/// [`Span`] that points to the Rust source code which defines this [GraphQL union][1].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub span: Span,
/// [`Mode`] to generate code in for this [GraphQL union][1].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
pub mode: Mode,
}
impl ToTokens for UnionDefinition {
fn to_tokens(&self, into: &mut TokenStream) {
let crate_path = self.mode.crate_path();
let name = &self.name;
let ty = &self.ty;
let context = self
.context
.as_ref()
.map(|ctx| quote! { #ctx })
.unwrap_or_else(|| quote! { () });
let scalar = self
.scalar
.as_ref()
.map(|scl| quote! { #scl })
.unwrap_or_else(|| quote! { __S });
let default_scalar = self
.scalar
.as_ref()
.map(|scl| quote! { #scl })
.unwrap_or_else(|| quote! { #crate_path::DefaultScalarValue });
let description = self
.description
.as_ref()
.map(|desc| quote! { .description(#desc) });
let var_types: Vec<_> = self.variants.iter().map(|var| &var.ty).collect();
let all_variants_unique = if var_types.len() > 1 {
Some(quote! { #crate_path::sa::assert_type_ne_all!(#(#var_types),*); })
} else {
None
};
let match_names = self.variants.iter().map(|var| {
let var_ty = &var.ty;
let var_check = &var.resolver_check;
quote! {
if #var_check {
return <#var_ty as #crate_path::GraphQLType<#scalar>>::name(&())
.unwrap().to_string();
}
}
});
let match_resolves: Vec<_> = self.variants.iter().map(|var| &var.resolver_code).collect();
let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| {
let var_ty = &var.ty;
let get_name = quote! { (<#var_ty as #crate_path::GraphQLType<#scalar>>::name(&())) };
quote! {
if type_name == #get_name.unwrap() {
return #crate_path::IntoResolvable::into(
{ #expr },
executor.context()
)
.and_then(|res| match res {
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
None => Ok(#crate_path::Value::null()),
});
}
}
});
let resolve_into_type_async =
self.variants
.iter()
.zip(match_resolves.iter())
.map(|(var, expr)| {
let var_ty = &var.ty;
let get_name = quote! {
(<#var_ty as #crate_path::GraphQLType<#scalar>>::name(&()))
};
quote! {
if type_name == #get_name.unwrap() {
let res = #crate_path::IntoResolvable::into(
{ #expr },
executor.context()
);
return #crate_path::futures::future::FutureExt::boxed(async move {
match res? {
Some((ctx, r)) => {
let subexec = executor.replaced_context(ctx);
subexec.resolve_with_ctx_async(&(), &r).await
},
None => Ok(#crate_path::Value::null()),
}
});
}
}
});
let (_, ty_generics, _) = self.generics.split_for_impl();
let mut base_generics = self.generics.clone();
if self.is_trait_object {
base_generics.params.push(parse_quote! { '__obj });
}
let (impl_generics, _, _) = base_generics.split_for_impl();
let mut ext_generics = base_generics.clone();
if self.scalar.is_none() {
ext_generics.params.push(parse_quote! { #scalar });
ext_generics
.where_clause
.get_or_insert_with(|| parse_quote! { where })
.predicates
.push(parse_quote! { #scalar: #crate_path::ScalarValue });
}
let (ext_impl_generics, _, where_clause) = ext_generics.split_for_impl();
let mut where_async = where_clause
.cloned()
.unwrap_or_else(|| parse_quote! { where });
where_async
.predicates
.push(parse_quote! { Self: Send + Sync });
if self.scalar.is_none() {
where_async
.predicates
.push(parse_quote! { #scalar: Send + Sync });
}
let mut ty_full = quote! { #ty#ty_generics };
if self.is_trait_object {
ty_full = quote! { dyn #ty_full + '__obj + Send + Sync };
}
let type_impl = quote! {
#[automatically_derived]
impl#ext_impl_generics #crate_path::GraphQLType<#scalar> for #ty_full
#where_clause
{
type Context = #context;
type TypeInfo = ();
fn name(_ : &Self::TypeInfo) -> Option<&str> {
Some(#name)
}
fn meta<'r>(
info: &Self::TypeInfo,
registry: &mut #crate_path::Registry<'r, #scalar>
) -> #crate_path::meta::MetaType<'r, #scalar>
where #scalar: 'r,
{
let types = &[
#( registry.get_type::<&#var_types>(&(())), )*
];
registry.build_union_type::<#ty_full>(info, types)
#description
.into_meta()
}
fn concrete_type_name(
&self,
context: &Self::Context,
_: &Self::TypeInfo,
) -> String {
#( #match_names )*
panic!(
"GraphQL union {} cannot be resolved into any of its variants in its \
current state",
#name,
);
}
fn resolve_into_type(
&self,
_: &Self::TypeInfo,
type_name: &str,
_: Option<&[#crate_path::Selection<#scalar>]>,
executor: &#crate_path::Executor<Self::Context, #scalar>,
) -> #crate_path::ExecutionResult<#scalar> {
let context = executor.context();
#( #resolve_into_type )*
panic!(
"Concrete type {} is not handled by instance resolvers on GraphQL union {}",
type_name, #name,
);
}
}
};
let async_type_impl = quote! {
#[automatically_derived]
impl#ext_impl_generics #crate_path::GraphQLTypeAsync<#scalar> for #ty_full
#where_async
{
fn resolve_into_type_async<'b>(
&'b self,
_: &'b Self::TypeInfo,
type_name: &str,
_: Option<&'b [#crate_path::Selection<'b, #scalar>]>,
executor: &'b #crate_path::Executor<'b, 'b, Self::Context, #scalar>
) -> #crate_path::BoxFuture<'b, #crate_path::ExecutionResult<#scalar>> {
let context = executor.context();
#( #resolve_into_type_async )*
panic!(
"Concrete type {} is not handled by instance resolvers on GraphQL union {}",
type_name, #name,
);
}
}
};
let output_type_impl = quote! {
#[automatically_derived]
impl#ext_impl_generics #crate_path::marker::IsOutputType<#scalar> for #ty_full
#where_clause
{
fn mark() {
#( <#var_types as #crate_path::marker::GraphQLObjectType<#scalar>>::mark(); )*
}
}
};
let union_impl = quote! {
#[automatically_derived]
impl#impl_generics #crate_path::marker::GraphQLUnion for #ty_full {
fn mark() {
#all_variants_unique
#( <#var_types as #crate_path::marker::GraphQLObjectType<
#default_scalar,
>>::mark(); )*
}
}
};
into.append_all(&[union_impl, output_type_impl, type_impl, async_type_impl]);
}
}
/// Emerges [`UnionMeta::external_resolvers`] into the given [GraphQL union][1] `variants`.
///
/// If duplication happens, then resolving code is overwritten with the one from
/// `external_resolvers`.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
fn emerge_union_variants_from_meta(
variants: &mut Vec<UnionVariantDefinition>,
external_resolvers: UnionMetaResolvers,
mode: Mode,
) {
if external_resolvers.is_empty() {
return;
}
let crate_path = mode.crate_path();
for (ty, rslvr) in external_resolvers {
let span = rslvr.span_joined();
let resolver_fn = rslvr.into_inner();
let resolver_code = parse_quote! {
#resolver_fn(self, #crate_path::FromContext::from(context))
};
// Doing this may be quite an expensive, because resolving may contain some heavy
// computation, so we're preforming it twice. Unfortunately, we have no other options here,
// until the `juniper::GraphQLType` itself will allow to do it in some cleverer way.
let resolver_check = parse_quote! {
({ #resolver_code } as ::std::option::Option<&#ty>).is_some()
};
if let Some(var) = variants.iter_mut().find(|v| v.ty == ty) {
var.resolver_code = resolver_code;
var.resolver_check = resolver_check;
var.span = span;
} else {
variants.push(UnionVariantDefinition {
ty,
resolver_code,
resolver_check,
enum_path: None,
context_ty: None,
span,
})
}
}
}
/// Checks whether all [GraphQL union][1] `variants` represent a different Rust type.
///
/// # Notice
///
/// This is not an optimal implementation, as it's possible to bypass this check by using a full
/// qualified path instead (`crate::Test` vs `Test`). Since this requirement is mandatory, the
/// static assertion [`assert_type_ne_all!`][2] is used to enforce this requirement in the generated
/// code. However, due to the bad error message this implementation should stay and provide
/// guidance.
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [2]: https://docs.rs/static_assertions/latest/static_assertions/macro.assert_type_ne_all.html
fn all_variants_different(variants: &Vec<UnionVariantDefinition>) -> bool {
let mut types: Vec<_> = variants.iter().map(|var| &var.ty).collect();
types.dedup();
types.len() == variants.len()
}

View file

@ -65,7 +65,7 @@ fn create(
let _type = match method.sig.output {
syn::ReturnType::Type(_, ref t) => *t.clone(),
syn::ReturnType::Default => {
error.custom(method.sig.span(), "return value required");
error.emit_custom(method.sig.span(), "return value required");
return None;
}
};
@ -228,6 +228,7 @@ fn create(
include_type_generics: false,
generic_scalar: false,
no_async: _impl.attrs.no_async.is_some(),
mode: is_internal.into(),
};
Ok(definition)

View file

@ -264,7 +264,7 @@ pub fn build_scalar(
executor: &'a #crate_name::Executor<Self::Context, #async_generic_type>,
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<#async_generic_type>> {
use #crate_name::GraphQLType;
use futures::future;
use #crate_name::futures::future;
let v = self.resolve(info, selection_set, executor);
Box::pin(future::ready(v))
}

View file

@ -1,223 +0,0 @@
use crate::{
result::GraphQLScope,
util::{self, span_container::SpanContainer},
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{ext::IdentExt, spanned::Spanned};
struct ResolverVariant {
pub ty: syn::Type,
pub resolver: syn::Expr,
}
struct ResolveBody {
pub variants: Vec<ResolverVariant>,
}
impl syn::parse::Parse for ResolveBody {
fn parse(input: syn::parse::ParseStream) -> Result<Self, syn::parse::Error> {
input.parse::<syn::token::Match>()?;
input.parse::<syn::token::SelfValue>()?;
let match_body;
syn::braced!( match_body in input );
let mut variants = Vec::new();
while !match_body.is_empty() {
let ty = match_body.parse::<syn::Type>()?;
match_body.parse::<syn::token::FatArrow>()?;
let resolver = match_body.parse::<syn::Expr>()?;
variants.push(ResolverVariant { ty, resolver });
// Optinal trailing comma.
match_body.parse::<syn::token::Comma>().ok();
}
if !input.is_empty() {
return Err(input.error("unexpected input"));
}
Ok(Self { variants })
}
}
pub fn impl_union(
is_internal: bool,
attrs: TokenStream,
body: TokenStream,
error: GraphQLScope,
) -> syn::Result<TokenStream> {
let body_span = body.span();
let _impl = util::parse_impl::ImplBlock::parse(attrs, body)?;
// FIXME: what is the purpose of this construct?
// Validate trait target name, if present.
if let Some((name, path)) = &_impl.target_trait {
if !(name == "GraphQLUnion" || name == "juniper.GraphQLUnion") {
return Err(error.custom_error(
path.span(),
"Invalid impl target trait: expected 'GraphQLUnion'",
));
}
}
let type_ident = &_impl.type_ident;
let name = _impl
.attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| type_ident.unraw().to_string());
let crate_name = util::juniper_path(is_internal);
let scalar = _impl
.attrs
.scalar
.as_ref()
.map(|s| quote!( #s ))
.unwrap_or_else(|| {
quote! { #crate_name::DefaultScalarValue }
});
let method = _impl
.methods
.iter()
.find(|&m| _impl.parse_resolve_method(&m).is_ok());
let method = match method {
Some(method) => method,
None => {
return Err(error.custom_error(
body_span,
"expected exactly one method with signature: fn resolve(&self) { ... }",
))
}
};
let resolve_args = _impl.parse_resolve_method(method)?;
let stmts = &method.block.stmts;
let body_raw = quote!( #( #stmts )* );
let body = syn::parse::<ResolveBody>(body_raw.into())?;
if body.variants.is_empty() {
error.not_empty(method.span())
}
proc_macro_error::abort_if_dirty();
let meta_types = body.variants.iter().map(|var| {
let var_ty = &var.ty;
quote! {
registry.get_type::<&#var_ty>(&(())),
}
});
let concrete_type_resolver = body.variants.iter().map(|var| {
let var_ty = &var.ty;
let resolve = &var.resolver;
quote! {
if ({#resolve} as std::option::Option<&#var_ty>).is_some() {
return <#var_ty as #crate_name::GraphQLType<#scalar>>::name(&()).unwrap().to_string();
}
}
});
let resolve_into_type = body.variants.iter().map(|var| {
let var_ty = &var.ty;
let resolve = &var.resolver;
quote! {
if type_name == (<#var_ty as #crate_name::GraphQLType<#scalar>>::name(&())).unwrap() {
return executor.resolve(&(), &{ #resolve });
}
}
});
let generics = _impl.generics;
let (impl_generics, _, where_clause) = generics.split_for_impl();
let description = match _impl.description.as_ref() {
Some(value) => quote!( .description( #value ) ),
None => quote!(),
};
let context = _impl
.attrs
.context
.map(|c| quote! { #c })
.unwrap_or_else(|| quote! { () });
let ty = _impl.target_type;
let object_marks = body.variants.iter().map(|field| {
let _ty = &field.ty;
quote!(
<#_ty as #crate_name::marker::GraphQLObjectType<#scalar>>::mark();
)
});
let output = quote! {
impl #impl_generics #crate_name::marker::IsOutputType<#scalar> for #ty #where_clause {
fn mark() {
#( #object_marks )*
}
}
impl #impl_generics #crate_name::GraphQLType<#scalar> for #ty #where_clause
{
type Context = #context;
type TypeInfo = ();
fn name(_ : &Self::TypeInfo) -> Option<&str> {
Some(#name)
}
fn meta<'r>(
info: &Self::TypeInfo,
registry: &mut #crate_name::Registry<'r, #scalar>
) -> #crate_name::meta::MetaType<'r, #scalar>
where
#scalar: 'r,
{
let types = &[
#( #meta_types )*
];
registry.build_union_type::<#ty>(
info, types
)
#description
.into_meta()
}
#[allow(unused_variables)]
fn concrete_type_name(&self, context: &Self::Context, _info: &Self::TypeInfo) -> String {
#( #concrete_type_resolver )*
panic!("Concrete type not handled by instance resolvers on {}", #name);
}
fn resolve_into_type(
&self,
_info: &Self::TypeInfo,
type_name: &str,
_: Option<&[#crate_name::Selection<#scalar>]>,
executor: &#crate_name::Executor<Self::Context, #scalar>,
) -> #crate_name::ExecutionResult<#scalar> {
let context = &executor.context();
#( #resolve_args )*
#( #resolve_into_type )*
panic!("Concrete type not handled by instance resolvers on {}", #name);
}
}
};
Ok(output.into())
}

View file

@ -16,15 +16,17 @@ mod derive_enum;
mod derive_input_object;
mod derive_object;
mod derive_scalar_value;
mod derive_union;
mod impl_object;
mod impl_scalar;
mod impl_union;
mod graphql_union;
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
use proc_macro_error::{proc_macro_error, ResultExt as _};
use result::GraphQLScope;
use self::util::Mode;
#[proc_macro_error]
#[proc_macro_derive(GraphQLEnum, attributes(graphql))]
pub fn derive_enum(input: TokenStream) -> TokenStream {
@ -93,16 +95,6 @@ pub fn derive_object_internal(input: TokenStream) -> TokenStream {
}
}
#[proc_macro_error]
#[proc_macro_derive(GraphQLUnion, attributes(graphql))]
pub fn derive_union(input: TokenStream) -> TokenStream {
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
let gen = derive_union::build_derive_union(ast, false, GraphQLScope::DeriveUnion);
match gen {
Ok(gen) => gen.into(),
Err(err) => proc_macro_error::abort!(err),
}
}
/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
/// derive.
///
@ -554,27 +546,616 @@ pub fn graphql_subscription_internal(args: TokenStream, input: TokenStream) -> T
))
}
/// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and
/// structs.
///
/// The `#[graphql]` helper attribute is used for configuring the derived implementation. Specifying
/// multiple `#[graphql]` attributes on the same definition is totally okay. They all will be
/// treated as a single attribute.
///
/// ```
/// use derive_more::From;
/// use juniper::{GraphQLObject, GraphQLUnion};
///
/// #[derive(GraphQLObject)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// #[derive(From, GraphQLUnion)]
/// enum CharacterEnum {
/// Human(Human),
/// Droid(Droid),
/// }
/// ```
///
/// # Custom name and description
///
/// The name of [GraphQL union][1] may be overriden with a `name` attribute's argument. By default,
/// a type name is used.
///
/// The description of [GraphQL union][1] may be specified either with a `description`/`desc`
/// attribute's argument, or with a regular Rust doc comment.
///
/// ```
/// # use juniper::{GraphQLObject, GraphQLUnion};
/// #
/// # #[derive(GraphQLObject)]
/// # struct Human {
/// # id: String,
/// # home_planet: String,
/// # }
/// #
/// # #[derive(GraphQLObject)]
/// # struct Droid {
/// # id: String,
/// # primary_function: String,
/// # }
/// #
/// #[derive(GraphQLUnion)]
/// #[graphql(name = "Character", desc = "Possible episode characters.")]
/// enum Chrctr {
/// Human(Human),
/// Droid(Droid),
/// }
///
/// // NOTICE: Rust docs are used as GraphQL description.
/// /// Possible episode characters.
/// #[derive(GraphQLUnion)]
/// enum CharacterWithDocs {
/// Human(Human),
/// Droid(Droid),
/// }
///
/// // NOTICE: `description` argument takes precedence over Rust docs.
/// /// Not a GraphQL description anymore.
/// #[derive(GraphQLUnion)]
/// #[graphql(description = "Possible episode characters.")]
/// enum CharacterWithDescription {
/// Human(Human),
/// Droid(Droid),
/// }
/// ```
///
/// # Custom context
///
/// By default, the generated implementation uses [unit type `()`][4] as context. To use a custom
/// context type for [GraphQL union][1] variants types or external resolver functions, specify it
/// with `context`/`Context` attribute's argument.
///
/// ```
/// # use juniper::{GraphQLObject, GraphQLUnion};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Context = CustomContext)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(Context = CustomContext)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// pub struct CustomContext;
/// impl juniper::Context for CustomContext {}
///
/// #[derive(GraphQLUnion)]
/// #[graphql(Context = CustomContext)]
/// enum Character {
/// Human(Human),
/// Droid(Droid),
/// }
/// ```
///
/// # Custom `ScalarValue`
///
/// By default, this macro generates code, which is generic over a `ScalarValue` type.
/// This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a
/// concrete `ScalarValue` type in its implementation. To resolve such problem, a concrete
/// `ScalarValue` type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's
/// argument.
///
/// ```
/// # use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Scalar = DefaultScalarValue)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// // NOTICE: Removing `Scalar` argument will fail compilation.
/// #[derive(GraphQLUnion)]
/// #[graphql(Scalar = DefaultScalarValue)]
/// enum Character {
/// Human(Human),
/// Droid(Droid),
/// }
/// ```
///
/// # Ignoring enum variants
///
/// To omit exposing an enum variant in the GraphQL schema, use an `ignore`/`skip` attribute's
/// argument directly on that variant.
///
/// > __WARNING__:
/// > It's the _library user's responsibility_ to ensure that ignored enum variant is _never_
/// > returned from resolvers, otherwise resolving the GraphQL query will __panic at runtime__.
///
/// ```
/// # use std::marker::PhantomData;
/// use derive_more::From;
/// use juniper::{GraphQLObject, GraphQLUnion};
///
/// #[derive(GraphQLObject)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// #[derive(From, GraphQLUnion)]
/// enum Character<S> {
/// Human(Human),
/// Droid(Droid),
/// #[from(ignore)]
/// #[graphql(ignore)] // or `#[graphql(skip)]`, your choice
/// _State(PhantomData<S>),
/// }
/// ```
///
/// # External resolver functions
///
/// To use a custom logic for resolving a [GraphQL union][1] variant, an external resolver function
/// may be specified with:
/// - either a `with` attribute's argument on an enum variant;
/// - or an `on` attribute's argument on an enum/struct itself.
///
/// ```
/// # use juniper::{GraphQLObject, GraphQLUnion};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Context = CustomContext)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(Context = CustomContext)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// pub struct CustomContext {
/// droid: Droid,
/// }
/// impl juniper::Context for CustomContext {}
///
/// #[derive(GraphQLUnion)]
/// #[graphql(Context = CustomContext)]
/// enum Character {
/// Human(Human),
/// #[graphql(with = Character::droid_from_context)]
/// Droid(Droid),
/// }
///
/// impl Character {
/// // NOTICE: The function signature must contain `&self` and `&Context`,
/// // and return `Option<&VariantType>`.
/// fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> {
/// Some(&ctx.droid)
/// }
/// }
///
/// #[derive(GraphQLUnion)]
/// #[graphql(Context = CustomContext)]
/// #[graphql(on Droid = CharacterWithoutDroid::droid_from_context)]
/// enum CharacterWithoutDroid {
/// Human(Human),
/// #[graphql(ignore)]
/// Droid,
/// }
///
/// impl CharacterWithoutDroid {
/// fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> {
/// if let Self::Droid = self {
/// Some(&ctx.droid)
/// } else {
/// None
/// }
/// }
/// }
/// ```
///
/// # Deriving structs
///
/// Specifying external resolver functions is mandatory for using a struct as a [GraphQL union][1],
/// because this is the only way to declare [GraphQL union][1] variants in this case.
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{GraphQLObject, GraphQLUnion};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Context = Database)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(Context = Database)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// struct Database {
/// humans: HashMap<String, Human>,
/// droids: HashMap<String, Droid>,
/// }
/// impl juniper::Context for Database {}
///
/// #[derive(GraphQLUnion)]
/// #[graphql(
/// Context = Database,
/// on Human = Character::get_human,
/// on Droid = Character::get_droid,
/// )]
/// struct Character {
/// id: String,
/// }
///
/// impl Character {
/// fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human>{
/// ctx.humans.get(&self.id)
/// }
///
/// fn get_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid>{
/// ctx.droids.get(&self.id)
/// }
/// }
/// ```
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html
#[proc_macro_error]
#[proc_macro_derive(GraphQLUnion, attributes(graphql))]
pub fn derive_union(input: TokenStream) -> TokenStream {
self::graphql_union::derive::expand(input.into(), Mode::Public)
.unwrap_or_abort()
.into()
}
#[proc_macro_error]
#[proc_macro_derive(GraphQLUnionInternal, attributes(graphql))]
#[doc(hidden)]
pub fn derive_union_internal(input: TokenStream) -> TokenStream {
self::graphql_union::derive::expand(input.into(), Mode::Internal)
.unwrap_or_abort()
.into()
}
/// `#[graphql_union]` macro for deriving a [GraphQL union][1] implementation for traits.
///
/// Specifying multiple `#[graphql_union]` attributes on the same definition is totally okay. They
/// all will be treated as a single attribute.
///
/// A __trait has to be [object safe][2]__, because schema resolvers will need to return a
/// [trait object][3] to specify a [GraphQL union][1] behind it. The [trait object][3] has to be
/// [`Send`] and [`Sync`].
///
/// ```
/// use juniper::{graphql_union, GraphQLObject};
///
/// #[derive(GraphQLObject)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// #[graphql_union]
/// trait Character {
/// // NOTICE: The method signature must contain `&self` and return `Option<&VariantType>`.
/// fn as_human(&self) -> Option<&Human> { None }
/// fn as_droid(&self) -> Option<&Droid> { None }
/// }
///
/// impl Character for Human {
/// fn as_human(&self) -> Option<&Human> { Some(&self) }
/// }
///
/// impl Character for Droid {
/// fn as_droid(&self) -> Option<&Droid> { Some(&self) }
/// }
/// ```
///
/// # Custom name and description
///
/// The name of [GraphQL union][1] may be overriden with a `name` attribute's argument. By default,
/// a type name is used.
///
/// The description of [GraphQL union][1] may be specified either with a `description`/`desc`
/// attribute's argument, or with a regular Rust doc comment.
///
/// ```
/// # use juniper::{graphql_union, GraphQLObject};
/// #
/// # #[derive(GraphQLObject)]
/// # struct Human {
/// # id: String,
/// # home_planet: String,
/// # }
/// #
/// # #[derive(GraphQLObject)]
/// # struct Droid {
/// # id: String,
/// # primary_function: String,
/// # }
/// #
/// #[graphql_union(name = "Character", desc = "Possible episode characters.")]
/// trait Chrctr {
/// fn as_human(&self) -> Option<&Human> { None }
/// fn as_droid(&self) -> Option<&Droid> { None }
/// }
///
/// // NOTICE: Rust docs are used as GraphQL description.
/// /// Possible episode characters.
/// trait CharacterWithDocs {
/// fn as_human(&self) -> Option<&Human> { None }
/// fn as_droid(&self) -> Option<&Droid> { None }
/// }
///
/// // NOTICE: `description` argument takes precedence over Rust docs.
/// /// Not a GraphQL description anymore.
/// #[graphql_union(description = "Possible episode characters.")]
/// trait CharacterWithDescription {
/// fn as_human(&self) -> Option<&Human> { None }
/// fn as_droid(&self) -> Option<&Droid> { None }
/// }
/// #
/// # impl Chrctr for Human {}
/// # impl Chrctr for Droid {}
/// # impl CharacterWithDocs for Human {}
/// # impl CharacterWithDocs for Droid {}
/// # impl CharacterWithDescription for Human {}
/// # impl CharacterWithDescription for Droid {}
/// ```
///
/// # Custom context
///
/// By default, the generated implementation tries to infer `juniper::Context` type from signatures
/// of trait methods, and uses [unit type `()`][4] if signatures contains no context arguments.
///
/// If `juniper::Context` type cannot be inferred or is inferred incorrectly, then specify it
/// explicitly with `context`/`Context` attribute's argument.
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{graphql_union, GraphQLObject};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Context = Database)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(Context = Database)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// struct Database {
/// humans: HashMap<String, Human>,
/// droids: HashMap<String, Droid>,
/// }
/// impl juniper::Context for Database {}
///
/// #[graphql_union(Context = Database)]
/// trait Character {
/// fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> { None }
/// fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> { None }
/// }
///
/// impl Character for Human {
/// fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
/// ctx.humans.get(&self.id)
/// }
/// }
///
/// impl Character for Droid {
/// fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> {
/// ctx.droids.get(&self.id)
/// }
/// }
/// ```
///
/// # Custom `ScalarValue`
///
/// By default, `#[graphql_union]` macro generates code, which is generic over a `ScalarValue` type.
/// This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a
/// concrete `ScalarValue` type in its implementation. To resolve such problem, a concrete
/// `ScalarValue` type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's
/// argument.
///
/// ```
/// # use juniper::{graphql_union, DefaultScalarValue, GraphQLObject};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Scalar = DefaultScalarValue)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// // NOTICE: Removing `Scalar` argument will fail compilation.
/// #[graphql_union(Scalar = DefaultScalarValue)]
/// trait Character {
/// fn as_human(&self) -> Option<&Human> { None }
/// fn as_droid(&self) -> Option<&Droid> { None }
/// }
/// #
/// # impl Character for Human {}
/// # impl Character for Droid {}
/// ```
///
/// # Ignoring trait methods
///
/// To omit some trait method to be assumed as a [GraphQL union][1] variant and ignore it, use an
/// `ignore`/`skip` attribute's argument directly on that method.
///
/// ```
/// # use juniper::{graphql_union, GraphQLObject};
/// #
/// # #[derive(GraphQLObject)]
/// # struct Human {
/// # id: String,
/// # home_planet: String,
/// # }
/// #
/// # #[derive(GraphQLObject)]
/// # struct Droid {
/// # id: String,
/// # primary_function: String,
/// # }
/// #
/// #[graphql_union]
/// trait Character {
/// fn as_human(&self) -> Option<&Human> { None }
/// fn as_droid(&self) -> Option<&Droid> { None }
/// #[graphql_union(ignore)] // or `#[graphql_union(skip)]`, your choice
/// fn id(&self) -> &str;
/// }
/// #
/// # impl Character for Human {
/// # fn id(&self) -> &str { self.id.as_str() }
/// # }
/// #
/// # impl Character for Droid {
/// # fn id(&self) -> &str { self.id.as_str() }
/// # }
/// ```
///
/// # External resolver functions
///
/// It's not mandatory to use trait methods as [GraphQL union][1] variant resolvers, and instead
/// custom functions may be specified with an `on` attribute's argument.
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{graphql_union, GraphQLObject};
/// #
/// #[derive(GraphQLObject)]
/// #[graphql(Context = Database)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(Context = Database)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
///
/// struct Database {
/// humans: HashMap<String, Human>,
/// droids: HashMap<String, Droid>,
/// }
/// impl juniper::Context for Database {}
///
/// #[graphql_union(Context = Database)]
/// #[graphql_union(
/// on Human = DynCharacter::get_human,
/// on Droid = get_droid,
/// )]
/// trait Character {
/// #[graphql_union(ignore)]
/// fn id(&self) -> &str;
/// }
///
/// impl Character for Human {
/// fn id(&self) -> &str { self.id.as_str() }
/// }
///
/// impl Character for Droid {
/// fn id(&self) -> &str { self.id.as_str() }
/// }
///
/// // NOTICE: The trait object is always `Send` and `Sync`.
/// type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
///
/// impl<'a> DynCharacter<'a> {
/// fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
/// ctx.humans.get(self.id())
/// }
/// }
///
/// // NOTICE: Custom resolver 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: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droid> {
/// ctx.droids.get(ch.id())
/// }
/// ```
///
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety
/// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html
/// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html
#[proc_macro_error]
#[proc_macro_attribute]
pub fn graphql_union(attrs: TokenStream, body: TokenStream) -> TokenStream {
let attrs = proc_macro2::TokenStream::from(attrs);
let body = proc_macro2::TokenStream::from(body);
let gen = impl_union::impl_union(false, attrs, body, GraphQLScope::ImplUnion);
match gen {
Ok(gen) => gen.into(),
Err(err) => proc_macro_error::abort!(err),
}
pub fn graphql_union(attr: TokenStream, body: TokenStream) -> TokenStream {
self::graphql_union::attr::expand(attr.into(), body.into(), Mode::Public)
.unwrap_or_abort()
.into()
}
#[proc_macro_error]
#[proc_macro_attribute]
#[doc(hidden)]
pub fn graphql_union_internal(attrs: TokenStream, body: TokenStream) -> TokenStream {
let attrs = proc_macro2::TokenStream::from(attrs);
let body = proc_macro2::TokenStream::from(body);
let gen = impl_union::impl_union(true, attrs, body, GraphQLScope::ImplUnion);
match gen {
Ok(gen) => gen.into(),
Err(err) => proc_macro_error::abort!(err),
}
pub fn graphql_union_internal(attr: TokenStream, body: TokenStream) -> TokenStream {
self::graphql_union::attr::expand(attr.into(), body.into(), Mode::Internal)
.unwrap_or_abort()
.into()
}

View file

@ -5,28 +5,29 @@ use proc_macro2::Span;
use proc_macro_error::{Diagnostic, Level};
use std::fmt;
pub const GRAPHQL_SPECIFICATION: &'static str = "https://spec.graphql.org/June2018/";
/// URL of the GraphQL specification (June 2018 Edition).
pub const SPEC_URL: &'static str = "https://spec.graphql.org/June2018/";
#[allow(unused_variables)]
pub enum GraphQLScope {
UnionAttr,
DeriveObject,
DeriveInputObject,
DeriveUnion,
UnionDerive,
DeriveEnum,
DeriveScalar,
ImplUnion,
ImplScalar,
ImplObject,
}
impl GraphQLScope {
pub fn specification_section(&self) -> &str {
pub fn spec_section(&self) -> &str {
match self {
GraphQLScope::DeriveObject | GraphQLScope::ImplObject => "#sec-Objects",
GraphQLScope::DeriveInputObject => "#sec-Input-Objects",
GraphQLScope::DeriveUnion | GraphQLScope::ImplUnion => "#sec-Unions",
GraphQLScope::DeriveEnum => "#sec-Enums",
GraphQLScope::DeriveScalar | GraphQLScope::ImplScalar => "#sec-Scalars",
Self::DeriveObject | Self::ImplObject => "#sec-Objects",
Self::DeriveInputObject => "#sec-Input-Objects",
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
Self::DeriveEnum => "#sec-Enums",
Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
}
}
}
@ -34,11 +35,11 @@ impl GraphQLScope {
impl fmt::Display for GraphQLScope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let name = match self {
GraphQLScope::DeriveObject | GraphQLScope::ImplObject => "object",
GraphQLScope::DeriveInputObject => "input object",
GraphQLScope::DeriveUnion | GraphQLScope::ImplUnion => "union",
GraphQLScope::DeriveEnum => "enum",
GraphQLScope::DeriveScalar | GraphQLScope::ImplScalar => "scalar",
Self::DeriveObject | Self::ImplObject => "object",
Self::DeriveInputObject => "input object",
Self::UnionAttr | Self::UnionDerive => "union",
Self::DeriveEnum => "enum",
Self::DeriveScalar | Self::ImplScalar => "scalar",
};
write!(f, "GraphQL {}", name)
@ -51,20 +52,22 @@ pub enum UnsupportedAttribute {
Skip,
Interface,
Scalar,
Description,
Deprecation,
Default,
}
impl GraphQLScope {
fn specification_link(&self) -> String {
format!("{}{}", GRAPHQL_SPECIFICATION, self.specification_section())
fn spec_link(&self) -> String {
format!("{}{}", SPEC_URL, self.spec_section())
}
pub fn custom<S: AsRef<str>>(&self, span: Span, msg: S) {
pub fn custom<S: AsRef<str>>(&self, span: Span, msg: S) -> Diagnostic {
Diagnostic::spanned(span, Level::Error, format!("{} {}", self, msg.as_ref()))
.note(self.specification_link())
.emit();
.note(self.spec_link())
}
pub fn emit_custom<S: AsRef<str>>(&self, span: Span, msg: S) {
self.custom(span, msg).emit()
}
pub fn custom_error<S: AsRef<str>>(&self, span: Span, msg: S) -> syn::Error {
@ -97,7 +100,7 @@ impl GraphQLScope {
Level::Error,
format!("{} expects at least one field", self),
)
.note(self.specification_link())
.note(self.spec_link())
.emit();
}
@ -120,7 +123,7 @@ impl GraphQLScope {
),
)
.help(format!("There is at least one other field with the same name `{}`, possibly renamed via the #[graphql] attribute", dup.name))
.note(self.specification_link())
.note(self.spec_link())
.emit();
});
})
@ -130,9 +133,12 @@ impl GraphQLScope {
Diagnostic::spanned(
field,
Level::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 GraphQLs introspection system.".to_string(),
"All types and directives defined within a schema must not have a name which begins \
with `__` (two underscores), as this is used exclusively by GraphQLs introspection \
system."
.into(),
)
.note(format!("{}#sec-Schema", GRAPHQL_SPECIFICATION))
.emit();
.note(format!("{}#sec-Schema", SPEC_URL))
.emit();
}
}

View file

@ -1,9 +1,13 @@
#![allow(clippy::single_match)]
pub mod duplicate;
pub mod mode;
pub mod option_ext;
pub mod parse_impl;
pub mod span_container;
use std::ops::Deref as _;
use proc_macro2::{Span, TokenStream};
use proc_macro_error::abort;
use quote::quote;
@ -14,10 +18,7 @@ use syn::{
MetaNameValue, NestedMeta, Token,
};
pub fn juniper_path(is_internal: bool) -> syn::Path {
let name = if is_internal { "crate" } else { "juniper" };
syn::parse_str::<syn::Path>(name).unwrap()
}
pub use self::{mode::Mode, option_ext::OptionExt};
/// Returns the name of a type.
/// If the type does not end in a simple ident, `None` is returned.
@ -74,6 +75,15 @@ pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool {
}
}
/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested
/// [`syn::TypeParen`]s asap).
pub fn unparenthesize(ty: &syn::Type) -> &syn::Type {
match ty {
syn::Type::Paren(ty) => unparenthesize(ty.elem.deref()),
_ => ty,
}
}
#[derive(Debug)]
pub struct DeprecationAttr {
pub reason: Option<String>,
@ -85,6 +95,16 @@ pub fn find_graphql_attr(attrs: &[Attribute]) -> Option<&Attribute> {
.find(|attr| path_eq_single(&attr.path, "graphql"))
}
/// Filters given `attrs` to contain attributes only with the given `name`.
pub fn filter_attrs<'a>(
name: &'a str,
attrs: &'a [Attribute],
) -> impl Iterator<Item = &'a Attribute> + 'a {
attrs
.iter()
.filter(move |attr| path_eq_single(&attr.path, name))
}
pub fn get_deprecated(attrs: &[Attribute]) -> Option<SpanContainer<DeprecationAttr>> {
attrs
.iter()
@ -669,6 +689,7 @@ pub struct GraphQLTypeDefiniton {
pub generic_scalar: bool,
// FIXME: make this redundant.
pub no_async: bool,
pub mode: Mode,
}
impl GraphQLTypeDefiniton {
@ -867,7 +888,7 @@ impl GraphQLTypeDefiniton {
Err(e) => Err(e),
}
};
use futures::future;
use #juniper_crate_name::futures::future;
future::FutureExt::boxed(f)
},
)
@ -884,7 +905,7 @@ impl GraphQLTypeDefiniton {
Err(e) => Err(e),
}
};
use futures::future;
use #juniper_crate_name::futures::future;
future::FutureExt::boxed(f)
)
} else {
@ -894,7 +915,7 @@ impl GraphQLTypeDefiniton {
Ok(None) => Ok(#juniper_crate_name::Value::null()),
Err(e) => Err(e),
};
use futures::future;
use #juniper_crate_name::futures::future;
future::FutureExt::boxed(future::ready(v))
)
};
@ -934,7 +955,7 @@ impl GraphQLTypeDefiniton {
) -> #juniper_crate_name::BoxFuture<'b, #juniper_crate_name::ExecutionResult<#scalar>>
where #scalar: Send + Sync,
{
use futures::future;
use #juniper_crate_name::futures::future;
use #juniper_crate_name::GraphQLType;
match field {
#( #resolve_matches_async )*
@ -1177,7 +1198,7 @@ impl GraphQLTypeDefiniton {
};
quote!(
#name => {
futures::FutureExt::boxed(async move {
#juniper_crate_name::futures::FutureExt::boxed(async move {
let res #_type = { #code };
let res = #juniper_crate_name::IntoFieldResult::<_, #scalar>::into_result(res)?;
let executor= executor.as_owned_executor();
@ -1267,7 +1288,7 @@ impl GraphQLTypeDefiniton {
args: #juniper_crate_name::Arguments<'args, #scalar>,
executor: &'ref_e #juniper_crate_name::Executor<'ref_e, 'e, Self::Context, #scalar>,
) -> std::pin::Pin<Box<
dyn futures::future::Future<
dyn #juniper_crate_name::futures::future::Future<
Output = Result<
#juniper_crate_name::Value<#juniper_crate_name::ValuesStream<'res, #scalar>>,
#juniper_crate_name::FieldError<#scalar>
@ -1284,7 +1305,7 @@ impl GraphQLTypeDefiniton {
'res: 'f,
{
use #juniper_crate_name::Value;
use futures::stream::StreamExt as _;
use #juniper_crate_name::futures::stream::StreamExt as _;
match field_name {
#( #resolve_matches_async )*
@ -1302,245 +1323,6 @@ impl GraphQLTypeDefiniton {
)
}
pub fn into_union_tokens(self, juniper_crate_name: &str) -> TokenStream {
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
let name = &self.name;
let ty = &self._type;
let context = self
.context
.as_ref()
.map(|ctx| quote!( #ctx ))
.unwrap_or_else(|| quote!(()));
let scalar = self
.scalar
.as_ref()
.map(|s| quote!( #s ))
.unwrap_or_else(|| {
if self.generic_scalar {
// If generic_scalar is true, we always insert a generic scalar.
// See more comments below.
quote!(__S)
} else {
quote!(#juniper_crate_name::DefaultScalarValue)
}
});
let description = self
.description
.as_ref()
.map(|description| quote!( .description(#description) ));
let meta_types = self.fields.iter().map(|field| {
let var_ty = &field._type;
quote! {
registry.get_type::<&#var_ty>(&(())),
}
});
let matcher_variants = self
.fields
.iter()
.map(|field| {
let var_ty = &field._type;
let resolver_code = &field.resolver_code;
quote!(
#resolver_code(ref x) => <#var_ty as #juniper_crate_name::GraphQLType<#scalar>>::name(&()).unwrap().to_string(),
)
});
let concrete_type_resolver = quote!(
match self {
#( #matcher_variants )*
}
);
let matcher_expr: Vec<_> = self
.fields
.iter()
.map(|field| {
let resolver_code = &field.resolver_code;
quote!(
match self { #resolver_code(ref val) => Some(val), _ => None, }
)
})
.collect();
let resolve_into_type = self.fields.iter().zip(matcher_expr.iter()).map(|(field, expr)| {
let var_ty = &field._type;
quote! {
if type_name == (<#var_ty as #juniper_crate_name::GraphQLType<#scalar>>::name(&())).unwrap() {
return #juniper_crate_name::IntoResolvable::into(
{ #expr },
executor.context()
)
.and_then(|res| {
match res {
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
None => Ok(#juniper_crate_name::Value::null()),
}
});
}
}
});
let resolve_into_type_async = self.fields.iter().zip(matcher_expr.iter()).map(|(field, expr)| {
let var_ty = &field._type;
quote! {
if type_name == (<#var_ty as #juniper_crate_name::GraphQLType<#scalar>>::name(&())).unwrap() {
let inner_res = #juniper_crate_name::IntoResolvable::into(
{ #expr },
executor.context()
);
let f = async move {
match inner_res {
Ok(Some((ctx, r))) => {
let subexec = executor.replaced_context(ctx);
subexec.resolve_with_ctx_async(&(), &r).await
},
Ok(None) => Ok(#juniper_crate_name::Value::null()),
Err(e) => Err(e),
}
};
use futures::future;
return future::FutureExt::boxed(f);
}
}
});
let mut generics = self.generics.clone();
if self.scalar.is_none() && self.generic_scalar {
// No custom scalar specified, but always generic specified.
// Therefore we inject the generic scalar.
generics.params.push(parse_quote!(__S));
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
// Insert ScalarValue constraint.
where_clause
.predicates
.push(parse_quote!(__S: #juniper_crate_name::ScalarValue));
}
let (impl_generics, _, where_clause) = generics.split_for_impl();
let mut where_async = where_clause.cloned().unwrap_or_else(|| parse_quote!(where));
where_async
.predicates
.push(parse_quote!( #scalar: Send + Sync ));
where_async.predicates.push(parse_quote!(Self: Send + Sync));
let async_type_impl = quote!(
impl#impl_generics #juniper_crate_name::GraphQLTypeAsync<#scalar> for #ty
#where_async
{
fn resolve_into_type_async<'b>(
&'b self,
_info: &'b Self::TypeInfo,
type_name: &str,
_: Option<&'b [#juniper_crate_name::Selection<'b, #scalar>]>,
executor: &'b #juniper_crate_name::Executor<'b, 'b, Self::Context, #scalar>
) -> #juniper_crate_name::BoxFuture<'b, #juniper_crate_name::ExecutionResult<#scalar>> {
let context = &executor.context();
#( #resolve_into_type_async )*
panic!("Concrete type not handled by instance resolvers on {}", #name);
}
}
);
let convesion_impls = self.fields.iter().map(|field| {
let variant_ty = &field._type;
let resolver_code = &field.resolver_code;
quote!(
impl std::convert::From<#variant_ty> for #ty {
fn from(val: #variant_ty) -> Self {
#resolver_code(val)
}
}
)
});
let object_marks = self.fields.iter().map(|field| {
let _ty = &field._type;
quote!(
<#_ty as #juniper_crate_name::marker::GraphQLObjectType<#scalar>>::mark();
)
});
let mut type_impl = quote! {
#( #convesion_impls )*
impl #impl_generics #juniper_crate_name::marker::IsOutputType<#scalar> for #ty #where_clause {
fn mark() {
#( #object_marks )*
}
}
impl #impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #where_clause
{
type Context = #context;
type TypeInfo = ();
fn name(_ : &Self::TypeInfo) -> Option<&str> {
Some(#name)
}
fn meta<'r>(
info: &Self::TypeInfo,
registry: &mut #juniper_crate_name::Registry<'r, #scalar>
) -> #juniper_crate_name::meta::MetaType<'r, #scalar>
where
#scalar: 'r,
{
let types = &[
#( #meta_types )*
];
registry.build_union_type::<#ty>(
info, types
)
#description
.into_meta()
}
#[allow(unused_variables)]
fn concrete_type_name(&self, context: &Self::Context, _info: &Self::TypeInfo) -> String {
#concrete_type_resolver
}
fn resolve_into_type(
&self,
_info: &Self::TypeInfo,
type_name: &str,
_: Option<&[#juniper_crate_name::Selection<#scalar>]>,
executor: &#juniper_crate_name::Executor<Self::Context, #scalar>,
) -> #juniper_crate_name::ExecutionResult<#scalar> {
let context = &executor.context();
#( #resolve_into_type )*
panic!("Concrete type not handled by instance resolvers on {}", #name);
}
}
};
if !self.no_async {
type_impl.extend(async_type_impl)
}
type_impl
}
pub fn into_enum_tokens(self, juniper_crate_name: &str) -> TokenStream {
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
@ -1660,7 +1442,7 @@ impl GraphQLTypeDefiniton {
executor: &'a #juniper_crate_name::Executor<Self::Context, #scalar>,
) -> #juniper_crate_name::BoxFuture<'a, #juniper_crate_name::ExecutionResult<#scalar>> {
use #juniper_crate_name::GraphQLType;
use futures::future;
use #juniper_crate_name::futures::future;
let v = self.resolve(info, selection_set, executor);
future::FutureExt::boxed(future::ready(v))
}

View file

@ -0,0 +1,32 @@
//! Code generation mode.
/// Code generation mode for macros.
#[derive(Clone, Copy, Debug)]
pub enum Mode {
/// Generated code is intended to be used by library users.
Public,
/// Generated code is use only inside the library itself.
Internal,
}
impl Mode {
pub fn crate_path(&self) -> syn::Path {
syn::parse_str::<syn::Path>(match self {
Self::Public => "::juniper",
Self::Internal => "crate",
})
.unwrap_or_else(|e| proc_macro_error::abort!(e))
}
}
// TODO: Remove once all macros are refactored with `Mode`.
impl From<bool> for Mode {
fn from(is_internal: bool) -> Self {
if is_internal {
Mode::Internal
} else {
Mode::Public
}
}
}

View file

@ -0,0 +1,24 @@
/// Handy extension of [`Option`] methods used in this crate.
pub trait OptionExt {
type Inner;
/// Transforms the `Option<T>` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)`
/// to `Err(err(v))`.
fn none_or_else<E, F>(self, err: F) -> Result<(), E>
where
F: FnOnce(Self::Inner) -> E;
}
impl<T> OptionExt for Option<T> {
type Inner = T;
fn none_or_else<E, F>(self, err: F) -> Result<(), E>
where
F: FnOnce(T) -> E,
{
match self {
Some(v) => Err(err(v)),
None => Ok(()),
}
}
}

View file

@ -19,36 +19,6 @@ pub struct ImplBlock {
}
impl ImplBlock {
/// Parse a `fn resolve()` method declaration found in most
/// generators which rely on `impl` blocks.
pub fn parse_resolve_method(
&self,
method: &syn::ImplItemMethod,
) -> syn::Result<Vec<TokenStream>> {
if method.sig.ident != "resolve" {
return Err(syn::Error::new(
method.sig.ident.span(),
"expect the method named `resolve`",
));
}
if let syn::ReturnType::Type(_, _) = &method.sig.output {
return Err(syn::Error::new(
method.sig.output.span(),
"method must not have a declared return type",
));
}
//NOTICE: `fn resolve()` is a subset of `fn <NAME>() -> <TYPE>`
self.parse_method(method, false, |captured, _, _| {
Err(syn::Error::new(
captured.span(),
"only executor or context types are allowed",
))
})
.map(|(tokens, _empty)| tokens)
}
/// Parse a `fn <NAME>() -> <TYPE>` method declaration found in
/// objects.
pub fn parse_method<

View file

@ -1,8 +1,12 @@
use std::{
hash::{Hash, Hasher},
ops,
};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::cmp::{Eq, PartialEq};
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct SpanContainer<T> {
expr: Option<Span>,
ident: Span,
@ -24,6 +28,19 @@ impl<T> SpanContainer<T> {
self.ident
}
pub fn span_joined(&self) -> Span {
if let Some(s) = self.expr {
// TODO: Use `Span::join` once stabilized and available on stable:
// https://github.com/rust-lang/rust/issues/54725
// self.ident.join(s).unwrap()
// At the moment, just return the second, more meaningful part.
s
} else {
self.ident
}
}
pub fn into_inner(self) -> T {
self.val
}
@ -47,7 +64,7 @@ impl<T> AsRef<T> for SpanContainer<T> {
}
}
impl<T> std::ops::Deref for SpanContainer<T> {
impl<T> ops::Deref for SpanContainer<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
@ -68,3 +85,12 @@ impl<T: PartialEq> PartialEq<T> for SpanContainer<T> {
&self.val == other
}
}
impl<T: Hash> Hash for SpanContainer<T> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.val.hash(state)
}
}