- 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:
parent
c866e091a4
commit
1aa1000c3b
82 changed files with 3493 additions and 6053 deletions
|
@ -45,32 +45,14 @@ trait Character {
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
#[graphql(impl = CharacterValue)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface]
|
#
|
||||||
impl Character for Droid {
|
# fn main() {}
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# fn main() {
|
|
||||||
let human = Human { id: "human-32".to_owned() };
|
|
||||||
// Values type for interface has `From` implementations for all its implementers,
|
|
||||||
// so we don't need to bother with enum variant names.
|
|
||||||
let character: CharacterValue = human.into();
|
|
||||||
assert_eq!(character.id(), "human-32");
|
|
||||||
# }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Also, enum name can be specified explicitly, if desired.
|
Also, enum name can be specified explicitly, if desired.
|
||||||
|
@ -90,71 +72,11 @@ struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
#
|
||||||
# fn main() {}
|
# 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
|
### Ignoring trait methods
|
||||||
|
|
||||||
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
|
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 {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
#
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
@ -278,24 +194,6 @@ struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
name: 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() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
@ -309,120 +207,51 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`]
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# extern crate juniper;
|
# 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
|
#[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter
|
||||||
trait Character<S: ScalarValue> {
|
trait Character<S: ScalarValue> {
|
||||||
// If a field argument is named `executor`, it's automatically assumed
|
// If a field argument is named `executor`, it's automatically assumed
|
||||||
// as an executor argument.
|
// as an executor argument.
|
||||||
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str;
|
||||||
where
|
|
||||||
S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
|
|
||||||
|
|
||||||
// Otherwise, you may mark it explicitly as an executor argument.
|
// Otherwise, you may mark it explicitly as an executor argument.
|
||||||
async fn name<'b>(
|
fn name<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
#[graphql(executor)] another: &Executor<'_, '_, (), S>,
|
#[graphql(executor)] another: &Executor<'_, '_, (), S>,
|
||||||
) -> &'b str
|
) -> &'b str;
|
||||||
where
|
|
||||||
S: Send + Sync;
|
fn home_planet(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue<__S>)]
|
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
home_planet: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface(scalar = S)]
|
#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
|
||||||
impl<S: ScalarValue> Character<S> for Human {
|
impl Human {
|
||||||
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
executor.look_ahead().field_name()
|
executor.look_ahead().field_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
|
async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str {
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
&self.name
|
&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() {}
|
# 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
|
## `ScalarValue` considerations
|
||||||
|
@ -445,12 +274,6 @@ struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
|
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
|
||||||
|
@ -458,12 +281,6 @@ struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
}
|
}
|
||||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
|
||||||
impl Character for Droid {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
#
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
|
@ -10,7 +10,7 @@ use actix_web::{
|
||||||
|
|
||||||
use juniper::{
|
use juniper::{
|
||||||
graphql_object, graphql_subscription, graphql_value,
|
graphql_object, graphql_subscription, graphql_value,
|
||||||
tests::fixtures::starwars::schema::{Character as _, Database, Query},
|
tests::fixtures::starwars::schema::{Database, Query},
|
||||||
EmptyMutation, FieldError, RootNode,
|
EmptyMutation, FieldError, RootNode,
|
||||||
};
|
};
|
||||||
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
|
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
|
||||||
|
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -1,19 +1,8 @@
|
||||||
use juniper::{graphql_interface, GraphQLObject};
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
|
||||||
test: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {
|
trait Character {
|
||||||
fn id(&self, __num: i32) -> &str {
|
fn id(&self, __num: i32) -> &str;
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -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 GraphQL’s introspection system.
|
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
|
||||||
--> fail/interface/argument_double_underscored.rs:14:18
|
--> fail/interface/argument_double_underscored.rs:5: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
|
|
||||||
|
|
|
|
||||||
4 | #[graphql(impl = CharacterValue)]
|
5 | fn id(&self, __num: i32) -> &str;
|
||||||
| ^^^^^^^^^^^^^^ not found in this scope
|
| ^^^^^
|
||||||
|
|
|
||||||
error[E0405]: cannot find trait `Character` in this scope
|
= note: https://spec.graphql.org/June2018/#sec-Schema
|
||||||
--> fail/interface/argument_double_underscored.rs:10:6
|
|
||||||
|
|
|
||||||
10 | impl Character for ObjA {}
|
|
||||||
| ^^^^^^^^^ not found in this scope
|
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
use juniper::{graphql_interface, GraphQLObject};
|
use juniper::{graphql_interface, GraphQLObject};
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
pub struct ObjA {
|
||||||
test: String,
|
test: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {
|
|
||||||
fn id(&self, obj: Self) -> &str {
|
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {
|
trait Character {
|
||||||
fn id(&self, obj: ObjA) -> &str;
|
fn id(&self, obj: ObjA) -> &str;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
|
error[E0277]: the trait bound `ObjA: IsInputType<__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)]
|
8 | #[graphql_interface]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented 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)
|
= 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
|
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)]
|
8 | #[graphql_interface]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
| ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
||||||
|
|
|
|
||||||
note: required by a bound in `Registry::<'r, S>::arg`
|
note: required by a bound in `Registry::<'r, S>::arg`
|
||||||
--> $WORKSPACE/juniper/src/executor/mod.rs
|
--> $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>,
|
| T: GraphQLType<S> + FromInputValue<S>,
|
||||||
| ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg`
|
| ^^^^^^^^^^^^^^^^^ 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)
|
= 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)
|
|
||||||
|
|
|
@ -1,23 +1,8 @@
|
||||||
use juniper::{graphql_interface, GraphQLObject};
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
|
||||||
test: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {
|
trait Character {
|
||||||
fn wrong(
|
fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool;
|
||||||
&self,
|
|
||||||
#[graphql(default = [true, false, false])]
|
|
||||||
input: [bool; 2],
|
|
||||||
) -> bool {
|
|
||||||
input[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
|
error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
|
||||||
--> fail/interface/argument_wrong_default_array.rs:12:1
|
--> fail/interface/argument_wrong_default_array.rs:3:1
|
||||||
|
|
|
|
||||||
12 | #[graphql_interface(for = ObjA)]
|
3 | #[graphql_interface]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
| ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<[T; LANES] as From<Simd<T, LANES>>>
|
<[T; LANES] as From<Simd<T, LANES>>>
|
||||||
<[bool; LANES] as From<Mask<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: 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)
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
|
@ -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() {}
|
|
|
@ -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
|
|
|
@ -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() {}
|
|
|
@ -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
|
|
|
@ -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() {}
|
|
|
@ -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
|
|
|
@ -1,19 +1,8 @@
|
||||||
use juniper::{graphql_interface, GraphQLObject};
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
|
||||||
test: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {
|
trait Character {
|
||||||
fn __id(&self) -> &str {
|
fn __id(&self) -> &str;
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -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 GraphQL’s introspection system.
|
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system.
|
||||||
--> fail/interface/field_double_underscored.rs:14:8
|
--> fail/interface/field_double_underscored.rs:5: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
|
|
||||||
|
|
|
|
||||||
4 | #[graphql(impl = CharacterValue)]
|
5 | fn __id(&self) -> &str;
|
||||||
| ^^^^^^^^^^^^^^ not found in this scope
|
| ^^^^
|
||||||
|
|
|
||||||
error[E0405]: cannot find trait `Character` in this scope
|
= note: https://spec.graphql.org/June2018/#sec-Schema
|
||||||
--> fail/interface/field_double_underscored.rs:10:6
|
|
||||||
|
|
|
||||||
10 | impl Character for ObjA {}
|
|
||||||
| ^^^^^^^^^ not found in this scope
|
|
||||||
|
|
|
@ -1,24 +1,13 @@
|
||||||
use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject};
|
use juniper::{graphql_interface, GraphQLInputObject};
|
||||||
|
|
||||||
#[derive(GraphQLInputObject)]
|
#[derive(GraphQLInputObject)]
|
||||||
pub struct ObjB {
|
pub struct ObjB {
|
||||||
id: i32,
|
id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
|
||||||
test: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {
|
trait Character {
|
||||||
fn id(&self) -> ObjB {
|
fn id(&self) -> ObjB;
|
||||||
ObjB { id: 34 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
|
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
|
||||||
--> fail/interface/field_non_output_return_type.rs:17:1
|
--> fail/interface/field_non_output_return_type.rs:8:1
|
||||||
|
|
|
|
||||||
17 | #[graphql_interface(for = ObjA)]
|
8 | #[graphql_interface]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
| ^^^^^^^^^^^^^^^^^^^^ 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)
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
|
@ -1,24 +1,11 @@
|
||||||
use juniper::{graphql_interface, GraphQLObject};
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
|
||||||
test: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {
|
trait Character {
|
||||||
fn id(&self) -> &str {
|
fn id(&self) -> &str;
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql(name = "id")]
|
#[graphql(name = "id")]
|
||||||
fn id2(&self) -> &str {
|
fn id2(&self) -> &str;
|
||||||
"funB"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,25 +1,12 @@
|
||||||
error: GraphQL interface must have a different name for each field
|
error: GraphQL interface must have a different name for each field
|
||||||
--> fail/interface/fields_duplicate.rs:13:1
|
--> fail/interface/fields_duplicate.rs:4: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
|
|
||||||
|
|
|
|
||||||
4 | #[graphql(impl = CharacterValue)]
|
4 | / trait Character {
|
||||||
| ^^^^^^^^^^^^^^ not found in this scope
|
5 | | fn id(&self) -> &str;
|
||||||
|
6 | |
|
||||||
error[E0405]: cannot find trait `Character` in this scope
|
7 | | #[graphql(name = "id")]
|
||||||
--> fail/interface/fields_duplicate.rs:10:6
|
8 | | fn id2(&self) -> &str;
|
||||||
|
|
9 | | }
|
||||||
10 | impl Character for ObjA {}
|
| |_^
|
||||||
| ^^^^^^^^^ not found in this scope
|
|
|
||||||
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||||
|
|
|
@ -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() {}
|
|
|
@ -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)
|
|
|
@ -3,14 +3,7 @@ use juniper::{graphql_interface, GraphQLObject};
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
#[graphql(impl = CharacterValue)]
|
||||||
pub struct ObjA {
|
pub struct ObjA {
|
||||||
test: String,
|
id: String,
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for ObjA {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface(for = [ObjA, ObjA])]
|
#[graphql_interface(for = [ObjA, ObjA])]
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
error: duplicated attribute argument found
|
error: duplicated attribute argument found
|
||||||
--> $DIR/implementers_duplicate_pretty.rs:16:34
|
--> fail/interface/implementers_duplicate_pretty.rs:9:34
|
||||||
|
|
|
|
||||||
16 | #[graphql_interface(for = [ObjA, ObjA])]
|
9 | #[graphql_interface(for = [ObjA, ObjA])]
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
||||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
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)]
|
4 | #[graphql(impl = CharacterValue)]
|
||||||
| ^^^^^^^^^^^^^^ not found in this scope
|
| ^^^^^^^^^^^^^^ 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
|
|
||||||
|
|
|
@ -3,18 +3,11 @@ use juniper::{graphql_interface, GraphQLObject};
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
#[graphql(impl = CharacterValue)]
|
||||||
pub struct ObjA {
|
pub struct ObjA {
|
||||||
test: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjAlias = ObjA;
|
type ObjAlias = ObjA;
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for ObjA {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface(for = [ObjA, ObjAlias])]
|
#[graphql_interface(for = [ObjA, ObjAlias])]
|
||||||
trait Character {
|
trait Character {
|
||||||
fn id(&self) -> &str;
|
fn id(&self) -> &str;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
error[E0119]: conflicting implementations of trait `<CharacterValue as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
|
error[E0119]: conflicting implementations of trait `<CharacterValueEnum<ObjA, ObjA> as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
|
||||||
--> $DIR/implementers_duplicate_ugly.rs:18:1
|
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
||||||
|
|
|
|
||||||
18 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
11 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
| |
|
| |
|
||||||
| first implementation here
|
| 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)
|
= 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`
|
error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValueEnum<ObjA, ObjA>`
|
||||||
--> $DIR/implementers_duplicate_ugly.rs:18:1
|
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
||||||
|
|
|
|
||||||
18 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
11 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
| |
|
| |
|
||||||
| first implementation here
|
| 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)
|
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
|
#[graphql_interface]
|
||||||
|
trait Character {
|
||||||
|
fn id(&self) -> &str {
|
||||||
|
"default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -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
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -2,9 +2,7 @@ use juniper::graphql_interface;
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
trait __Character {
|
trait __Character {
|
||||||
fn id(&self) -> &str {
|
fn id(&self) -> &str;
|
||||||
"funA"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
use juniper::{graphql_interface, GraphQLObject};
|
use juniper::graphql_interface;
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(impl = CharacterValue)]
|
|
||||||
pub struct ObjA {
|
|
||||||
test: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_interface]
|
#[graphql_interface]
|
||||||
impl Character for ObjA {}
|
|
||||||
|
|
||||||
#[graphql_interface(for = ObjA)]
|
|
||||||
trait Character {}
|
trait Character {}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,19 +1,7 @@
|
||||||
error: GraphQL interface must have at least one field
|
error: GraphQL interface must have at least one field
|
||||||
--> fail/interface/no_fields.rs:13:1
|
--> fail/interface/no_fields.rs:4: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
|
|
||||||
|
|
|
|
||||||
4 | #[graphql(impl = CharacterValue)]
|
4 | trait Character {}
|
||||||
| ^^^^^^^^^^^^^^ not found in this scope
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
error[E0405]: cannot find trait `Character` in this scope
|
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||||
--> fail/interface/no_fields.rs:10:6
|
|
||||||
|
|
|
||||||
10 | impl Character for ObjA {}
|
|
||||||
| ^^^^^^^^^ not found in this scope
|
|
||||||
|
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
|
@ -1,5 +1,5 @@
|
||||||
error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only
|
error: #[graphql_interface] attribute is applicable to trait definitions only
|
||||||
--> $DIR/wrong_item.rs:8:1
|
--> fail/interface/wrong_item_enum.rs:8:1
|
||||||
|
|
|
|
||||||
8 | #[graphql_interface(for = ObjA)]
|
8 | #[graphql_interface(for = ObjA)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -0,0 +1,11 @@
|
||||||
|
use juniper::{graphql_interface, GraphQLObject};
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
pub struct ObjA {
|
||||||
|
test: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_interface(for = ObjA)]
|
||||||
|
impl ObjA {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -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
|
@ -20,13 +20,6 @@ struct Human {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
#[graphql(impl = CharacterValue)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
|
@ -34,13 +27,6 @@ struct Droid {
|
||||||
serial_number: String,
|
serial_number: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Droid {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[graphql_object]
|
#[graphql_object]
|
||||||
impl Query {
|
impl Query {
|
||||||
fn characters() -> Vec<CharacterValue> {
|
fn characters() -> Vec<CharacterValue> {
|
||||||
|
|
|
@ -18,13 +18,6 @@ struct Human {
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
#[graphql(impl = CharacterValue)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
|
@ -32,13 +25,6 @@ struct Droid {
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Droid {
|
|
||||||
fn id(&self) -> &str {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLUnion)]
|
#[derive(GraphQLUnion)]
|
||||||
enum FieldResult {
|
enum FieldResult {
|
||||||
Human(Human),
|
Human(Human),
|
||||||
|
|
|
@ -38,17 +38,6 @@ struct Human {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
|
||||||
impl Character for Human {
|
|
||||||
fn id(&self) -> i32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> String {
|
|
||||||
self.name.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(impl = CharacterValue)]
|
#[graphql(impl = CharacterValue)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
|
@ -56,17 +45,6 @@ struct Droid {
|
||||||
pub name: String,
|
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>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
- Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987))
|
- 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))
|
- 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))
|
- 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
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -296,6 +296,7 @@ impl<S> InputValue<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve all variables to their values.
|
/// Resolve all variables to their values.
|
||||||
|
#[must_use]
|
||||||
pub fn into_const(self, vars: &Variables<S>) -> Self
|
pub fn into_const(self, vars: &Variables<S>) -> Self
|
||||||
where
|
where
|
||||||
S: Clone,
|
S: Clone,
|
||||||
|
|
|
@ -701,7 +701,7 @@ where
|
||||||
FieldPath::Root(_) => unreachable!(),
|
FieldPath::Root(_) => unreachable!(),
|
||||||
};
|
};
|
||||||
self.parent_selection_set
|
self.parent_selection_set
|
||||||
.map(|p| {
|
.and_then(|p| {
|
||||||
// Search the parent's fields to find this field within the set
|
// Search the parent's fields to find this field within the set
|
||||||
let found_field = p.iter().find(|&x| {
|
let found_field = p.iter().find(|&x| {
|
||||||
match *x {
|
match *x {
|
||||||
|
@ -721,7 +721,6 @@ where
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.flatten()
|
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// We didn't find a field in the parent's selection matching
|
// We didn't find a field in the parent's selection matching
|
||||||
// this field, which means we're inside a FragmentSpread
|
// this field, which means we're inside a FragmentSpread
|
||||||
|
|
|
@ -49,6 +49,7 @@ where
|
||||||
S: Clone,
|
S: Clone,
|
||||||
{
|
{
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[must_use]
|
||||||
pub fn type_sub_executor(
|
pub fn type_sub_executor(
|
||||||
&self,
|
&self,
|
||||||
type_name: Option<&str>,
|
type_name: Option<&str>,
|
||||||
|
@ -76,6 +77,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[must_use]
|
||||||
pub fn field_sub_executor(
|
pub fn field_sub_executor(
|
||||||
&self,
|
&self,
|
||||||
field_alias: &'a str,
|
field_alias: &'a str,
|
||||||
|
|
|
@ -3,72 +3,28 @@ mod interface {
|
||||||
graphql_interface, graphql_object,
|
graphql_interface, graphql_object,
|
||||||
schema::model::RootNode,
|
schema::model::RootNode,
|
||||||
types::scalars::{EmptyMutation, EmptySubscription},
|
types::scalars::{EmptyMutation, EmptySubscription},
|
||||||
|
GraphQLObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[graphql_interface(for = [Cat, Dog])]
|
#[graphql_interface(for = [Cat, Dog])]
|
||||||
trait Pet {
|
trait Pet {
|
||||||
fn name(&self) -> &str;
|
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 {
|
struct Dog {
|
||||||
name: String,
|
name: String,
|
||||||
woofs: bool,
|
woofs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface]
|
#[derive(GraphQLObject)]
|
||||||
impl Pet for Dog {
|
#[graphql(impl = PetValue)]
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Cat {
|
struct Cat {
|
||||||
name: String,
|
name: String,
|
||||||
meows: bool,
|
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 {
|
struct Schema {
|
||||||
pets: Vec<PetValue>,
|
pets: Vec<PetValue>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,7 @@ impl<S: ScalarValue> GraphQLScalar for Scalar {
|
||||||
#[graphql_interface(name = "SampleInterface", for = Root)]
|
#[graphql_interface(name = "SampleInterface", for = Root)]
|
||||||
trait Interface {
|
trait Interface {
|
||||||
/// A sample field in the interface
|
/// A sample field in the interface
|
||||||
fn sample_enum(&self) -> Sample {
|
fn sample_enum(&self) -> Sample;
|
||||||
Sample::One
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
@ -66,9 +64,6 @@ impl Root {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
|
||||||
impl Interface for Root {}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_execution() {
|
async fn test_execution() {
|
||||||
let doc = r#"
|
let doc = r#"
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_use]
|
||||||
|
pub mod reflect;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod graphql_input_value;
|
mod graphql_input_value;
|
||||||
|
|
985
juniper/src/macros/reflect.rs
Normal file
985
juniper/src/macros/reflect.rs
Normal 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
|
||||||
|
}};
|
||||||
|
}
|
|
@ -112,9 +112,7 @@ where
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
_,
|
_,
|
||||||
) => Ok(parser
|
) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))),
|
||||||
.next_token()?
|
|
||||||
.map(|_| InputValue::enum_value(name.to_owned()))),
|
|
||||||
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)),
|
_ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -447,6 +447,7 @@ impl<'a, S> ScalarMeta<'a, S> {
|
||||||
/// Sets the `description` of this [`ScalarMeta`] type.
|
/// Sets the `description` of this [`ScalarMeta`] type.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -457,6 +458,7 @@ impl<'a, S> ScalarMeta<'a, S> {
|
||||||
/// Overwrites any previously set [specification URL][0].
|
/// Overwrites any previously set [specification URL][0].
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec--specifiedBy
|
/// [0]: https://spec.graphql.org/October2021#sec--specifiedBy
|
||||||
|
#[must_use]
|
||||||
pub fn specified_by_url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
|
pub fn specified_by_url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
|
||||||
self.specified_by_url = Some(url.into());
|
self.specified_by_url = Some(url.into());
|
||||||
self
|
self
|
||||||
|
@ -515,6 +517,7 @@ impl<'a, S> ObjectMeta<'a, S> {
|
||||||
/// Sets the `description` of this [`ObjectMeta`] type.
|
/// Sets the `description` of this [`ObjectMeta`] type.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -523,6 +526,7 @@ impl<'a, S> ObjectMeta<'a, S> {
|
||||||
/// Set the `interfaces` this [`ObjectMeta`] type implements.
|
/// Set the `interfaces` this [`ObjectMeta`] type implements.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set list of interfaces.
|
/// Overwrites any previously set list of interfaces.
|
||||||
|
#[must_use]
|
||||||
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
|
pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self {
|
||||||
self.interface_names = interfaces
|
self.interface_names = interfaces
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -556,6 +560,7 @@ impl<'a, S> EnumMeta<'a, S> {
|
||||||
/// Sets the `description` of this [`EnumMeta`] type.
|
/// Sets the `description` of this [`EnumMeta`] type.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -584,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> {
|
||||||
/// Sets the `description` of this [`InterfaceMeta`] type.
|
/// Sets the `description` of this [`InterfaceMeta`] type.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -612,6 +618,7 @@ impl<'a> UnionMeta<'a> {
|
||||||
/// Sets the `description` of this [`UnionMeta`] type.
|
/// Sets the `description` of this [`UnionMeta`] type.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -643,6 +650,7 @@ impl<'a, S> InputObjectMeta<'a, S> {
|
||||||
/// Set the `description` of this [`InputObjectMeta`] type.
|
/// Set the `description` of this [`InputObjectMeta`] type.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -658,6 +666,7 @@ impl<'a, S> Field<'a, S> {
|
||||||
/// Set the `description` of this [`Field`].
|
/// Set the `description` of this [`Field`].
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -666,6 +675,7 @@ impl<'a, S> Field<'a, S> {
|
||||||
/// Adds an `argument` to this [`Field`].
|
/// Adds an `argument` to this [`Field`].
|
||||||
///
|
///
|
||||||
/// Arguments are unordered and can't contain duplicates by name.
|
/// Arguments are unordered and can't contain duplicates by name.
|
||||||
|
#[must_use]
|
||||||
pub fn argument(mut self, argument: Argument<'a, S>) -> Self {
|
pub fn argument(mut self, argument: Argument<'a, S>) -> Self {
|
||||||
match self.arguments {
|
match self.arguments {
|
||||||
None => {
|
None => {
|
||||||
|
@ -681,6 +691,7 @@ impl<'a, S> Field<'a, S> {
|
||||||
/// Sets this [`Field`] as deprecated with an optional `reason`.
|
/// Sets this [`Field`] as deprecated with an optional `reason`.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set deprecation reason.
|
/// Overwrites any previously set deprecation reason.
|
||||||
|
#[must_use]
|
||||||
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
|
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
|
||||||
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
|
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
|
||||||
self
|
self
|
||||||
|
@ -701,6 +712,7 @@ impl<'a, S> Argument<'a, S> {
|
||||||
/// Sets the `description` of this [`Argument`].
|
/// Sets the `description` of this [`Argument`].
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -709,6 +721,7 @@ impl<'a, S> Argument<'a, S> {
|
||||||
/// Set the default value of this [`Argument`].
|
/// Set the default value of this [`Argument`].
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set default value.
|
/// Overwrites any previously set default value.
|
||||||
|
#[must_use]
|
||||||
pub fn default_value(mut self, val: InputValue<S>) -> Self {
|
pub fn default_value(mut self, val: InputValue<S>) -> Self {
|
||||||
self.default_value = Some(val);
|
self.default_value = Some(val);
|
||||||
self
|
self
|
||||||
|
@ -728,6 +741,7 @@ impl EnumValue {
|
||||||
/// Sets the `description` of this [`EnumValue`].
|
/// Sets the `description` of this [`EnumValue`].
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set description.
|
/// Overwrites any previously set description.
|
||||||
|
#[must_use]
|
||||||
pub fn description(mut self, description: &str) -> Self {
|
pub fn description(mut self, description: &str) -> Self {
|
||||||
self.description = Some(description.to_owned());
|
self.description = Some(description.to_owned());
|
||||||
self
|
self
|
||||||
|
@ -736,6 +750,7 @@ impl EnumValue {
|
||||||
/// Sets this [`EnumValue`] as deprecated with an optional `reason`.
|
/// Sets this [`EnumValue`] as deprecated with an optional `reason`.
|
||||||
///
|
///
|
||||||
/// Overwrites any previously set deprecation reason.
|
/// Overwrites any previously set deprecation reason.
|
||||||
|
#[must_use]
|
||||||
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
|
pub fn deprecated(mut self, reason: Option<&str>) -> Self {
|
||||||
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
|
self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned));
|
||||||
self
|
self
|
||||||
|
|
89
juniper/src/tests/fixtures/starwars/schema.rs
vendored
89
juniper/src/tests/fixtures/starwars/schema.rs
vendored
|
@ -95,11 +95,7 @@ impl Human {
|
||||||
Self {
|
Self {
|
||||||
id: id.to_owned(),
|
id: id.to_owned(),
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
friend_ids: friend_ids
|
friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(),
|
||||||
.to_owned()
|
|
||||||
.into_iter()
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.collect(),
|
|
||||||
appears_in: appears_in.to_vec(),
|
appears_in: appears_in.to_vec(),
|
||||||
secret_backstory: secret_backstory.map(ToOwned::to_owned),
|
secret_backstory: secret_backstory.map(ToOwned::to_owned),
|
||||||
home_planet: home_planet.map(|p| p.to_owned()),
|
home_planet: home_planet.map(|p| p.to_owned()),
|
||||||
|
@ -111,54 +107,31 @@ impl Human {
|
||||||
#[graphql_object(context = Database, impl = CharacterValue)]
|
#[graphql_object(context = Database, impl = CharacterValue)]
|
||||||
impl Human {
|
impl Human {
|
||||||
/// The id of the human
|
/// The id of the human
|
||||||
fn id(&self) -> &str {
|
pub fn id(&self) -> &str {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the human
|
/// The name of the human
|
||||||
fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
Some(self.name.as_str())
|
Some(self.name.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The friends of the human
|
/// The friends of the human
|
||||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
pub fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
||||||
ctx.get_friends(self)
|
ctx.get_friends(&self.friend_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which movies they appear in
|
/// Which movies they appear in
|
||||||
fn appears_in(&self) -> &[Episode] {
|
pub fn appears_in(&self) -> &[Episode] {
|
||||||
&self.appears_in
|
&self.appears_in
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The home planet of the human
|
/// The home planet of the human
|
||||||
fn home_planet(&self) -> &Option<String> {
|
pub fn home_planet(&self) -> &Option<String> {
|
||||||
&self.home_planet
|
&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)]
|
#[derive(Clone)]
|
||||||
pub struct Droid {
|
pub struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
|
@ -182,11 +155,7 @@ impl Droid {
|
||||||
Self {
|
Self {
|
||||||
id: id.to_owned(),
|
id: id.to_owned(),
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
friend_ids: friend_ids
|
friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(),
|
||||||
.to_owned()
|
|
||||||
.into_iter()
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.collect(),
|
|
||||||
appears_in: appears_in.to_vec(),
|
appears_in: appears_in.to_vec(),
|
||||||
secret_backstory: secret_backstory.map(ToOwned::to_owned),
|
secret_backstory: secret_backstory.map(ToOwned::to_owned),
|
||||||
primary_function: primary_function.map(ToOwned::to_owned),
|
primary_function: primary_function.map(ToOwned::to_owned),
|
||||||
|
@ -198,54 +167,31 @@ impl Droid {
|
||||||
#[graphql_object(context = Database, impl = CharacterValue)]
|
#[graphql_object(context = Database, impl = CharacterValue)]
|
||||||
impl Droid {
|
impl Droid {
|
||||||
/// The id of the droid
|
/// The id of the droid
|
||||||
fn id(&self) -> &str {
|
pub fn id(&self) -> &str {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the droid
|
/// The name of the droid
|
||||||
fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
Some(self.name.as_str())
|
Some(self.name.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The friends of the droid
|
/// The friends of the droid
|
||||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
pub fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
||||||
ctx.get_friends(self)
|
ctx.get_friends(&self.friend_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which movies they appear in
|
/// Which movies they appear in
|
||||||
fn appears_in(&self) -> &[Episode] {
|
pub fn appears_in(&self) -> &[Episode] {
|
||||||
&self.appears_in
|
&self.appears_in
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The primary function of the droid
|
/// The primary function of the droid
|
||||||
fn primary_function(&self) -> &Option<String> {
|
pub fn primary_function(&self) -> &Option<String> {
|
||||||
&self.primary_function
|
&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)]
|
#[derive(Default, Clone)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
humans: HashMap<String, Human>,
|
humans: HashMap<String, Human>,
|
||||||
|
@ -373,10 +319,7 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_friends(&self, c: &dyn Character) -> Vec<CharacterValue> {
|
pub fn get_friends(&self, ids: &[String]) -> Vec<CharacterValue> {
|
||||||
c.friends_ids()
|
ids.iter().flat_map(|id| self.get_character(id)).collect()
|
||||||
.iter()
|
|
||||||
.flat_map(|id| self.get_character(id))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ impl<T> Nullable<T> {
|
||||||
|
|
||||||
/// Returns the nullable if it contains a value, otherwise returns `b`.
|
/// Returns the nullable if it contains a value, otherwise returns `b`.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn or(self, b: Self) -> Self {
|
pub fn or(self, b: Self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Some(_) => 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 nullable if it contains a value, otherwise calls `f` and
|
||||||
/// returns the result.
|
/// returns the result.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
|
pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
|
||||||
match self {
|
match self {
|
||||||
Self::Some(_) => 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
|
/// 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.
|
/// old value if present, leaving a `Some` in its place without deinitializing either one.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn replace(&mut self, value: T) -> Self {
|
pub fn replace(&mut self, value: T) -> Self {
|
||||||
std::mem::replace(self, Self::Some(value))
|
std::mem::replace(self, Self::Some(value))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{InputValue, Selection, ToInputValue},
|
ast::{InputValue, Selection, ToInputValue},
|
||||||
executor::{ExecutionResult, Executor, Registry},
|
executor::{ExecutionResult, Executor, Registry},
|
||||||
|
macros::reflect,
|
||||||
parser::{LexerError, ParseError, ScalarToken, Token},
|
parser::{LexerError, ParseError, ScalarToken, Token},
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
types::{
|
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
|
impl<S> GraphQLType<S> for str
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
|
|
|
@ -21,7 +21,7 @@ proc-macro = true
|
||||||
proc-macro-error = "1.0.2"
|
proc-macro-error = "1.0.2"
|
||||||
proc-macro2 = "1.0.1"
|
proc-macro2 = "1.0.1"
|
||||||
quote = "1.0.3"
|
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"
|
url = "2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -16,7 +16,6 @@ use syn::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
gen,
|
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _,
|
ParseBufferExt as _,
|
||||||
|
@ -65,20 +64,6 @@ pub(crate) struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
|
/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields
|
||||||
pub(crate) ignore: Option<SpanContainer<syn::Ident>>,
|
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 {
|
impl Parse for Attr {
|
||||||
|
@ -119,10 +104,6 @@ impl Parse for Attr {
|
||||||
.ignore
|
.ignore
|
||||||
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
|
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?,
|
.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 => {
|
name => {
|
||||||
return Err(err::unknown_arg(&ident, name));
|
return Err(err::unknown_arg(&ident, name));
|
||||||
}
|
}
|
||||||
|
@ -142,7 +123,6 @@ impl Attr {
|
||||||
description: try_merge_opt!(description: self, another),
|
description: try_merge_opt!(description: self, another),
|
||||||
deprecated: try_merge_opt!(deprecated: self, another),
|
deprecated: try_merge_opt!(deprecated: self, another),
|
||||||
ignore: try_merge_opt!(ignore: 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?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if let Some(ignore) = &attr.ignore {
|
if let Some(ignore) = &attr.ignore {
|
||||||
if attr.name.is_some()
|
if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() {
|
||||||
|| attr.description.is_some()
|
|
||||||
|| attr.deprecated.is_some()
|
|
||||||
|| attr.downcast.is_some()
|
|
||||||
{
|
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
ignore.span(),
|
ignore.span(),
|
||||||
"`ignore` attribute argument is not composable with any other arguments",
|
"`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() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs).map(|sc| {
|
attr.description = get_doc_comment(attrs).map(|sc| {
|
||||||
let span = sc.span_ident();
|
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,
|
/// Returns generated code for the [`marker::IsOutputType::mark`] method,
|
||||||
/// which performs static checks for this [GraphQL field][1].
|
/// 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
|
/// Returns generated code for the
|
||||||
/// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which
|
/// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which
|
||||||
/// resolves this [GraphQL field][1] as [subscription][2].
|
/// resolves this [GraphQL field][1] as [subscription][2].
|
||||||
|
|
|
@ -11,12 +11,14 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
ext::IdentExt as _,
|
ext::IdentExt as _,
|
||||||
parse::{Parse, ParseBuffer},
|
parse::{Parse, ParseBuffer},
|
||||||
parse_quote,
|
parse_quote,
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
token::{self, Token},
|
token::{self, Token},
|
||||||
|
visit_mut::VisitMut,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing.
|
/// 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`].
|
/// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`].
|
||||||
fn move_bounds_to_where_clause(&mut self);
|
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 {
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,12 +90,6 @@ impl Type {
|
||||||
matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_))
|
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`].
|
/// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn is_implicit_generic(&self) -> bool {
|
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`].
|
/// Returns a default [`ScalarValue`] type that is compatible with this [`Type`].
|
||||||
///
|
///
|
||||||
/// [`ScalarValue`]: juniper::ScalarValue
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
|
|
@ -237,6 +237,25 @@ fn impl_scalar_struct(
|
||||||
impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident
|
impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident
|
||||||
where #scalar: ::juniper::ScalarValue,
|
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)
|
Ok(content)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
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 syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -16,32 +16,22 @@ use crate::{
|
||||||
util::{path_eq_single, span_container::SpanContainer, RenameRule},
|
util::{path_eq_single, span_container::SpanContainer, RenameRule},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{Definition, TraitAttr};
|
||||||
inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast,
|
|
||||||
TraitAttr, TraitObjectType, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
|
/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::InterfaceAttr;
|
const ERR: GraphQLScope = GraphQLScope::InterfaceAttr;
|
||||||
|
|
||||||
/// Expands `#[graphql_interface]` macro into generated code.
|
/// Expands `#[graphql_interface]` macro into generated code.
|
||||||
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
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);
|
let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs);
|
||||||
ast.attrs = parse::attr::strip("graphql_interface", ast.attrs);
|
ast.attrs = parse::attr::strip("graphql_interface", ast.attrs);
|
||||||
return expand_on_trait(trait_attrs, ast);
|
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(
|
Err(syn::Error::new(
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
"#[graphql_interface] attribute is applicable to trait definitions and trait \
|
"#[graphql_interface] attribute is applicable to trait definitions only",
|
||||||
implementations only",
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,27 +61,6 @@ fn expand_on_trait(
|
||||||
|
|
||||||
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
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();
|
proc_macro_error::abort_if_dirty();
|
||||||
|
|
||||||
let renaming = attr
|
let renaming = attr
|
||||||
|
@ -103,22 +72,8 @@ fn expand_on_trait(
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
for item in &mut ast.items {
|
for item in &mut ast.items {
|
||||||
if let syn::TraitItem::Method(m) = item {
|
if let syn::TraitItem::Method(m) = item {
|
||||||
match TraitMethod::parse(m, &renaming) {
|
if let Some(f) = parse_field(m, &renaming) {
|
||||||
Some(TraitMethod::Field(f)) => fields.push(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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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! { () });
|
.unwrap_or_else(|| parse_quote! { () });
|
||||||
|
|
||||||
let is_trait_object = attr.r#dyn.is_some();
|
let enum_alias_ident = attr
|
||||||
|
.r#enum
|
||||||
let is_async_trait = attr.asyncness.is_some()
|
.as_deref()
|
||||||
|| ast
|
.cloned()
|
||||||
.items
|
.unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string()));
|
||||||
.iter()
|
let enum_ident = attr.r#enum.as_ref().map_or_else(
|
||||||
.find_map(|item| match item {
|
|| format_ident!("{}ValueEnum", trait_ident.to_string()),
|
||||||
syn::TraitItem::Method(m) => m.sig.asyncness,
|
|c| format_ident!("{}Enum", c.inner().to_string()),
|
||||||
_ => 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 generated_code = Definition {
|
let generated_code = Definition {
|
||||||
ty,
|
trait_generics: ast.generics.clone(),
|
||||||
|
vis: ast.vis.clone(),
|
||||||
|
enum_ident,
|
||||||
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.map(SpanContainer::into_inner),
|
description: attr.description.as_deref().cloned(),
|
||||||
|
|
||||||
context,
|
context,
|
||||||
scalar: scalar.clone(),
|
scalar,
|
||||||
|
|
||||||
fields,
|
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! {
|
Ok(quote! {
|
||||||
#ast
|
#ast
|
||||||
#generated_code
|
#generated_code
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands `#[graphql_interface]` macro placed on a trait implementation block.
|
/// Parses a [`field::Definition`] from the given trait method definition.
|
||||||
fn expand_on_impl(attrs: Vec<syn::Attribute>, mut ast: syn::ItemImpl) -> syn::Result<TokenStream> {
|
///
|
||||||
let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?;
|
/// 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()
|
// Remove repeated attributes from the method, to omit incorrect expansion.
|
||||||
|| ast
|
method.attrs = mem::take(&mut method.attrs)
|
||||||
.items
|
.into_iter()
|
||||||
.iter()
|
.filter(|attr| !path_eq_single(&attr.path, "graphql"))
|
||||||
.find_map(|item| match item {
|
.collect();
|
||||||
syn::ImplItem::Method(m) => m.sig.asyncness,
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
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 {
|
if attr.ignore.is_some() {
|
||||||
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
return None;
|
||||||
|
|
||||||
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 is_async_trait {
|
if method.default.is_some() {
|
||||||
inject_async_trait(
|
return err_default_impl_block(&method.default);
|
||||||
&mut ast.attrs,
|
}
|
||||||
ast.items.iter_mut().filter_map(|i| {
|
|
||||||
if let syn::ImplItem::Method(m) = i {
|
let name = attr
|
||||||
Some(&mut m.sig)
|
.name
|
||||||
} else {
|
.as_ref()
|
||||||
None
|
.map(|m| m.as_ref().value())
|
||||||
}
|
.unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string()));
|
||||||
}),
|
if name.starts_with("__") {
|
||||||
&ast.generics,
|
ERR.no_double_underscore(
|
||||||
|
attr.name
|
||||||
|
.as_ref()
|
||||||
|
.map(SpanContainer::span_ident)
|
||||||
|
.unwrap_or_else(|| method_ident.span()),
|
||||||
);
|
);
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(quote! { #ast })
|
let arguments = {
|
||||||
}
|
if method.sig.inputs.is_empty() {
|
||||||
|
return err_no_method_receiver(&method.sig.inputs);
|
||||||
/// 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 mut args_iter = method.sig.inputs.iter_mut();
|
||||||
if attr.downcast.is_some() {
|
match args_iter.next().unwrap() {
|
||||||
return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?)));
|
syn::FnArg::Receiver(rcv) => {
|
||||||
}
|
if rcv.reference.is_none() || rcv.mutability.is_some() {
|
||||||
|
return err_invalid_method_receiver(rcv);
|
||||||
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();
|
syn::FnArg::Typed(arg) => {
|
||||||
match args_iter.next().unwrap() {
|
if let syn::Pat::Ident(a) = &*arg.pat {
|
||||||
syn::FnArg::Receiver(rcv) => {
|
if a.ident.to_string().as_str() != "self" {
|
||||||
if rcv.reference.is_none() || rcv.mutability.is_some() {
|
return err_invalid_method_receiver(arg);
|
||||||
return err_invalid_method_receiver(rcv);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syn::FnArg::Typed(arg) => {
|
return err_no_method_receiver(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()
|
|
||||||
};
|
};
|
||||||
|
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 {
|
let mut ty = match &method.sig.output {
|
||||||
syn::ReturnType::Default => parse_quote! { () },
|
syn::ReturnType::Default => parse_quote! { () },
|
||||||
syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(),
|
syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(),
|
||||||
};
|
};
|
||||||
ty.lifetimes_anonymized();
|
ty.lifetimes_anonymized();
|
||||||
|
|
||||||
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
||||||
let deprecated = attr
|
let deprecated = attr
|
||||||
.deprecated
|
.deprecated
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|d| d.as_ref().map(syn::LitStr::value));
|
.map(|d| d.as_ref().map(syn::LitStr::value));
|
||||||
|
|
||||||
Some(field::Definition {
|
Some(field::Definition {
|
||||||
name,
|
name,
|
||||||
ty,
|
ty,
|
||||||
description,
|
description,
|
||||||
deprecated,
|
deprecated,
|
||||||
ident: method_ident.clone(),
|
ident: method_ident.clone(),
|
||||||
arguments: Some(arguments),
|
arguments: Some(arguments),
|
||||||
has_receiver: method.sig.receiver().is_some(),
|
has_receiver: method.sig.receiver().is_some(),
|
||||||
is_async: method.sig.asyncness.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
|
/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given
|
||||||
/// `span`.
|
/// `span`.
|
||||||
#[must_use]
|
|
||||||
fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
||||||
ERR.emit_custom(
|
ERR.emit_custom(
|
||||||
span.span(),
|
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
|
/// Emits "no trait method receiver" [`syn::Error`] pointing to the given
|
||||||
/// `span`.
|
/// `span`.
|
||||||
#[must_use]
|
|
||||||
fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
||||||
ERR.emit_custom(
|
ERR.emit_custom(
|
||||||
span.span(),
|
span.span(),
|
||||||
|
@ -495,42 +262,3 @@ fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
||||||
);
|
);
|
||||||
None
|
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
|
@ -18,10 +18,10 @@ use syn::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
field,
|
field, gen,
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _, TypeExt,
|
GenericsExt as _, ParseBufferExt as _, TypeExt,
|
||||||
},
|
},
|
||||||
scalar,
|
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
|
/// Returns generated code implementing [`GraphQLType`] trait for this
|
||||||
/// [GraphQL object][1].
|
/// [GraphQL object][1].
|
||||||
///
|
///
|
||||||
|
@ -439,6 +501,10 @@ impl ToTokens for Definition<Query> {
|
||||||
self.impl_graphql_value_tokens().to_tokens(into);
|
self.impl_graphql_value_tokens().to_tokens(into);
|
||||||
self.impl_graphql_value_async_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_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]
|
#[must_use]
|
||||||
fn impl_graphql_object_tokens(&self) -> TokenStream {
|
fn impl_graphql_object_tokens(&self) -> TokenStream {
|
||||||
let scalar = &self.scalar;
|
let scalar = &self.scalar;
|
||||||
|
let const_scalar = self.scalar.default_ty();
|
||||||
|
|
||||||
let (impl_generics, where_clause) = self.impl_generics(false);
|
let (impl_generics, where_clause) = self.impl_generics(false);
|
||||||
let ty = &self.ty;
|
let ty = &self.ty;
|
||||||
|
|
||||||
let interface_tys = self.interfaces.iter();
|
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,
|
// TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion,
|
||||||
// but considering generics.
|
// but considering generics.
|
||||||
//let interface_tys: Vec<_> = self.interfaces.iter().collect();
|
//let interface_tys: Vec<_> = self.interfaces.iter().collect();
|
||||||
|
@ -469,11 +549,210 @@ impl Definition<Query> {
|
||||||
{
|
{
|
||||||
fn mark() {
|
fn mark() {
|
||||||
#( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::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
|
/// Returns generated code implementing [`GraphQLValue`] trait for this
|
||||||
/// [GraphQL object][1].
|
/// [GraphQL object][1].
|
||||||
///
|
///
|
||||||
|
@ -490,22 +769,18 @@ impl Definition<Query> {
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
|
||||||
let fields_resolvers = self
|
let fields_resolvers = self.fields.iter().map(|f| {
|
||||||
.fields
|
let name = &f.name;
|
||||||
.iter()
|
quote! {
|
||||||
.filter_map(|f| f.method_resolve_field_tokens(scalar, None));
|
#name => {
|
||||||
let async_fields_err = {
|
::juniper::macros::reflect::Field::<
|
||||||
let names = self
|
#scalar,
|
||||||
.fields
|
{ ::juniper::macros::reflect::fnv1a128(#name) }
|
||||||
.iter()
|
>::call(self, info, args, executor)
|
||||||
.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 no_field_err =
|
let no_field_err =
|
||||||
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
|
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
|
||||||
|
|
||||||
|
@ -530,7 +805,6 @@ impl Definition<Query> {
|
||||||
) -> ::juniper::ExecutionResult<#scalar> {
|
) -> ::juniper::ExecutionResult<#scalar> {
|
||||||
match field {
|
match field {
|
||||||
#( #fields_resolvers )*
|
#( #fields_resolvers )*
|
||||||
#async_fields_err
|
|
||||||
_ => #no_field_err,
|
_ => #no_field_err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -559,10 +833,18 @@ impl Definition<Query> {
|
||||||
let ty = &self.ty;
|
let ty = &self.ty;
|
||||||
let ty_name = ty.to_token_stream().to_string();
|
let ty_name = ty.to_token_stream().to_string();
|
||||||
|
|
||||||
let fields_resolvers = self
|
let fields_resolvers = self.fields.iter().map(|f| {
|
||||||
.fields
|
let name = &f.name;
|
||||||
.iter()
|
quote! {
|
||||||
.map(|f| f.method_resolve_field_async_tokens(scalar, None));
|
#name => {
|
||||||
|
::juniper::macros::reflect::AsyncField::<
|
||||||
|
#scalar,
|
||||||
|
{ ::juniper::macros::reflect::fnv1a128(#name) }
|
||||||
|
>::call(self, info, args, executor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let no_field_err =
|
let no_field_err =
|
||||||
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
|
field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name);
|
||||||
|
|
||||||
|
|
|
@ -320,6 +320,7 @@ impl ToTokens for Definition {
|
||||||
self.impl_graphql_type_tokens().to_tokens(into);
|
self.impl_graphql_type_tokens().to_tokens(into);
|
||||||
self.impl_graphql_value_tokens().to_tokens(into);
|
self.impl_graphql_value_tokens().to_tokens(into);
|
||||||
self.impl_graphql_value_async_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.
|
/// Definition of [GraphQL union][1] variant for code generation.
|
||||||
|
|
|
@ -327,6 +327,25 @@ pub fn build_scalar(
|
||||||
#from_str_body
|
#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)
|
Ok(content)
|
||||||
|
|
|
@ -271,70 +271,36 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
/// Specifying multiple `#[graphql_interface]` attributes on the same definition
|
/// Specifying multiple `#[graphql_interface]` attributes on the same definition
|
||||||
/// is totally okay. They all will be treated as a single attribute.
|
/// 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
|
/// 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
|
/// 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
|
/// 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.
|
/// 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
|
/// Macro uses Rust enums only to represent a value type of a
|
||||||
/// default, however [trait object][3] may be used too (use `dyn` attribute
|
/// [GraphQL interface][1].
|
||||||
/// 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].
|
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use juniper::{graphql_interface, GraphQLObject};
|
/// use juniper::{graphql_interface, GraphQLObject};
|
||||||
///
|
///
|
||||||
/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this
|
/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this
|
||||||
/// // GraphQL interface.
|
/// // GraphQL interface.
|
||||||
/// #[graphql_interface(for = [Human, Droid])] // enumerating all implementers is mandatory
|
/// #[graphql_interface(for = Human)] // enumerating all implementers is mandatory
|
||||||
/// trait Character {
|
/// trait Character {
|
||||||
/// fn id(&self) -> &str;
|
/// 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)]
|
/// #[derive(GraphQLObject)]
|
||||||
/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name
|
/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name
|
||||||
/// struct Human {
|
/// struct Human {
|
||||||
/// id: String,
|
/// id: String, // this field is used to resolve Character::id
|
||||||
/// home_planet: String,
|
/// 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
|
/// # Custom name, description, deprecation and argument defaults
|
||||||
|
@ -390,7 +356,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
/// `none` (disables any renaming).
|
/// `none` (disables any renaming).
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use juniper::{graphql_interface, GraphQLObject};
|
/// # use juniper::{graphql_interface, graphql_object};
|
||||||
/// #
|
/// #
|
||||||
/// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming
|
/// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming
|
||||||
/// trait Character {
|
/// trait Character {
|
||||||
|
@ -399,19 +365,26 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
/// fn detailed_info(&self, info_kind: String) -> String;
|
/// fn detailed_info(&self, info_kind: String) -> String;
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(GraphQLObject)]
|
|
||||||
/// #[graphql(impl = CharacterValue)]
|
|
||||||
/// struct Human {
|
/// struct Human {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// home_planet: String,
|
/// home_planet: String,
|
||||||
/// }
|
/// }
|
||||||
/// #[graphql_interface]
|
///
|
||||||
/// impl Character for Human {
|
/// #[graphql_object(impl = CharacterValue, rename_all = "none")]
|
||||||
/// fn detailed_info(&self, info_kind: String) -> String {
|
/// 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")
|
/// (info_kind == "planet")
|
||||||
/// .then(|| &self.home_planet)
|
/// .then(|| &self.home_planet)
|
||||||
/// .unwrap_or(&self.id)
|
/// .unwrap_or(&self.id)
|
||||||
/// .clone()
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -447,7 +420,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::collections::HashMap;
|
/// # use std::collections::HashMap;
|
||||||
/// # use juniper::{graphql_interface, GraphQLObject};
|
/// # use juniper::{graphql_interface, graphql_object};
|
||||||
/// #
|
/// #
|
||||||
/// struct Database {
|
/// struct Database {
|
||||||
/// humans: HashMap<String, Human>,
|
/// 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>;
|
/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str>;
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(GraphQLObject)]
|
|
||||||
/// #[graphql(impl = CharacterValue, Context = Database)]
|
|
||||||
/// struct Human {
|
/// struct Human {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// home_planet: String,
|
/// home_planet: String,
|
||||||
/// }
|
/// }
|
||||||
/// #[graphql_interface]
|
/// #[graphql_object(impl = CharacterValue, Context = Database)]
|
||||||
/// impl Character for Human {
|
/// impl Human {
|
||||||
/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> {
|
/// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> {
|
||||||
/// db.humans.get(&self.id).map(|h| h.id.as_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())
|
/// 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 {
|
/// struct Droid {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// primary_function: String,
|
/// primary_function: String,
|
||||||
/// }
|
/// }
|
||||||
/// #[graphql_interface]
|
/// #[graphql_object(impl = CharacterValue, Context = Database)]
|
||||||
/// impl Character for Droid {
|
/// impl Droid {
|
||||||
/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> {
|
/// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str> {
|
||||||
/// db.droids.get(&self.id).map(|h| h.id.as_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())
|
/// 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.
|
/// 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.
|
/// // NOTICE: Specifying `ScalarValue` as existing type parameter.
|
||||||
/// #[graphql_interface(for = Human, scalar = S)]
|
/// #[graphql_interface(for = Human, scalar = S)]
|
||||||
/// trait Character<S: ScalarValue> {
|
/// trait Character<S: ScalarValue> {
|
||||||
/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
/// fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str;
|
||||||
/// where
|
|
||||||
/// S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
|
|
||||||
///
|
///
|
||||||
/// async fn name<'b>(
|
/// fn name<'b>(
|
||||||
/// &'b self,
|
/// &'b self,
|
||||||
/// #[graphql(executor)] another: &Executor<'_, '_, (), S>,
|
/// #[graphql(executor)] another: &Executor<'_, '_, (), S>,
|
||||||
/// ) -> &'b str
|
/// ) -> &'b str;
|
||||||
/// where
|
|
||||||
/// S: Send + Sync;
|
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(GraphQLObject)]
|
|
||||||
/// #[graphql(impl = CharacterValue<__S>)]
|
|
||||||
/// struct Human {
|
/// struct Human {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// name: String,
|
/// name: String,
|
||||||
/// }
|
/// }
|
||||||
/// #[graphql_interface(scalar = S)]
|
/// #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue<S>)]
|
||||||
/// impl<S: ScalarValue> Character<S> for Human {
|
/// impl Human {
|
||||||
/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
/// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
||||||
/// where
|
/// where
|
||||||
/// S: Send + Sync,
|
/// S: ScalarValue,
|
||||||
/// {
|
/// {
|
||||||
/// executor.look_ahead().field_name()
|
/// executor.look_ahead().field_name()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
|
/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str {
|
||||||
/// where
|
|
||||||
/// S: Send + Sync,
|
|
||||||
/// {
|
|
||||||
/// &self.name
|
/// &self.name
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
@ -557,7 +523,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
/// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject};
|
/// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject};
|
||||||
/// #
|
/// #
|
||||||
/// // NOTICE: Removing `Scalar` argument will fail compilation.
|
/// // NOTICE: Removing `Scalar` argument will fail compilation.
|
||||||
/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)]
|
/// #[graphql_interface(for = Human, scalar = DefaultScalarValue)]
|
||||||
/// trait Character {
|
/// trait Character {
|
||||||
/// fn id(&self) -> &str;
|
/// fn id(&self) -> &str;
|
||||||
/// }
|
/// }
|
||||||
|
@ -568,93 +534,6 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
/// id: String,
|
/// id: String,
|
||||||
/// home_planet: 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
|
/// [`Context`]: juniper::Context
|
||||||
|
|
|
@ -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 {
|
if !self.no_async {
|
||||||
|
@ -1153,6 +1172,28 @@ impl GraphQLTypeDefiniton {
|
||||||
].into_iter().collect())
|
].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 {
|
if !self.no_async {
|
||||||
|
|
|
@ -68,6 +68,7 @@ impl<CtxT> ConnectionConfig<CtxT> {
|
||||||
/// Specifies the maximum number of in-flight operations that a connection can have. If this
|
/// 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
|
/// number is exceeded, attempting to start more will result in an error. By default, there is
|
||||||
/// no limit to in-flight operations.
|
/// no limit to in-flight operations.
|
||||||
|
#[must_use]
|
||||||
pub fn with_max_in_flight_operations(mut self, max: usize) -> Self {
|
pub fn with_max_in_flight_operations(mut self, max: usize) -> Self {
|
||||||
self.max_in_flight_operations = max;
|
self.max_in_flight_operations = max;
|
||||||
self
|
self
|
||||||
|
@ -75,6 +76,7 @@ impl<CtxT> ConnectionConfig<CtxT> {
|
||||||
|
|
||||||
/// Specifies the interval at which to send keep-alives. Specifying a zero duration will
|
/// 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.
|
/// 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 {
|
pub fn with_keep_alive_interval(mut self, interval: Duration) -> Self {
|
||||||
self.keep_alive_interval = interval;
|
self.keep_alive_interval = interval;
|
||||||
self
|
self
|
||||||
|
|
|
@ -286,24 +286,24 @@ enum GraphQLRequestError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for GraphQLRequestError {
|
impl fmt::Display for GraphQLRequestError {
|
||||||
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match self {
|
||||||
GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f),
|
||||||
GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f),
|
||||||
GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f),
|
||||||
GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f),
|
||||||
GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for GraphQLRequestError {
|
impl Error for GraphQLRequestError {
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
match *self {
|
match self {
|
||||||
GraphQLRequestError::BodyHyper(ref err) => Some(err),
|
GraphQLRequestError::BodyHyper(err) => Some(err),
|
||||||
GraphQLRequestError::BodyUtf8(ref err) => Some(err),
|
GraphQLRequestError::BodyUtf8(err) => Some(err),
|
||||||
GraphQLRequestError::BodyJSONError(ref err) => Some(err),
|
GraphQLRequestError::BodyJSONError(err) => Some(err),
|
||||||
GraphQLRequestError::Variables(ref err) => Some(err),
|
GraphQLRequestError::Variables(err) => Some(err),
|
||||||
GraphQLRequestError::Invalid(_) => None,
|
GraphQLRequestError::Invalid(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,15 +311,15 @@ where
|
||||||
Subscription: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
|
Subscription: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
|
||||||
'a: '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 context = (self.context_factory)(req)?;
|
||||||
|
|
||||||
let graphql_request = match req.method {
|
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) {
|
method::Post => match req.headers.get::<ContentType>().map(ContentType::deref) {
|
||||||
Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() {
|
Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() {
|
||||||
"json" => self.handle_post_json(&mut req)?,
|
"json" => self.handle_post_json(req)?,
|
||||||
"graphql" => self.handle_post_graphql(&mut req)?,
|
"graphql" => self.handle_post_graphql(req)?,
|
||||||
_ => return Ok(Response::with(status::BadRequest)),
|
_ => return Ok(Response::with(status::BadRequest)),
|
||||||
},
|
},
|
||||||
_ => return Ok(Response::with(status::BadRequest)),
|
_ => return Ok(Response::with(status::BadRequest)),
|
||||||
|
@ -369,11 +369,11 @@ enum GraphQLIronError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for GraphQLIronError {
|
impl fmt::Display for GraphQLIronError {
|
||||||
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match self {
|
||||||
GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLIronError::Serde(err) => fmt::Display::fmt(err, f),
|
||||||
GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f),
|
GraphQLIronError::Url(err) => fmt::Display::fmt(err, f),
|
||||||
GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, &mut f),
|
GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ where
|
||||||
ready_vec.push(None);
|
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();
|
let mut obj_iterator = object.iter_mut();
|
||||||
|
|
||||||
// Due to having to modify `ready_vec` contents (by-move pattern)
|
// Due to having to modify `ready_vec` contents (by-move pattern)
|
||||||
|
@ -204,7 +204,7 @@ where
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
Value::Scalar(stream) => {
|
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(None) => return Poll::Ready(None),
|
||||||
Poll::Ready(Some(value)) => {
|
Poll::Ready(Some(value)) => {
|
||||||
*ready = Some((field_name.clone(), value));
|
*ready = Some((field_name.clone(), value));
|
||||||
|
|
Loading…
Reference in a new issue