Redesign #[graphql_interface] macro (#1009, #1000, #814)

- remove support for `#[graphql_interface(dyn)]`
- describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait`
- forbid default impls on non-skipped trait methods
- support additional nullable arguments on implementer
- support returning sub-type on implementer
This commit is contained in:
ilslv 2022-01-26 21:58:53 +03:00 committed by GitHub
parent c866e091a4
commit 1aa1000c3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
82 changed files with 3493 additions and 6053 deletions

View file

@ -45,32 +45,14 @@ trait Character {
struct Human {
id: String,
}
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Droid {
id: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
# fn main() {
let human = Human { id: "human-32".to_owned() };
// Values type for interface has `From` implementations for all its implementers,
// so we don't need to bother with enum variant names.
let character: CharacterValue = human.into();
assert_eq!(character.id(), "human-32");
# }
#
# fn main() {}
```
Also, enum name can be specified explicitly, if desired.
@ -90,71 +72,11 @@ struct Human {
id: String,
home_planet: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```
### Trait object values
If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, then it should be specified explicitly in the trait definition.
Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood.
> __NOTICE__:
> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it.
```rust
# extern crate juniper;
# extern crate tokio;
use juniper::{graphql_interface, GraphQLObject};
// `dyn` argument accepts the name of type alias for the required trait object,
// and macro generates this alias automatically.
#[graphql_interface(dyn = DynCharacter, for = Human)]
trait Character {
async fn id(&self) -> &str; // async fields are supported natively
}
#[derive(GraphQLObject)]
#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait,
struct Human { // so it may be specified explicitly when required
id: String,
}
#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
impl Character for Human {
async fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLObject)]
#[graphql(impl = DynCharacter<__S>)]
struct Droid {
id: String,
}
#[graphql_interface]
impl Character for Droid {
async fn id(&self) -> &str {
&self.id
}
}
# #[tokio::main]
# async fn main() {
let human = Human { id: "human-32".to_owned() };
let character: Box<DynCharacter> = Box::new(human);
assert_eq!(character.id().await, "human-32");
# }
```
### Ignoring trait methods
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
@ -176,12 +98,6 @@ trait Character {
struct Human {
id: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```
@ -278,24 +194,6 @@ struct Human {
id: String,
name: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self, db: &Database) -> Option<&str> {
if db.humans.contains_key(&self.id) {
Some(&self.id)
} else {
None
}
}
fn name(&self, db: &Database) -> Option<&str> {
if db.humans.contains_key(&self.id) {
Some(&self.name)
} else {
None
}
}
}
#
# fn main() {}
```
@ -309,120 +207,51 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`]
```rust
# extern crate juniper;
use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue};
#[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter
trait Character<S: ScalarValue> {
// If a field argument is named `executor`, it's automatically assumed
// as an executor argument.
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
where
S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str;
// Otherwise, you may mark it explicitly as an executor argument.
async fn name<'b>(
fn name<'b>(
&'b self,
#[graphql(executor)] another: &Executor<'_, '_, (), S>,
) -> &'b str
where
S: Send + Sync;
) -> &'b str;
fn home_planet(&self) -> &str;
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue<__S>)]
struct Human {
id: String,
name: String,
home_planet: String,
}
#[graphql_interface(scalar = S)]
impl<S: ScalarValue> Character<S> for Human {
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
impl Human {
async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
where
S: Send + Sync,
S: ScalarValue,
{
executor.look_ahead().field_name()
}
async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
where
S: Send + Sync,
{
async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str {
&self.name
}
fn home_planet<'c, S>(&'c self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'c str {
// Executor may not be present on the trait method ^^^^^^^^^^^^^^^^^^^^^^^^
&self.home_planet
}
}
#
# fn main() {}
```
### Downcasting
By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` macro argument is used).
However, if some custom logic is needed to downcast a [GraphQL interface][1] implementer, you may specify either an external function or a trait method to do so.
```rust
# extern crate juniper;
# use std::collections::HashMap;
use juniper::{graphql_interface, GraphQLObject};
struct Database {
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
#[graphql_interface(for = [Human, Droid], context = Database)]
#[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` function
trait Character {
fn id(&self) -> &str;
#[graphql(downcast)] // makes method a downcast to `Human`, not a field
// NOTICE: The method signature may optionally contain `&Database` context argument.
fn as_human(&self) -> Option<&Human> {
None
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Context = Database)]
struct Human {
id: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
fn as_human(&self) -> Option<&Self> {
Some(self)
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Context = Database)]
struct Droid {
id: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
// External downcast function doesn't have to be a method of a type.
// It's only a matter of the function signature to match the requirements.
fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> {
db.droids.get(ch.id())
}
#
# fn main() {}
```
The attribute syntax `#[graphql_interface(on ImplementerType = resolver_fn)]` follows the [GraphQL syntax for downcasting interface implementer](https://spec.graphql.org/June2018/#example-5cc55).
## `ScalarValue` considerations
@ -445,12 +274,6 @@ struct Human {
id: String,
home_planet: String,
}
#[graphql_interface(scalar = DefaultScalarValue)]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
@ -458,12 +281,6 @@ struct Droid {
id: String,
primary_function: String,
}
#[graphql_interface(scalar = DefaultScalarValue)]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
#
# fn main() {}
```

View file

@ -10,7 +10,7 @@ use actix_web::{
use juniper::{
graphql_object, graphql_subscription, graphql_value,
tests::fixtures::starwars::schema::{Character as _, Database, Query},
tests::fixtures::starwars::schema::{Database, Query},
EmptyMutation, FieldError, RootNode,
};
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};

View file

@ -0,0 +1,19 @@
use juniper::{graphql_interface, graphql_object};
pub struct ObjA {
id: String,
}
#[graphql_object(impl = CharacterValue)]
impl ObjA {
fn id(&self, is_present: bool) -> &str {
is_present.then(|| self.id.as_str()).unwrap_or("missing")
}
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/additional_non_nullable_argument.rs:14:1
|
14 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,19 +1,8 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
use juniper::graphql_interface;
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self, __num: i32) -> &str {
"funA"
}
fn id(&self, __num: i32) -> &str;
}
fn main() {}

View file

@ -1,19 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> fail/interface/argument_double_underscored.rs:14:18
|
14 | fn id(&self, __num: i32) -> &str {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/argument_double_underscored.rs:4:18
--> fail/interface/argument_double_underscored.rs:5:18
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/argument_double_underscored.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope
5 | fn id(&self, __num: i32) -> &str;
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,19 +1,11 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
#[graphql_interface]
impl Character for ObjA {
fn id(&self, obj: Self) -> &str {
"funA"
}
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self, obj: ObjA) -> &str;
}

View file

@ -1,16 +1,16 @@
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
--> fail/interface/argument_non_input_type.rs:16:1
|
16 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/interface/argument_non_input_type.rs:8:1
|
8 | #[graphql_interface]
| ^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> fail/interface/argument_non_input_type.rs:16:1
--> fail/interface/argument_non_input_type.rs:8:1
|
16 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
8 | #[graphql_interface]
| ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
note: required by a bound in `Registry::<'r, S>::arg`
--> $WORKSPACE/juniper/src/executor/mod.rs
@ -18,11 +18,3 @@ note: required by a bound in `Registry::<'r, S>::arg`
| T: GraphQLType<S> + FromInputValue<S>,
| ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> fail/interface/argument_non_input_type.rs:16:1
|
16 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,23 +1,8 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
use juniper::graphql_interface;
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {
fn wrong(
&self,
#[graphql(default = [true, false, false])]
input: [bool; 2],
) -> bool {
input[0]
}
fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool;
}
fn main() {}

View file

@ -1,11 +1,11 @@
error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
--> fail/interface/argument_wrong_default_array.rs:12:1
|
12 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
= help: the following implementations were found:
<[T; LANES] as From<Simd<T, LANES>>>
<[bool; LANES] as From<Mask<T, LANES>>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/interface/argument_wrong_default_array.rs:3:1
|
3 | #[graphql_interface]
| ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
= help: the following implementations were found:
<[T; LANES] as From<Simd<T, LANES>>>
<[bool; LANES] as From<Mask<T, LANES>>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,29 +0,0 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
#[graphql_interface]
impl Character for ObjA {
fn id(&self, _: i32) -> &str {
"funA"
}
fn as_obja(&self) -> Option<&ObjA> {
Some(self)
}
}
#[graphql_interface(for = ObjA)]
#[graphql_interface(on ObjA = downcast_obja)]
trait Character {
fn id(&self, num: i32) -> &str;
#[graphql(downcast)]
fn as_obja(&self) -> Option<&ObjA>;
}
fn main() {}

View file

@ -1,20 +0,0 @@
error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA`
--> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:26:5
|
26 | fn as_obja(&self) -> Option<&ObjA>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
= note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:4:18
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:10:6
|
10 | impl Character for ObjA {
| ^^^^^^^^^ not found in this scope

View file

@ -1,24 +0,0 @@
use juniper::{graphql_interface, GraphQLObject};
#[graphql_interface(for = Human)]
trait Character {
fn id(&self) -> i32 {
0
}
#[graphql(downcast)]
fn a(&self, ctx: &(), rand: u8) -> Option<&Human> {
None
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct Human {
id: String,
}
#[graphql_interface]
impl Character for Human {}
fn main() {}

View file

@ -1,19 +0,0 @@
error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context`
--> fail/interface/downcast_method_wrong_input_args.rs:10:10
|
10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/downcast_method_wrong_input_args.rs:16:18
|
16 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/downcast_method_wrong_input_args.rs:22:6
|
22 | impl Character for Human {}
| ^^^^^^^^^ not found in this scope

View file

@ -1,24 +0,0 @@
use juniper::{graphql_interface, GraphQLObject};
#[graphql_interface(for = Human)]
trait Character {
fn id(&self) -> i32 {
0
}
#[graphql(downcast)]
fn a(&self, ctx: &(), rand: u8) -> &Human {
unimplemented!()
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct Human {
id: String,
}
#[graphql_interface]
impl Character for Human {}
fn main() {}

View file

@ -1,19 +0,0 @@
error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only
--> fail/interface/downcast_method_wrong_return_type.rs:10:40
|
10 | fn a(&self, ctx: &(), rand: u8) -> &Human {
| ^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/downcast_method_wrong_return_type.rs:16:18
|
16 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/downcast_method_wrong_return_type.rs:22:6
|
22 | impl Character for Human {}
| ^^^^^^^^^ not found in this scope

View file

@ -1,19 +1,8 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
use juniper::graphql_interface;
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {
fn __id(&self) -> &str {
"funA"
}
fn __id(&self) -> &str;
}
fn main() {}

View file

@ -1,19 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> fail/interface/field_double_underscored.rs:14:8
|
14 | fn __id(&self) -> &str {
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/field_double_underscored.rs:4:18
--> fail/interface/field_double_underscored.rs:5:8
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/field_double_underscored.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope
5 | fn __id(&self) -> &str;
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,24 +1,13 @@
use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject};
use juniper::{graphql_interface, GraphQLInputObject};
#[derive(GraphQLInputObject)]
pub struct ObjB {
id: i32,
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> ObjB {
ObjB { id: 34 }
}
fn id(&self) -> ObjB;
}
fn main() {}

View file

@ -1,7 +1,7 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> fail/interface/field_non_output_return_type.rs:17:1
|
17 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
--> fail/interface/field_non_output_return_type.rs:8:1
|
8 | #[graphql_interface]
| ^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,24 +1,11 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
use juniper::graphql_interface;
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str {
"funA"
}
fn id(&self) -> &str;
#[graphql(name = "id")]
fn id2(&self) -> &str {
"funB"
}
fn id2(&self) -> &str;
}
fn main() {}

View file

@ -1,25 +1,12 @@
error: GraphQL interface must have a different name for each field
--> fail/interface/fields_duplicate.rs:13:1
|
13 | / trait Character {
14 | | fn id(&self) -> &str {
15 | | "funA"
16 | | }
... |
21 | | }
22 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/fields_duplicate.rs:4:18
--> fail/interface/fields_duplicate.rs:4:1
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/fields_duplicate.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope
4 | / trait Character {
5 | | fn id(&self) -> &str;
6 | |
7 | | #[graphql(name = "id")]
8 | | fn id2(&self) -> &str;
9 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces

View file

@ -1,20 +0,0 @@
use juniper::{graphql_interface, GraphQLInputObject};
#[derive(GraphQLInputObject)]
pub struct ObjA {
test: String,
}
#[graphql_interface]
impl Character for ObjA {
fn id(&self) -> &str {
"funA"
}
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str;
}
fn main() {}

View file

@ -1,15 +0,0 @@
error[E0277]: the trait bound `ObjA: GraphQLObject<__S>` is not satisfied
--> fail/interface/implementer_non_object_type.rs:15:1
|
15 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied
--> fail/interface/implementer_non_object_type.rs:15:1
|
15 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -3,14 +3,7 @@ use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
#[graphql_interface]
impl Character for ObjA {
fn id(&self) -> &str {
"funA"
}
id: String,
}
#[graphql_interface(for = [ObjA, ObjA])]

View file

@ -1,17 +1,11 @@
error: duplicated attribute argument found
--> $DIR/implementers_duplicate_pretty.rs:16:34
|
16 | #[graphql_interface(for = [ObjA, ObjA])]
| ^^^^
--> fail/interface/implementers_duplicate_pretty.rs:9:34
|
9 | #[graphql_interface(for = [ObjA, ObjA])]
| ^^^^
error[E0412]: cannot find type `CharacterValue` in this scope
--> $DIR/implementers_duplicate_pretty.rs:4:18
--> fail/interface/implementers_duplicate_pretty.rs:4:18
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> $DIR/implementers_duplicate_pretty.rs:10:6
|
10 | impl Character for ObjA {
| ^^^^^^^^^ not found in this scope

View file

@ -3,18 +3,11 @@ use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
id: String,
}
type ObjAlias = ObjA;
#[graphql_interface]
impl Character for ObjA {
fn id(&self) -> &str {
"funA"
}
}
#[graphql_interface(for = [ObjA, ObjAlias])]
trait Character {
fn id(&self) -> &str;

View file

@ -1,7 +1,7 @@
error[E0119]: conflicting implementations of trait `<CharacterValue as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
--> $DIR/implementers_duplicate_ugly.rs:18:1
error[E0119]: conflicting implementations of trait `<CharacterValueEnum<ObjA, ObjA> as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
18 | #[graphql_interface(for = [ObjA, ObjAlias])]
11 | #[graphql_interface(for = [ObjA, ObjAlias])]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
@ -9,13 +9,13 @@ error[E0119]: conflicting implementations of trait `<CharacterValue as juniper::
|
= note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValue`
--> $DIR/implementers_duplicate_ugly.rs:18:1
error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValueEnum<ObjA, ObjA>`
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
18 | #[graphql_interface(for = [ObjA, ObjAlias])]
11 | #[graphql_interface(for = [ObjA, ObjAlias])]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `CharacterValue`
| conflicting implementation for `CharacterValueEnum<ObjA, ObjA>`
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,10 @@
use juniper::graphql_interface;
#[graphql_interface]
trait Character {
fn id(&self) -> &str {
"default"
}
}
fn main() {}

View file

@ -0,0 +1,10 @@
error: GraphQL interface trait method can't have default implementation
--> fail/interface/method_default_impl.rs:5:26
|
5 | fn id(&self) -> &str {
| __________________________^
6 | | "default"
7 | | }
| |_____^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces

View file

@ -0,0 +1,14 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/missing_field.rs:9:1
|
9 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/missing_field.rs:9:1
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,19 @@
use juniper::{graphql_interface, graphql_object};
pub struct ObjA {
id: String,
}
#[graphql_object(impl = CharacterValue)]
impl ObjA {
fn id(&self) -> &String {
&self.id
}
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self, is_present: bool) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/missing_field_argument.rs:14:1
|
14 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/missing_field_argument.rs:14:1
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,14 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
id: String,
}
#[graphql_interface]
trait Character {
fn id(&self) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/missing_for_attr.rs:3:10
|
3 | #[derive(GraphQLObject)]
| ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/missing_for_attr.rs:3:10
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,13 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
pub struct ObjA {
id: String,
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/missing_impl_attr.rs:8:1
|
8 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/missing_impl_attr.rs:8:1
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -2,9 +2,7 @@ use juniper::graphql_interface;
#[graphql_interface]
trait __Character {
fn id(&self) -> &str {
"funA"
}
fn id(&self) -> &str;
}
fn main() {}

View file

@ -1,15 +1,6 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
use juniper::graphql_interface;
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {}
fn main() {}

View file

@ -1,19 +1,7 @@
error: GraphQL interface must have at least one field
--> fail/interface/no_fields.rs:13:1
|
13 | trait Character {}
| ^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
error[E0412]: cannot find type `CharacterValue` in this scope
--> fail/interface/no_fields.rs:4:18
--> fail/interface/no_fields.rs:4:1
|
4 | #[graphql(impl = CharacterValue)]
| ^^^^^^^^^^^^^^ not found in this scope
error[E0405]: cannot find trait `Character` in this scope
--> fail/interface/no_fields.rs:10:6
|
10 | impl Character for ObjA {}
| ^^^^^^^^^ not found in this scope
4 | trait Character {}
| ^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces

View file

@ -0,0 +1,14 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
id: Vec<String>,
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/non_subtype_return.rs:9:1
|
9 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/non_subtype_return.rs:9:1
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,19 @@
use juniper::{graphql_interface, graphql_object};
pub struct ObjA {
id: String,
}
#[graphql_object(impl = CharacterValue)]
impl ObjA {
fn id(&self, _is_present: i32) -> &str {
&self.id
}
}
#[graphql_interface(for = ObjA)]
trait Character {
fn id(&self, is_present: bool) -> &str;
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0080]: evaluation of constant value failed
--> fail/interface/wrong_argument_type.rs:14:1
|
14 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/wrong_argument_type.rs:14:1
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,5 +1,5 @@
error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only
--> $DIR/wrong_item.rs:8:1
error: #[graphql_interface] attribute is applicable to trait definitions only
--> fail/interface/wrong_item_enum.rs:8:1
|
8 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,11 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
pub struct ObjA {
test: String,
}
#[graphql_interface(for = ObjA)]
impl ObjA {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: #[graphql_interface] attribute is applicable to trait definitions only
--> fail/interface/wrong_item_impl_block.rs:8:1
|
8 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

File diff suppressed because it is too large Load diff

View file

@ -20,13 +20,6 @@ struct Human {
name: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Droid {
@ -34,13 +27,6 @@ struct Droid {
serial_number: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
#[graphql_object]
impl Query {
fn characters() -> Vec<CharacterValue> {

View file

@ -18,13 +18,6 @@ struct Human {
home_planet: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Droid {
@ -32,13 +25,6 @@ struct Droid {
primary_function: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
}
#[derive(GraphQLUnion)]
enum FieldResult {
Human(Human),

View file

@ -38,17 +38,6 @@ struct Human {
pub name: String,
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> i32 {
self.id
}
fn name(&self) -> String {
self.name.clone()
}
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
struct Droid {
@ -56,17 +45,6 @@ struct Droid {
pub name: String,
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> i32 {
self.id
}
fn name(&self) -> String {
self.name.clone()
}
}
type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
#[tokio::test]

View file

@ -10,6 +10,13 @@
- Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987))
- Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987))
- Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
- Redesign `#[graphql_interface]` macro: ([#1009](https://github.com/graphql-rust/juniper/pull/1009))
- Remove support for `#[graphql_interface(dyn)]` (interface values as trait objects).
- Remove support for `downcast` (custom resolution into implementer types).
- Remove support for `async` trait methods (not required anymore).
- Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields).
- Forbid default impls on non-ignored trait methods.
- Support coercion of additional nullable arguments and return sub-typing on implementer.
## Features

View file

@ -296,6 +296,7 @@ impl<S> InputValue<S> {
}
/// Resolve all variables to their values.
#[must_use]
pub fn into_const(self, vars: &Variables<S>) -> Self
where
S: Clone,

View file

@ -701,7 +701,7 @@ where
FieldPath::Root(_) => unreachable!(),
};
self.parent_selection_set
.map(|p| {
.and_then(|p| {
// Search the parent's fields to find this field within the set
let found_field = p.iter().find(|&x| {
match *x {
@ -721,7 +721,6 @@ where
None
}
})
.flatten()
.unwrap_or_else(|| {
// We didn't find a field in the parent's selection matching
// this field, which means we're inside a FragmentSpread

View file

@ -49,6 +49,7 @@ where
S: Clone,
{
#[doc(hidden)]
#[must_use]
pub fn type_sub_executor(
&self,
type_name: Option<&str>,
@ -76,6 +77,7 @@ where
}
#[doc(hidden)]
#[must_use]
pub fn field_sub_executor(
&self,
field_alias: &'a str,

View file

@ -3,72 +3,28 @@ mod interface {
graphql_interface, graphql_object,
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
GraphQLObject,
};
#[graphql_interface(for = [Cat, Dog])]
trait Pet {
fn name(&self) -> &str;
#[graphql(downcast)]
fn as_dog(&self) -> Option<&Dog> {
None
}
#[graphql(downcast)]
fn as_cat(&self) -> Option<&Cat> {
None
}
}
#[derive(GraphQLObject)]
#[graphql(impl = PetValue)]
struct Dog {
name: String,
woofs: bool,
}
#[graphql_interface]
impl Pet for Dog {
fn name(&self) -> &str {
&self.name
}
fn as_dog(&self) -> Option<&Dog> {
Some(self)
}
}
#[graphql_object(impl = PetValue)]
impl Dog {
fn name(&self) -> &str {
&self.name
}
fn woofs(&self) -> bool {
self.woofs
}
}
#[derive(GraphQLObject)]
#[graphql(impl = PetValue)]
struct Cat {
name: String,
meows: bool,
}
#[graphql_interface]
impl Pet for Cat {
fn name(&self) -> &str {
&self.name
}
fn as_cat(&self) -> Option<&Cat> {
Some(self)
}
}
#[graphql_object(impl = PetValue)]
impl Cat {
fn name(&self) -> &str {
&self.name
}
fn meows(&self) -> bool {
self.meows
}
}
struct Schema {
pets: Vec<PetValue>,
}

View file

@ -43,9 +43,7 @@ impl<S: ScalarValue> GraphQLScalar for Scalar {
#[graphql_interface(name = "SampleInterface", for = Root)]
trait Interface {
/// A sample field in the interface
fn sample_enum(&self) -> Sample {
Sample::One
}
fn sample_enum(&self) -> Sample;
}
struct Root;
@ -66,9 +64,6 @@ impl Root {
}
}
#[graphql_interface(scalar = DefaultScalarValue)]
impl Interface for Root {}
#[tokio::test]
async fn test_execution() {
let doc = r#"

View file

@ -2,6 +2,9 @@
#[doc(hidden)]
pub mod helper;
#[doc(hidden)]
#[macro_use]
pub mod reflect;
#[macro_use]
mod graphql_input_value;

View file

@ -0,0 +1,985 @@
//! Compile-time reflection of Rust types into GraphQL types.
use std::{rc::Rc, sync::Arc};
use futures::future::BoxFuture;
use crate::{
Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue,
};
/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name
/// in a GraphQL schema.
///
/// See [`BaseType`] for more info.
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Scalars
/// [3]: https://spec.graphql.org/October2021#sec-Interfaces
pub type Type = &'static str;
/// Alias for a slice of [`Type`]s.
///
/// See [`BaseSubTypes`] for more info.
pub type Types = &'static [Type];
/// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`].
///
/// This trait is transparent to [`Option`], [`Vec`] and other containers, so to
/// fully represent a [GraphQL object][1] we additionally use [`WrappedType`].
///
/// Different Rust types may have the same [`NAME`]. For example, [`String`] and
/// `&`[`str`](prim@str) share `String!` GraphQL type.
///
/// [`NAME`]: Self::NAME
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Scalars
/// [3]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait BaseType<S> {
/// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Scalars
/// [3]: https://spec.graphql.org/October2021#sec-Interfaces
const NAME: Type;
}
impl<'a, S, T: BaseType<S> + ?Sized> BaseType<S> for &'a T {
const NAME: Type = T::NAME;
}
impl<'ctx, S, T> BaseType<S> for (&'ctx T::Context, T)
where
S: ScalarValue,
T: BaseType<S> + GraphQLValue<S>,
{
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S>> BaseType<S> for Option<T> {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S>> BaseType<S> for Nullable<T> {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S>, E> BaseType<S> for Result<T, E> {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S>> BaseType<S> for Vec<T> {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S>> BaseType<S> for [T] {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S>, const N: usize> BaseType<S> for [T; N] {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S> + ?Sized> BaseType<S> for Box<T> {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S> + ?Sized> BaseType<S> for Arc<T> {
const NAME: Type = T::NAME;
}
impl<S, T: BaseType<S> + ?Sized> BaseType<S> for Rc<T> {
const NAME: Type = T::NAME;
}
/// [Sub-types][2] of a [GraphQL object][1].
///
/// This trait is transparent to [`Option`], [`Vec`] and other containers.
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC
pub trait BaseSubTypes<S> {
/// Sub-[`Types`] of the [GraphQL object][1].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
const NAMES: Types;
}
impl<'a, S, T: BaseSubTypes<S> + ?Sized> BaseSubTypes<S> for &'a T {
const NAMES: Types = T::NAMES;
}
impl<'ctx, S, T> BaseSubTypes<S> for (&'ctx T::Context, T)
where
S: ScalarValue,
T: BaseSubTypes<S> + GraphQLValue<S>,
{
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S>> BaseSubTypes<S> for Option<T> {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S>> BaseSubTypes<S> for Nullable<T> {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S>, E> BaseSubTypes<S> for Result<T, E> {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S>> BaseSubTypes<S> for Vec<T> {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S>> BaseSubTypes<S> for [T] {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S>, const N: usize> BaseSubTypes<S> for [T; N] {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S> + ?Sized> BaseSubTypes<S> for Box<T> {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S> + ?Sized> BaseSubTypes<S> for Arc<T> {
const NAMES: Types = T::NAMES;
}
impl<S, T: BaseSubTypes<S> + ?Sized> BaseSubTypes<S> for Rc<T> {
const NAMES: Types = T::NAMES;
}
/// Alias for a value of a [`WrappedType`] (composed GraphQL type).
pub type WrappedValue = u128;
// TODO: Just use `&str`s once they're allowed in `const` generics.
/// Encoding of a composed GraphQL type in numbers.
///
/// To fully represent a [GraphQL object][1] it's not enough to use [`Type`],
/// because of the [wrapping types][2]. To work around this we use a
/// [`WrappedValue`] which is represented via [`u128`] number in the following
/// encoding:
/// - In base case of non-nullable [object][1] [`VALUE`] is `1`.
/// - To represent nullability we "append" `2` to the [`VALUE`], so
/// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`.
/// - To represent list we "append" `3` to the [`VALUE`], so
/// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`.
///
/// This approach allows us to uniquely represent any [GraphQL object][1] with a
/// combination of [`Type`] and [`WrappedValue`] and even format it via
/// [`format_type!`] macro in a `const` context.
///
/// # Examples
///
/// ```rust
/// # use juniper::{
/// # format_type,
/// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type},
/// # DefaultScalarValue,
/// # };
/// #
/// assert_eq!(<Option<i32> as WrappedType<DefaultScalarValue>>::VALUE, 12);
/// assert_eq!(<Vec<i32> as WrappedType<DefaultScalarValue>>::VALUE, 13);
/// assert_eq!(<Vec<Option<i32>> as WrappedType<DefaultScalarValue>>::VALUE, 123);
/// assert_eq!(<Option<Vec<i32>> as WrappedType<DefaultScalarValue>>::VALUE, 132);
/// assert_eq!(<Option<Vec<Option<i32>>> as WrappedType<DefaultScalarValue>>::VALUE, 1232);
///
/// const TYPE_STRING: Type = <Option<Vec<Option<String>>> as BaseType<DefaultScalarValue>>::NAME;
/// const WRAP_VAL_STRING: WrappedValue = <Option<Vec<Option<String>>> as WrappedType<DefaultScalarValue>>::VALUE;
/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]");
///
/// const TYPE_STR: Type = <Option<Vec<Option<&str>>> as BaseType<DefaultScalarValue>>::NAME;
/// const WRAP_VAL_STR: WrappedValue = <Option<Vec<Option<&str>>> as WrappedType<DefaultScalarValue>>::VALUE;
/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]");
/// ```
///
/// [`VALUE`]: Self::VALUE
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Wrapping-Types
pub trait WrappedType<S> {
/// [`WrappedValue`] of this type.
const VALUE: WrappedValue;
}
impl<'ctx, S, T: WrappedType<S>> WrappedType<S> for (&'ctx T::Context, T)
where
S: ScalarValue,
T: GraphQLValue<S>,
{
const VALUE: u128 = T::VALUE;
}
impl<S, T: WrappedType<S>> WrappedType<S> for Option<T> {
const VALUE: u128 = T::VALUE * 10 + 2;
}
impl<S, T: WrappedType<S>> WrappedType<S> for Nullable<T> {
const VALUE: u128 = T::VALUE * 10 + 2;
}
impl<S, T: WrappedType<S>, E> WrappedType<S> for Result<T, E> {
const VALUE: u128 = T::VALUE;
}
impl<S, T: WrappedType<S>> WrappedType<S> for Vec<T> {
const VALUE: u128 = T::VALUE * 10 + 3;
}
impl<S, T: WrappedType<S>> WrappedType<S> for [T] {
const VALUE: u128 = T::VALUE * 10 + 3;
}
impl<S, T: WrappedType<S>, const N: usize> WrappedType<S> for [T; N] {
const VALUE: u128 = T::VALUE * 10 + 3;
}
impl<'a, S, T: WrappedType<S> + ?Sized> WrappedType<S> for &'a T {
const VALUE: u128 = T::VALUE;
}
impl<S, T: WrappedType<S> + ?Sized> WrappedType<S> for Box<T> {
const VALUE: u128 = T::VALUE;
}
impl<S, T: WrappedType<S> + ?Sized> WrappedType<S> for Arc<T> {
const VALUE: u128 = T::VALUE;
}
impl<S, T: WrappedType<S> + ?Sized> WrappedType<S> for Rc<T> {
const VALUE: u128 = T::VALUE;
}
/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name.
///
/// See [`Fields`] for more info.
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Name = &'static str;
/// Alias for a slice of [`Name`]s.
///
/// See [`Fields`] for more info.
pub type Names = &'static [Name];
/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Argument = (Name, Type, WrappedValue);
/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and
/// [`WrappedValue`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub type Arguments = &'static [(Name, Type, WrappedValue)];
/// Alias for a `const`-hashed [`Name`] used in a `const` context.
pub type FieldName = u128;
/// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments
pub trait Fields<S> {
/// [`Names`] of the [GraphQL object][1] or [interface][2]
/// [field arguments][3].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments
const NAMES: Names;
}
/// [`Types`] of the [GraphQL interfaces][1] implemented by this type.
///
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait Implements<S> {
/// [`Types`] of the [GraphQL interfaces][1] implemented by this type.
///
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
const NAMES: Types;
}
/// Stores meta information of a [GraphQL field][1]:
/// - [`Context`] and [`TypeInfo`].
/// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`].
/// - [`ARGUMENTS`].
///
/// [`ARGUMENTS`]: Self::ARGUMENTS
/// [`Context`]: Self::Context
/// [`SUB_TYPES`]: Self::SUB_TYPES
/// [`TYPE`]: Self::TYPE
/// [`TypeInfo`]: Self::TypeInfo
/// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
pub trait FieldMeta<S, const N: FieldName> {
/// [`GraphQLValue::Context`] of this [field][1].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
type Context;
/// [`GraphQLValue::TypeInfo`] of this [GraphQL field][1].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
type TypeInfo;
/// [`Types`] of [GraphQL field's][1] return type.
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
const TYPE: Type;
/// [Sub-types][1] of [GraphQL field's][2] return type.
///
/// [1]: BaseSubTypes
/// [2]: https://spec.graphql.org/October2021#sec-Language.Fields
const SUB_TYPES: Types;
/// [`WrappedValue`] of [GraphQL field's][1] return type.
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
const WRAPPED_VALUE: WrappedValue;
/// [GraphQL field's][1] [`Arguments`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
const ARGUMENTS: Arguments;
}
/// Synchronous field of a [GraphQL object][1] or [interface][2].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait Field<S, const N: FieldName>: FieldMeta<S, N> {
/// Resolves the [`Value`] of this synchronous [`Field`].
///
/// The `arguments` object contains all the specified arguments, with the
/// default values being substituted for the ones not provided by the query.
///
/// The `executor` can be used to drive selections into sub-[objects][1].
///
/// [`Value`]: crate::Value
/// [1]: https://spec.graphql.org/October2021#sec-Objects
fn call(
&self,
info: &Self::TypeInfo,
args: &FieldArguments<S>,
executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S>;
}
/// Asynchronous field of a GraphQL [object][1] or [interface][2].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
pub trait AsyncField<S, const N: FieldName>: FieldMeta<S, N> {
/// Resolves the [`Value`] of this asynchronous [`AsyncField`].
///
/// The `arguments` object contains all the specified arguments, with the
/// default values being substituted for the ones not provided by the query.
///
/// The `executor` can be used to drive selections into sub-[objects][1].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
fn call<'b>(
&'b self,
info: &'b Self::TypeInfo,
args: &'b FieldArguments<S>,
executor: &'b Executor<Self::Context, S>,
) -> BoxFuture<'b, ExecutionResult<S>>;
}
/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in
/// `const` generics. See [spec] for more info.
///
/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
#[must_use]
pub const fn fnv1a128(str: Name) -> u128 {
const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d;
const FNV_PRIME: u128 = 0x0000000001000000000000000000013b;
let bytes = str.as_bytes();
let mut hash = FNV_OFFSET_BASIS;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u128;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
/// Length __in bytes__ of the [`format_type!`] macro result.
#[must_use]
pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize {
let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type!
let mut curr = val;
while curr % 10 != 0 {
match curr % 10 {
2 => len -= "!".as_bytes().len(), // remove !
3 => len += "[]!".as_bytes().len(), // [Type]!
_ => {}
}
curr /= 10;
}
len
}
/// Checks whether the given GraphQL [object][1] represents a `subtype` of the
/// given GraphQL `ty`pe, basing on the [`WrappedType`] encoding.
///
/// To fully determine the sub-typing relation the [`Type`] should be one of the
/// [`BaseSubTypes::NAMES`].
///
/// [1]: https://spec.graphql.org/October2021#sec-Objects
#[must_use]
pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool {
let ty_curr = ty % 10;
let sub_curr = subtype % 10;
if ty_curr == sub_curr {
if ty_curr == 1 {
true
} else {
can_be_subtype(ty / 10, subtype / 10)
}
} else if ty_curr == 2 {
can_be_subtype(ty / 10, subtype)
} else {
false
}
}
/// Checks whether the given `val` exists in the given `arr`.
#[must_use]
pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool {
let mut i = 0;
while i < arr.len() {
if str_eq(val, arr[i]) {
return true;
}
i += 1;
}
false
}
/// Compares strings in a `const` context.
///
/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to
/// write custom comparison function.
///
/// [`Eq`]: std::cmp::Eq
// TODO: Remove once `Eq` trait is allowed in `const` context.
pub const fn str_eq(l: &str, r: &str) -> bool {
let (l, r) = (l.as_bytes(), r.as_bytes());
if l.len() != r.len() {
return false;
}
let mut i = 0;
while i < l.len() {
if l[i] != r[i] {
return false;
}
i += 1;
}
true
}
/// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing
/// this interface in the `impl = ...` attribute argument.
///
/// Symmetrical to [`assert_interfaces_impls!`].
#[macro_export]
macro_rules! assert_implemented_for {
($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => {
const _: () = {
$({
let is_present = $crate::macros::reflect::str_exists_in_arr(
<$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME,
<$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES,
);
if !is_present {
const MSG: &str = $crate::const_concat!(
"Failed to implement interface `",
<$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `",
<$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`: missing implementer reference in interface's `for` attribute.",
);
::std::panic!("{}", MSG);
}
})*
};
};
}
/// Asserts that `impl = ...` attribute argument has all the types referencing
/// this GraphQL type in `#[graphql_interface(for = ...)]`.
///
/// Symmetrical to [`assert_implemented_for!`].
#[macro_export]
macro_rules! assert_interfaces_impls {
($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => {
const _: () = {
$({
let is_present = $crate::macros::reflect::str_exists_in_arr(
<$interface as ::juniper::macros::reflect::BaseType<$scalar>>::NAME,
<$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES,
);
if !is_present {
const MSG: &str = $crate::const_concat!(
"Failed to implement interface `",
<$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"` on `",
<$implementers as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`: missing interface reference in implementer's `impl` attribute.",
);
::std::panic!("{}", MSG);
}
})*
};
};
}
/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`].
///
/// This assertion is a combination of [`assert_subtype`] and
/// [`assert_field_args`].
///
/// See [spec][1] for more info.
///
/// [1]: https://spec.graphql.org/October2021#IsValidImplementation()
#[macro_export]
macro_rules! assert_field {
(
$base_ty: ty,
$impl_ty: ty,
$scalar: ty,
$field_name: expr $(,)?
) => {
$crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name);
$crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name);
};
}
/// Asserts validness of a [`Field`] return type.
///
/// See [spec][1] for more info.
///
/// [1]: https://spec.graphql.org/October2021#IsValidImplementationFieldType()
#[macro_export]
macro_rules! assert_subtype {
(
$base_ty: ty,
$impl_ty: ty,
$scalar: ty,
$field_name: expr $(,)?
) => {
const _: () = {
const BASE_TY: $crate::macros::reflect::Type =
<$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const IMPL_TY: $crate::macros::reflect::Type =
<$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const ERR_PREFIX: &str = $crate::const_concat!(
"Failed to implement interface `",
BASE_TY,
"` on `",
IMPL_TY,
"`: ",
);
const FIELD_NAME: $crate::macros::reflect::Name =
$field_name;
const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflect::WrappedValue =
<$base_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) },
>>::WRAPPED_VALUE;
const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflect::WrappedValue =
<$impl_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) },
>>::WRAPPED_VALUE;
const BASE_RETURN_TY: $crate::macros::reflect::Type =
<$base_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) },
>>::TYPE;
const IMPL_RETURN_TY: $crate::macros::reflect::Type =
<$impl_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) },
>>::TYPE;
const BASE_RETURN_SUB_TYPES: $crate::macros::reflect::Types =
<$base_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) },
>>::SUB_TYPES;
let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES)
&& $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL);
if !is_subtype {
const MSG: &str = $crate::const_concat!(
ERR_PREFIX,
"Field `",
FIELD_NAME,
"`: implementor is expected to return a subtype of interface's return object: `",
$crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL),
"` is not a subtype of `",
$crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL),
"`.",
);
::std::panic!("{}", MSG);
}
};
};
}
/// Asserts validness of the [`Field`]s arguments. See [spec][1] for more
/// info.
///
/// [1]: https://spec.graphql.org/October2021#sel-IAHZhCHCDEEFAAADHD8Cxob
#[macro_export]
macro_rules! assert_field_args {
(
$base_ty: ty,
$impl_ty: ty,
$scalar: ty,
$field_name: expr $(,)?
) => {
const _: () = {
const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const IMPL_NAME: &str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME;
const ERR_PREFIX: &str = $crate::const_concat!(
"Failed to implement interface `",
BASE_NAME,
"` on `",
IMPL_NAME,
"`: ",
);
const FIELD_NAME: &str = $field_name;
const BASE_ARGS: ::juniper::macros::reflect::Arguments =
<$base_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) },
>>::ARGUMENTS;
const IMPL_ARGS: ::juniper::macros::reflect::Arguments =
<$impl_ty as $crate::macros::reflect::FieldMeta<
$scalar,
{ $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) },
>>::ARGUMENTS;
struct Error {
cause: Cause,
base: ::juniper::macros::reflect::Argument,
implementation: ::juniper::macros::reflect::Argument,
}
enum Cause {
RequiredField,
AdditionalNonNullableField,
TypeMismatch,
}
const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error {
match v {
// Unfortunately we can't use `unreachable!()` here, as this
// branch will be executed either way.
Ok(()) => Error {
cause: Cause::RequiredField,
base: ("unreachable", "unreachable", 1),
implementation: ("unreachable", "unreachable", 1),
},
Err(err) => err,
}
}
const fn check() -> Result<(), Error> {
let mut base_i = 0;
while base_i < BASE_ARGS.len() {
let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i];
let mut impl_i = 0;
let mut was_found = false;
while impl_i < IMPL_ARGS.len() {
let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i];
if $crate::macros::reflect::str_eq(base_name, impl_name) {
if $crate::macros::reflect::str_eq(base_type, impl_type)
&& base_wrap_val == impl_wrap_val
{
was_found = true;
break;
} else {
return Err(Error {
cause: Cause::TypeMismatch,
base: (base_name, base_type, base_wrap_val),
implementation: (impl_name, impl_type, impl_wrap_val),
});
}
}
impl_i += 1;
}
if !was_found {
return Err(Error {
cause: Cause::RequiredField,
base: (base_name, base_type, base_wrap_val),
implementation: (base_name, base_type, base_wrap_val),
});
}
base_i += 1;
}
let mut impl_i = 0;
while impl_i < IMPL_ARGS.len() {
let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i];
impl_i += 1;
if impl_wrapped_val % 10 == 2 {
continue;
}
let mut base_i = 0;
let mut was_found = false;
while base_i < BASE_ARGS.len() {
let (base_name, _, _) = BASE_ARGS[base_i];
if $crate::macros::reflect::str_eq(base_name, impl_name) {
was_found = true;
break;
}
base_i += 1;
}
if !was_found {
return Err(Error {
cause: Cause::AdditionalNonNullableField,
base: (impl_name, impl_type, impl_wrapped_val),
implementation: (impl_name, impl_type, impl_wrapped_val),
});
}
}
Ok(())
}
const RES: ::std::result::Result<(), Error> = check();
if RES.is_err() {
const ERROR: Error = unwrap_error(RES);
const BASE_ARG_NAME: &str = ERROR.base.0;
const IMPL_ARG_NAME: &str = ERROR.implementation.0;
const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2);
const IMPL_TYPE_FORMATTED: &str =
$crate::format_type!(ERROR.implementation.1, ERROR.implementation.2);
const MSG: &str = match ERROR.cause {
Cause::TypeMismatch => {
$crate::const_concat!(
"Argument `",
BASE_ARG_NAME,
"`: expected type `",
BASE_TYPE_FORMATTED,
"`, found: `",
IMPL_TYPE_FORMATTED,
"`.",
)
}
Cause::RequiredField => {
$crate::const_concat!(
"Argument `",
BASE_ARG_NAME,
"` of type `",
BASE_TYPE_FORMATTED,
"` was expected, but not found."
)
}
Cause::AdditionalNonNullableField => {
$crate::const_concat!(
"Argument `",
IMPL_ARG_NAME,
"` of type `",
IMPL_TYPE_FORMATTED,
"` isn't present on the interface and so has to be nullable."
)
}
};
const ERROR_MSG: &str =
$crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG);
::std::panic!("{}", ERROR_MSG);
}
};
};
}
/// Concatenates `const` [`str`](prim@str)s in a `const` context.
#[macro_export]
macro_rules! const_concat {
($($s:expr),* $(,)?) => {{
const LEN: usize = 0 $(+ $s.as_bytes().len())*;
const CNT: usize = [$($s),*].len();
const fn concat(input: [&str; CNT]) -> [u8; LEN] {
let mut bytes = [0; LEN];
let (mut i, mut byte) = (0, 0);
while i < CNT {
let mut b = 0;
while b < input[i].len() {
bytes[byte] = input[i].as_bytes()[b];
byte += 1;
b += 1;
}
i += 1;
}
bytes
}
const CON: [u8; LEN] = concat([$($s),*]);
// TODO: Use `str::from_utf8()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one
// after another byte-by-byte.
#[allow(unsafe_code)]
unsafe { ::std::str::from_utf8_unchecked(&CON) }
}};
}
/// Ensures that the given `$impl_ty` implements [`Field`] and returns a
/// [`fnv1a128`] hash for it, otherwise panics with understandable message.
#[macro_export]
macro_rules! checked_hash {
($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{
let exists = $crate::macros::reflect::str_exists_in_arr(
$field_name,
<$impl_ty as $crate::macros::reflect::Fields<$scalar>>::NAMES,
);
if exists {
$crate::macros::reflect::fnv1a128(FIELD_NAME)
} else {
const MSG: &str = $crate::const_concat!(
$($prefix,)?
"Field `",
$field_name,
"` isn't implemented on `",
<$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME,
"`."
);
::std::panic!("{}", MSG)
}
}};
}
/// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type
/// name.
///
/// # Examples
///
/// ```rust
/// # use juniper::format_type;
/// #
/// assert_eq!(format_type!("String", 123), "[String]!");
/// assert_eq!(format_type!("🦀", 123), "[🦀]!");
/// ```
#[macro_export]
macro_rules! format_type {
($ty: expr, $wrapped_value: expr $(,)?) => {{
const TYPE: (
$crate::macros::reflect::Type,
$crate::macros::reflect::WrappedValue,
) = ($ty, $wrapped_value);
const RES_LEN: usize = $crate::macros::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1);
const OPENING_BRACKET: &str = "[";
const CLOSING_BRACKET: &str = "]";
const BANG: &str = "!";
const fn format_type_arr() -> [u8; RES_LEN] {
let (ty, wrap_val) = TYPE;
let mut type_arr: [u8; RES_LEN] = [0; RES_LEN];
let mut current_start = 0;
let mut current_end = RES_LEN - 1;
let mut current_wrap_val = wrap_val;
let mut is_null = false;
while current_wrap_val % 10 != 0 {
match current_wrap_val % 10 {
2 => is_null = true, // Skips writing `BANG` later.
3 => {
// Write `OPENING_BRACKET` at `current_start`.
let mut i = 0;
while i < OPENING_BRACKET.as_bytes().len() {
type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i];
i += 1;
}
current_start += i;
if !is_null {
// Write `BANG` at `current_end`.
i = 0;
while i < BANG.as_bytes().len() {
type_arr[current_end - BANG.as_bytes().len() + i + 1] =
BANG.as_bytes()[i];
i += 1;
}
current_end -= i;
}
// Write `CLOSING_BRACKET` at `current_end`.
i = 0;
while i < CLOSING_BRACKET.as_bytes().len() {
type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] =
CLOSING_BRACKET.as_bytes()[i];
i += 1;
}
current_end -= i;
is_null = false;
}
_ => {}
}
current_wrap_val /= 10;
}
// Writes `Type` at `current_start`.
let mut i = 0;
while i < ty.as_bytes().len() {
type_arr[current_start + i] = ty.as_bytes()[i];
i += 1;
}
i = 0;
if !is_null {
// Writes `BANG` at `current_end`.
while i < BANG.as_bytes().len() {
type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i];
i += 1;
}
}
type_arr
}
const TYPE_ARR: [u8; RES_LEN] = format_type_arr();
// TODO: Use `str::from_utf8()` once it becomes `const`.
// SAFETY: This is safe, as we concatenate multiple UTF-8 strings one
// after another byte-by-byte.
#[allow(unsafe_code)]
const TYPE_FORMATTED: &str =
unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) };
TYPE_FORMATTED
}};
}

View file

@ -112,9 +112,7 @@ where
..
},
_,
) => Ok(parser
.next_token()?
.map(|_| InputValue::enum_value(name.to_owned()))),
) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))),
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)),
}
}

