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:
parent
31d08888e4
commit
ddc1488195
99 changed files with 5693 additions and 1567 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
use juniper::graphql_union;
|
||||
|
||||
#[graphql_union]
|
||||
enum Character {}
|
||||
|
||||
fn main() {}
|
|
@ -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)
|
|
@ -1,12 +0,0 @@
|
|||
#[derive(juniper::GraphQLEnum)]
|
||||
pub enum Test {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLUnion)]
|
||||
enum Character {
|
||||
Test(Test),
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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)
|
|
@ -1,4 +0,0 @@
|
|||
#[derive(juniper::GraphQLUnion)]
|
||||
enum Character {}
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,6 @@
|
|||
use juniper::GraphQLUnion;
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
union Character { id: i32 }
|
||||
|
||||
fn main() {}
|
|
@ -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 }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -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() {}
|
|
@ -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
|
|
@ -0,0 +1,13 @@
|
|||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
enum __Character {
|
||||
A(Human),
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
pub struct Human {
|
||||
id: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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 GraphQL’s introspection system.
|
||||
--> $DIR/enum_name_double_underscored.rs:4:6
|
||||
|
|
||||
4 | enum __Character {
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,6 @@
|
|||
use juniper::GraphQLUnion;
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
enum Character {}
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -0,0 +1,14 @@
|
|||
use juniper::{GraphQLEnum, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
pub enum Test {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
enum Character {
|
||||
Test(Test),
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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)
|
|
@ -0,0 +1,9 @@
|
|||
use juniper::GraphQLUnion;
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
enum Character {
|
||||
A(u8),
|
||||
B(u8),
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -1,4 +1,6 @@
|
|||
#[derive(juniper::GraphQLUnion)]
|
||||
use juniper::GraphQLUnion;
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
enum Character {
|
||||
A(std::string::String),
|
||||
B(String),
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -1,10 +0,0 @@
|
|||
enum Character {}
|
||||
|
||||
#[juniper::graphql_union]
|
||||
impl Character {
|
||||
fn resolve(&self) {
|
||||
match self {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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() {}
|
|
@ -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 GraphQL’s introspection system.
|
||||
--> $DIR/struct_name_double_underscored.rs:5:8
|
||||
|
|
||||
5 | struct __Character;
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,6 @@
|
|||
use juniper::GraphQLUnion;
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
struct Character;
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -0,0 +1,5 @@
|
|||
error: duplicated attribute
|
||||
--> $DIR/struct_same_type_pretty.rs:5:14
|
||||
|
|
||||
5 | #[graphql(on i32 = Character::b)]
|
||||
| ^^^
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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 GraphQL’s introspection system.
|
||||
--> $DIR/trait_name_double_underscored.rs:4:7
|
||||
|
|
||||
4 | trait __Character {
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Schema
|
|
@ -0,0 +1,6 @@
|
|||
use juniper::graphql_union;
|
||||
|
||||
#[graphql_union]
|
||||
trait Character {}
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -0,0 +1,9 @@
|
|||
use juniper::graphql_union;
|
||||
|
||||
#[graphql_union]
|
||||
trait Character {
|
||||
fn a(&self) -> Option<&u8>;
|
||||
fn b(&self) -> Option<&u8>;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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
|
|
@ -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" }
|
||||
|
|
|
@ -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![]
|
||||
))
|
||||
);
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
1077
integration_tests/juniper_tests/src/codegen/union_attr.rs
Normal file
1077
integration_tests/juniper_tests/src/codegen/union_attr.rs
Normal file
File diff suppressed because it is too large
Load diff
1388
integration_tests/juniper_tests/src/codegen/union_derive.rs
Normal file
1388
integration_tests/juniper_tests/src/codegen/union_derive.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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()");
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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" };
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
345
juniper_codegen/src/graphql_union/attr.rs
Normal file
345
juniper_codegen/src/graphql_union/attr.rs
Normal 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()),
|
||||
}
|
||||
}
|
226
juniper_codegen/src/graphql_union/derive.rs
Normal file
226
juniper_codegen/src/graphql_union/derive.rs
Normal 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,
|
||||
})
|
||||
}
|
700
juniper_codegen/src/graphql_union/mod.rs
Normal file
700
juniper_codegen/src/graphql_union/mod.rs
Normal 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()
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 GraphQL’s 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 GraphQL’s introspection \
|
||||
system."
|
||||
.into(),
|
||||
)
|
||||
.note(format!("{}#sec-Schema", GRAPHQL_SPECIFICATION))
|
||||
.emit();
|
||||
.note(format!("{}#sec-Schema", SPEC_URL))
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
32
juniper_codegen/src/util/mode.rs
Normal file
32
juniper_codegen/src/util/mode.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
24
juniper_codegen/src/util/option_ext.rs
Normal file
24
juniper_codegen/src/util/option_ext.rs
Normal 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(()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue