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
|
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.
|
||||||
only exception is that they don't contain fields on their own.
|
|
||||||
|
|
||||||
In Juniper, the `graphql_union!` has identical syntax to the
|
For implementing [GraphQL unions][1] Juniper provides:
|
||||||
[interface macro](interfaces.md), but does not support defining
|
- `#[derive(GraphQLUnion)]` macro for enums and structs.
|
||||||
fields. Therefore, the same considerations about using traits,
|
- `#[graphql_union]` for traits.
|
||||||
placeholder types, or enums still apply to unions. For simple
|
|
||||||
situations, Juniper provides `#[derive(GraphQLUnion)]` for enums.
|
|
||||||
|
|
||||||
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
|
```rust
|
||||||
#[derive(juniper::GraphQLObject)]
|
# #![allow(dead_code)]
|
||||||
|
use derive_more::From;
|
||||||
|
use juniper::{GraphQLObject, GraphQLUnion};
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: 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 {
|
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_human(&self) -> Option<&Human> { None }
|
||||||
fn as_droid(&self) -> Option<&Droid> { None }
|
fn as_droid(&self) -> Option<&Droid> { None }
|
||||||
}
|
}
|
||||||
|
@ -42,34 +274,28 @@ impl Character for Human {
|
||||||
impl Character for Droid {
|
impl Character for Droid {
|
||||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
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() {}
|
# 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
|
```rust
|
||||||
|
# #![allow(unused_variables)]
|
||||||
# use std::collections::HashMap;
|
# use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
use juniper::{graphql_union, GraphQLObject};
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(Context = Database)]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(Context = Database)]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
|
@ -80,10 +306,107 @@ struct Database {
|
||||||
humans: HashMap<String, Human>,
|
humans: HashMap<String, Human>,
|
||||||
droids: HashMap<String, Droid>,
|
droids: HashMap<String, Droid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl juniper::Context for Database {}
|
impl juniper::Context for Database {}
|
||||||
|
|
||||||
|
#[graphql_union(Context = Database)]
|
||||||
trait Character {
|
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;
|
fn id(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,125 +418,61 @@ impl Character for Droid {
|
||||||
fn id(&self) -> &str { self.id.as_str() }
|
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(
|
impl<'a> DynCharacter<'a> {
|
||||||
Context = Database
|
fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
|
||||||
)]
|
ctx.humans.get(self.id())
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {}
|
# 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
|
```rust
|
||||||
# use std::collections::HashMap;
|
# #![allow(dead_code)]
|
||||||
#[derive(juniper::GraphQLObject)]
|
use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
|
||||||
#[graphql(Context = Database)]
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(Scalar = DefaultScalarValue)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(Context = Database)]
|
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Database {
|
#[derive(GraphQLUnion)]
|
||||||
humans: HashMap<String, Human>,
|
#[graphql(Scalar = DefaultScalarValue)] // removing this line will fail compilation
|
||||||
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)]
|
|
||||||
enum Character {
|
enum Character {
|
||||||
Human(Human),
|
Human(Human),
|
||||||
Droid(Droid),
|
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() {}
|
# 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)]
|
[1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||||
enum Character {
|
[2]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
|
||||||
Human(Human),
|
|
||||||
Droid(Droid),
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ build = "build.rs"
|
||||||
juniper = { path = "../../../juniper" }
|
juniper = { path = "../../../juniper" }
|
||||||
juniper_iron = { path = "../../../juniper_iron" }
|
juniper_iron = { path = "../../../juniper_iron" }
|
||||||
juniper_subscriptions = { path = "../../../juniper_subscriptions" }
|
juniper_subscriptions = { path = "../../../juniper_subscriptions" }
|
||||||
|
|
||||||
|
derive_more = "0.99.7"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
tokio = { version = "0.2", features = ["rt-core", "blocking", "stream", "rt-util"] }
|
tokio = { version = "0.2", features = ["rt-core", "blocking", "stream", "rt-util"] }
|
||||||
iron = "0.5.0"
|
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 {
|
enum Character {
|
||||||
A(std::string::String),
|
A(std::string::String),
|
||||||
B(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]
|
[package]
|
||||||
name = "juniper_tests"
|
name = "juniper_tests"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
publish = false
|
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
juniper = { path = "../../juniper" }
|
derive_more = "0.99.7"
|
||||||
futures = "0.3.1"
|
futures = "0.3.1"
|
||||||
|
juniper = { path = "../../juniper" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = { version = "1" }
|
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_input_object;
|
||||||
mod derive_object;
|
mod derive_object;
|
||||||
mod derive_object_with_raw_idents;
|
mod derive_object_with_raw_idents;
|
||||||
mod derive_union;
|
|
||||||
mod impl_object;
|
mod impl_object;
|
||||||
mod impl_scalar;
|
mod impl_scalar;
|
||||||
mod impl_union;
|
|
||||||
mod scalar_value_transparent;
|
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
|
## 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`:
|
- Most error types now implement `std::error::Error`:
|
||||||
- `GraphQLError`
|
- `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))
|
- 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
|
- Better error messages for all proc macros (see
|
||||||
[#631](https://github.com/graphql-rust/juniper/pull/631)
|
[#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
|
## Breaking Changes
|
||||||
|
|
||||||
|
@ -45,10 +56,10 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).
|
||||||
|
|
||||||
- Remove deprecated `ScalarValue` custom derive (renamed to GraphQLScalarValue)
|
- 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
|
- `ScalarRefValue` trait removed. Trait was not required.
|
||||||
Trait was not required.
|
|
||||||
|
|
||||||
- Changed return type of GraphQLType::resolve to `ExecutionResult`
|
- Changed return type of GraphQLType::resolve to `ExecutionResult`
|
||||||
This was done to unify the return type of all resolver methods
|
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 type to `RootNode`,
|
||||||
add subscription endpoint to `playground_source()`
|
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
|
`#[graphql(scalar = "DefaultScalarValue")]`. Only
|
||||||
`#[derive(GraphQLInputObject)]` supported this syntax. The
|
`#[derive(GraphQLInputObject)]` supported this syntax. The
|
||||||
refactoring of GraphQLInputObject allowed to drop the support
|
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.
|
- 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)
|
# [[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)
|
- 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"] }
|
indexmap = { version = "1.0.0", features = ["serde-1"] }
|
||||||
serde = { version = "1.0.8", features = ["derive"] }
|
serde = { version = "1.0.8", features = ["derive"] }
|
||||||
serde_json = { version="1.0.2", optional = true }
|
serde_json = { version="1.0.2", optional = true }
|
||||||
|
static_assertions = "1.1"
|
||||||
url = { version = "2", optional = true }
|
url = { version = "2", optional = true }
|
||||||
uuid = { version = "0.8", optional = true }
|
uuid = { version = "0.8", optional = true }
|
||||||
|
|
||||||
|
|
|
@ -242,8 +242,9 @@ impl<S> IntoFieldError<S> for FieldError<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait IntoResolvable<'a, S, T: GraphQLType<S>, C>: Sized
|
pub trait IntoResolvable<'a, S, T, C>
|
||||||
where
|
where
|
||||||
|
T: GraphQLType<S>,
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -404,7 +405,7 @@ where
|
||||||
pub fn resolve_with_ctx<NewCtxT, T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
|
pub fn resolve_with_ctx<NewCtxT, T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
|
||||||
where
|
where
|
||||||
NewCtxT: FromContext<CtxT>,
|
NewCtxT: FromContext<CtxT>,
|
||||||
T: GraphQLType<S, Context = NewCtxT>,
|
T: GraphQLType<S, Context = NewCtxT> + ?Sized,
|
||||||
{
|
{
|
||||||
self.replaced_context(<NewCtxT as FromContext<CtxT>>::from(self.context))
|
self.replaced_context(<NewCtxT as FromContext<CtxT>>::from(self.context))
|
||||||
.resolve(info, value)
|
.resolve(info, value)
|
||||||
|
@ -413,7 +414,7 @@ where
|
||||||
/// Resolve a single arbitrary value into an `ExecutionResult`
|
/// Resolve a single arbitrary value into an `ExecutionResult`
|
||||||
pub fn resolve<T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
|
pub fn resolve<T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S, Context = CtxT>,
|
T: GraphQLType<S, Context = CtxT> + ?Sized,
|
||||||
{
|
{
|
||||||
value.resolve(info, self.current_selection_set, self)
|
value.resolve(info, self.current_selection_set, self)
|
||||||
}
|
}
|
||||||
|
@ -421,7 +422,7 @@ where
|
||||||
/// Resolve a single arbitrary value into an `ExecutionResult`
|
/// Resolve a single arbitrary value into an `ExecutionResult`
|
||||||
pub async fn resolve_async<T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
|
pub async fn resolve_async<T>(&self, info: &T::TypeInfo, value: &T) -> ExecutionResult<S>
|
||||||
where
|
where
|
||||||
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
|
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
CtxT: Send + Sync,
|
CtxT: Send + Sync,
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
|
@ -468,18 +469,15 @@ where
|
||||||
/// If the field fails to resolve, `null` will be returned.
|
/// 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>
|
pub async fn resolve_into_value_async<T>(&self, info: &T::TypeInfo, value: &T) -> Value<S>
|
||||||
where
|
where
|
||||||
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
|
T: crate::GraphQLTypeAsync<S, Context = CtxT> + Send + Sync + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
CtxT: Send + Sync,
|
CtxT: Send + Sync,
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
{
|
{
|
||||||
match self.resolve_async(info, value).await {
|
self.resolve_async(info, value).await.unwrap_or_else(|e| {
|
||||||
Ok(v) => v,
|
self.push_error(e);
|
||||||
Err(e) => {
|
Value::null()
|
||||||
self.push_error(e);
|
})
|
||||||
Value::null()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive a new executor by replacing the context
|
/// Derive a new executor by replacing the context
|
||||||
|
@ -1103,7 +1101,7 @@ where
|
||||||
/// construct its metadata and store it.
|
/// construct its metadata and store it.
|
||||||
pub fn get_type<T>(&mut self, info: &T::TypeInfo) -> Type<'r>
|
pub fn get_type<T>(&mut self, info: &T::TypeInfo) -> Type<'r>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized,
|
||||||
{
|
{
|
||||||
if let Some(name) = T::name(info) {
|
if let Some(name) = T::name(info) {
|
||||||
let validated_name = name.parse::<Name>().unwrap();
|
let validated_name = name.parse::<Name>().unwrap();
|
||||||
|
@ -1124,7 +1122,7 @@ where
|
||||||
/// Create a field with the provided name
|
/// Create a field with the provided name
|
||||||
pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S>
|
pub fn field<T>(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized,
|
||||||
{
|
{
|
||||||
Field {
|
Field {
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
|
@ -1156,7 +1154,7 @@ where
|
||||||
/// Create an argument with the provided name
|
/// Create an argument with the provided name
|
||||||
pub fn arg<T>(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S>
|
pub fn arg<T>(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S> + FromInputValue<S>,
|
T: GraphQLType<S> + FromInputValue<S> + ?Sized,
|
||||||
{
|
{
|
||||||
Argument::new(name, self.get_type::<T>(info))
|
Argument::new(name, self.get_type::<T>(info))
|
||||||
}
|
}
|
||||||
|
@ -1172,7 +1170,7 @@ where
|
||||||
info: &T::TypeInfo,
|
info: &T::TypeInfo,
|
||||||
) -> Argument<'r, S>
|
) -> Argument<'r, S>
|
||||||
where
|
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())
|
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`.
|
/// This expects the type to implement `FromInputValue`.
|
||||||
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
|
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
|
||||||
where
|
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()");
|
let name = T::name(info).expect("Scalar types must be named. Implement name()");
|
||||||
ScalarMeta::new::<T>(Cow::Owned(name.to_string()))
|
ScalarMeta::new::<T>(Cow::Owned(name.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a list meta type
|
/// 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);
|
let of_type = self.get_type::<T>(info);
|
||||||
ListMeta::new(of_type)
|
ListMeta::new(of_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a nullable meta 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,
|
&mut self,
|
||||||
info: &T::TypeInfo,
|
info: &T::TypeInfo,
|
||||||
) -> NullableMeta<'r> {
|
) -> NullableMeta<'r> {
|
||||||
|
@ -1219,7 +1220,7 @@ where
|
||||||
fields: &[Field<'r, S>],
|
fields: &[Field<'r, S>],
|
||||||
) -> ObjectMeta<'r, S>
|
) -> ObjectMeta<'r, S>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized,
|
||||||
{
|
{
|
||||||
let name = T::name(info).expect("Object types must be named. Implement name()");
|
let name = T::name(info).expect("Object types must be named. Implement name()");
|
||||||
|
|
||||||
|
@ -1235,7 +1236,7 @@ where
|
||||||
values: &[EnumValue],
|
values: &[EnumValue],
|
||||||
) -> EnumMeta<'r, S>
|
) -> EnumMeta<'r, S>
|
||||||
where
|
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()");
|
let name = T::name(info).expect("Enum types must be named. Implement name()");
|
||||||
|
|
||||||
|
@ -1250,7 +1251,7 @@ where
|
||||||
fields: &[Field<'r, S>],
|
fields: &[Field<'r, S>],
|
||||||
) -> InterfaceMeta<'r, S>
|
) -> InterfaceMeta<'r, S>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized,
|
||||||
{
|
{
|
||||||
let name = T::name(info).expect("Interface types must be named. Implement name()");
|
let name = T::name(info).expect("Interface types must be named. Implement name()");
|
||||||
|
|
||||||
|
@ -1262,7 +1263,7 @@ where
|
||||||
/// Create a union meta type
|
/// Create a union meta type
|
||||||
pub fn build_union_type<T>(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r>
|
pub fn build_union_type<T>(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized,
|
||||||
{
|
{
|
||||||
let name = T::name(info).expect("Union types must be named. Implement name()");
|
let name = T::name(info).expect("Union types must be named. Implement name()");
|
||||||
|
|
||||||
|
@ -1276,7 +1277,7 @@ where
|
||||||
args: &[Argument<'r, S>],
|
args: &[Argument<'r, S>],
|
||||||
) -> InputObjectMeta<'r, S>
|
) -> InputObjectMeta<'r, S>
|
||||||
where
|
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()");
|
let name = T::name(info).expect("Input object types must be named. Implement name()");
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,7 @@ mod union {
|
||||||
value::Value,
|
value::Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[crate::graphql_union_internal]
|
||||||
trait Pet {
|
trait Pet {
|
||||||
fn as_dog(&self) -> Option<&Dog> {
|
fn as_dog(&self) -> Option<&Dog> {
|
||||||
None
|
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 {
|
struct Dog {
|
||||||
name: String,
|
name: String,
|
||||||
woofs: bool,
|
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")]
|
#![doc(html_root_url = "https://docs.rs/juniper/0.14.2")]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub extern crate serde;
|
pub extern crate serde;
|
||||||
|
|
||||||
|
@ -111,6 +113,13 @@ extern crate uuid;
|
||||||
#[cfg(any(test, feature = "bson"))]
|
#[cfg(any(test, feature = "bson"))]
|
||||||
extern crate 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.
|
// Depend on juniper_codegen and re-export everything in it.
|
||||||
// This allows users to just depend on juniper and get the derive
|
// This allows users to just depend on juniper and get the derive
|
||||||
// functionality automatically.
|
// functionality automatically.
|
||||||
|
@ -124,7 +133,7 @@ pub use juniper_codegen::{
|
||||||
use juniper_codegen::{
|
use juniper_codegen::{
|
||||||
graphql_object_internal, graphql_scalar_internal, graphql_subscription_internal,
|
graphql_object_internal, graphql_scalar_internal, graphql_subscription_internal,
|
||||||
graphql_union_internal, GraphQLEnumInternal, GraphQLInputObjectInternal,
|
graphql_union_internal, GraphQLEnumInternal, GraphQLInputObjectInternal,
|
||||||
GraphQLScalarValueInternal,
|
GraphQLScalarValueInternal, GraphQLUnionInternal,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -161,7 +170,6 @@ use crate::{
|
||||||
parser::{parse_document_source, ParseError, Spanning},
|
parser::{parse_document_source, ParseError, Spanning},
|
||||||
validation::{validate_input_values, visit_all_rules, ValidatorContext},
|
validation::{validate_input_values, visit_all_rules, ValidatorContext},
|
||||||
};
|
};
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
ast::{FromInputValue, InputValue, Selection, ToInputValue, Type},
|
ast::{FromInputValue, InputValue, Selection, ToInputValue, Type},
|
||||||
|
@ -179,7 +187,7 @@ pub use crate::{
|
||||||
types::{
|
types::{
|
||||||
async_await::GraphQLTypeAsync,
|
async_await::GraphQLTypeAsync,
|
||||||
base::{Arguments, GraphQLType, TypeKind},
|
base::{Arguments, GraphQLType, TypeKind},
|
||||||
marker,
|
marker::{self, GraphQLUnion},
|
||||||
scalars::{EmptyMutation, EmptySubscription, ID},
|
scalars::{EmptyMutation, EmptySubscription, ID},
|
||||||
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
|
subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator},
|
||||||
},
|
},
|
||||||
|
@ -187,9 +195,6 @@ pub use crate::{
|
||||||
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value},
|
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
|
/// An error that prevented query execution
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
|
|
@ -14,81 +14,70 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::InputValue,
|
ast::InputValue,
|
||||||
|
graphql_object_internal,
|
||||||
schema::model::RootNode,
|
schema::model::RootNode,
|
||||||
types::scalars::{EmptyMutation, EmptySubscription},
|
types::scalars::{EmptyMutation, EmptySubscription},
|
||||||
value::{DefaultScalarValue, Object, Value},
|
value::{DefaultScalarValue, Object, Value},
|
||||||
|
GraphQLUnionInternal,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Concrete;
|
struct Concrete;
|
||||||
|
|
||||||
enum CustomName {
|
#[graphql_object_internal]
|
||||||
Concrete(Concrete),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WithLifetime<'a> {
|
|
||||||
Int(PhantomData<&'a i32>),
|
|
||||||
}
|
|
||||||
enum WithGenerics<T> {
|
|
||||||
Generic(T),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DescriptionFirst {
|
|
||||||
Concrete(Concrete),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Root;
|
|
||||||
|
|
||||||
#[crate::graphql_object_internal]
|
|
||||||
impl Concrete {
|
impl Concrete {
|
||||||
fn simple() -> i32 {
|
fn simple() -> i32 {
|
||||||
123
|
123
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::graphql_union_internal(name = "ACustomNamedUnion")]
|
#[derive(GraphQLUnionInternal)]
|
||||||
impl CustomName {
|
#[graphql(name = "ACustomNamedUnion", scalar = DefaultScalarValue)]
|
||||||
fn resolve(&self) {
|
enum CustomName {
|
||||||
match self {
|
Concrete(Concrete),
|
||||||
Concrete => match *self {
|
}
|
||||||
CustomName::Concrete(ref c) => Some(c),
|
|
||||||
},
|
#[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> {
|
impl<'a> WithLifetime<'a> {
|
||||||
fn resolve(&self) {
|
fn resolve(&self, _: &()) -> Option<&Concrete> {
|
||||||
match self {
|
if matches!(self, Self::Int(_)) {
|
||||||
Concrete => match *self {
|
Some(&Concrete)
|
||||||
WithLifetime::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> {
|
impl<T> WithGenerics<T> {
|
||||||
fn resolve(&self) {
|
fn resolve(&self, _: &()) -> Option<&Concrete> {
|
||||||
match self {
|
if matches!(self, Self::Generic(_)) {
|
||||||
Concrete => match *self {
|
Some(&Concrete)
|
||||||
WithGenerics::Generic(_) => Some(&Concrete),
|
} else {
|
||||||
},
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::graphql_union_internal(description = "A description")]
|
#[derive(GraphQLUnionInternal)]
|
||||||
impl DescriptionFirst {
|
#[graphql(description = "A description", scalar = DefaultScalarValue)]
|
||||||
fn resolve(&self) {
|
enum DescriptionFirst {
|
||||||
match self {
|
Concrete(Concrete),
|
||||||
Concrete => match *self {
|
|
||||||
DescriptionFirst::Concrete(ref c) => Some(c),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Root;
|
||||||
|
|
||||||
// FIXME: make async work
|
// FIXME: make async work
|
||||||
#[crate::graphql_object_internal(noasync)]
|
#[crate::graphql_object_internal(noasync)]
|
||||||
impl<'a> Root {
|
impl<'a> Root {
|
||||||
|
|
|
@ -572,8 +572,10 @@ where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
/// Build a new input type with the specified name and input fields
|
/// 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
|
pub fn new<T>(name: Cow<'a, str>, input_fields: &[Argument<'a, S>]) -> Self
|
||||||
where {
|
where
|
||||||
|
T: FromInputValue<S> + ?Sized,
|
||||||
|
{
|
||||||
InputObjectMeta {
|
InputObjectMeta {
|
||||||
name,
|
name,
|
||||||
description: None,
|
description: None,
|
||||||
|
|
|
@ -98,7 +98,7 @@ fn resolve_selection_set_into_async<'a, 'e, T, CtxT, S>(
|
||||||
executor: &'e Executor<'e, 'e, CtxT, S>,
|
executor: &'e Executor<'e, 'e, CtxT, S>,
|
||||||
) -> BoxFuture<'a, Value<S>>
|
) -> BoxFuture<'a, Value<S>>
|
||||||
where
|
where
|
||||||
T: GraphQLTypeAsync<S, Context = CtxT>,
|
T: GraphQLTypeAsync<S, Context = CtxT> + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
CtxT: 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>,
|
executor: &'a Executor<'a, 'a, CtxT, S>,
|
||||||
) -> Value<S>
|
) -> Value<S>
|
||||||
where
|
where
|
||||||
T: GraphQLTypeAsync<S, Context = CtxT> + Send + Sync,
|
T: GraphQLTypeAsync<S, Context = CtxT> + Send + Sync + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
CtxT: 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
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
|
@ -355,7 +355,7 @@ pub(crate) fn resolve_selection_set_into<T, CtxT, S>(
|
||||||
result: &mut Object<S>,
|
result: &mut Object<S>,
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
T: GraphQLType<S, Context = CtxT>,
|
T: GraphQLType<S, Context = CtxT> + ?Sized,
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
let meta_type = executor
|
let meta_type = executor
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{FromInputValue, InputValue, Selection, ToInputValue},
|
ast::{FromInputValue, InputValue, Selection, ToInputValue},
|
||||||
executor::ExecutionResult,
|
executor::{ExecutionResult, Executor, Registry},
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
|
types::{async_await::GraphQLTypeAsync, base::GraphQLType},
|
||||||
value::{ScalarValue, Value},
|
value::{ScalarValue, Value},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
executor::{Executor, Registry},
|
|
||||||
types::base::GraphQLType,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl<S, T, CtxT> GraphQLType<S> for Option<T>
|
impl<S, T, CtxT> GraphQLType<S> for Option<T>
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
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>
|
impl<S, T> FromInputValue<S> for Option<T>
|
||||||
where
|
where
|
||||||
T: FromInputValue<S>,
|
T: FromInputValue<S>,
|
||||||
|
@ -50,10 +70,7 @@ where
|
||||||
fn from_input_value<'a>(v: &'a InputValue<S>) -> Option<Option<T>> {
|
fn from_input_value<'a>(v: &'a InputValue<S>) -> Option<Option<T>> {
|
||||||
match v {
|
match v {
|
||||||
&InputValue::Null => Some(None),
|
&InputValue::Null => Some(None),
|
||||||
v => match v.convert() {
|
v => v.convert().map(Some),
|
||||||
Some(x) => Some(Some(x)),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
impl<T, S> FromInputValue<S> for Vec<T>
|
||||||
where
|
where
|
||||||
T: FromInputValue<S>,
|
T: FromInputValue<S>,
|
||||||
|
@ -117,13 +152,7 @@ where {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ref other => {
|
ref other => other.convert().map(|e| vec![e]),
|
||||||
if let Some(e) = other.convert() {
|
|
||||||
Some(vec![e])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,11 +163,11 @@ where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
fn to_input_value(&self) -> InputValue<S> {
|
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
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
T: GraphQLType<S, Context = CtxT>,
|
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]
|
impl<'a, T, S> ToInputValue<S> for &'a [T]
|
||||||
where
|
where
|
||||||
T: ToInputValue<S>,
|
T: ToInputValue<S>,
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
fn to_input_value(&self) -> InputValue<S> {
|
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>,
|
executor: &Executor<T::Context, S>,
|
||||||
info: &T::TypeInfo,
|
info: &T::TypeInfo,
|
||||||
iter: I,
|
iter: I,
|
||||||
) -> ExecutionResult<S>
|
) -> ExecutionResult<S>
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
I: Iterator<Item = T> + ExactSizeIterator,
|
I: Iterator<Item = &'t T> + ExactSizeIterator,
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized + 't,
|
||||||
{
|
{
|
||||||
let stop_on_null = executor
|
let stop_on_null = executor
|
||||||
.current_type()
|
.current_type()
|
||||||
|
@ -195,30 +242,26 @@ where
|
||||||
let mut result = Vec::with_capacity(iter.len());
|
let mut result = Vec::with_capacity(iter.len());
|
||||||
|
|
||||||
for o in iter {
|
for o in iter {
|
||||||
match executor.resolve(info, &o) {
|
let val = executor.resolve(info, o)?;
|
||||||
Ok(value) => {
|
if stop_on_null && val.is_null() {
|
||||||
if stop_on_null && value.is_null() {
|
return Ok(val);
|
||||||
return Ok(value);
|
} else {
|
||||||
} else {
|
result.push(val)
|
||||||
result.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::list(result))
|
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>,
|
executor: &'a Executor<'a, 'a, T::Context, S>,
|
||||||
info: &'a T::TypeInfo,
|
info: &'a T::TypeInfo,
|
||||||
items: I,
|
items: I,
|
||||||
) -> ExecutionResult<S>
|
) -> ExecutionResult<S>
|
||||||
where
|
where
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
I: Iterator<Item = T> + ExactSizeIterator,
|
I: Iterator<Item = &'t T> + ExactSizeIterator,
|
||||||
T: crate::GraphQLTypeAsync<S>,
|
T: GraphQLTypeAsync<S> + ?Sized + 't,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
T::Context: Send + Sync,
|
T::Context: Send + Sync,
|
||||||
{
|
{
|
||||||
|
@ -231,8 +274,7 @@ where
|
||||||
.expect("Current type is not a list type")
|
.expect("Current type is not a list type")
|
||||||
.is_non_null();
|
.is_non_null();
|
||||||
|
|
||||||
let iter =
|
let iter = items.map(|item| async move { executor.resolve_into_value_async(info, item).await });
|
||||||
items.map(|item| async move { executor.resolve_into_value_async(info, &item).await });
|
|
||||||
let mut futures = FuturesOrdered::from_iter(iter);
|
let mut futures = FuturesOrdered::from_iter(iter);
|
||||||
|
|
||||||
let mut values = Vec::with_capacity(futures.len());
|
let mut values = Vec::with_capacity(futures.len());
|
||||||
|
@ -245,63 +287,3 @@ where
|
||||||
|
|
||||||
Ok(Value::list(values))
|
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() {}
|
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.
|
/// Marker trait for types which can be used as output types.
|
||||||
///
|
///
|
||||||
/// The GraphQL specification differentiates between input and output
|
/// 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 std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
ast::{FromInputValue, InputValue, Selection, ToInputValue},
|
||||||
executor::{ExecutionResult, Executor, Registry},
|
executor::{ExecutionResult, Executor, Registry},
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
types::base::{Arguments, GraphQLType},
|
types::{
|
||||||
|
async_await::GraphQLTypeAsync,
|
||||||
|
base::{Arguments, GraphQLType},
|
||||||
|
},
|
||||||
value::ScalarValue,
|
value::ScalarValue,
|
||||||
|
BoxFuture,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<S, T, CtxT> GraphQLType<S> for Box<T>
|
impl<S, T, CtxT> GraphQLType<S> for Box<T>
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
T: GraphQLType<S, Context = CtxT>,
|
T: GraphQLType<S, Context = CtxT> + ?Sized,
|
||||||
{
|
{
|
||||||
type Context = CtxT;
|
type Context = CtxT;
|
||||||
type TypeInfo = T::TypeInfo;
|
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>
|
impl<T, S> FromInputValue<S> for Box<T>
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
|
@ -83,7 +104,7 @@ where
|
||||||
impl<'e, S, T, CtxT> GraphQLType<S> for &'e T
|
impl<'e, S, T, CtxT> GraphQLType<S> for &'e T
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
T: GraphQLType<S, Context = CtxT>,
|
T: GraphQLType<S, Context = CtxT> + ?Sized,
|
||||||
{
|
{
|
||||||
type Context = CtxT;
|
type Context = CtxT;
|
||||||
type TypeInfo = T::TypeInfo;
|
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
|
where
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
T: crate::GraphQLTypeAsync<S>,
|
T: GraphQLTypeAsync<S> + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
T::Context: Send + Sync,
|
T::Context: Send + Sync,
|
||||||
{
|
{
|
||||||
|
@ -142,8 +163,8 @@ where
|
||||||
field_name: &'b str,
|
field_name: &'b str,
|
||||||
arguments: &'b Arguments<S>,
|
arguments: &'b Arguments<S>,
|
||||||
executor: &'b Executor<Self::Context, S>,
|
executor: &'b Executor<Self::Context, S>,
|
||||||
) -> crate::BoxFuture<'b, ExecutionResult<S>> {
|
) -> BoxFuture<'b, ExecutionResult<S>> {
|
||||||
crate::GraphQLTypeAsync::resolve_field_async(&**self, info, field_name, arguments, executor)
|
GraphQLTypeAsync::resolve_field_async(&**self, info, field_name, arguments, executor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_async<'a>(
|
fn resolve_async<'a>(
|
||||||
|
@ -151,8 +172,8 @@ where
|
||||||
info: &'a Self::TypeInfo,
|
info: &'a Self::TypeInfo,
|
||||||
selection_set: Option<&'a [Selection<S>]>,
|
selection_set: Option<&'a [Selection<S>]>,
|
||||||
executor: &'a Executor<Self::Context, S>,
|
executor: &'a Executor<Self::Context, S>,
|
||||||
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
|
) -> BoxFuture<'a, ExecutionResult<S>> {
|
||||||
crate::GraphQLTypeAsync::resolve_async(&**self, info, selection_set, executor)
|
GraphQLTypeAsync::resolve_async(&**self, info, selection_set, executor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +190,7 @@ where
|
||||||
impl<S, T> GraphQLType<S> for Arc<T>
|
impl<S, T> GraphQLType<S> for Arc<T>
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
T: GraphQLType<S>,
|
T: GraphQLType<S> + ?Sized,
|
||||||
{
|
{
|
||||||
type Context = T::Context;
|
type Context = T::Context;
|
||||||
type TypeInfo = T::TypeInfo;
|
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
|
where
|
||||||
T: crate::GraphQLTypeAsync<S, Context = CtxT>,
|
|
||||||
T::TypeInfo: Send + Sync,
|
|
||||||
S: ScalarValue + 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>(
|
fn resolve_async<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
info: &'a Self::TypeInfo,
|
info: &'a Self::TypeInfo,
|
||||||
selection_set: Option<&'a [Selection<S>]>,
|
selection_set: Option<&'a [Selection<S>]>,
|
||||||
executor: &'a Executor<Self::Context, S>,
|
executor: &'a Executor<Self::Context, S>,
|
||||||
) -> crate::BoxFuture<'a, crate::ExecutionResult<S>> {
|
) -> BoxFuture<'a, 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>> {
|
|
||||||
(**self).resolve_async(info, selection_set, executor)
|
(**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
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
|
@ -216,11 +216,11 @@ where
|
||||||
_: Option<&[Selection<S>]>,
|
_: Option<&[Selection<S>]>,
|
||||||
_: &Executor<Self::Context, S>,
|
_: &Executor<Self::Context, S>,
|
||||||
) -> ExecutionResult<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
|
where
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
{
|
{
|
||||||
|
@ -325,11 +325,11 @@ where
|
||||||
/// If you instantiate `RootNode` with this as the mutation, no mutation will be
|
/// If you instantiate `RootNode` with this as the mutation, no mutation will be
|
||||||
/// generated for the schema.
|
/// generated for the schema.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EmptyMutation<T> {
|
pub struct EmptyMutation<T: ?Sized = ()> {
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> EmptyMutation<T> {
|
impl<T: ?Sized> EmptyMutation<T> {
|
||||||
/// Construct a new empty mutation
|
/// Construct a new empty mutation
|
||||||
pub fn new() -> EmptyMutation<T> {
|
pub fn new() -> EmptyMutation<T> {
|
||||||
EmptyMutation {
|
EmptyMutation {
|
||||||
|
@ -339,7 +339,7 @@ impl<T> EmptyMutation<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is safe because `T` is never used.
|
// 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>
|
impl<S, T> GraphQLType<S> for EmptyMutation<T>
|
||||||
where
|
where
|
||||||
|
@ -382,14 +382,14 @@ impl<T> Default for EmptyMutation<T> {
|
||||||
///
|
///
|
||||||
/// If you instantiate `RootNode` with this as the subscription,
|
/// If you instantiate `RootNode` with this as the subscription,
|
||||||
/// no subscriptions will be generated for the schema.
|
/// no subscriptions will be generated for the schema.
|
||||||
pub struct EmptySubscription<T> {
|
pub struct EmptySubscription<T: ?Sized = ()> {
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is safe due to never using `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
|
/// Construct a new empty subscription
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
EmptySubscription {
|
EmptySubscription {
|
||||||
|
|
|
@ -184,7 +184,7 @@ where
|
||||||
'e: 'fut,
|
'e: 'fut,
|
||||||
'ref_e: 'fut,
|
'ref_e: 'fut,
|
||||||
'res: 'fut,
|
'res: 'fut,
|
||||||
T: GraphQLSubscriptionType<S, Context = CtxT>,
|
T: GraphQLSubscriptionType<S, Context = CtxT> + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
CtxT: 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>,
|
executor: &'ref_e Executor<'ref_e, 'e, CtxT, S>,
|
||||||
) -> Value<ValuesStream<'res, S>>
|
) -> Value<ValuesStream<'res, S>>
|
||||||
where
|
where
|
||||||
T: GraphQLSubscriptionType<S, Context = CtxT> + Send + Sync,
|
T: GraphQLSubscriptionType<S, Context = CtxT> + Send + Sync + ?Sized,
|
||||||
T::TypeInfo: Send + Sync,
|
T::TypeInfo: Send + Sync,
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
CtxT: Send + Sync,
|
CtxT: Send + Sync,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "juniper_codegen"
|
name = "juniper_codegen"
|
||||||
version = "0.14.2"
|
version = "0.14.2"
|
||||||
|
edition = "2018"
|
||||||
authors = [
|
authors = [
|
||||||
"Magnus Hallin <mhallin@fastmail.com>",
|
"Magnus Hallin <mhallin@fastmail.com>",
|
||||||
"Christoph Herzog <chris@theduke.at>",
|
"Christoph Herzog <chris@theduke.at>",
|
||||||
|
@ -9,20 +10,20 @@ description = "Internal custom derive trait for Juniper GraphQL"
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
documentation = "https://docs.rs/juniper"
|
documentation = "https://docs.rs/juniper"
|
||||||
repository = "https://github.com/graphql-rust/juniper"
|
repository = "https://github.com/graphql-rust/juniper"
|
||||||
edition = "2018"
|
|
||||||
|
[badges]
|
||||||
|
travis-ci = { repository = "graphql-rust/juniper" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[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-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]
|
[dev-dependencies]
|
||||||
juniper = { version = "0.14.2", path = "../juniper"}
|
derive_more = "0.99.7"
|
||||||
|
futures = "0.3.1"
|
||||||
[badges]
|
juniper = { version = "0.14.2", path = "../juniper" }
|
||||||
travis-ci = { repository = "graphql-rust/juniper" }
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ pub fn impl_enum(
|
||||||
let _type = match field.fields {
|
let _type = match field.fields {
|
||||||
Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(),
|
Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(),
|
||||||
_ => {
|
_ => {
|
||||||
error.custom(
|
error.emit_custom(
|
||||||
field.fields.span(),
|
field.fields.span(),
|
||||||
"all fields of the enum must be unnamed, e.g., None",
|
"all fields of the enum must be unnamed, e.g., None",
|
||||||
);
|
);
|
||||||
|
@ -145,6 +145,7 @@ pub fn impl_enum(
|
||||||
include_type_generics: true,
|
include_type_generics: true,
|
||||||
generic_scalar: true,
|
generic_scalar: true,
|
||||||
no_async: attrs.no_async.is_some(),
|
no_async: attrs.no_async.is_some(),
|
||||||
|
mode: is_internal.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
|
|
|
@ -145,6 +145,7 @@ pub fn impl_input_object(
|
||||||
include_type_generics: true,
|
include_type_generics: true,
|
||||||
generic_scalar: true,
|
generic_scalar: true,
|
||||||
no_async: attrs.no_async.is_some(),
|
no_async: attrs.no_async.is_some(),
|
||||||
|
mode: is_internal.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
|
|
|
@ -132,6 +132,7 @@ pub fn build_derive_object(
|
||||||
include_type_generics: true,
|
include_type_generics: true,
|
||||||
generic_scalar: true,
|
generic_scalar: true,
|
||||||
no_async: attrs.no_async.is_some(),
|
no_async: attrs.no_async.is_some(),
|
||||||
|
mode: is_internal.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
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>,
|
executor: &'a #crate_name::Executor<Self::Context, __S>,
|
||||||
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<__S>> {
|
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<__S>> {
|
||||||
use #crate_name::GraphQLType;
|
use #crate_name::GraphQLType;
|
||||||
use futures::future;
|
use #crate_name::futures::future;
|
||||||
let v = self.resolve(info, selection_set, executor);
|
let v = self.resolve(info, selection_set, executor);
|
||||||
Box::pin(future::ready(v))
|
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 {
|
let _type = match method.sig.output {
|
||||||
syn::ReturnType::Type(_, ref t) => *t.clone(),
|
syn::ReturnType::Type(_, ref t) => *t.clone(),
|
||||||
syn::ReturnType::Default => {
|
syn::ReturnType::Default => {
|
||||||
error.custom(method.sig.span(), "return value required");
|
error.emit_custom(method.sig.span(), "return value required");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -228,6 +228,7 @@ fn create(
|
||||||
include_type_generics: false,
|
include_type_generics: false,
|
||||||
generic_scalar: false,
|
generic_scalar: false,
|
||||||
no_async: _impl.attrs.no_async.is_some(),
|
no_async: _impl.attrs.no_async.is_some(),
|
||||||
|
mode: is_internal.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(definition)
|
Ok(definition)
|
||||||
|
|
|
@ -264,7 +264,7 @@ pub fn build_scalar(
|
||||||
executor: &'a #crate_name::Executor<Self::Context, #async_generic_type>,
|
executor: &'a #crate_name::Executor<Self::Context, #async_generic_type>,
|
||||||
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<#async_generic_type>> {
|
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<#async_generic_type>> {
|
||||||
use #crate_name::GraphQLType;
|
use #crate_name::GraphQLType;
|
||||||
use futures::future;
|
use #crate_name::futures::future;
|
||||||
let v = self.resolve(info, selection_set, executor);
|
let v = self.resolve(info, selection_set, executor);
|
||||||
Box::pin(future::ready(v))
|
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_input_object;
|
||||||
mod derive_object;
|
mod derive_object;
|
||||||
mod derive_scalar_value;
|
mod derive_scalar_value;
|
||||||
mod derive_union;
|
|
||||||
mod impl_object;
|
mod impl_object;
|
||||||
mod impl_scalar;
|
mod impl_scalar;
|
||||||
mod impl_union;
|
|
||||||
|
mod graphql_union;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro_error::proc_macro_error;
|
use proc_macro_error::{proc_macro_error, ResultExt as _};
|
||||||
use result::GraphQLScope;
|
use result::GraphQLScope;
|
||||||
|
|
||||||
|
use self::util::Mode;
|
||||||
|
|
||||||
#[proc_macro_error]
|
#[proc_macro_error]
|
||||||
#[proc_macro_derive(GraphQLEnum, attributes(graphql))]
|
#[proc_macro_derive(GraphQLEnum, attributes(graphql))]
|
||||||
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
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)]
|
/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
|
||||||
/// derive.
|
/// 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_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn graphql_union(attrs: TokenStream, body: TokenStream) -> TokenStream {
|
pub fn graphql_union(attr: TokenStream, body: TokenStream) -> TokenStream {
|
||||||
let attrs = proc_macro2::TokenStream::from(attrs);
|
self::graphql_union::attr::expand(attr.into(), body.into(), Mode::Public)
|
||||||
let body = proc_macro2::TokenStream::from(body);
|
.unwrap_or_abort()
|
||||||
let gen = impl_union::impl_union(false, attrs, body, GraphQLScope::ImplUnion);
|
.into()
|
||||||
match gen {
|
|
||||||
Ok(gen) => gen.into(),
|
|
||||||
Err(err) => proc_macro_error::abort!(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_error]
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn graphql_union_internal(attrs: TokenStream, body: TokenStream) -> TokenStream {
|
pub fn graphql_union_internal(attr: TokenStream, body: TokenStream) -> TokenStream {
|
||||||
let attrs = proc_macro2::TokenStream::from(attrs);
|
self::graphql_union::attr::expand(attr.into(), body.into(), Mode::Internal)
|
||||||
let body = proc_macro2::TokenStream::from(body);
|
.unwrap_or_abort()
|
||||||
let gen = impl_union::impl_union(true, attrs, body, GraphQLScope::ImplUnion);
|
.into()
|
||||||
match gen {
|
|
||||||
Ok(gen) => gen.into(),
|
|
||||||
Err(err) => proc_macro_error::abort!(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,28 +5,29 @@ use proc_macro2::Span;
|
||||||
use proc_macro_error::{Diagnostic, Level};
|
use proc_macro_error::{Diagnostic, Level};
|
||||||
use std::fmt;
|
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)]
|
#[allow(unused_variables)]
|
||||||
pub enum GraphQLScope {
|
pub enum GraphQLScope {
|
||||||
|
UnionAttr,
|
||||||
DeriveObject,
|
DeriveObject,
|
||||||
DeriveInputObject,
|
DeriveInputObject,
|
||||||
DeriveUnion,
|
UnionDerive,
|
||||||
DeriveEnum,
|
DeriveEnum,
|
||||||
DeriveScalar,
|
DeriveScalar,
|
||||||
ImplUnion,
|
|
||||||
ImplScalar,
|
ImplScalar,
|
||||||
ImplObject,
|
ImplObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLScope {
|
impl GraphQLScope {
|
||||||
pub fn specification_section(&self) -> &str {
|
pub fn spec_section(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
GraphQLScope::DeriveObject | GraphQLScope::ImplObject => "#sec-Objects",
|
Self::DeriveObject | Self::ImplObject => "#sec-Objects",
|
||||||
GraphQLScope::DeriveInputObject => "#sec-Input-Objects",
|
Self::DeriveInputObject => "#sec-Input-Objects",
|
||||||
GraphQLScope::DeriveUnion | GraphQLScope::ImplUnion => "#sec-Unions",
|
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
|
||||||
GraphQLScope::DeriveEnum => "#sec-Enums",
|
Self::DeriveEnum => "#sec-Enums",
|
||||||
GraphQLScope::DeriveScalar | GraphQLScope::ImplScalar => "#sec-Scalars",
|
Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,11 +35,11 @@ impl GraphQLScope {
|
||||||
impl fmt::Display for GraphQLScope {
|
impl fmt::Display for GraphQLScope {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let name = match self {
|
let name = match self {
|
||||||
GraphQLScope::DeriveObject | GraphQLScope::ImplObject => "object",
|
Self::DeriveObject | Self::ImplObject => "object",
|
||||||
GraphQLScope::DeriveInputObject => "input object",
|
Self::DeriveInputObject => "input object",
|
||||||
GraphQLScope::DeriveUnion | GraphQLScope::ImplUnion => "union",
|
Self::UnionAttr | Self::UnionDerive => "union",
|
||||||
GraphQLScope::DeriveEnum => "enum",
|
Self::DeriveEnum => "enum",
|
||||||
GraphQLScope::DeriveScalar | GraphQLScope::ImplScalar => "scalar",
|
Self::DeriveScalar | Self::ImplScalar => "scalar",
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "GraphQL {}", name)
|
write!(f, "GraphQL {}", name)
|
||||||
|
@ -51,20 +52,22 @@ pub enum UnsupportedAttribute {
|
||||||
Skip,
|
Skip,
|
||||||
Interface,
|
Interface,
|
||||||
Scalar,
|
Scalar,
|
||||||
Description,
|
|
||||||
Deprecation,
|
Deprecation,
|
||||||
Default,
|
Default,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLScope {
|
impl GraphQLScope {
|
||||||
fn specification_link(&self) -> String {
|
fn spec_link(&self) -> String {
|
||||||
format!("{}{}", GRAPHQL_SPECIFICATION, self.specification_section())
|
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()))
|
Diagnostic::spanned(span, Level::Error, format!("{} {}", self, msg.as_ref()))
|
||||||
.note(self.specification_link())
|
.note(self.spec_link())
|
||||||
.emit();
|
}
|
||||||
|
|
||||||
|
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 {
|
pub fn custom_error<S: AsRef<str>>(&self, span: Span, msg: S) -> syn::Error {
|
||||||
|
@ -97,7 +100,7 @@ impl GraphQLScope {
|
||||||
Level::Error,
|
Level::Error,
|
||||||
format!("{} expects at least one field", self),
|
format!("{} expects at least one field", self),
|
||||||
)
|
)
|
||||||
.note(self.specification_link())
|
.note(self.spec_link())
|
||||||
.emit();
|
.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))
|
.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();
|
.emit();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -130,9 +133,12 @@ impl GraphQLScope {
|
||||||
Diagnostic::spanned(
|
Diagnostic::spanned(
|
||||||
field,
|
field,
|
||||||
Level::Error,
|
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))
|
.note(format!("{}#sec-Schema", SPEC_URL))
|
||||||
.emit();
|
.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
pub mod duplicate;
|
pub mod duplicate;
|
||||||
|
pub mod mode;
|
||||||
|
pub mod option_ext;
|
||||||
pub mod parse_impl;
|
pub mod parse_impl;
|
||||||
pub mod span_container;
|
pub mod span_container;
|
||||||
|
|
||||||
|
use std::ops::Deref as _;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use proc_macro_error::abort;
|
use proc_macro_error::abort;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -14,10 +18,7 @@ use syn::{
|
||||||
MetaNameValue, NestedMeta, Token,
|
MetaNameValue, NestedMeta, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn juniper_path(is_internal: bool) -> syn::Path {
|
pub use self::{mode::Mode, option_ext::OptionExt};
|
||||||
let name = if is_internal { "crate" } else { "juniper" };
|
|
||||||
syn::parse_str::<syn::Path>(name).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the name of a type.
|
/// Returns the name of a type.
|
||||||
/// If the type does not end in a simple ident, `None` is returned.
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub struct DeprecationAttr {
|
pub struct DeprecationAttr {
|
||||||
pub reason: Option<String>,
|
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"))
|
.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>> {
|
pub fn get_deprecated(attrs: &[Attribute]) -> Option<SpanContainer<DeprecationAttr>> {
|
||||||
attrs
|
attrs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -669,6 +689,7 @@ pub struct GraphQLTypeDefiniton {
|
||||||
pub generic_scalar: bool,
|
pub generic_scalar: bool,
|
||||||
// FIXME: make this redundant.
|
// FIXME: make this redundant.
|
||||||
pub no_async: bool,
|
pub no_async: bool,
|
||||||
|
pub mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLTypeDefiniton {
|
impl GraphQLTypeDefiniton {
|
||||||
|
@ -867,7 +888,7 @@ impl GraphQLTypeDefiniton {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
use futures::future;
|
use #juniper_crate_name::futures::future;
|
||||||
future::FutureExt::boxed(f)
|
future::FutureExt::boxed(f)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -884,7 +905,7 @@ impl GraphQLTypeDefiniton {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
use futures::future;
|
use #juniper_crate_name::futures::future;
|
||||||
future::FutureExt::boxed(f)
|
future::FutureExt::boxed(f)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -894,7 +915,7 @@ impl GraphQLTypeDefiniton {
|
||||||
Ok(None) => Ok(#juniper_crate_name::Value::null()),
|
Ok(None) => Ok(#juniper_crate_name::Value::null()),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
};
|
};
|
||||||
use futures::future;
|
use #juniper_crate_name::futures::future;
|
||||||
future::FutureExt::boxed(future::ready(v))
|
future::FutureExt::boxed(future::ready(v))
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -934,7 +955,7 @@ impl GraphQLTypeDefiniton {
|
||||||
) -> #juniper_crate_name::BoxFuture<'b, #juniper_crate_name::ExecutionResult<#scalar>>
|
) -> #juniper_crate_name::BoxFuture<'b, #juniper_crate_name::ExecutionResult<#scalar>>
|
||||||
where #scalar: Send + Sync,
|
where #scalar: Send + Sync,
|
||||||
{
|
{
|
||||||
use futures::future;
|
use #juniper_crate_name::futures::future;
|
||||||
use #juniper_crate_name::GraphQLType;
|
use #juniper_crate_name::GraphQLType;
|
||||||
match field {
|
match field {
|
||||||
#( #resolve_matches_async )*
|
#( #resolve_matches_async )*
|
||||||
|
@ -1177,7 +1198,7 @@ impl GraphQLTypeDefiniton {
|
||||||
};
|
};
|
||||||
quote!(
|
quote!(
|
||||||
#name => {
|
#name => {
|
||||||
futures::FutureExt::boxed(async move {
|
#juniper_crate_name::futures::FutureExt::boxed(async move {
|
||||||
let res #_type = { #code };
|
let res #_type = { #code };
|
||||||
let res = #juniper_crate_name::IntoFieldResult::<_, #scalar>::into_result(res)?;
|
let res = #juniper_crate_name::IntoFieldResult::<_, #scalar>::into_result(res)?;
|
||||||
let executor= executor.as_owned_executor();
|
let executor= executor.as_owned_executor();
|
||||||
|
@ -1267,7 +1288,7 @@ impl GraphQLTypeDefiniton {
|
||||||
args: #juniper_crate_name::Arguments<'args, #scalar>,
|
args: #juniper_crate_name::Arguments<'args, #scalar>,
|
||||||
executor: &'ref_e #juniper_crate_name::Executor<'ref_e, 'e, Self::Context, #scalar>,
|
executor: &'ref_e #juniper_crate_name::Executor<'ref_e, 'e, Self::Context, #scalar>,
|
||||||
) -> std::pin::Pin<Box<
|
) -> std::pin::Pin<Box<
|
||||||
dyn futures::future::Future<
|
dyn #juniper_crate_name::futures::future::Future<
|
||||||
Output = Result<
|
Output = Result<
|
||||||
#juniper_crate_name::Value<#juniper_crate_name::ValuesStream<'res, #scalar>>,
|
#juniper_crate_name::Value<#juniper_crate_name::ValuesStream<'res, #scalar>>,
|
||||||
#juniper_crate_name::FieldError<#scalar>
|
#juniper_crate_name::FieldError<#scalar>
|
||||||
|
@ -1284,7 +1305,7 @@ impl GraphQLTypeDefiniton {
|
||||||
'res: 'f,
|
'res: 'f,
|
||||||
{
|
{
|
||||||
use #juniper_crate_name::Value;
|
use #juniper_crate_name::Value;
|
||||||
use futures::stream::StreamExt as _;
|
use #juniper_crate_name::futures::stream::StreamExt as _;
|
||||||
|
|
||||||
match field_name {
|
match field_name {
|
||||||
#( #resolve_matches_async )*
|
#( #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 {
|
pub fn into_enum_tokens(self, juniper_crate_name: &str) -> TokenStream {
|
||||||
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
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>,
|
executor: &'a #juniper_crate_name::Executor<Self::Context, #scalar>,
|
||||||
) -> #juniper_crate_name::BoxFuture<'a, #juniper_crate_name::ExecutionResult<#scalar>> {
|
) -> #juniper_crate_name::BoxFuture<'a, #juniper_crate_name::ExecutionResult<#scalar>> {
|
||||||
use #juniper_crate_name::GraphQLType;
|
use #juniper_crate_name::GraphQLType;
|
||||||
use futures::future;
|
use #juniper_crate_name::futures::future;
|
||||||
let v = self.resolve(info, selection_set, executor);
|
let v = self.resolve(info, selection_set, executor);
|
||||||
future::FutureExt::boxed(future::ready(v))
|
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 {
|
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
|
/// Parse a `fn <NAME>() -> <TYPE>` method declaration found in
|
||||||
/// objects.
|
/// objects.
|
||||||
pub fn parse_method<
|
pub fn parse_method<
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
use std::{
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
ops,
|
||||||
|
};
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use std::cmp::{Eq, PartialEq};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SpanContainer<T> {
|
pub struct SpanContainer<T> {
|
||||||
expr: Option<Span>,
|
expr: Option<Span>,
|
||||||
ident: Span,
|
ident: Span,
|
||||||
|
@ -24,6 +28,19 @@ impl<T> SpanContainer<T> {
|
||||||
self.ident
|
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 {
|
pub fn into_inner(self) -> T {
|
||||||
self.val
|
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;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -68,3 +85,12 @@ impl<T: PartialEq> PartialEq<T> for SpanContainer<T> {
|
||||||
&self.val == other
|
&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