View file

@ -447,6 +447,7 @@ impl<'a, S> ScalarMeta<'a, S> {
/// Sets the `description` of this [`ScalarMeta`] type.
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -457,6 +458,7 @@ impl<'a, S> ScalarMeta<'a, S> {
/// Overwrites any previously set [specification URL][0].
///
/// [0]: https://spec.graphql.org/October2021#sec--specifiedBy
#[must_use]
pub fn specified_by_url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
self.specified_by_url = Some(url.into());
self
@ -515,6 +517,7 @@ impl<'a, S> ObjectMeta<'a, S> {
/// Sets the `description` of this [`ObjectMeta`] type.
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -523,6 +526,7 @@ impl<'a, S> ObjectMeta<'a, S> {
/// Set the `interfaces` this [`ObjectMeta`] type implements.
///
/// Overwrites any previously set list of interfaces.
#[must_use]
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
self.interface_names = interfaces
.iter()
@ -556,6 +560,7 @@ impl<'a, S> EnumMeta<'a, S> {
/// Sets the `description` of this [`EnumMeta`] type.
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -584,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> {
/// Sets the `description` of this [`InterfaceMeta`] type.
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -612,6 +618,7 @@ impl<'a> UnionMeta<'a> {
/// Sets the `description` of this [`UnionMeta`] type.
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -643,6 +650,7 @@ impl<'a, S> InputObjectMeta<'a, S> {
/// Set the `description` of this [`InputObjectMeta`] type.
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -658,6 +666,7 @@ impl<'a, S> Field<'a, S> {
/// Set the `description` of this [`Field`].
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -666,6 +675,7 @@ impl<'a, S> Field<'a, S> {
/// Adds an `argument` to this [`Field`].
///
/// Arguments are unordered and can't contain duplicates by name.
#[must_use]
pub fn argument(mut self, argument: Argument<'a, S>) -> Self {
match self.arguments {
None => {
@ -681,6 +691,7 @@ impl<'a, S> Field<'a, S> {
/// Sets this [`Field`] as deprecated with an optional `reason`.
///
/// Overwrites any previously set deprecation reason.
#[must_use]
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
self
@ -701,6 +712,7 @@ impl<'a, S> Argument<'a, S> {
/// Sets the `description` of this [`Argument`].
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -709,6 +721,7 @@ impl<'a, S> Argument<'a, S> {
/// Set the default value of this [`Argument`].
///
/// Overwrites any previously set default value.
#[must_use]
pub fn default_value(mut self, val: InputValue<S>) -> Self {
self.default_value = Some(val);
self
@ -728,6 +741,7 @@ impl EnumValue {
/// Sets the `description` of this [`EnumValue`].
///
/// Overwrites any previously set description.
#[must_use]
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
@ -736,6 +750,7 @@ impl EnumValue {
/// Sets this [`EnumValue`] as deprecated with an optional `reason`.
///
/// Overwrites any previously set deprecation reason.
#[must_use]
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
self

View file

@ -95,11 +95,7 @@ impl Human {
Self {
id: id.to_owned(),
name: name.to_owned(),
friend_ids: friend_ids
.to_owned()
.into_iter()
.map(ToOwned::to_owned)
.collect(),
friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(),
appears_in: appears_in.to_vec(),
secret_backstory: secret_backstory.map(ToOwned::to_owned),
home_planet: home_planet.map(|p| p.to_owned()),
@ -111,54 +107,31 @@ impl Human {
#[graphql_object(context = Database, impl = CharacterValue)]
impl Human {
/// The id of the human
fn id(&self) -> &str {
pub fn id(&self) -> &str {
&self.id
}
/// The name of the human
fn name(&self) -> Option<&str> {
pub fn name(&self) -> Option<&str> {
Some(self.name.as_str())
}
/// The friends of the human
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
ctx.get_friends(self)
pub fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
ctx.get_friends(&self.friend_ids)
}
/// Which movies they appear in
fn appears_in(&self) -> &[Episode] {
pub fn appears_in(&self) -> &[Episode] {
&self.appears_in
}
/// The home planet of the human
fn home_planet(&self) -> &Option<String> {
pub fn home_planet(&self) -> &Option<String> {
&self.home_planet
}
}
#[graphql_interface]
impl Character for Human {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> Option<&str> {
Some(&self.name)
}
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
ctx.get_friends(self)
}
fn appears_in(&self) -> &[Episode] {
&self.appears_in
}
fn friends_ids(&self) -> &[String] {
&self.friend_ids
}
}
#[derive(Clone)]
pub struct Droid {
id: String,
@ -182,11 +155,7 @@ impl Droid {
Self {
id: id.to_owned(),
name: name.to_owned(),
friend_ids: friend_ids
.to_owned()
.into_iter()
.map(ToOwned::to_owned)
.collect(),
friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(),
appears_in: appears_in.to_vec(),
secret_backstory: secret_backstory.map(ToOwned::to_owned),
primary_function: primary_function.map(ToOwned::to_owned),
@ -198,54 +167,31 @@ impl Droid {
#[graphql_object(context = Database, impl = CharacterValue)]
impl Droid {
/// The id of the droid
fn id(&self) -> &str {
pub fn id(&self) -> &str {
&self.id
}
/// The name of the droid
fn name(&self) -> Option<&str> {
pub fn name(&self) -> Option<&str> {
Some(self.name.as_str())
}
/// The friends of the droid
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
ctx.get_friends(self)
pub fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
ctx.get_friends(&self.friend_ids)
}
/// Which movies they appear in
fn appears_in(&self) -> &[Episode] {
pub fn appears_in(&self) -> &[Episode] {
&self.appears_in
}
/// The primary function of the droid
fn primary_function(&self) -> &Option<String> {
pub fn primary_function(&self) -> &Option<String> {
&self.primary_function
}
}
#[graphql_interface]
impl Character for Droid {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> Option<&str> {
Some(&self.name)
}
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
ctx.get_friends(self)
}
fn appears_in(&self) -> &[Episode] {
&self.appears_in
}
fn friends_ids(&self) -> &[String] {
&self.friend_ids
}
}
#[derive(Default, Clone)]
pub struct Database {
humans: HashMap<String, Human>,
@ -373,10 +319,7 @@ impl Database {
}
}
pub fn get_friends(&self, c: &dyn Character) -> Vec<CharacterValue> {
c.friends_ids()
.iter()
.flat_map(|id| self.get_character(id))
.collect()
pub fn get_friends(&self, ids: &[String]) -> Vec<CharacterValue> {
ids.iter().flat_map(|id| self.get_character(id)).collect()
}
}

View file

@ -141,6 +141,7 @@ impl<T> Nullable<T> {
/// Returns the nullable if it contains a value, otherwise returns `b`.
#[inline]
#[must_use]
pub fn or(self, b: Self) -> Self {
match self {
Self::Some(_) => self,
@ -151,6 +152,7 @@ impl<T> Nullable<T> {
/// Returns the nullable if it contains a value, otherwise calls `f` and
/// returns the result.
#[inline]
#[must_use]
pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
match self {
Self::Some(_) => self,
@ -161,6 +163,7 @@ impl<T> Nullable<T> {
/// Replaces the actual value in the nullable by the value given in parameter, returning the
/// old value if present, leaving a `Some` in its place without deinitializing either one.
#[inline]
#[must_use]
pub fn replace(&mut self, value: T) -> Self {
std::mem::replace(self, Self::Some(value))
}

View file

@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::{
ast::{InputValue, Selection, ToInputValue},
executor::{ExecutionResult, Executor, Registry},
macros::reflect,
parser::{LexerError, ParseError, ScalarToken, Token},
schema::meta::MetaType,
types::{
@ -202,6 +203,18 @@ where
})
}
impl<S> reflect::WrappedType<S> for str {
const VALUE: reflect::WrappedValue = 1;
}
impl<S> reflect::BaseType<S> for str {
const NAME: reflect::Type = "String";
}
impl<S> reflect::BaseSubTypes<S> for str {
const NAMES: reflect::Types = &[<Self as reflect::BaseType<S>>::NAME];
}
impl<S> GraphQLType<S> for str
where
S: ScalarValue,

View file

@ -21,7 +21,7 @@ proc-macro = true
proc-macro-error = "1.0.2"
proc-macro2 = "1.0.1"
quote = "1.0.3"
syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing"], default-features = false }
syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit", "visit-mut"], default-features = false }
url = "2.0"
[dev-dependencies]

View file

@ -16,7 +16,6 @@ use syn::{
use crate::{
common::{
gen,
parse::{
attr::{err, OptionExt as _},
ParseBufferExt as _,
@ -65,20 +64,6 @@ pub(crate) struct Attr {
///
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
pub(crate) ignore: Option<SpanContainer<syn::Ident>>,
/// Explicitly specified marker indicating that this trait method doesn't
/// represent a [GraphQL field][1], but is a downcasting function into the
/// [GraphQL object][2] implementer type returned by this trait method.
///
/// Once this marker is specified, the [GraphQL object][2] implementer type
/// cannot be downcast via another trait method or external downcasting
/// function.
///
/// Omit using this field if you're generating code for [GraphQL object][2].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
/// [2]: https://spec.graphql.org/June2018/#sec-Objects
pub(crate) downcast: Option<SpanContainer<syn::Ident>>,
}
impl Parse for Attr {
@ -119,10 +104,6 @@ impl Parse for Attr {
.ignore
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
.none_or_else(|_| err::dup_arg(&ident))?,
"downcast" => out
.downcast
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
.none_or_else(|_| err::dup_arg(&ident))?,
name => {
return Err(err::unknown_arg(&ident, name));
}
@ -142,7 +123,6 @@ impl Attr {
description: try_merge_opt!(description: self, another),
deprecated: try_merge_opt!(deprecated: self, another),
ignore: try_merge_opt!(ignore: self, another),
downcast: try_merge_opt!(downcast: self, another),
})
}
@ -156,11 +136,7 @@ impl Attr {
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
if let Some(ignore) = &attr.ignore {
if attr.name.is_some()
|| attr.description.is_some()
|| attr.deprecated.is_some()
|| attr.downcast.is_some()
{
if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() {
return Err(syn::Error::new(
ignore.span(),
"`ignore` attribute argument is not composable with any other arguments",
@ -168,19 +144,6 @@ impl Attr {
}
}
if let Some(downcast) = &attr.downcast {
if attr.name.is_some()
|| attr.description.is_some()
|| attr.deprecated.is_some()
|| attr.ignore.is_some()
{
return Err(syn::Error::new(
downcast.span(),
"`downcast` attribute argument is not composable with any other arguments",
));
}
}
if attr.description.is_none() {
attr.description = get_doc_comment(attrs).map(|sc| {
let span = sc.span_ident();
@ -288,28 +251,6 @@ impl Definition {
}
}
/// Returns generated code that errors about [GraphQL fields][1] tried to be
/// resolved asynchronously in the [`GraphQLValue::resolve_field`] method
/// (which is synchronous itself).
///
/// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
#[must_use]
pub(crate) fn method_resolve_field_err_async_field_tokens(
field_names: &[&str],
scalar: &scalar::Type,
ty_name: &str,
) -> TokenStream {
quote! {
#( #field_names )|* => return Err(::juniper::FieldError::from(format!(
"Tried to resolve async field `{}` on type `{}` with a sync resolver",
field,
<Self as ::juniper::GraphQLType<#scalar>>::name(info)
.ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?,
))),
}
}
/// Returns generated code for the [`marker::IsOutputType::mark`] method,
/// which performs static checks for this [GraphQL field][1].
///
@ -390,106 +331,6 @@ impl Definition {
}
}
/// Returns generated code for the [`GraphQLValue::resolve_field`][0]
/// method, which resolves this [GraphQL field][1] synchronously.
///
/// Returns [`None`] if this [`Definition::is_async`].
///
/// [0]: juniper::GraphQLValue::resolve_field
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
#[must_use]
pub(crate) fn method_resolve_field_tokens(
&self,
scalar: &scalar::Type,
trait_ty: Option<&syn::Type>,
) -> Option<TokenStream> {
if self.is_async {
return None;
}
let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident);
let res = if self.is_method() {
let args = self
.arguments
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar, false));
let rcv = self.has_receiver.then(|| {
quote! { self, }
});
if trait_ty.is_some() {
quote! { <Self as #trait_ty>::#ident(#rcv #( #args ),*) }
} else {
quote! { Self::#ident(#rcv #( #args ),*) }
}
} else {
ty = parse_quote! { _ };
quote! { &self.#ident }
};
let resolving_code = gen::sync_resolving_code();
Some(quote! {
#name => {
let res: #ty = #res;
#resolving_code
}
})
}
/// Returns generated code for the
/// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves
/// this [GraphQL field][1] asynchronously.
///
/// [0]: juniper::GraphQLValueAsync::resolve_field_async
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
#[must_use]
pub(crate) fn method_resolve_field_async_tokens(
&self,
scalar: &scalar::Type,
trait_ty: Option<&syn::Type>,
) -> TokenStream {
let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident);
let mut fut = if self.is_method() {
let args = self
.arguments
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar, true));
let rcv = self.has_receiver.then(|| {
quote! { self, }
});
if trait_ty.is_some() {
quote! { <Self as #trait_ty>::#ident(#rcv #( #args ),*) }
} else {
quote! { Self::#ident(#rcv #( #args ),*) }
}
} else {
ty = parse_quote! { _ };
quote! { &self.#ident }
};
if !self.is_async {
fut = quote! { ::juniper::futures::future::ready(#fut) };
}
let resolving_code = gen::async_resolving_code(Some(&ty));
quote! {
#name => {
let fut = #fut;
#resolving_code
}
}
}
/// Returns generated code for the
/// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which
/// resolves this [GraphQL field][1] as [subscription][2].

View file

@ -11,12 +11,14 @@ use std::{
};
use proc_macro2::Span;
use quote::quote;
use syn::{
ext::IdentExt as _,
parse::{Parse, ParseBuffer},
parse_quote,
punctuated::Punctuated,
token::{self, Token},
visit_mut::VisitMut,
};
/// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing.
@ -250,6 +252,10 @@ pub(crate) trait GenericsExt {
/// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`].
fn move_bounds_to_where_clause(&mut self);
/// Replaces generic parameters in the given [`syn::Type`] with default
/// ones, provided by these [`syn::Generics`].
fn replace_type_with_defaults(&self, ty: &mut syn::Type);
}
impl GenericsExt for syn::Generics {
@ -299,4 +305,42 @@ impl GenericsExt for syn::Generics {
}
}
}
fn replace_type_with_defaults(&self, ty: &mut syn::Type) {
struct Replace<'a>(&'a syn::Generics);
impl<'a> VisitMut for Replace<'a> {
fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) {
match arg {
syn::GenericArgument::Lifetime(lf) => {
*lf = parse_quote! { 'static };
}
syn::GenericArgument::Type(ty) => {
let is_generic = self
.0
.params
.iter()
.filter_map(|par| match par {
syn::GenericParam::Type(ty) => Some(&ty.ident),
_ => None,
})
.any(|par| {
let par = quote! { #par }.to_string();
let ty = quote! { #ty }.to_string();
par == ty
});
if is_generic {
// Replace with `DefaultScalarValue` instead of `()`
// because generic parameter may be scalar.
*ty = parse_quote!(::juniper::DefaultScalarValue);
}
}
_ => {}
}
}
}
Replace(self).visit_type_mut(ty)
}
}

View file

@ -90,12 +90,6 @@ impl Type {
matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_))
}
/// Indicates whether this [`Type`] is [`Type::ExplicitGeneric`].
#[must_use]
pub(crate) fn is_explicit_generic(&self) -> bool {
matches!(self, Self::ExplicitGeneric(_))
}
/// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`].
#[must_use]
pub(crate) fn is_implicit_generic(&self) -> bool {
@ -123,16 +117,6 @@ impl Type {
}
}
/// Returns a type parameter identifier that suits this [`Type`].
#[must_use]
pub(crate) fn generic_ty(&self) -> syn::Type {
match self {
Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param },
Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(),
Self::ImplicitGeneric(None) | Self::Concrete(_) => parse_quote! { __S },
}
}
/// Returns a default [`ScalarValue`] type that is compatible with this [`Type`].
///
/// [`ScalarValue`]: juniper::ScalarValue

View file

@ -237,6 +237,25 @@ fn impl_scalar_struct(
impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident
where #scalar: ::juniper::ScalarValue,
{ }
impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident
where #scalar: ::juniper::ScalarValue,
{
const NAME: ::juniper::macros::reflect::Type = #name;
}
impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident
where #scalar: ::juniper::ScalarValue,
{
const NAMES: ::juniper::macros::reflect::Types =
&[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
}
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident
where #scalar: ::juniper::ScalarValue,
{
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
}
);
Ok(content)

View file

@ -3,7 +3,7 @@
use std::mem;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens as _};
use quote::{format_ident, quote};
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
use crate::{
@ -16,32 +16,22 @@ use crate::{
util::{path_eq_single, span_container::SpanContainer, RenameRule},
};
use super::{
inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast,
TraitAttr, TraitObjectType, Type,
};
use super::{Definition, TraitAttr};
/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
const ERR: GraphQLScope = GraphQLScope::InterfaceAttr;
/// Expands `#[graphql_interface]` macro into generated code.
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
if let Ok(mut ast) = syn::parse2::<syn::ItemTrait>(body.clone()) {
if let Ok(mut ast) = syn::parse2::<syn::ItemTrait>(body) {
let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs);
ast.attrs = parse::attr::strip("graphql_interface", ast.attrs);
return expand_on_trait(trait_attrs, ast);
} else if let Ok(mut ast) = syn::parse2::<syn::ItemImpl>(body) {
if ast.trait_.is_some() {
let impl_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs);
ast.attrs = parse::attr::strip("graphql_interface", ast.attrs);
return expand_on_impl(impl_attrs, ast);
}
}
Err(syn::Error::new(
Span::call_site(),
"#[graphql_interface] attribute is applicable to trait definitions and trait \
implementations only",
"#[graphql_interface] attribute is applicable to trait definitions only",
))
}
@ -71,27 +61,6 @@ fn expand_on_trait(
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
let mut implementers: Vec<_> = attr
.implementers
.iter()
.map(|ty| Implementer {
ty: ty.as_ref().clone(),
downcast: None,
context: None,
scalar: scalar.clone(),
})
.collect();
for (ty, downcast) in &attr.external_downcasts {
match implementers.iter_mut().find(|i| &i.ty == ty) {
Some(impler) => {
impler.downcast = Some(ImplementerDowncast::External {
path: downcast.inner().clone(),
});
}
None => err_only_implementer_downcast(&downcast.span_joined()),
}
}
proc_macro_error::abort_if_dirty();
let renaming = attr
@ -103,22 +72,8 @@ fn expand_on_trait(
let mut fields = vec![];
for item in &mut ast.items {
if let syn::TraitItem::Method(m) = item {
match TraitMethod::parse(m, &renaming) {
Some(TraitMethod::Field(f)) => fields.push(f),
Some(TraitMethod::Downcast(d)) => {
match implementers.iter_mut().find(|i| i.ty == d.ty) {
Some(impler) => {
if let Some(external) = &impler.downcast {
err_duplicate_downcast(m, external, &impler.ty);
} else {
impler.downcast = d.downcast;
impler.context = d.context;
}
}
None => err_only_implementer_downcast(&m.sig),
}
}
_ => {}
if let Some(f) = parse_field(m, &renaming) {
fields.push(f)
}
}
}
@ -147,336 +102,149 @@ fn expand_on_trait(
})
})
})
.or_else(|| {
implementers
.iter()
.find_map(|impler| impler.context.as_ref())
.cloned()
})
.unwrap_or_else(|| parse_quote! { () });
let is_trait_object = attr.r#dyn.is_some();
let is_async_trait = attr.asyncness.is_some()
|| ast
.items
.iter()
.find_map(|item| match item {
syn::TraitItem::Method(m) => m.sig.asyncness,
_ => None,
})
.is_some();
let has_default_async_methods = ast.items.iter().any(|item| match item {
syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(),
_ => false,
});
let ty = if is_trait_object {
Type::TraitObject(Box::new(TraitObjectType::new(
&ast,
&attr,
scalar.clone(),
context.clone(),
)))
} else {
Type::Enum(Box::new(EnumType::new(
&ast,
&attr,
&implementers,
scalar.clone(),
)))
};
let enum_alias_ident = attr
.r#enum
.as_deref()
.cloned()
.unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string()));
let enum_ident = attr.r#enum.as_ref().map_or_else(
|| format_ident!("{}ValueEnum", trait_ident.to_string()),
|c| format_ident!("{}Enum", c.inner().to_string()),
);
let generated_code = Definition {
ty,
trait_generics: ast.generics.clone(),
vis: ast.vis.clone(),
enum_ident,
enum_alias_ident,
name,
description: attr.description.map(SpanContainer::into_inner),
description: attr.description.as_deref().cloned(),
context,
scalar: scalar.clone(),
scalar,
fields,
implementers,
implementers: attr
.implementers
.iter()
.map(|c| c.inner().clone())
.collect(),
};
// Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used.
if is_trait_object {
ast.attrs.push(parse_quote! {
#[allow(unused_qualifications, clippy::type_repetition_in_bounds)]
});
let scalar_ty = scalar.generic_ty();
if !scalar.is_explicit_generic() {
let default_ty = scalar.default_ty();
ast.generics
.params
.push(parse_quote! { #scalar_ty = #default_ty });
}
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { #scalar_ty: ::juniper::ScalarValue });
ast.supertraits
.push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> });
}
if is_async_trait {
if has_default_async_methods {
// Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits
ast.supertraits.push(parse_quote! { Sync });
}
inject_async_trait(
&mut ast.attrs,
ast.items.iter_mut().filter_map(|i| {
if let syn::TraitItem::Method(m) = i {
Some(&mut m.sig)
} else {
None
}
}),
&ast.generics,
);
}
Ok(quote! {
#ast
#generated_code
})
}
/// Expands `#[graphql_interface]` macro placed on a trait implementation block.
fn expand_on_impl(attrs: Vec<syn::Attribute>, mut ast: syn::ItemImpl) -> syn::Result<TokenStream> {
let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?;
/// Parses a [`field::Definition`] from the given trait method definition.
///
/// Returns [`None`] if parsing fails, or the method field is ignored.
#[must_use]
fn parse_field(
method: &mut syn::TraitItemMethod,
renaming: &RenameRule,
) -> Option<field::Definition> {
let method_ident = &method.sig.ident;
let method_attrs = method.attrs.clone();
let is_async_trait = attr.asyncness.is_some()
|| ast
.items
.iter()
.find_map(|item| match item {
syn::ImplItem::Method(m) => m.sig.asyncness,
_ => None,
})
.is_some();
// Remove repeated attributes from the method, to omit incorrect expansion.
method.attrs = mem::take(&mut method.attrs)
.into_iter()
.filter(|attr| !path_eq_single(&attr.path, "graphql"))
.collect();
let is_trait_object = attr.r#dyn.is_some();
let attr = field::Attr::from_attrs("graphql", &method_attrs)
.map_err(|e| proc_macro_error::emit_error!(e))
.ok()?;
if is_trait_object {
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
ast.attrs.push(parse_quote! {
#[allow(unused_qualifications, clippy::type_repetition_in_bounds)]
});
if scalar.is_implicit_generic() {
ast.generics.params.push(parse_quote! { #scalar });
}
if scalar.is_generic() {
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync });
}
if !scalar.is_explicit_generic() {
let (_, trait_path, _) = ast.trait_.as_mut().unwrap();
let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments;
if let syn::PathArguments::None = trait_params {
*trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> });
}
if let syn::PathArguments::AngleBracketed(a) = trait_params {
a.args.push(parse_quote! { #scalar });
}
}
if attr.ignore.is_some() {
return None;
}
if is_async_trait {
inject_async_trait(
&mut ast.attrs,
ast.items.iter_mut().filter_map(|i| {
if let syn::ImplItem::Method(m) = i {
Some(&mut m.sig)
} else {
None
}
}),
&ast.generics,
if method.default.is_some() {
return err_default_impl_block(&method.default);
}
let name = attr
.name
.as_ref()
.map(|m| m.as_ref().value())
.unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string()));
if name.starts_with("__") {
ERR.no_double_underscore(
attr.name
.as_ref()
.map(SpanContainer::span_ident)
.unwrap_or_else(|| method_ident.span()),
);
return None;
}
Ok(quote! { #ast })
}
/// Representation of parsed Rust trait method for `#[graphql_interface]` macro code generation.
enum TraitMethod {
/// Method represents a [`Field`] of [GraphQL interface][1].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
Field(field::Definition),
/// Method represents a custom downcasting function into the [`Implementer`] of
/// [GraphQL interface][1].
///
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
Downcast(Box<Implementer>),
}
impl TraitMethod {
/// Parses this [`TraitMethod`] from the given trait method definition.
///
/// Returns [`None`] if the trait method marked with `#[graphql(ignore)]` attribute,
/// or parsing fails.
#[must_use]
fn parse(method: &mut syn::TraitItemMethod, renaming: &RenameRule) -> Option<Self> {
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(|attr| !path_eq_single(&attr.path, "graphql"))
.collect();
let attr = field::Attr::from_attrs("graphql", &method_attrs)
.map_err(|e| proc_macro_error::emit_error!(e))
.ok()?;
if attr.ignore.is_some() {
return None;
let arguments = {
if method.sig.inputs.is_empty() {
return err_no_method_receiver(&method.sig.inputs);
}
if attr.downcast.is_some() {
return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?)));
}
Some(Self::Field(Self::parse_field(method, attr, renaming)?))
}
/// Parses [`TraitMethod::Downcast`] from the given trait method definition.
///
/// Returns [`None`] if parsing fails.
#[must_use]
fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option<Implementer> {
let method_ident = &method.sig.ident;
let ty = parse::downcaster::output_type(&method.sig.output)
.map_err(|span| {
ERR.emit_custom(
span,
"expects trait method return type to be `Option<&ImplementerType>` only",
)
})
.ok()?;
let context_ty = parse::downcaster::context_ty(&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(),
"async downcast to interface implementer is not supported",
);
return None;
}
let downcast = ImplementerDowncast::Method {
name: method_ident.clone(),
with_context: context_ty.is_some(),
};
Some(Implementer {
ty,
downcast: Some(downcast),
context: context_ty,
scalar: scalar::Type::ImplicitGeneric(None),
})
}
/// Parses [`TraitMethod::Field`] from the given trait method definition.
///
/// Returns [`None`] if parsing fails.
#[must_use]
fn parse_field(
method: &mut syn::TraitItemMethod,
attr: field::Attr,
renaming: &RenameRule,
) -> Option<field::Definition> {
let method_ident = &method.sig.ident;
let name = attr
.name
.as_ref()
.map(|m| m.as_ref().value())
.unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string()));
if name.starts_with("__") {
ERR.no_double_underscore(
attr.name
.as_ref()
.map(SpanContainer::span_ident)
.unwrap_or_else(|| method_ident.span()),
);
return None;
}
let arguments = {
if method.sig.inputs.is_empty() {
return err_no_method_receiver(&method.sig.inputs);
let mut args_iter = method.sig.inputs.iter_mut();
match args_iter.next().unwrap() {
syn::FnArg::Receiver(rcv) => {
if rcv.reference.is_none() || rcv.mutability.is_some() {
return err_invalid_method_receiver(rcv);
}
}
let mut args_iter = method.sig.inputs.iter_mut();
match args_iter.next().unwrap() {
syn::FnArg::Receiver(rcv) => {
if rcv.reference.is_none() || rcv.mutability.is_some() {
return err_invalid_method_receiver(rcv);
syn::FnArg::Typed(arg) => {
if let syn::Pat::Ident(a) = &*arg.pat {
if a.ident.to_string().as_str() != "self" {
return err_invalid_method_receiver(arg);
}
}
syn::FnArg::Typed(arg) => {
if let syn::Pat::Ident(a) = &*arg.pat {
if a.ident.to_string().as_str() != "self" {
return err_invalid_method_receiver(arg);
}
}
return err_no_method_receiver(arg);
}
};
args_iter
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR),
})
.collect()
return err_no_method_receiver(arg);
}
};
args_iter
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR),
})
.collect()
};
let mut ty = match &method.sig.output {
syn::ReturnType::Default => parse_quote! { () },
syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(),
};
ty.lifetimes_anonymized();
let mut ty = match &method.sig.output {
syn::ReturnType::Default => parse_quote! { () },
syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(),
};
ty.lifetimes_anonymized();
let description = attr.description.as_ref().map(|d| d.as_ref().value());
let deprecated = attr
.deprecated
.as_deref()
.map(|d| d.as_ref().map(syn::LitStr::value));
let description = attr.description.as_ref().map(|d| d.as_ref().value());
let deprecated = attr
.deprecated
.as_deref()
.map(|d| d.as_ref().map(syn::LitStr::value));
Some(field::Definition {
name,
ty,
description,
deprecated,
ident: method_ident.clone(),
arguments: Some(arguments),
has_receiver: method.sig.receiver().is_some(),
is_async: method.sig.asyncness.is_some(),
})
}
Some(field::Definition {
name,
ty,
description,
deprecated,
ident: method_ident.clone(),
arguments: Some(arguments),
has_receiver: method.sig.receiver().is_some(),
is_async: method.sig.asyncness.is_some(),
})
}
/// Emits "trait method can't have default implementation" [`syn::Error`]
/// pointing to the given `span`.
fn err_default_impl_block<T, S: Spanned>(span: &S) -> Option<T> {
ERR.emit_custom(
span.span(),
"trait method can't have default implementation",
);
None
}
/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given
/// `span`.
#[must_use]
fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
ERR.emit_custom(
span.span(),
@ -487,7 +255,6 @@ fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
/// Emits "no trait method receiver" [`syn::Error`] pointing to the given
/// `span`.
#[must_use]
fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
ERR.emit_custom(
span.span(),
@ -495,42 +262,3 @@ fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
);
None
}
/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given
/// `span`.
fn err_only_implementer_downcast<S: Spanned>(span: &S) {
ERR.emit_custom(
span.span(),
"downcasting is possible only to interface implementers",
);
}
/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and
/// `external` [`ImplementerDowncast`] function.
fn err_duplicate_downcast(
method: &syn::TraitItemMethod,
external: &ImplementerDowncast,
impler_ty: &syn::Type,
) {
let external = match external {
ImplementerDowncast::External { path } => path,
_ => unreachable!(),
};
ERR.custom(
method.span(),
format!(
"trait method `{}` conflicts with the external downcast function \
`{}` declared on the trait to downcast into the implementer type \
`{}`",
method.sig.ident,
external.to_token_stream(),
impler_ty.to_token_stream(),
),
)
.note(String::from(
"use `#[graphql(ignore)]` attribute argument to ignore this trait \
method for interface implementers downcasting",
))
.emit()
}

File diff suppressed because it is too large Load diff

View file

@ -18,10 +18,10 @@ use syn::{
use crate::{
common::{
field,
field, gen,
parse::{
attr::{err, OptionExt as _},
ParseBufferExt as _, TypeExt,
GenericsExt as _, ParseBufferExt as _, TypeExt,
},
scalar,
},
@ -361,6 +361,68 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
}
}
/// Returns generated code implementing [`BaseType`], [`BaseSubTypes`],
/// [`WrappedType`] and [`Fields`] traits for this [GraphQL object][1].
///
/// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes
/// [`BaseType`]: juniper::macros::reflect::BaseType
/// [`Fields`]: juniper::macros::reflect::Fields
/// [`WrappedType`]: juniper::macros::reflect::WrappedType
/// [1]: https://spec.graphql.org/June2018/#sec-Objects
#[must_use]
pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream {
let scalar = &self.scalar;
let name = &self.name;
let (impl_generics, where_clause) = self.impl_generics(false);
let ty = &self.ty;
let fields = self.fields.iter().map(|f| &f.name);
let interfaces = self.interfaces.iter();
quote! {
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar>
for #ty
#where_clause
{
const NAME: ::juniper::macros::reflect::Type = #name;
}
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar>
for #ty
#where_clause
{
const NAMES: ::juniper::macros::reflect::Types =
&[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
}
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::Implements<#scalar>
for #ty
#where_clause
{
const NAMES: ::juniper::macros::reflect::Types =
&[#(<#interfaces as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*];
}
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
for #ty
#where_clause
{
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
}
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::Fields<#scalar>
for #ty
#where_clause
{
const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*];
}
}
}
/// Returns generated code implementing [`GraphQLType`] trait for this
/// [GraphQL object][1].
///
@ -439,6 +501,10 @@ impl ToTokens for Definition<Query> {
self.impl_graphql_value_tokens().to_tokens(into);
self.impl_graphql_value_async_tokens().to_tokens(into);
self.impl_as_dyn_graphql_value_tokens().to_tokens(into);
self.impl_reflection_traits_tokens().to_tokens(into);
self.impl_field_meta_tokens().to_tokens(into);
self.impl_field_tokens().to_tokens(into);
self.impl_async_field_tokens().to_tokens(into);
}
}
@ -451,11 +517,25 @@ impl Definition<Query> {
#[must_use]
fn impl_graphql_object_tokens(&self) -> TokenStream {
let scalar = &self.scalar;
let const_scalar = self.scalar.default_ty();
let (impl_generics, where_clause) = self.impl_generics(false);
let ty = &self.ty;
let interface_tys = self.interfaces.iter();
let generics = {
let mut generics = self.generics.clone();
if scalar.is_implicit_generic() {
generics.params.push(parse_quote! { #scalar })
}
generics
};
let const_interface_tys = interface_tys.clone().cloned().map(|mut ty| {
generics.replace_type_with_defaults(&mut ty);
ty
});
// TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion,
// but considering generics.
//let interface_tys: Vec<_> = self.interfaces.iter().collect();
@ -469,11 +549,210 @@ impl Definition<Query> {
{
fn mark() {
#( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )*
::juniper::assert_implemented_for!(
#const_scalar, #ty, #(#const_interface_tys),*
);
}
}
}
}
/// Returns generated code implementing [`FieldMeta`] traits for each field
/// of this [GraphQL object][1].
///
/// [`FieldMeta`]: juniper::FieldMeta
/// [1]: https://spec.graphql.org/June2018/#sec-Objects
#[must_use]
fn impl_field_meta_tokens(&self) -> TokenStream {
let impl_ty = &self.ty;
let scalar = &self.scalar;
let context = &self.context;
let (impl_generics, where_clause) = self.impl_generics(false);
self.fields
.iter()
.map(|field| {
let (name, ty) = (&field.name, field.ty.clone());
let arguments = field
.arguments
.as_ref()
.iter()
.flat_map(|vec| vec.iter())
.filter_map(|arg| match arg {
field::MethodArgument::Regular(arg) => {
let (name, ty) = (&arg.name, &arg.ty);
Some(quote! {(
#name,
<#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME,
<#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE,
)})
}
field::MethodArgument::Executor | field::MethodArgument::Context(_) => None,
})
.collect::<Vec<_>>();
quote! {
#[allow(deprecated, non_snake_case)]
#[automatically_derived]
impl #impl_generics ::juniper::macros::reflect::FieldMeta<
#scalar,
{ ::juniper::macros::reflect::fnv1a128(#name) }
> for #impl_ty #where_clause {
type Context = #context;
type TypeInfo = ();
const TYPE: ::juniper::macros::reflect::Type =
<#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME;
const SUB_TYPES: ::juniper::macros::reflect::Types =
<#ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES;
const WRAPPED_VALUE: juniper::macros::reflect::WrappedValue =
<#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE;
const ARGUMENTS: &'static [(
::juniper::macros::reflect::Name,
::juniper::macros::reflect::Type,
::juniper::macros::reflect::WrappedValue,
)] = &[#(#arguments,)*];
}
}
})
.collect()
}
/// Returns generated code implementing [`Field`] trait for each field of
/// this [GraphQL object][1].
///
/// [`Field`]: juniper::Field
/// [1]: https://spec.graphql.org/June2018/#sec-Objects
#[must_use]
fn impl_field_tokens(&self) -> TokenStream {
let (impl_ty, scalar) = (&self.ty, &self.scalar);
let (impl_generics, where_clause) = self.impl_generics(false);
self.fields
.iter()
.map(|field| {
let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident);
let resolve = if field.is_async {
quote! {
::std::panic!(
"Tried to resolve async field `{}` on type `{}` with a sync resolver",
#name,
<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME,
);
}
} else {
let res = if field.is_method() {
let args = field
.arguments
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar, false));
let rcv = field.has_receiver.then(|| {
quote! { self, }
});
quote! { Self::#ident(#rcv #( #args ),*) }
} else {
res_ty = parse_quote! { _ };
quote! { &self.#ident }
};
let resolving_code = gen::sync_resolving_code();
quote! {
let res: #res_ty = #res;
#resolving_code
}
};
quote! {
#[allow(deprecated, non_snake_case)]
#[automatically_derived]
impl #impl_generics ::juniper::macros::reflect::Field<
#scalar,
{ ::juniper::macros::reflect::fnv1a128(#name) }
> for #impl_ty
#where_clause
{
fn call(
&self,
info: &Self::TypeInfo,
args: &::juniper::Arguments<#scalar>,
executor: &::juniper::Executor<Self::Context, #scalar>,
) -> ::juniper::ExecutionResult<#scalar> {
#resolve
}
}
}
})
.collect()
}
/// Returns generated code implementing [`AsyncField`] trait for each field
/// of this [GraphQL object][1].
///
/// [`AsyncField`]: juniper::AsyncField
/// [1]: https://spec.graphql.org/June2018/#sec-Objects
#[must_use]
fn impl_async_field_tokens(&self) -> TokenStream {
let (impl_ty, scalar) = (&self.ty, &self.scalar);
let (impl_generics, where_clause) = self.impl_generics(true);
self.fields
.iter()
.map(|field| {
let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident);
let mut res = if field.is_method() {
let args = field
.arguments
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar, true));
let rcv = field.has_receiver.then(|| {
quote! { self, }
});
quote! { Self::#ident(#rcv #( #args ),*) }
} else {
res_ty = parse_quote! { _ };
quote! { &self.#ident }
};
if !field.is_async {
res = quote! { ::juniper::futures::future::ready(#res) };
}
let resolving_code = gen::async_resolving_code(Some(&res_ty));
quote! {
#[allow(deprecated, non_snake_case)]
#[automatically_derived]
impl #impl_generics ::juniper::macros::reflect::AsyncField<
#scalar,
{ ::juniper::macros::reflect::fnv1a128(#name) }
> for #impl_ty
#where_clause
{
fn call<'b>(
&'b self,
info: &'b Self::TypeInfo,
args: &'b ::juniper::Arguments<#scalar>,
executor: &'b ::juniper::Executor<Self::Context, #scalar>,
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
let fut = #res;
#resolving_code
}
}
}
})
.collect()
}
/// Returns generated code implementing [`GraphQLValue`] trait for this
/// [GraphQL object][1].
///
@ -490,22 +769,18 @@ impl Definition<Query> {
let name = &self.name;
let fields_resolvers = self
.fields
.iter()
.filter_map(|f| f.method_resolve_field_tokens(scalar, None));
let async_fields_err = {
let names = self
.fields
.iter()
.filter_map(|f| f.is_async.then(|| f.name.as_str()))
.collect::<Vec<_>>();
(!names.is_empty()).then(|| {
field::Definition::method_resolve_field_err_async_field_tokens(
&names, scalar, &ty_name,
)
})
};
let fields_resolvers = self.fields.iter().map(|f| {
let name = &f.name;
quote! {
#name => {
::juniper::macros::reflect::Field::<
#scalar,
{ ::juniper::macros::reflect::fnv1a128(#name) }
>::call(self, info, args, executor)
}
}
});
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
@ -530,7 +805,6 @@ impl Definition<Query> {
) -> ::juniper::ExecutionResult<#scalar> {
match field {
#( #fields_resolvers )*
#async_fields_err
_ => #no_field_err,
}
}
@ -559,10 +833,18 @@ impl Definition<Query> {
let ty = &self.ty;
let ty_name = ty.to_token_stream().to_string();
let fields_resolvers = self
.fields
.iter()
.map(|f| f.method_resolve_field_async_tokens(scalar, None));
let fields_resolvers = self.fields.iter().map(|f| {
let name = &f.name;
quote! {
#name => {
::juniper::macros::reflect::AsyncField::<
#scalar,
{ ::juniper::macros::reflect::fnv1a128(#name) }
>::call(self, info, args, executor)
}
}
});
let no_field_err =
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);

View file

@ -320,6 +320,7 @@ impl ToTokens for Definition {
self.impl_graphql_type_tokens().to_tokens(into);
self.impl_graphql_value_tokens().to_tokens(into);
self.impl_graphql_value_async_tokens().to_tokens(into);
self.impl_reflection_traits_tokens().to_tokens(into);
}
}
@ -609,6 +610,50 @@ impl Definition {
}
}
}
/// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
/// [`WrappedType`] traits for this [GraphQL union][1].
///
/// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes
/// [`BaseType`]: juniper::macros::reflect::BaseType
/// [`WrappedType`]: juniper::macros::reflect::WrappedType
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
#[must_use]
pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream {
let scalar = &self.scalar;
let name = &self.name;
let variants = self.variants.iter().map(|var| &var.ty);
let (impl_generics, ty, where_clause) = self.impl_generics(false);
quote! {
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar>
for #ty
#where_clause
{
const NAME: ::juniper::macros::reflect::Type = #name;
}
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar>
for #ty
#where_clause
{
const NAMES: ::juniper::macros::reflect::Types = &[
<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME,
#(<#variants as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*
];
}
#[automatically_derived]
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
for #ty
#where_clause
{
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
}
}
}
}
/// Definition of [GraphQL union][1] variant for code generation.

View file

@ -327,6 +327,25 @@ pub fn build_scalar(
#from_str_body
}
}
impl#generic_type_decl ::juniper::macros::reflect::BaseType<#generic_type> for #impl_for_type
#generic_type_bound
{
const NAME: ::juniper::macros::reflect::Type = #name;
}
impl#generic_type_decl ::juniper::macros::reflect::BaseSubTypes<#generic_type> for #impl_for_type
#generic_type_bound
{
const NAMES: ::juniper::macros::reflect::Types =
&[<Self as ::juniper::macros::reflect::BaseType<#generic_type>>::NAME];
}
impl#generic_type_decl ::juniper::macros::reflect::WrappedType<#generic_type> for #impl_for_type
#generic_type_bound
{
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
}
);
Ok(content)

View file

@ -271,70 +271,36 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// Specifying multiple `#[graphql_interface]` attributes on the same definition
/// is totally okay. They all will be treated as a single attribute.
///
/// The main difference between [GraphQL interface][1] type and Rust trait is
/// [GraphQL interfaces][1] are more like structurally-typed interfaces, while
/// Rust's traits are more like type classes. Using `impl Trait` isn't an
/// option, so you have to cover all trait's methods with type's fields or
/// impl block. But no one is stopping you from additionally implementing trait
/// manually.
///
/// Another difference between [GraphQL interface][1] type and Rust trait is
/// that the former serves both as an _abstraction_ and a _value downcastable to
/// concrete implementers_, while in Rust, a trait is an _abstraction only_ and
/// you need a separate type to downcast into a concrete implementer, like enum
/// or [trait object][3], because trait doesn't represent a type itself.
/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by
/// default, however [trait object][3] may be used too (use `dyn` attribute
/// argument for that).
///
/// A __trait has to be [object safe][2]__ if its values are represented by
/// [trait object][3], because schema resolvers will need to return that
/// [trait object][3]. The [trait object][3] has to be [`Send`] and [`Sync`],
/// and the macro automatically generate a convenien type alias for such
/// [trait object][3].
/// Macro uses Rust enums only to represent a value type of a
/// [GraphQL interface][1].
///
/// ```
/// use juniper::{graphql_interface, GraphQLObject};
///
/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this
/// // GraphQL interface.
/// #[graphql_interface(for = [Human, Droid])] // enumerating all implementers is mandatory
/// #[graphql_interface(for = Human)] // enumerating all implementers is mandatory
/// trait Character {
/// fn id(&self) -> &str;
/// }
///
/// // NOTICE: `dyn` attribute argument enables trait object usage to represent values of this
/// // GraphQL interface. Also, for trait objects a trait is slightly modified
/// // under-the-hood by adding a `ScalarValue` type parameter.
/// #[graphql_interface(dyn = DynSerial, for = Droid)]
/// trait Serial {
/// fn number(&self) -> i32;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name
/// struct Human {
/// id: String,
/// id: String, // this field is used to resolve Character::id
/// home_planet: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = [CharacterValue, DynSerial<__S>])] // notice the trait object referred by alias
/// struct Droid { // and its parametrization by generic
/// id: String, // `ScalarValue`
/// primary_function: String,
/// }
/// #[graphql_interface]
/// impl Character for Droid {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
/// #[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
/// impl Serial for Droid {
/// fn number(&self) -> i32 {
/// 78953
/// }
/// }
/// ```
///
/// # Custom name, description, deprecation and argument defaults
@ -390,7 +356,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// `none` (disables any renaming).
///
/// ```
/// # use juniper::{graphql_interface, GraphQLObject};
/// # use juniper::{graphql_interface, graphql_object};
/// #
/// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming
/// trait Character {
@ -399,19 +365,26 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// fn detailed_info(&self, info_kind: String) -> String;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn detailed_info(&self, info_kind: String) -> String {
///
/// #[graphql_object(impl = CharacterValue, rename_all = "none")]
/// impl Human {
/// fn id(&self) -> &str {
/// &self.id
/// }
///
/// fn home_planet(&self) -> &str {
/// &self.home_planet
/// }
///
/// // You can return `&str` even if trait definition returns `String`.
/// fn detailed_info(&self, info_kind: String) -> &str {
/// (info_kind == "planet")
/// .then(|| &self.home_planet)
/// .unwrap_or(&self.id)
/// .clone()
/// }
/// }
/// ```
@ -447,7 +420,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{graphql_interface, GraphQLObject};
/// # use juniper::{graphql_interface, graphql_object};
/// #
/// struct Database {
/// humans: HashMap<String, Human>,
@ -461,36 +434,38 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str>;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// db.humans.get(&self.id).map(|h| h.id.as_str())
/// #[graphql_object(impl = CharacterValue, Context = Database)]
/// impl Human {
/// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> {
/// context.humans.get(&self.id).map(|h| h.id.as_str())
/// }
/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> {
/// db.humans.get(&self.id).map(|h| h.home_planet.as_str())
/// }
/// fn home_planet(&self) -> &str {
/// &self.home_planet
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
/// #[graphql_interface]
/// impl Character for Droid {
/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// db.droids.get(&self.id).map(|h| h.id.as_str())
/// #[graphql_object(impl = CharacterValue, Context = Database)]
/// impl Droid {
/// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str> {
/// ctx.droids.get(&self.id).map(|h| h.id.as_str())
/// }
/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> {
/// db.droids.get(&self.id).map(|h| h.primary_function.as_str())
/// }
/// fn primary_function(&self) -> &str {
/// &self.primary_function
/// }
/// }
/// ```
///
@ -503,42 +478,33 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so.
///
/// ```
/// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
/// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue};
/// #
/// // NOTICE: Specifying `ScalarValue` as existing type parameter.
/// #[graphql_interface(for = Human, scalar = S)]
/// trait Character<S: ScalarValue> {
/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
/// where
/// S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
/// fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str;
///
/// async fn name<'b>(
/// fn name<'b>(
/// &'b self,
/// #[graphql(executor)] another: &Executor<'_, '_, (), S>,
/// ) -> &'b str
/// where
/// S: Send + Sync;
/// ) -> &'b str;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue<__S>)]
/// struct Human {
/// id: String,
/// name: String,
/// }
/// #[graphql_interface(scalar = S)]
/// impl<S: ScalarValue> Character<S> for Human {
/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
/// #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
/// impl Human {
/// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
/// where
/// S: Send + Sync,
/// S: ScalarValue,
/// {
/// executor.look_ahead().field_name()
/// }
///
/// async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
/// where
/// S: Send + Sync,
/// {
/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str {
/// &self.name
/// }
/// }
@ -557,7 +523,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject};
/// #
/// // NOTICE: Removing `Scalar` argument will fail compilation.
/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)]
/// #[graphql_interface(for = Human, scalar = DefaultScalarValue)]
/// trait Character {
/// fn id(&self) -> &str;
/// }
@ -568,93 +534,6 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
/// id: String,
/// home_planet: String,
/// }
/// #[graphql_interface(scalar = DefaultScalarValue)]
/// impl Character for Human {
/// fn id(&self) -> &str{
/// &self.id
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
/// #[graphql_interface(scalar = DefaultScalarValue)]
/// impl Character for Droid {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
/// ```
///
/// # Downcasting
///
/// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via
/// matching the enum variant or downcasting the trait object (if `dyn` attribute's argument is
/// used).
///
/// To use a custom logic for downcasting a [GraphQL interface][1] into its implementer, there may
/// be specified:
/// - either a `downcast` attribute's argument directly on a trait method;
/// - or an `on` attribute's argument on aa trait definition referring an exteranl function.
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{graphql_interface, GraphQLObject};
/// #
/// struct Database {
/// humans: HashMap<String, Human>,
/// droids: HashMap<String, Droid>,
/// }
/// impl juniper::Context for Database {}
///
/// #[graphql_interface(for = [Human, Droid], Context = Database)]
/// #[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()`
/// trait Character {
/// fn id(&self) -> &str;
///
/// #[graphql(downcast)] // makes method a downcast to `Human`, not a field
/// // NOTICE: The method signature may optionally contain `&Database` context argument.
/// fn as_human(&self) -> Option<&Human> {
/// None
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Human {
/// id: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn id(&self) -> &str {
/// &self.id
/// }
///
/// fn as_human(&self) -> Option<&Self> {
/// Some(self)
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Droid {
/// id: String,
/// }
/// #[graphql_interface]
/// impl Character for Droid {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
///
/// // External downcast function doesn't have to be a method of a type.
/// // It's only a matter of the function signature to match the requirements.
/// fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> {
/// db.droids.get(ch.id())
/// }
/// ```
///
/// [`Context`]: juniper::Context

View file

@ -906,6 +906,25 @@ impl GraphQLTypeDefiniton {
}
}
}
impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty
#where_clause
{
const NAME: ::juniper::macros::reflect::Type = #name;
}
impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty
#where_clause
{
const NAMES: ::juniper::macros::reflect::Types =
&[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
}
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty
#where_clause
{
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
}
);
if !self.no_async {
@ -1153,6 +1172,28 @@ impl GraphQLTypeDefiniton {
].into_iter().collect())
}
}
impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar>
for #ty #type_generics_tokens
#where_clause
{
const NAME: ::juniper::macros::reflect::Type = #name;
}
impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar>
for #ty #type_generics_tokens
#where_clause
{
const NAMES: ::juniper::macros::reflect::Types =
&[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
}
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar>
for #ty #type_generics_tokens
#where_clause
{
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
}
);
if !self.no_async {

View file

@ -68,6 +68,7 @@ impl<CtxT> ConnectionConfig<CtxT> {
/// Specifies the maximum number of in-flight operations that a connection can have. If this
/// number is exceeded, attempting to start more will result in an error. By default, there is
/// no limit to in-flight operations.
#[must_use]
pub fn with_max_in_flight_operations(mut self, max: usize) -> Self {
self.max_in_flight_operations = max;
self
@ -75,6 +76,7 @@ impl<CtxT> ConnectionConfig<CtxT> {
/// Specifies the interval at which to send keep-alives. Specifying a zero duration will
/// disable keep-alives. By default, keep-alives are sent every 15 seconds.
#[must_use]
pub fn with_keep_alive_interval(mut self, interval: Duration) -> Self {
self.keep_alive_interval = interval;
self

View file

@ -286,24 +286,24 @@ enum GraphQLRequestError {
}
impl fmt::Display for GraphQLRequestError {
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, &mut f),
GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, &mut f),
GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, &mut f),
GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, &mut f),
GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, &mut f),
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, f),
}
}
}
impl Error for GraphQLRequestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
GraphQLRequestError::BodyHyper(ref err) => Some(err),
GraphQLRequestError::BodyUtf8(ref err) => Some(err),
GraphQLRequestError::BodyJSONError(ref err) => Some(err),
GraphQLRequestError::Variables(ref err) => Some(err),
match self {
GraphQLRequestError::BodyHyper(err) => Some(err),
GraphQLRequestError::BodyUtf8(err) => Some(err),
GraphQLRequestError::BodyJSONError(err) => Some(err),
GraphQLRequestError::Variables(err) => Some(err),
GraphQLRequestError::Invalid(_) => None,
}
}

View file

@ -311,15 +311,15 @@ where
Subscription: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
'a: 'static,
{
fn handle(&self, mut req: &mut Request) -> IronResult<Response> {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = (self.context_factory)(req)?;
let graphql_request = match req.method {
method::Get => self.handle_get(&mut req)?,
method::Get => self.handle_get(req)?,
method::Post => match req.headers.get::<ContentType>().map(ContentType::deref) {
Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() {
"json" => self.handle_post_json(&mut req)?,
"graphql" => self.handle_post_graphql(&mut req)?,
"json" => self.handle_post_json(req)?,
"graphql" => self.handle_post_graphql(req)?,
_ => return Ok(Response::with(status::BadRequest)),
},
_ => return Ok(Response::with(status::BadRequest)),
@ -369,11 +369,11 @@ enum GraphQLIronError {
}
impl fmt::Display for GraphQLIronError {
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f),
GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f),
GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, &mut f),
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GraphQLIronError::Serde(err) => fmt::Display::fmt(err, f),
GraphQLIronError::Url(err) => fmt::Display::fmt(err, f),
GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f),
}
}
}

View file

@ -183,7 +183,7 @@ where
ready_vec.push(None);
}
let stream = stream::poll_fn(move |mut ctx| -> Poll<Option<ExecutionOutput<S>>> {
let stream = stream::poll_fn(move |ctx| -> Poll<Option<ExecutionOutput<S>>> {
let mut obj_iterator = object.iter_mut();
// Due to having to modify `ready_vec` contents (by-move pattern)
@ -204,7 +204,7 @@ where
match val {
Value::Scalar(stream) => {
match Pin::new(stream).poll_next(&mut ctx) {
match Pin::new(stream).poll_next(ctx) {
Poll::Ready(None) => return Poll::Ready(None),
Poll::Ready(Some(value)) => {
*ready = Some((field_name.clone(), value));