From 1aa1000c3b6528713abe9922a4bf4c34789fbc22 Mon Sep 17 00:00:00 2001 From: ilslv <47687266+ilslv@users.noreply.github.com> Date: Wed, 26 Jan 2022 21:58:53 +0300 Subject: [PATCH] Redesign `#[graphql_interface]` macro (#1009, #1000, #814) - remove support for `#[graphql_interface(dyn)]` - describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait` - forbid default impls on non-skipped trait methods - support additional nullable arguments on implementer - support returning sub-type on implementer --- docs/book/content/types/interfaces.md | 221 +- examples/actix_subscriptions/src/main.rs | 2 +- .../additional_non_nullable_argument.rs | 19 + .../additional_non_nullable_argument.stderr | 7 + .../interface/argument_double_underscored.rs | 15 +- .../argument_double_underscored.stderr | 22 +- .../fail/interface/argument_non_input_type.rs | 8 - .../interface/argument_non_input_type.stderr | 26 +- .../interface/argument_wrong_default_array.rs | 19 +- .../argument_wrong_default_array.stderr | 20 +- ...hod_conflicts_with_external_downcast_fn.rs | 29 - ...conflicts_with_external_downcast_fn.stderr | 20 - .../downcast_method_wrong_input_args.rs | 24 - .../downcast_method_wrong_input_args.stderr | 19 - .../downcast_method_wrong_return_type.rs | 24 - .../downcast_method_wrong_return_type.stderr | 19 - .../interface/field_double_underscored.rs | 15 +- .../interface/field_double_underscored.stderr | 22 +- .../interface/field_non_output_return_type.rs | 15 +- .../field_non_output_return_type.stderr | 12 +- .../fail/interface/fields_duplicate.rs | 19 +- .../fail/interface/fields_duplicate.stderr | 33 +- .../interface/implementer_non_object_type.rs | 20 - .../implementer_non_object_type.stderr | 15 - .../implementers_duplicate_pretty.rs | 9 +- .../implementers_duplicate_pretty.stderr | 16 +- .../interface/implementers_duplicate_ugly.rs | 9 +- .../implementers_duplicate_ugly.stderr | 14 +- .../fail/interface/method_default_impl.rs | 10 + .../fail/interface/method_default_impl.stderr | 10 + .../fail/interface/missing_field.rs | 14 + .../fail/interface/missing_field.stderr | 7 + .../fail/interface/missing_field_argument.rs | 19 + .../interface/missing_field_argument.stderr | 7 + .../fail/interface/missing_for_attr.rs | 14 + .../fail/interface/missing_for_attr.stderr | 7 + .../fail/interface/missing_impl_attr.rs | 13 + .../fail/interface/missing_impl_attr.stderr | 7 + .../fail/interface/name_double_underscored.rs | 4 +- .../codegen_fail/fail/interface/no_fields.rs | 13 +- .../fail/interface/no_fields.stderr | 22 +- .../fail/interface/non_subtype_return.rs | 14 + .../fail/interface/non_subtype_return.stderr | 7 + .../fail/interface/wrong_argument_type.rs | 19 + .../fail/interface/wrong_argument_type.stderr | 7 + .../{wrong_item.rs => wrong_item_enum.rs} | 0 ...ong_item.stderr => wrong_item_enum.stderr} | 4 +- .../fail/interface/wrong_item_impl_block.rs | 11 + .../interface/wrong_item_impl_block.stderr | 7 + .../src/codegen/interface_attr.rs | 4166 ++++------------- .../juniper_tests/src/issue_407.rs | 14 - .../juniper_tests/src/issue_798.rs | 14 - .../juniper_tests/src/issue_922.rs | 22 - juniper/CHANGELOG.md | 7 + juniper/src/ast.rs | 1 + juniper/src/executor/mod.rs | 3 +- juniper/src/executor/owned_executor.rs | 2 + .../src/executor_tests/interfaces_unions.rs | 54 +- .../src/executor_tests/introspection/mod.rs | 7 +- juniper/src/macros/mod.rs | 3 + juniper/src/macros/reflect.rs | 985 ++++ juniper/src/parser/value.rs | 4 +- juniper/src/schema/meta.rs | 15 + juniper/src/tests/fixtures/starwars/schema.rs | 89 +- juniper/src/types/nullable.rs | 3 + juniper/src/types/scalars.rs | 13 + juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/common/field/mod.rs | 161 +- juniper_codegen/src/common/parse/mod.rs | 44 + juniper_codegen/src/common/scalar.rs | 16 - juniper_codegen/src/derive_scalar_value.rs | 19 + juniper_codegen/src/graphql_interface/attr.rs | 500 +- juniper_codegen/src/graphql_interface/mod.rs | 1787 +++---- juniper_codegen/src/graphql_object/mod.rs | 328 +- juniper_codegen/src/graphql_union/mod.rs | 45 + juniper_codegen/src/impl_scalar.rs | 19 + juniper_codegen/src/lib.rs | 225 +- juniper_codegen/src/util/mod.rs | 41 + juniper_graphql_ws/src/lib.rs | 2 + juniper_hyper/src/lib.rs | 24 +- juniper_iron/src/lib.rs | 18 +- juniper_subscriptions/src/lib.rs | 4 +- 82 files changed, 3493 insertions(+), 6053 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/method_default_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/method_default_impl.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_for_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr rename integration_tests/codegen_fail/fail/interface/{wrong_item.rs => wrong_item_enum.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{wrong_item.stderr => wrong_item_enum.stderr} (80%) create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr create mode 100644 juniper/src/macros/reflect.rs diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 414d2f29..aa9c543e 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -45,32 +45,14 @@ trait Character { struct Human { id: String, } -#[graphql_interface] // implementing requires macro attribute too, (°o°)! -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { id: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - -# fn main() { -let human = Human { id: "human-32".to_owned() }; -// Values type for interface has `From` implementations for all its implementers, -// so we don't need to bother with enum variant names. -let character: CharacterValue = human.into(); -assert_eq!(character.id(), "human-32"); -# } +# +# fn main() {} ``` Also, enum name can be specified explicitly, if desired. @@ -90,71 +72,11 @@ struct Human { id: String, home_planet: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` -### Trait object values - -If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, then it should be specified explicitly in the trait definition. - -Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood. - -> __NOTICE__: -> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it. - -```rust -# extern crate juniper; -# extern crate tokio; -use juniper::{graphql_interface, GraphQLObject}; - -// `dyn` argument accepts the name of type alias for the required trait object, -// and macro generates this alias automatically. -#[graphql_interface(dyn = DynCharacter, for = Human)] -trait Character { - async fn id(&self) -> &str; // async fields are supported natively -} - -#[derive(GraphQLObject)] -#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait, -struct Human { // so it may be specified explicitly when required - id: String, -} -#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too -impl Character for Human { - async fn id(&self) -> &str { - &self.id - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = DynCharacter<__S>)] -struct Droid { - id: String, -} -#[graphql_interface] -impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } -} - -# #[tokio::main] -# async fn main() { -let human = Human { id: "human-32".to_owned() }; -let character: Box = Box::new(human); -assert_eq!(character.id().await, "human-32"); -# } -``` - - ### Ignoring trait methods We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. @@ -176,12 +98,6 @@ trait Character { struct Human { id: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` @@ -278,24 +194,6 @@ struct Human { id: String, name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self, db: &Database) -> Option<&str> { - if db.humans.contains_key(&self.id) { - Some(&self.id) - } else { - None - } - } - - fn name(&self, db: &Database) -> Option<&str> { - if db.humans.contains_key(&self.id) { - Some(&self.name) - } else { - None - } - } -} # # fn main() {} ``` @@ -309,120 +207,51 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`] ```rust # extern crate juniper; -use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; #[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter trait Character { // If a field argument is named `executor`, it's automatically assumed // as an executor argument. - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ + fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; // Otherwise, you may mark it explicitly as an executor argument. - async fn name<'b>( + fn name<'b>( &'b self, #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; + ) -> &'b str; + + fn home_planet(&self) -> &str; } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, name: String, + home_planet: String, } -#[graphql_interface(scalar = S)] -impl Character for Human { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str +#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] +impl Human { + async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: Send + Sync, + S: ScalarValue, { executor.look_ahead().field_name() } - async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { + async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str { &self.name } + + fn home_planet<'c, S>(&'c self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'c str { + // Executor may not be present on the trait method ^^^^^^^^^^^^^^^^^^^^^^^^ + &self.home_planet + } } # # fn main() {} ``` -### Downcasting - -By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` macro argument is used). - -However, if some custom logic is needed to downcast a [GraphQL interface][1] implementer, you may specify either an external function or a trait method to do so. - -```rust -# extern crate juniper; -# use std::collections::HashMap; -use juniper::{graphql_interface, GraphQLObject}; - -struct Database { - droids: HashMap, -} -impl juniper::Context for Database {} - -#[graphql_interface(for = [Human, Droid], context = Database)] -#[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` function -trait Character { - fn id(&self) -> &str; - - #[graphql(downcast)] // makes method a downcast to `Human`, not a field - // NOTICE: The method signature may optionally contain `&Database` context argument. - fn as_human(&self) -> Option<&Human> { - None - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue, Context = Database)] -struct Human { - id: String, -} -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn as_human(&self) -> Option<&Self> { - Some(self) - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue, Context = Database)] -struct Droid { - id: String, -} -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - -// External downcast function doesn't have to be a method of a type. -// It's only a matter of the function signature to match the requirements. -fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> { - db.droids.get(ch.id()) -} -# -# fn main() {} -``` - -The attribute syntax `#[graphql_interface(on ImplementerType = resolver_fn)]` follows the [GraphQL syntax for downcasting interface implementer](https://spec.graphql.org/June2018/#example-5cc55). - - ## `ScalarValue` considerations @@ -445,12 +274,6 @@ struct Human { id: String, home_planet: String, } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} #[derive(GraphQLObject)] #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)] @@ -458,12 +281,6 @@ struct Droid { id: String, primary_function: String, } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 6da5ad56..d36c34d5 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -10,7 +10,7 @@ use actix_web::{ use juniper::{ graphql_object, graphql_subscription, graphql_value, - tests::fixtures::starwars::schema::{Character as _, Database, Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, FieldError, RootNode, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs new file mode 100644 index 00000000..c69f0f66 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr new file mode 100644 index 00000000..589ed21b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs index d7227c38..449d6748 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs @@ -1,19 +1,8 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self, __num: i32) -> &str { - "funA" - } + fn id(&self, __num: i32) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr index 0c47e615..0a6e79eb 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -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. - --> fail/interface/argument_double_underscored.rs:14:18 - | -14 | fn id(&self, __num: i32) -> &str { - | ^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Schema - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/argument_double_underscored.rs:4:18 + --> fail/interface/argument_double_underscored.rs:5:18 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/argument_double_underscored.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +5 | fn id(&self, __num: i32) -> &str; + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs index db6ad300..abca8275 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs @@ -1,19 +1,11 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] pub struct ObjA { test: String, } #[graphql_interface] -impl Character for ObjA { - fn id(&self, obj: Self) -> &str { - "funA" - } -} - -#[graphql_interface(for = ObjA)] trait Character { fn id(&self, obj: ObjA) -> &str; } diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr index 8b87b402..e20698e5 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr @@ -1,16 +1,16 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/argument_non_input_type.rs:8:1 + | +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 + --> fail/interface/argument_non_input_type.rs:8:1 | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` | note: required by a bound in `Registry::<'r, S>::arg` --> $WORKSPACE/juniper/src/executor/mod.rs @@ -18,11 +18,3 @@ note: required by a bound in `Registry::<'r, S>::arg` | T: GraphQLType + FromInputValue, | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg` = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs index 85156a3e..00bec5f4 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs @@ -1,23 +1,8 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn wrong( - &self, - #[graphql(default = [true, false, false])] - input: [bool; 2], - ) -> bool { - input[0] - } + fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr index 0b149ee5..b745a82d 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr @@ -1,11 +1,11 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:12:1 - | -12 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` - | - = help: the following implementations were found: - <[T; LANES] as From>> - <[bool; LANES] as From>> - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/argument_wrong_default_array.rs:3:1 + | +3 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | + = help: the following implementations were found: + <[T; LANES] as From>> + <[bool; LANES] as From>> + = 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) diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs deleted file mode 100644 index fdc9dfb0..00000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs +++ /dev/null @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr deleted file mode 100644 index 1aede59e..00000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr +++ /dev/null @@ -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 diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs deleted file mode 100644 index 29cfc225..00000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs +++ /dev/null @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr deleted file mode 100644 index ee9e4834..00000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr +++ /dev/null @@ -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 diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs deleted file mode 100644 index 2fd26790..00000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs +++ /dev/null @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr deleted file mode 100644 index 22aa7aba..00000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr +++ /dev/null @@ -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 diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs index 43115d32..10d00119 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs @@ -1,19 +1,8 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn __id(&self) -> &str { - "funA" - } + fn __id(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr index 2cdfafa6..6c683ed5 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -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. - --> fail/interface/field_double_underscored.rs:14:8 - | -14 | fn __id(&self) -> &str { - | ^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Schema - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/field_double_underscored.rs:4:18 + --> fail/interface/field_double_underscored.rs:5:8 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/field_double_underscored.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +5 | fn __id(&self) -> &str; + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs index a860ace3..ad802cf5 100644 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs @@ -1,24 +1,13 @@ -use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject}; +use juniper::{graphql_interface, GraphQLInputObject}; #[derive(GraphQLInputObject)] pub struct ObjB { id: i32, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} - #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self) -> ObjB { - ObjB { id: 34 } - } + fn id(&self) -> ObjB; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr index 1491540e..5bd7c8c9 100644 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr @@ -1,7 +1,7 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/field_non_output_return_type.rs:17:1 - | -17 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/field_non_output_return_type.rs:8:1 + | +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs index aba568b3..b69704d2 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs @@ -1,24 +1,11 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self) -> &str { - "funA" - } + fn id(&self) -> &str; #[graphql(name = "id")] - fn id2(&self) -> &str { - "funB" - } + fn id2(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr index 284a3e1d..143f85da 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr @@ -1,25 +1,12 @@ error: GraphQL interface must have a different name for each field - --> fail/interface/fields_duplicate.rs:13:1 - | -13 | / trait Character { -14 | | fn id(&self) -> &str { -15 | | "funA" -16 | | } -... | -21 | | } -22 | | } - | |_^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/fields_duplicate.rs:4:18 + --> fail/interface/fields_duplicate.rs:4:1 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/fields_duplicate.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +4 | / trait Character { +5 | | fn id(&self) -> &str; +6 | | +7 | | #[graphql(name = "id")] +8 | | fn id2(&self) -> &str; +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs deleted file mode 100644 index 0aba2508..00000000 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs +++ /dev/null @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr deleted file mode 100644 index 35c2dd0c..00000000 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr +++ /dev/null @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs index fbdc8f12..889fbd01 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs @@ -3,14 +3,7 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } + id: String, } #[graphql_interface(for = [ObjA, ObjA])] diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr index 40db273e..ce72fa2d 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr @@ -1,17 +1,11 @@ error: duplicated attribute argument found - --> $DIR/implementers_duplicate_pretty.rs:16:34 - | -16 | #[graphql_interface(for = [ObjA, ObjA])] - | ^^^^ + --> fail/interface/implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ error[E0412]: cannot find type `CharacterValue` in this scope - --> $DIR/implementers_duplicate_pretty.rs:4:18 + --> fail/interface/implementers_duplicate_pretty.rs:4:18 | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> $DIR/implementers_duplicate_pretty.rs:10:6 - | -10 | impl Character for ObjA { - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs index 1acb56c0..a0b7c7ed 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs @@ -3,18 +3,11 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] pub struct ObjA { - test: String, + id: String, } type ObjAlias = ObjA; -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } -} - #[graphql_interface(for = [ObjA, ObjAlias])] trait Character { fn id(&self) -> &str; diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr index d06dda36..61340f73 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr @@ -1,7 +1,7 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` - --> $DIR/implementers_duplicate_ugly.rs:18:1 +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/implementers_duplicate_ugly.rs:11:1 | -18 | #[graphql_interface(for = [ObjA, ObjAlias])] +11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here @@ -9,13 +9,13 @@ error[E0119]: conflicting implementations of trait `` for type `CharacterValue` - --> $DIR/implementers_duplicate_ugly.rs:18:1 +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/implementers_duplicate_ugly.rs:11:1 | -18 | #[graphql_interface(for = [ObjA, ObjAlias])] +11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here - | conflicting implementation for `CharacterValue` + | conflicting implementation for `CharacterValueEnum` | = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.rs b/integration_tests/codegen_fail/fail/interface/method_default_impl.rs new file mode 100644 index 00000000..e7ac7832 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.rs @@ -0,0 +1,10 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn id(&self) -> &str { + "default" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr new file mode 100644 index 00000000..887107a6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr @@ -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 diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.rs b/integration_tests/codegen_fail/fail/interface/missing_field.rs new file mode 100644 index 00000000..64155693 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field.rs @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/missing_field.stderr new file mode 100644 index 00000000..d0efa351 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs b/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs new file mode 100644 index 00000000..b71a38d6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr new file mode 100644 index 00000000..719cfd1a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs new file mode 100644 index 00000000..77a3043a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr new file mode 100644 index 00000000..84072d87 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs new file mode 100644 index 00000000..c35b23b3 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr new file mode 100644 index 00000000..767a5320 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs index b233119f..77343a97 100644 --- a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait __Character { - fn id(&self) -> &str { - "funA" - } + fn id(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/no_fields.rs index 11596035..1308cdde 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.rs +++ b/integration_tests/codegen_fail/fail/interface/no_fields.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character {} -fn main() {} \ No newline at end of file +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/no_fields.stderr index ddab0d4c..70ab5745 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/no_fields.stderr @@ -1,19 +1,7 @@ error: GraphQL interface must have at least one field - --> fail/interface/no_fields.rs:13:1 - | -13 | trait Character {} - | ^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/no_fields.rs:4:18 + --> fail/interface/no_fields.rs:4:1 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/no_fields.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +4 | trait Character {} + | ^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs new file mode 100644 index 00000000..8eab508d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr new file mode 100644 index 00000000..ca48f846 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs new file mode 100644 index 00000000..388aea1e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs @@ -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() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr new file mode 100644 index 00000000..204bd123 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr @@ -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) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.rs b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_item.rs rename to integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/interface/wrong_item.stderr rename to integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr index a86b15c2..9c2607dd 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr @@ -1,5 +1,5 @@ -error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only - --> $DIR/wrong_item.rs:8:1 +error: #[graphql_interface] attribute is applicable to trait definitions only + --> fail/interface/wrong_item_enum.rs:8:1 | 8 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs new file mode 100644 index 00000000..61e14241 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs @@ -0,0 +1,11 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +impl ObjA {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr new file mode 100644 index 00000000..712206c0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr @@ -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) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d8869a04..7750a150 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3,7 +3,7 @@ use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, - GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -39,86 +39,61 @@ mod no_implers { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero)] - trait Hero { - fn info(&self) -> &str; - } - struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { unimplemented!() } - - fn hero(&self) -> Box> { - unimplemented!() - } } #[tokio::test] async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } #[tokio::test] async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } @@ -130,49 +105,25 @@ mod trivial { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { + #[graphql_object(impl = CharacterValue)] + impl Droid { + async fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -183,7 +134,7 @@ mod trivial { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -199,24 +150,10 @@ mod trivial { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -231,14 +168,17 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -253,58 +193,17 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -326,73 +225,44 @@ mod trivial { } #[tokio::test] - async fn dyn_resolves_info_field() { + async fn is_graphql_interface() { const DOC: &str = r#"{ - hero { - info + __type(name: "Character") { + kind } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_graphql_interface() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } #[tokio::test] async fn registers_all_implementers() { + const DOC: &str = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - possibleTypes {{ - kind - name - }} - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] @@ -417,7 +287,6 @@ mod trivial { Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, - {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -427,45 +296,34 @@ mod trivial { #[tokio::test] async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } @@ -484,25 +342,20 @@ mod explicit_alias { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterEnum)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterEnum)] + impl Droid { fn id(&self) -> &str { &self.id } + + fn primary_function(&self) -> &str { + &self.primary_function + } } #[derive(Clone, Copy)] @@ -529,6 +382,182 @@ mod explicit_alias { } } + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial_async { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + async fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + #[tokio::test] async fn resolves_human() { const DOC: &str = r#"{ @@ -611,6 +640,60 @@ mod explicit_alias { ); } + #[tokio::test] + async fn registers_all_implementers() { + const DOC: &str = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); + + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + #[tokio::test] async fn uses_trait_name() { const DOC: &str = r#"{ @@ -644,611 +727,6 @@ mod explicit_alias { } } -mod trivial_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - async fn id(&self) -> &str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } - } - - #[tokio::test] - async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - possibleTypes {{ - kind - name - }} - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } - } - - #[tokio::test] - async fn registers_itself_in_implementers() { - let schema = schema(QueryRoot::Human); - - for object in &["Human", "Droid"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - interfaces {{ - kind - name - }} - }} - }}"#, - object, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"interfaces": [ - {"kind": "INTERFACE", "name": "Character"}, - {"kind": "INTERFACE", "name": "Hero"}, - ]}}), - vec![], - )), - ); - } - } - - #[tokio::test] - async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } - } - - #[tokio::test] - async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } - } -} - -mod explicit_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - - async fn info(&self) -> String { - "None available".to_owned() - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn id(&self) -> &str { - "Non-identified" - } - - fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - async fn info(&self) -> String { - format!("Home planet is {}", &self.home_planet) - } - } - - #[graphql_interface(async, dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface(async)] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id(&self) -> &str { - &self.id - } - - fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_fields() { - const DOC: &str = r#"{ - character { - id - info - } - }"#; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-32", "Home planet is earth"), - (QueryRoot::Droid, "droid-99", "None available"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_fields() { - const DOC: &str = r#"{ - hero { - id - info - } - }"#; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "Non-identified", "earth"), - (QueryRoot::Droid, "droid-99", "run"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } -} - mod fallible_field { use super::*; @@ -1265,50 +743,26 @@ mod fallible_field { fn id(&self) -> Result<&str, CustomError>; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> Result<&str, CustomError>; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> Result<&str, CustomError> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> Result<&str, CustomError> { - Ok(&self.home_planet) - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> Result<&str, CustomError> { - Ok(&self.id) + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> Result { + Ok(self.id.clone()) } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> Result<&str, CustomError> { - Ok(&self.primary_function) + fn primary_function(&self) -> &str { + &self.primary_function } } @@ -1318,7 +772,7 @@ mod fallible_field { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -1334,24 +788,10 @@ mod fallible_field { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -1373,7 +813,7 @@ mod fallible_field { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1395,51 +835,7 @@ mod fallible_field { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -1461,65 +857,39 @@ mod fallible_field { } #[tokio::test] - async fn dyn_resolves_info_field() { + async fn has_correct_graphql_type() { const DOC: &str = r#"{ - hero { - info + __type(name: "Character") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn has_correct_graphql_type() { let schema = schema(QueryRoot::Human); - for (interface, field) in &[("Character", "id"), ("Hero", "info")] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - kind - fields {{ - name - type {{ - kind - ofType {{ - name - }} - }} - }} - }} - }}"#, - interface, - ); - - let expected_name: &str = *interface; - let expected_field_name: &str = *field; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": { - "name": expected_name, - "kind": "INTERFACE", - "fields": [{ - "name": expected_field_name, - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, - }] - }}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Character", + "kind": "INTERFACE", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }], + }}), + vec![], + )), + ); } } @@ -1531,49 +901,25 @@ mod generic { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -1584,7 +930,7 @@ mod generic { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -1600,24 +946,10 @@ mod generic { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -1639,7 +971,7 @@ mod generic { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1661,51 +993,7 @@ mod generic { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -1726,149 +1014,11 @@ mod generic { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - #[tokio::test] async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } - } -} - -mod generic_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - async fn id(&self) -> &str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, DynHero])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } + __type(name: "Character") { + name } }"#; @@ -1876,386 +1026,9 @@ mod generic_async { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } - } -} - -mod generic_lifetime_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character<'me, A> { - async fn id<'a>(&'a self) -> &'a str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero<'me, A> { - async fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl<'me, A> Character<'me, A> for Human { - async fn id<'a>(&'a self) -> &'a str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl<'me, A> Hero<'me, A> for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl<'me, A> Character<'me, A> for Droid { - async fn id<'a>(&'a self) -> &'a str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl<'me, A> Hero<'me, A> for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue<'_, ()> { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } - } } mod argument { @@ -2265,26 +1038,25 @@ mod argument { trait Character { fn id_wide(&self, is_number: bool) -> &str; - async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; + fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } - #[graphql_interface(dyn = DynHero, for = Human)] - trait Hero { - fn info_wide(&self, is_planet: bool) -> &str; - - async fn info_wide2(&self, is_planet: bool, r#async: Option) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id_wide(&self, is_number: bool) -> &str { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + async fn id_wide(&self, is_number: bool) -> &str { if is_number { &self.id } else { @@ -2292,7 +1064,7 @@ mod argument { } } - async fn id_wide2(&self, is_number: bool, _: Option) -> &str { + async fn id_wide2(&self, is_number: bool, _async: Option) -> &str { if is_number { &self.id } else { @@ -2301,28 +1073,9 @@ mod argument { } } - #[graphql_interface(dyn)] - impl Hero for Human { - fn info_wide(&self, is_planet: bool) -> &str { - if is_planet { - &self.home_planet - } else { - &self.id - } - } - - async fn info_wide2(&self, is_planet: bool, _: Option) -> &str { - if is_planet { - &self.home_planet - } else { - &self.id - } - } - } - struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { Human { @@ -2331,17 +1084,10 @@ mod argument { } .into() } - - fn hero(&self) -> Box> { - Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }) - } } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { let schema = schema(QueryRoot); for (input, expected) in &[ @@ -2369,141 +1115,91 @@ mod argument { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - let schema = schema(QueryRoot); - - for (input, expected) in &[ - ( - "{ hero { infoWide(isPlanet: true), infoWide2(isPlanet: true) } }", - "earth", - ), - ( - "{ hero { infoWide(isPlanet: false), infoWide2(isPlanet: false, async: 3) } }", - "human-32", - ), - ] { - let expected: &str = *expected; - - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": { - "infoWide": expected, - "infoWide2": expected, - }}), - vec![], - )), - ); - } - } - #[tokio::test] async fn camelcases_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + let schema = schema(QueryRoot); - for (interface, field, arg) in &[ - ("Character", "idWide", "isNumber"), - ("Hero", "infoWide", "isPlanet"), - ] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - args {{ - name - }} - }} - }} - }}"#, - interface, - ); - - let expected_field_name: &str = *field; - let expected_field_name2: &str = &format!("{}2", field); - let expected_arg_name: &str = *arg; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{ - "name": expected_field_name, - "args": [ - {"name": expected_arg_name}, - ], - }, { - "name": expected_field_name2, - "args": [ - {"name": expected_arg_name}, - {"name": "async"}, - ], - }]}}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{ + "name": "idWide", + "args": [ + {"name": "isNumber"}, + ], + }, { + "name": "idWide2", + "args": [ + {"name": "isNumber"}, + {"name": "async"}, + ], + }]}}), + vec![], + )), + ); } #[tokio::test] async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + args { + description + } + } + } + }"#; + let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - args {{ - description - }} - }} - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"args": [{"description": null}]}, - {"args": [{"description": null}, {"description": null}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"description": null}]}, + {"args": [{"description": null}, {"description": null}]}, + ]}}), + vec![], + )), + ); } #[tokio::test] async fn has_no_defaults() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + args { + defaultValue + } + } + } + }"#; + let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - args {{ - defaultValue - }} - }} - }} - }}"#, - interface, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"args": [{"defaultValue": null}]}, - {"args": [{"defaultValue": null}, {"defaultValue": null}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"defaultValue": null}]}, + {"args": [{"defaultValue": null}, {"defaultValue": null}]}, + ]}}), + vec![], + )), + ); } } @@ -2517,27 +1213,24 @@ mod default_argument { #[graphql_interface(for = Human)] trait Character { - async fn id( + fn id( &self, #[graphql(default)] first: String, #[graphql(default = "second".to_string())] second: String, #[graphql(default = "t")] third: String, ) -> String; - fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { + fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32; + } + + struct Human; + + #[graphql_object(impl = CharacterValue)] + impl Human { + async fn info(&self, coord: Point) -> i32 { coord.x } - } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - info: i32, - } - - #[graphql_interface] - impl Character for Human { async fn id(&self, first: String, second: String, third: String) -> String { format!("{}|{}&{}", first, second, third) } @@ -2548,11 +1241,7 @@ mod default_argument { #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { - Human { - id: "human-32".to_string(), - info: 0, - } - .into() + Human.into() } } @@ -2668,13 +1357,6 @@ mod description_from_doc_comment { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - struct QueryRoot; #[graphql_object] @@ -2722,28 +1404,34 @@ mod deprecation_from_attr { fn id(&self) -> &str; #[deprecated] - fn a(&self) -> &str { - "a" - } + fn a(&self) -> &str; #[deprecated(note = "Use `id`.")] - fn b(&self) -> &str { - "b" - } + fn b(&self) -> &str; } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { fn id(&self) -> &str { &self.id } + + fn human_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> &'static str { + "a" + } + + fn b() -> String { + "b".to_owned() + } } struct QueryRoot; @@ -2858,27 +1546,33 @@ mod explicit_name_description_and_deprecation { #[graphql(deprecated)] #[deprecated(note = "Should be omitted.")] - fn a(&self) -> &str { - "a" - } + fn a(&self) -> &str; - fn b(&self) -> &str { - "b" - } + fn b(&self) -> &str; } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self, _: Option) -> &str { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn my_id(&self, #[graphql(name = "myName")] _: Option) -> &str { &self.id } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> String { + "a".to_owned() + } + + fn b() -> &'static str { + "b" + } } struct QueryRoot; @@ -3031,17 +1725,11 @@ mod renamed_all_fields_and_args { #[graphql_interface(rename_all = "none", for = Human)] trait Character { - fn id(&self) -> &str { - "human-32" - } + fn id(&self) -> &str; - async fn home_planet(&self, planet_name: String) -> String { - planet_name - } + fn home_planet(&self, planet_name: String) -> String; - async fn r#async_info(&self, r#my_num: i32) -> i32 { - r#my_num - } + fn r#async_info(&self, r#my_num: i32) -> i32; } struct Human; @@ -3052,18 +1740,15 @@ mod renamed_all_fields_and_args { "human-32" } - fn home_planet(planet_name: String) -> String { + async fn home_planet(planet_name: String) -> String { planet_name } - fn r#async_info(r#my_num: i32) -> i32 { + async fn r#async_info(r#my_num: i32) -> i32 { r#my_num } } - #[graphql_interface] - impl Character for Human {} - struct QueryRoot; #[graphql_object] @@ -3136,50 +1821,25 @@ mod explicit_scalar { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(scalar = DefaultScalarValue)] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = DefaultScalarValue)] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = DefaultScalarValue)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = DefaultScalarValue)] - impl Character for Droid { + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn, scalar = DefaultScalarValue)] - impl Hero for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -3206,24 +1866,10 @@ mod explicit_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3245,7 +1891,7 @@ mod explicit_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3267,51 +1913,7 @@ mod explicit_scalar { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -3331,25 +1933,6 @@ mod explicit_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod custom_scalar { @@ -3359,53 +1942,28 @@ mod custom_scalar { #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] trait Character { - async fn id(&self) -> &str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(scalar = MyScalarValue)] - trait Hero { - async fn info(&self) -> &str; + fn id(&self) -> &str; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = MyScalarValue)] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = MyScalarValue)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = MyScalarValue)] - impl Character for Droid { - async fn id(&self) -> &str { + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn, scalar = MyScalarValue)] - impl Hero for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -3432,24 +1990,10 @@ mod custom_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3471,7 +2015,7 @@ mod custom_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3493,51 +2037,7 @@ mod custom_scalar { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema_with_scalar::(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema_with_scalar::(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -3557,25 +2057,6 @@ mod custom_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema_with_scalar::(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod explicit_generic_scalar { @@ -3586,50 +2067,26 @@ mod explicit_generic_scalar { fn id(&self) -> FieldResult<&str, S>; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)] - trait Hero { - async fn info(&self) -> FieldResult<&str, S>; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S)] - impl Character for Human { - fn id(&self) -> FieldResult<&str, S> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Human { - async fn info(&self) -> FieldResult<&str, S> { - Ok(&self.home_planet) - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = S)] - impl Character for Droid { - fn id(&self) -> FieldResult<&str, S> { - Ok(&self.id) + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id } - } - #[graphql_interface(dyn, scalar = S)] - impl Hero for Droid { - async fn info(&self) -> FieldResult<&str, S> { - Ok(&self.primary_function) + fn primary_function(&self) -> &str { + &self.primary_function } } @@ -3639,7 +2096,7 @@ mod explicit_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(scalar = S: ScalarValue)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3655,24 +2112,10 @@ mod explicit_generic_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3694,7 +2137,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3716,51 +2159,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -3780,25 +2179,6 @@ mod explicit_generic_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod bounded_generic_scalar { @@ -3809,49 +2189,25 @@ mod bounded_generic_scalar { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S: ScalarValue + Clone)] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = S: ScalarValue + Clone)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S: ScalarValue + Clone)] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = S: ScalarValue + Clone)] - impl Character for Droid { + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] - impl Hero for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -3862,7 +2218,7 @@ mod bounded_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Clone + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3878,24 +2234,10 @@ mod bounded_generic_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3917,7 +2259,7 @@ mod bounded_generic_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3939,51 +2281,7 @@ mod bounded_generic_scalar { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -4003,25 +2301,6 @@ mod bounded_generic_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod explicit_custom_context { @@ -4033,93 +2312,57 @@ mod explicit_custom_context { #[graphql_interface(for = [Human, Droid], context = CustomContext)] trait Character { - async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; + fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - async fn info<'b>(&'b self, ctx: &()) -> &'b str; + fn info<'b>(&'b self, ctx: &()) -> &'b str; fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(context = CustomContext)] - trait Hero { - async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - - async fn info<'b>(&'b self, ctx: &()) -> &'b str; - - fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Human { + async fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { &self.id } - async fn info<'b>(&'b self, _: &()) -> &'b str { + async fn home_planet(&self) -> &str { &self.home_planet } - fn more(&self, _: &CustomContext) -> &'static str { + fn info<'b>(&'b self, _ctx: &()) -> &'b str { + &self.home_planet + } + + fn more(&self, #[graphql(context)] _: &CustomContext) -> &'static str { "human" } } - #[graphql_interface(dyn)] - impl Hero for Human { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { - &self.id - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { - &self.home_planet - } - - fn more(&self, _: &CustomContext) -> &'static str { - "human" - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Droid { + async fn id<'a>(&'a self) -> &'a str { &self.id } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn primary_function(&self) -> &str { &self.primary_function } - fn more(&self, _: &CustomContext) -> &'static str { - "droid" - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { - &self.id - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { + async fn info<'b>(&'b self) -> &'b str { &self.primary_function } - fn more(&self, _: &CustomContext) -> &'static str { + fn more(&self) -> &'static str { "droid" } } @@ -4130,7 +2373,7 @@ mod explicit_custom_context { Droid, } - #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(context = CustomContext)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -4146,24 +2389,10 @@ mod explicit_custom_context { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -4185,7 +2414,7 @@ mod explicit_custom_context { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -4206,87 +2435,36 @@ mod explicit_custom_context { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - more - }} - }}"#, - interface, - ); - - let expected_interface: &str = *interface; - - for (root, expected_id, expected_info, expexted_more) in &[ - (QueryRoot::Human, "human-32", "earth", "human"), - (QueryRoot::Droid, "droid-99", "run", "droid"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - let expexted_more: &str = *expexted_more; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({expected_interface: { - "id": expected_id, - "info": expected_info, - "more": expexted_more, - }}), - vec![], - )), - ); + const DOC: &str = r#"{ + character { + id + info + more } + }"#; + + for (root, expected_id, expected_info, expexted_more) in &[ + (QueryRoot::Human, "human-32", "earth", "human"), + (QueryRoot::Droid, "droid-99", "run", "droid"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + let expexted_more: &str = *expexted_more; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + "more": expexted_more, + }}), + vec![], + )), + ); } } } @@ -4302,70 +2480,43 @@ mod inferred_custom_context_from_field { trait Character { fn id<'a>(&self, context: &'a CustomContext) -> &'a str; - fn info<'b>(&'b self, context: &()) -> &'b str; + fn info(&self, context: &()) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn id<'a>(&self, context: &'a CustomContext) -> &'a str; - - async fn info<'b>(&'b self, context: &()) -> &'b str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { - id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Human { fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } - fn info<'b>(&'b self, _: &()) -> &'b str { + fn home_planet(&self) -> &str { + &self.home_planet + } + + fn info<'b>(&'b self, _context: &()) -> &'b str { &self.home_planet } } - #[graphql_interface(dyn)] - impl Hero for Human { - async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { - &ctx.0 - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { - id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Droid { fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } - fn info<'b>(&'b self, _: &()) -> &'b str { + fn primary_function(&self) -> &str { &self.primary_function } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { - &ctx.0 - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self) -> &'b str { &self.primary_function } } @@ -4376,40 +2527,24 @@ mod inferred_custom_context_from_field { Droid, } - #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(context = CustomContext)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), home_planet: "earth".to_string(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), primary_function: "run".to_string(), } .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -4425,14 +2560,14 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "in-ctx", "homePlanet": "earth"}}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -4448,53 +2583,7 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let ctx = CustomContext("in-ctx".into()); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let ctx = CustomContext("in-droid".into()); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "in-droid", "primaryFunction": "run"}}), vec![], )), ); @@ -4502,305 +2591,31 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - }} - }}"#, - interface, - ); - - let expected_interface: &str = *interface; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-ctx", "earth"), - (QueryRoot::Droid, "droid-ctx", "run"), - ] { - let schema = schema(*root); - let ctx = CustomContext(expected_id.to_string()); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({expected_interface: { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } - } -} - -mod inferred_custom_context_from_downcast { - use super::*; - - struct Database { - droid: Option, - } - - impl juniper::Context for Database {} - - #[graphql_interface(for = [Human, Droid])] - trait Character { - #[graphql(downcast)] - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human>; - - async fn id(&self) -> &str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - #[graphql(downcast)] - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid>; - - async fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { - Some(self) - } - - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn as_droid<'db>(&self, _: &'db Database) -> Option<&'db Droid> { - None - } - - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { - None - } - - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { - db.droid.as_ref() - } - - async fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - droid: Some(Droid { - id: "droid-88".to_string(), - primary_function: "sit".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - droid: Some(Droid { - id: "droid-88".to_string(), - primary_function: "sit".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-88", "primaryFunction": "sit"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - let db = Database { droid: None }; - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { info } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - let db = Database { droid: None }; + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "human-ctx", "earth"), + (QueryRoot::Droid, "droid-ctx", "run"), + ] { + let schema = schema(*root); + let ctx = CustomContext(expected_id.to_string()); + let expected_id: &str = *expected_id; let expected_info: &str = *expected_info; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + }}), + vec![], + )), ); } } @@ -4813,90 +2628,53 @@ mod executor { #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync, - { - executor.look_ahead().field_name() - } + fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; - async fn info<'b>( + fn info<'b>( &'b self, arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; + ) -> &'b str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)] - trait Hero { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync, - { - executor.look_ahead().field_name() - } - - async fn info<'b>( - &'b self, - arg: Option, - #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Human { - id: String, home_planet: String, } - #[graphql_interface(scalar = S)] - impl Character for Human { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { + #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] + impl Human { + async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() + } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + async fn info<'b>(&'b self, _arg: Option) -> &'b str { &self.home_planet } } - #[graphql_interface(dyn, scalar = S)] - impl Hero for Human { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Droid { - id: String, primary_function: String, } - #[graphql_interface(scalar = S)] - impl Character for Droid { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() + } + + fn primary_function(&self) -> &str { &self.primary_function } - } - #[graphql_interface(dyn, scalar = S)] - impl Hero for Droid { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { + async fn info<'b, S: ScalarValue>( + &'b self, + _arg: Option, + _executor: &Executor<'_, '_, (), S>, + ) -> &'b str { &self.primary_function } } @@ -4912,35 +2690,19 @@ mod executor { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), home_planet: "earth".to_string(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), primary_function: "run".to_string(), } .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -4955,14 +2717,14 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "humanId", "homePlanet": "earth"}}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -4977,51 +2739,7 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "droidId", "primaryFunction": "run"}}), vec![], )), ); @@ -5029,64 +2747,52 @@ mod executor { #[tokio::test] async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - }} - }}"#, - interface, - ); - - let expected_interface: &str = *interface; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({expected_interface: {"id": "id", "info": expected_info}}), - vec![], - )), - ); + const DOC: &str = r#"{ + character { + id + info } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"id": "id", "info": expected_info}}), + vec![], + )), + ); } } #[tokio::test] async fn not_arg() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - args {{ - name - }} - }} - }} - }}"#, - interface, - ); + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; - let schema = schema(QueryRoot::Human); + let schema = schema(QueryRoot::Human); - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"name": "id", "args": []}, - {"name": "info", "args": [{"name": "arg"}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "info", "args": [{"name": "arg"}]}, + ]}}), + vec![], + )), + ); } } @@ -5113,13 +2819,6 @@ mod ignored_method { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - struct QueryRoot; #[graphql_object] @@ -5193,86 +2892,44 @@ mod ignored_method { } } -mod downcast_method { +mod field_return_subtyping { use super::*; #[graphql_interface(for = [Human, Droid])] trait Character { - fn id(&self) -> &str; - - #[graphql(downcast)] - fn as_human(&self) -> Option<&Human> { - None - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - - #[graphql(downcast)] - fn as_droid(&self) -> Option<&Droid> { - None - } + fn id(&self) -> Option; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn as_human(&self) -> Option<&Human> { - Some(self) - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } - - fn as_droid(&self) -> Option<&Droid> { - Some(self) - } } - #[derive(Clone)] + #[derive(Clone, Copy)] enum QueryRoot { Human, Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -5288,24 +2945,10 @@ mod downcast_method { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -5327,7 +2970,7 @@ mod downcast_method { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -5349,12 +2992,122 @@ mod downcast_method { } #[tokio::test] - async fn dyn_resolves_human() { + async fn resolves_id_field() { const DOC: &str = r#"{ - hero { + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod field_return_union_subtyping { + use super::*; + + #[derive(GraphQLObject)] + struct Strength { + value: i32, + } + + #[derive(GraphQLObject)] + struct Knowledge { + value: i32, + } + + #[allow(dead_code)] + #[derive(GraphQLUnion)] + enum KeyFeature { + Strength(Strength), + Knowledge(Knowledge), + } + + #[graphql_interface(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + + fn key_feature(&self) -> KeyFeature; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + key_feature: Knowledge, + } + + struct Droid { + id: String, + primary_function: String, + strength: i32, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + + fn key_feature(&self) -> Strength { + Strength { + value: self.strength, + } + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + key_feature: Knowledge { value: 10 }, + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + strength: 42, + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { ... on Human { humanId: id homePlanet + keyFeature { + value + } } } }"#; @@ -5364,19 +3117,26 @@ mod downcast_method { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + "keyFeature": {"value": 10}, + }}), vec![], )), ); } #[tokio::test] - async fn dyn_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ - hero { + character { ... on Droid { droidId: id primaryFunction + keyFeature { + value + } } } }"#; @@ -5386,67 +3146,47 @@ mod downcast_method { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + "keyFeature": {"value": 42}, + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_fields() { const DOC: &str = r#"{ character { id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); - let expected_info: &str = *expected_info; + let expected_id: &str = *expected_id; + let expected_val = *expected_val; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_not_field() { - let schema = schema(QueryRoot::Human); - - for (doc, field) in &[ - (r#"{__type(name: "Character") { fields { name } } }"#, "id"), - (r#"{__type(name: "Hero") { fields { name } } }"#, "info"), - ] { - let expected_field: &str = *field; - - assert_eq!( - execute(*doc, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"fields": [{"name": expected_field}]}}), + graphql_value!({"character": { + "id": expected_id, + "keyFeature": {"value": expected_val}, + }}), vec![], )), ); @@ -5454,90 +3194,47 @@ mod downcast_method { } } -mod external_downcast { +mod nullable_argument_subtyping { use super::*; - struct Database { - human: Option, - droid: Option, - } - - impl juniper::Context for Database {} - #[graphql_interface(for = [Human, Droid])] - #[graphql_interface(context = Database, on Human = CharacterValue::as_human)] trait Character { - fn id(&self) -> &str; - } - - impl CharacterValue { - fn as_human<'db>(&self, db: &'db Database) -> Option<&'db Human> { - db.human.as_ref() - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(context = Database)] - #[graphql_interface(on Droid = DynHero::as_droid)] - trait Hero { - fn info(&self) -> &str; - } - - impl<'a, S: ScalarValue> DynHero<'a, S> { - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { - db.droid.as_ref() - } + fn id(&self) -> Option; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } - #[derive(Clone)] + #[derive(Clone, Copy)] enum QueryRoot { Human, Droid, } - #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -5553,24 +3250,10 @@ mod external_downcast { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -5581,42 +3264,31 @@ mod external_downcast { }"#; let schema = schema(QueryRoot::Human); - let db = Database { - human: Some(Human { - id: "human-64".to_string(), - home_planet: "mars".to_string(), - }), - droid: None, - }; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-64", "homePlanet": "mars"}}), + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { - droidId: id + droidId: id(isPresent: true) primaryFunction } } }"#; let schema = schema(QueryRoot::Droid); - let db = Database { - human: None, - droid: None, - }; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), vec![], @@ -5625,114 +3297,24 @@ mod external_downcast { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { - human: None, - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - human: None, - droid: Some(Droid { - id: "droid-01".to_string(), - primary_function: "swim".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-01", "primaryFunction": "swim"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id } }"#; - let db = Database { - human: Some(Human { - id: "human-64".to_string(), - home_planet: "mars".to_string(), - }), - droid: None, - }; - for (root, expected_id) in &[ (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), + (QueryRoot::Droid, "missing"), ] { - let schema = schema(root.clone()); + let schema = schema(*root); let expected_id: &str = *expected_id; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - let db = Database { - human: None, - droid: Some(Droid { - id: "droid-01".to_string(), - primary_function: "swim".to_string(), - }), - }; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/integration_tests/juniper_tests/src/issue_407.rs index 51bf4211..245398c3 100644 --- a/integration_tests/juniper_tests/src/issue_407.rs +++ b/integration_tests/juniper_tests/src/issue_407.rs @@ -20,13 +20,6 @@ struct Human { name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -34,13 +27,6 @@ struct Droid { serial_number: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - #[graphql_object] impl Query { fn characters() -> Vec { diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/integration_tests/juniper_tests/src/issue_798.rs index fc23bbd6..fed322fc 100644 --- a/integration_tests/juniper_tests/src/issue_798.rs +++ b/integration_tests/juniper_tests/src/issue_798.rs @@ -18,13 +18,6 @@ struct Human { home_planet: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -32,13 +25,6 @@ struct Droid { primary_function: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLUnion)] enum FieldResult { Human(Human), diff --git a/integration_tests/juniper_tests/src/issue_922.rs b/integration_tests/juniper_tests/src/issue_922.rs index 60418de4..fd28b45f 100644 --- a/integration_tests/juniper_tests/src/issue_922.rs +++ b/integration_tests/juniper_tests/src/issue_922.rs @@ -38,17 +38,6 @@ struct Human { pub name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> i32 { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -56,17 +45,6 @@ struct Droid { pub name: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> i32 { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } -} - type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 836472fb..41141339 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -10,6 +10,13 @@ - Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) +- Redesign `#[graphql_interface]` macro: ([#1009](https://github.com/graphql-rust/juniper/pull/1009)) + - Remove support for `#[graphql_interface(dyn)]` (interface values as trait objects). + - Remove support for `downcast` (custom resolution into implementer types). + - Remove support for `async` trait methods (not required anymore). + - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). + - Forbid default impls on non-ignored trait methods. + - Support coercion of additional nullable arguments and return sub-typing on implementer. ## Features diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index f7805eeb..ea2c0fec 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -296,6 +296,7 @@ impl InputValue { } /// Resolve all variables to their values. + #[must_use] pub fn into_const(self, vars: &Variables) -> Self where S: Clone, diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 553af48e..9b9de949 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -701,7 +701,7 @@ where FieldPath::Root(_) => unreachable!(), }; self.parent_selection_set - .map(|p| { + .and_then(|p| { // Search the parent's fields to find this field within the set let found_field = p.iter().find(|&x| { match *x { @@ -721,7 +721,6 @@ where None } }) - .flatten() .unwrap_or_else(|| { // We didn't find a field in the parent's selection matching // this field, which means we're inside a FragmentSpread diff --git a/juniper/src/executor/owned_executor.rs b/juniper/src/executor/owned_executor.rs index 28b03c3a..cce1a208 100644 --- a/juniper/src/executor/owned_executor.rs +++ b/juniper/src/executor/owned_executor.rs @@ -49,6 +49,7 @@ where S: Clone, { #[doc(hidden)] + #[must_use] pub fn type_sub_executor( &self, type_name: Option<&str>, @@ -76,6 +77,7 @@ where } #[doc(hidden)] + #[must_use] pub fn field_sub_executor( &self, field_alias: &'a str, diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 02787ce5..1044baaf 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -3,72 +3,28 @@ mod interface { graphql_interface, graphql_object, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, + GraphQLObject, }; #[graphql_interface(for = [Cat, Dog])] trait Pet { fn name(&self) -> &str; - - #[graphql(downcast)] - fn as_dog(&self) -> Option<&Dog> { - None - } - #[graphql(downcast)] - fn as_cat(&self) -> Option<&Cat> { - None - } } + #[derive(GraphQLObject)] + #[graphql(impl = PetValue)] struct Dog { name: String, woofs: bool, } - #[graphql_interface] - impl Pet for Dog { - fn name(&self) -> &str { - &self.name - } - fn as_dog(&self) -> Option<&Dog> { - Some(self) - } - } - - #[graphql_object(impl = PetValue)] - impl Dog { - fn name(&self) -> &str { - &self.name - } - fn woofs(&self) -> bool { - self.woofs - } - } - + #[derive(GraphQLObject)] + #[graphql(impl = PetValue)] struct Cat { name: String, meows: bool, } - #[graphql_interface] - impl Pet for Cat { - fn name(&self) -> &str { - &self.name - } - fn as_cat(&self) -> Option<&Cat> { - Some(self) - } - } - - #[graphql_object(impl = PetValue)] - impl Cat { - fn name(&self) -> &str { - &self.name - } - fn meows(&self) -> bool { - self.meows - } - } - struct Schema { pets: Vec, } diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index a14a0f29..a5ec22cb 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -43,9 +43,7 @@ impl GraphQLScalar for Scalar { #[graphql_interface(name = "SampleInterface", for = Root)] trait Interface { /// A sample field in the interface - fn sample_enum(&self) -> Sample { - Sample::One - } + fn sample_enum(&self) -> Sample; } struct Root; @@ -66,9 +64,6 @@ impl Root { } } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Interface for Root {} - #[tokio::test] async fn test_execution() { let doc = r#" diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index cecfee38..4bbcfb5f 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -2,6 +2,9 @@ #[doc(hidden)] pub mod helper; +#[doc(hidden)] +#[macro_use] +pub mod reflect; #[macro_use] mod graphql_input_value; diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs new file mode 100644 index 00000000..64b04c2c --- /dev/null +++ b/juniper/src/macros/reflect.rs @@ -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 { + /// [`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 + ?Sized> BaseType for &'a T { + const NAME: Type = T::NAME; +} + +impl<'ctx, S, T> BaseType for (&'ctx T::Context, T) +where + S: ScalarValue, + T: BaseType + GraphQLValue, +{ + const NAME: Type = T::NAME; +} + +impl> BaseType for Option { + const NAME: Type = T::NAME; +} + +impl> BaseType for Nullable { + const NAME: Type = T::NAME; +} + +impl, E> BaseType for Result { + const NAME: Type = T::NAME; +} + +impl> BaseType for Vec { + const NAME: Type = T::NAME; +} + +impl> BaseType for [T] { + const NAME: Type = T::NAME; +} + +impl, const N: usize> BaseType for [T; N] { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Box { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Arc { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Rc { + 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 { + /// Sub-[`Types`] of the [GraphQL object][1]. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Objects + const NAMES: Types; +} + +impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { + const NAMES: Types = T::NAMES; +} + +impl<'ctx, S, T> BaseSubTypes for (&'ctx T::Context, T) +where + S: ScalarValue, + T: BaseSubTypes + GraphQLValue, +{ + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Option { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Nullable { + const NAMES: Types = T::NAMES; +} + +impl, E> BaseSubTypes for Result { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Vec { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for [T] { + const NAMES: Types = T::NAMES; +} + +impl, const N: usize> BaseSubTypes for [T; N] { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Box { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Arc { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Rc { + 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!( as WrappedType>::VALUE, 12); +/// assert_eq!( as WrappedType>::VALUE, 13); +/// assert_eq!(> as WrappedType>::VALUE, 123); +/// assert_eq!(> as WrappedType>::VALUE, 132); +/// assert_eq!(>> as WrappedType>::VALUE, 1232); +/// +/// const TYPE_STRING: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STRING: WrappedValue = >> as WrappedType>::VALUE; +/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// +/// const TYPE_STR: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::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 { + /// [`WrappedValue`] of this type. + const VALUE: WrappedValue; +} + +impl<'ctx, S, T: WrappedType> WrappedType for (&'ctx T::Context, T) +where + S: ScalarValue, + T: GraphQLValue, +{ + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Option { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +impl> WrappedType for Nullable { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +impl, E> WrappedType for Result { + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Vec { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl> WrappedType for [T] { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl, const N: usize> WrappedType for [T; N] { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Box { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Arc { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Rc { + 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 { + /// [`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 { + /// [`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 { + /// [`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: FieldMeta { + /// 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, + executor: &Executor, + ) -> ExecutionResult; +} + +/// 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: FieldMeta { + /// 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, + executor: &'b Executor, + ) -> BoxFuture<'b, ExecutionResult>; +} + +/// 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 + }}; +} diff --git a/juniper/src/parser/value.rs b/juniper/src/parser/value.rs index 8d66661c..3ff6d5ef 100644 --- a/juniper/src/parser/value.rs +++ b/juniper/src/parser/value.rs @@ -112,9 +112,7 @@ where .. }, _, - ) => Ok(parser - .next_token()? - .map(|_| InputValue::enum_value(name.to_owned()))), + ) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))), _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), } } diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index d4f35084..ccd43a99 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -447,6 +447,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Sets the `description` of this [`ScalarMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -457,6 +458,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Overwrites any previously set [specification URL][0]. /// /// [0]: https://spec.graphql.org/October2021#sec--specifiedBy + #[must_use] pub fn specified_by_url(mut self, url: impl Into>) -> Self { self.specified_by_url = Some(url.into()); self @@ -515,6 +517,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Sets the `description` of this [`ObjectMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -523,6 +526,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Set the `interfaces` this [`ObjectMeta`] type implements. /// /// Overwrites any previously set list of interfaces. + #[must_use] pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { self.interface_names = interfaces .iter() @@ -556,6 +560,7 @@ impl<'a, S> EnumMeta<'a, S> { /// Sets the `description` of this [`EnumMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -584,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> { /// Sets the `description` of this [`InterfaceMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -612,6 +618,7 @@ impl<'a> UnionMeta<'a> { /// Sets the `description` of this [`UnionMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -643,6 +650,7 @@ impl<'a, S> InputObjectMeta<'a, S> { /// Set the `description` of this [`InputObjectMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -658,6 +666,7 @@ impl<'a, S> Field<'a, S> { /// Set the `description` of this [`Field`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -666,6 +675,7 @@ impl<'a, S> Field<'a, S> { /// Adds an `argument` to this [`Field`]. /// /// Arguments are unordered and can't contain duplicates by name. + #[must_use] pub fn argument(mut self, argument: Argument<'a, S>) -> Self { match self.arguments { None => { @@ -681,6 +691,7 @@ impl<'a, S> Field<'a, S> { /// Sets this [`Field`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. + #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self @@ -701,6 +712,7 @@ impl<'a, S> Argument<'a, S> { /// Sets the `description` of this [`Argument`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -709,6 +721,7 @@ impl<'a, S> Argument<'a, S> { /// Set the default value of this [`Argument`]. /// /// Overwrites any previously set default value. + #[must_use] pub fn default_value(mut self, val: InputValue) -> Self { self.default_value = Some(val); self @@ -728,6 +741,7 @@ impl EnumValue { /// Sets the `description` of this [`EnumValue`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -736,6 +750,7 @@ impl EnumValue { /// Sets this [`EnumValue`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. + #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index d5eebbe5..d9c795de 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -95,11 +95,7 @@ impl Human { Self { id: id.to_owned(), name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(ToOwned::to_owned) - .collect(), + friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(ToOwned::to_owned), home_planet: home_planet.map(|p| p.to_owned()), @@ -111,54 +107,31 @@ impl Human { #[graphql_object(context = Database, impl = CharacterValue)] impl Human { /// The id of the human - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the human - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the human - fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) + pub fn friends(&self, ctx: &Database) -> Vec { + ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The home planet of the human - fn home_planet(&self) -> &Option { + pub fn home_planet(&self) -> &Option { &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 { - ctx.get_friends(self) - } - - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - - fn friends_ids(&self) -> &[String] { - &self.friend_ids - } -} - #[derive(Clone)] pub struct Droid { id: String, @@ -182,11 +155,7 @@ impl Droid { Self { id: id.to_owned(), name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(ToOwned::to_owned) - .collect(), + friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(ToOwned::to_owned), primary_function: primary_function.map(ToOwned::to_owned), @@ -198,54 +167,31 @@ impl Droid { #[graphql_object(context = Database, impl = CharacterValue)] impl Droid { /// The id of the droid - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the droid - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the droid - fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) + pub fn friends(&self, ctx: &Database) -> Vec { + ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The primary function of the droid - fn primary_function(&self) -> &Option { + pub fn primary_function(&self) -> &Option { &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 { - ctx.get_friends(self) - } - - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - - fn friends_ids(&self) -> &[String] { - &self.friend_ids - } -} - #[derive(Default, Clone)] pub struct Database { humans: HashMap, @@ -373,10 +319,7 @@ impl Database { } } - pub fn get_friends(&self, c: &dyn Character) -> Vec { - c.friends_ids() - .iter() - .flat_map(|id| self.get_character(id)) - .collect() + pub fn get_friends(&self, ids: &[String]) -> Vec { + ids.iter().flat_map(|id| self.get_character(id)).collect() } } diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 81341c59..7e8d01cc 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -141,6 +141,7 @@ impl Nullable { /// Returns the nullable if it contains a value, otherwise returns `b`. #[inline] + #[must_use] pub fn or(self, b: Self) -> Self { match self { Self::Some(_) => self, @@ -151,6 +152,7 @@ impl Nullable { /// Returns the nullable if it contains a value, otherwise calls `f` and /// returns the result. #[inline] + #[must_use] pub fn or_else Nullable>(self, f: F) -> Nullable { match self { Self::Some(_) => self, @@ -161,6 +163,7 @@ impl Nullable { /// Replaces the actual value in the nullable by the value given in parameter, returning the /// old value if present, leaving a `Some` in its place without deinitializing either one. #[inline] + #[must_use] pub fn replace(&mut self, value: T) -> Self { std::mem::replace(self, Self::Some(value)) } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index d55c6ac3..72b55939 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, + macros::reflect, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, types::{ @@ -202,6 +203,18 @@ where }) } +impl reflect::WrappedType for str { + const VALUE: reflect::WrappedValue = 1; +} + +impl reflect::BaseType for str { + const NAME: reflect::Type = "String"; +} + +impl reflect::BaseSubTypes for str { + const NAMES: reflect::Types = &[>::NAME]; +} + impl GraphQLType for str where S: ScalarValue, diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index acd1a33e..746c5494 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true proc-macro-error = "1.0.2" proc-macro2 = "1.0.1" quote = "1.0.3" -syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing"], default-features = false } +syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit", "visit-mut"], default-features = false } url = "2.0" [dev-dependencies] diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 0437f97f..bf299b45 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -16,7 +16,6 @@ use syn::{ use crate::{ common::{ - gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -65,20 +64,6 @@ pub(crate) struct Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) ignore: Option>, - - /// 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>, } impl Parse for Attr { @@ -119,10 +104,6 @@ impl Parse for Attr { .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, - "downcast" => out - .downcast - .replace(SpanContainer::new(ident.span(), None, ident.clone())) - .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } @@ -142,7 +123,6 @@ impl Attr { description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), - downcast: try_merge_opt!(downcast: self, another), }) } @@ -156,11 +136,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if let Some(ignore) = &attr.ignore { - if attr.name.is_some() - || attr.description.is_some() - || attr.deprecated.is_some() - || attr.downcast.is_some() - { + if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() { return Err(syn::Error::new( ignore.span(), "`ignore` attribute argument is not composable with any other arguments", @@ -168,19 +144,6 @@ impl Attr { } } - if let Some(downcast) = &attr.downcast { - if attr.name.is_some() - || attr.description.is_some() - || attr.deprecated.is_some() - || attr.ignore.is_some() - { - return Err(syn::Error::new( - downcast.span(), - "`downcast` attribute argument is not composable with any other arguments", - )); - } - } - if attr.description.is_none() { attr.description = get_doc_comment(attrs).map(|sc| { let span = sc.span_ident(); @@ -288,28 +251,6 @@ impl Definition { } } - /// Returns generated code that errors about [GraphQL fields][1] tried to be - /// resolved asynchronously in the [`GraphQLValue::resolve_field`] method - /// (which is synchronous itself). - /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_err_async_field_tokens( - field_names: &[&str], - scalar: &scalar::Type, - ty_name: &str, - ) -> TokenStream { - quote! { - #( #field_names )|* => return Err(::juniper::FieldError::from(format!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - field, - >::name(info) - .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?, - ))), - } - } - /// Returns generated code for the [`marker::IsOutputType::mark`] method, /// which performs static checks for this [GraphQL field][1]. /// @@ -390,106 +331,6 @@ impl Definition { } } - /// Returns generated code for the [`GraphQLValue::resolve_field`][0] - /// method, which resolves this [GraphQL field][1] synchronously. - /// - /// Returns [`None`] if this [`Definition::is_async`]. - /// - /// [0]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_tokens( - &self, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - ) -> Option { - 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! { ::#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! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - if !self.is_async { - fut = quote! { ::juniper::futures::future::ready(#fut) }; - } - - let resolving_code = gen::async_resolving_code(Some(&ty)); - - quote! { - #name => { - let fut = #fut; - #resolving_code - } - } - } - /// Returns generated code for the /// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which /// resolves this [GraphQL field][1] as [subscription][2]. diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index cae19a01..5f541eaa 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -11,12 +11,14 @@ use std::{ }; use proc_macro2::Span; +use quote::quote; use syn::{ ext::IdentExt as _, parse::{Parse, ParseBuffer}, parse_quote, punctuated::Punctuated, token::{self, Token}, + visit_mut::VisitMut, }; /// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing. @@ -250,6 +252,10 @@ pub(crate) trait GenericsExt { /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`]. fn move_bounds_to_where_clause(&mut self); + + /// Replaces generic parameters in the given [`syn::Type`] with default + /// ones, provided by these [`syn::Generics`]. + fn replace_type_with_defaults(&self, ty: &mut syn::Type); } impl GenericsExt for syn::Generics { @@ -299,4 +305,42 @@ impl GenericsExt for syn::Generics { } } } + + fn replace_type_with_defaults(&self, ty: &mut syn::Type) { + struct Replace<'a>(&'a syn::Generics); + + impl<'a> VisitMut for Replace<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote! { 'static }; + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + // Replace with `DefaultScalarValue` instead of `()` + // because generic parameter may be scalar. + *ty = parse_quote!(::juniper::DefaultScalarValue); + } + } + _ => {} + } + } + } + + Replace(self).visit_type_mut(ty) + } } diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs index 1569a539..8f11833d 100644 --- a/juniper_codegen/src/common/scalar.rs +++ b/juniper_codegen/src/common/scalar.rs @@ -90,12 +90,6 @@ impl Type { matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_)) } - /// Indicates whether this [`Type`] is [`Type::ExplicitGeneric`]. - #[must_use] - pub(crate) fn is_explicit_generic(&self) -> bool { - matches!(self, Self::ExplicitGeneric(_)) - } - /// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`]. #[must_use] pub(crate) fn is_implicit_generic(&self) -> bool { @@ -123,16 +117,6 @@ impl Type { } } - /// Returns a type parameter identifier that suits this [`Type`]. - #[must_use] - pub(crate) fn generic_ty(&self) -> syn::Type { - match self { - Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, - Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(), - Self::ImplicitGeneric(None) | Self::Concrete(_) => parse_quote! { __S }, - } - } - /// Returns a default [`ScalarValue`] type that is compatible with this [`Type`]. /// /// [`ScalarValue`]: juniper::ScalarValue diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 3e758224..3888d757 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -237,6 +237,25 @@ fn impl_scalar_struct( impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { } + + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } ); Ok(content) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 2aedbb2a..88f8b0d6 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -3,7 +3,7 @@ use std::mem; use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens as _}; +use quote::{format_ident, quote}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -16,32 +16,22 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{ - inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, - TraitAttr, TraitObjectType, Type, -}; +use super::{Definition, TraitAttr}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body.clone()) { + if let Ok(mut ast) = syn::parse2::(body) { let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); return expand_on_trait(trait_attrs, ast); - } else if let Ok(mut ast) = syn::parse2::(body) { - if ast.trait_.is_some() { - let impl_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); - return expand_on_impl(impl_attrs, ast); - } } Err(syn::Error::new( Span::call_site(), - "#[graphql_interface] attribute is applicable to trait definitions and trait \ - implementations only", + "#[graphql_interface] attribute is applicable to trait definitions only", )) } @@ -71,27 +61,6 @@ fn expand_on_trait( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - let mut implementers: Vec<_> = attr - .implementers - .iter() - .map(|ty| Implementer { - ty: ty.as_ref().clone(), - downcast: None, - context: None, - scalar: scalar.clone(), - }) - .collect(); - for (ty, downcast) in &attr.external_downcasts { - match implementers.iter_mut().find(|i| &i.ty == ty) { - Some(impler) => { - impler.downcast = Some(ImplementerDowncast::External { - path: downcast.inner().clone(), - }); - } - None => err_only_implementer_downcast(&downcast.span_joined()), - } - } - proc_macro_error::abort_if_dirty(); let renaming = attr @@ -103,22 +72,8 @@ fn expand_on_trait( let mut fields = vec![]; for item in &mut ast.items { if let syn::TraitItem::Method(m) = item { - match TraitMethod::parse(m, &renaming) { - Some(TraitMethod::Field(f)) => fields.push(f), - Some(TraitMethod::Downcast(d)) => { - match implementers.iter_mut().find(|i| i.ty == d.ty) { - Some(impler) => { - if let Some(external) = &impler.downcast { - err_duplicate_downcast(m, external, &impler.ty); - } else { - impler.downcast = d.downcast; - impler.context = d.context; - } - } - None => err_only_implementer_downcast(&m.sig), - } - } - _ => {} + if let Some(f) = parse_field(m, &renaming) { + fields.push(f) } } } @@ -147,336 +102,149 @@ fn expand_on_trait( }) }) }) - .or_else(|| { - implementers - .iter() - .find_map(|impler| impler.context.as_ref()) - .cloned() - }) .unwrap_or_else(|| parse_quote! { () }); - let is_trait_object = attr.r#dyn.is_some(); - - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - let has_default_async_methods = ast.items.iter().any(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), - _ => false, - }); - - let ty = if is_trait_object { - Type::TraitObject(Box::new(TraitObjectType::new( - &ast, - &attr, - scalar.clone(), - context.clone(), - ))) - } else { - Type::Enum(Box::new(EnumType::new( - &ast, - &attr, - &implementers, - scalar.clone(), - ))) - }; + let enum_alias_ident = attr + .r#enum + .as_deref() + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + let enum_ident = attr.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", trait_ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); let generated_code = Definition { - ty, - + trait_generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, name, - description: attr.description.map(SpanContainer::into_inner), - + description: attr.description.as_deref().cloned(), context, - scalar: scalar.clone(), - + scalar, fields, - implementers, + implementers: attr + .implementers + .iter() + .map(|c| c.inner().clone()) + .collect(), }; - // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. - if is_trait_object { - ast.attrs.push(parse_quote! { - #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - }); - - let scalar_ty = scalar.generic_ty(); - if !scalar.is_explicit_generic() { - let default_ty = scalar.default_ty(); - ast.generics - .params - .push(parse_quote! { #scalar_ty = #default_ty }); - } - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); - ast.supertraits - .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); - } - - if is_async_trait { - if has_default_async_methods { - // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits - ast.supertraits.push(parse_quote! { Sync }); - } - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - } - Ok(quote! { #ast #generated_code }) } -/// Expands `#[graphql_interface]` macro placed on a trait implementation block. -fn expand_on_impl(attrs: Vec, mut ast: syn::ItemImpl) -> syn::Result { - let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?; +/// Parses a [`field::Definition`] from the given trait method definition. +/// +/// Returns [`None`] if parsing fails, or the method field is ignored. +#[must_use] +fn parse_field( + method: &mut syn::TraitItemMethod, + renaming: &RenameRule, +) -> Option { + let method_ident = &method.sig.ident; + let method_attrs = method.attrs.clone(); - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::ImplItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); + // Remove repeated attributes from the method, to omit incorrect expansion. + method.attrs = mem::take(&mut method.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); - let is_trait_object = attr.r#dyn.is_some(); + let attr = field::Attr::from_attrs("graphql", &method_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; - if is_trait_object { - let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - - ast.attrs.push(parse_quote! { - #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - }); - - if scalar.is_implicit_generic() { - ast.generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); - } - - if !scalar.is_explicit_generic() { - let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); - let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); - } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { #scalar }); - } - } + if attr.ignore.is_some() { + return None; } - if is_async_trait { - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::ImplItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, + if method.default.is_some() { + return err_default_impl_block(&method.default); + } + + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| method_ident.span()), ); + return None; } - Ok(quote! { #ast }) -} - -/// Representation of parsed Rust trait method for `#[graphql_interface]` macro code generation. -enum TraitMethod { - /// Method represents a [`Field`] of [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Field(field::Definition), - - /// Method represents a custom downcasting function into the [`Implementer`] of - /// [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Downcast(Box), -} - -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 { - let method_attrs = method.attrs.clone(); - - // Remove repeated attributes from the method, to omit incorrect expansion. - method.attrs = mem::take(&mut method.attrs) - .into_iter() - .filter(|attr| !path_eq_single(&attr.path, "graphql")) - .collect(); - - let attr = field::Attr::from_attrs("graphql", &method_attrs) - .map_err(|e| proc_macro_error::emit_error!(e)) - .ok()?; - - if attr.ignore.is_some() { - return None; + let arguments = { + if method.sig.inputs.is_empty() { + return err_no_method_receiver(&method.sig.inputs); } - - if attr.downcast.is_some() { - return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?))); - } - - Some(Self::Field(Self::parse_field(method, attr, renaming)?)) - } - - /// Parses [`TraitMethod::Downcast`] from the given trait method definition. - /// - /// Returns [`None`] if parsing fails. - #[must_use] - fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { - 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 { - let method_ident = &method.sig.ident; - - let name = attr - .name - .as_ref() - .map(|m| m.as_ref().value()) - .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); - if name.starts_with("__") { - ERR.no_double_underscore( - attr.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| method_ident.span()), - ); - return None; - } - - let arguments = { - if method.sig.inputs.is_empty() { - return err_no_method_receiver(&method.sig.inputs); + let mut args_iter = method.sig.inputs.iter_mut(); + match args_iter.next().unwrap() { + syn::FnArg::Receiver(rcv) => { + if rcv.reference.is_none() || rcv.mutability.is_some() { + return err_invalid_method_receiver(rcv); + } } - let mut args_iter = method.sig.inputs.iter_mut(); - match args_iter.next().unwrap() { - syn::FnArg::Receiver(rcv) => { - if rcv.reference.is_none() || rcv.mutability.is_some() { - return err_invalid_method_receiver(rcv); + syn::FnArg::Typed(arg) => { + if let syn::Pat::Ident(a) = &*arg.pat { + if a.ident.to_string().as_str() != "self" { + return err_invalid_method_receiver(arg); } } - syn::FnArg::Typed(arg) => { - if let syn::Pat::Ident(a) = &*arg.pat { - if a.ident.to_string().as_str() != "self" { - return err_invalid_method_receiver(arg); - } - } - return err_no_method_receiver(arg); - } - }; - args_iter - .filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), - }) - .collect() + return err_no_method_receiver(arg); + } }; + args_iter + .filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), + }) + .collect() + }; - let mut ty = match &method.sig.output { - syn::ReturnType::Default => parse_quote! { () }, - syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), - }; - ty.lifetimes_anonymized(); + let mut ty = match &method.sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), + }; + ty.lifetimes_anonymized(); - let description = attr.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = attr - .deprecated - .as_deref() - .map(|d| d.as_ref().map(syn::LitStr::value)); + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr + .deprecated + .as_deref() + .map(|d| d.as_ref().map(syn::LitStr::value)); - Some(field::Definition { - name, - ty, - description, - deprecated, - ident: method_ident.clone(), - arguments: Some(arguments), - has_receiver: method.sig.receiver().is_some(), - is_async: method.sig.asyncness.is_some(), - }) - } + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: method_ident.clone(), + arguments: Some(arguments), + has_receiver: method.sig.receiver().is_some(), + is_async: method.sig.asyncness.is_some(), + }) +} + +/// Emits "trait method can't have default implementation" [`syn::Error`] +/// pointing to the given `span`. +fn err_default_impl_block(span: &S) -> Option { + ERR.emit_custom( + span.span(), + "trait method can't have default implementation", + ); + None } /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given /// `span`. -#[must_use] fn err_invalid_method_receiver(span: &S) -> Option { ERR.emit_custom( span.span(), @@ -487,7 +255,6 @@ fn err_invalid_method_receiver(span: &S) -> Option { /// Emits "no trait method receiver" [`syn::Error`] pointing to the given /// `span`. -#[must_use] fn err_no_method_receiver(span: &S) -> Option { ERR.emit_custom( span.span(), @@ -495,42 +262,3 @@ fn err_no_method_receiver(span: &S) -> Option { ); None } - -/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given -/// `span`. -fn err_only_implementer_downcast(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() -} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0e4ff066..263d1c5f 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -4,19 +4,18 @@ pub mod attr; -use std::{ - collections::{HashMap, HashSet}, - convert::TryInto as _, -}; +use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, + punctuated::Punctuated, spanned::Spanned as _, token, + visit::Visit, }; use crate::{ @@ -52,32 +51,21 @@ struct TraitAttr { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions description: Option>, - /// Explicitly specified identifier of the enum Rust type behind the trait, - /// being an actual implementation of a [GraphQL interface][1] type. + /// Explicitly specified identifier of the type alias of Rust enum type + /// behind the trait, being an actual implementation of a + /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces r#enum: Option>, - /// Explicitly specified identifier of the Rust type alias of the - /// [trait object][2], being an actual implementation of a - /// [GraphQL interface][1] type. - /// - /// Effectively makes code generation to use a [trait object][2] as a - /// [GraphQL interface][1] type rather than an enum. If [`None`], then enum - /// is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - r#dyn: Option>, - /// Explicitly specified Rust types of [GraphQL objects][2] implementing /// this [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - implementers: HashSet>, + implementers: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -109,22 +97,6 @@ struct TraitAttr { /// it contains async methods. asyncness: Option>, - /// Explicitly specified external downcasting functions for - /// [GraphQL interface][1] implementers. - /// - /// If [`None`], then macro will downcast to the implementers via enum - /// dispatch or dynamic dispatch (if the one is chosen). That's why - /// specifying an external resolver function has sense, when some custom - /// [interface][1] implementer resolving logic is involved. - /// - /// Once the downcasting function is specified for some [GraphQL object][2] - /// implementer type, it cannot be downcast another such function or trait - /// method marked with a [`MethodMeta::downcast`] marker. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - external_downcasts: HashMap>, - /// Explicitly specified [`RenameRule`] for all fields of this /// [GraphQL interface][1] type. /// @@ -183,7 +155,7 @@ impl Parse for TraitAttr { "for" | "implementers" => { input.parse::()?; for impler in input.parse_maybe_wrapped_and_punctuated::< - syn::Type, token::Bracket, token::Comma, + syn::TypePath, token::Bracket, token::Comma, >()? { let impler_span = impler.span(); out @@ -192,13 +164,6 @@ impl Parse for TraitAttr { .none_or_else(|_| err::dup_arg(impler_span))?; } } - "dyn" => { - input.parse::()?; - let alias = input.parse::()?; - out.r#dyn - .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) - .none_or_else(|_| err::dup_arg(&ident))? - } "enum" => { input.parse::()?; let alias = input.parse::()?; @@ -212,16 +177,6 @@ impl Parse for TraitAttr { .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } - "on" => { - let ty = input.parse::()?; - input.parse::()?; - let dwncst = input.parse::()?; - let dwncst_spanned = SpanContainer::new(ident.span(), Some(ty.span()), dwncst); - let dwncst_span = dwncst_spanned.span_joined(); - out.external_downcasts - .insert(ty, dwncst_spanned) - .none_or_else(|_| err::dup_arg(dwncst_span))? - } "rename_all" => { input.parse::()?; let val = input.parse::()?; @@ -256,12 +211,8 @@ impl TraitAttr { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), - r#dyn: try_merge_opt!(r#dyn: self, another), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), - external_downcasts: try_merge_hashmap!( - external_downcasts: self, another => span_joined - ), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -274,15 +225,6 @@ impl TraitAttr { .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - if let Some(as_dyn) = &attr.r#dyn { - if attr.r#enum.is_some() { - return Err(syn::Error::new( - as_dyn.span(), - "`dyn` attribute argument is not composable with `enum` attribute argument", - )); - } - } - if attr.description.is_none() { attr.description = get_doc_comment(attrs); } @@ -291,109 +233,32 @@ impl TraitAttr { } } -/// Available arguments behind `#[graphql_interface]` attribute placed on a -/// trait implementation block, when generating code for [GraphQL interface][1] -/// type. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug, Default)] -struct ImplAttr { - /// Explicitly specified type (or type parameter with its bounds) of - /// [`ScalarValue`] to implementing the [GraphQL interface][1] type with. - /// - /// If absent, then generated code will be generic over any [`ScalarValue`] - /// type, which, in turn, requires all [interface][1] implementers to be - /// generic over any [`ScalarValue`] type too. That's why this type should - /// be specified only if the implementer itself implements [`GraphQLType`] - /// in a non-generic way over [`ScalarValue`] type. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - scalar: Option>, - - /// Explicitly specified marker indicating that the trait implementation - /// block should be transformed with applying [`async_trait`]. - /// - /// If absent, then trait will be transformed with applying [`async_trait`] - /// only if it contains async methods. - /// - /// This marker is especially useful when Rust trait contains async default - /// methods, while the implementation block doesn't. - asyncness: Option>, - - /// Explicitly specified marker indicating that the implemented - /// [GraphQL interface][1] type is represented as a [trait object][2] in - /// Rust type system rather then an enum (default mode, when the marker is - /// absent). - /// - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - r#dyn: Option>, -} - -impl Parse for ImplAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "dyn" => { - let span = ident.span(); - out.r#dyn - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - "async" => { - let span = ident.span(); - out.asyncness - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - Ok(out) - } -} - -impl ImplAttr { - /// Tries to merge two [`ImplAttr`]s into a single one, reporting about - /// duplicates, if any. - fn try_merge(self, mut another: Self) -> syn::Result { - Ok(Self { - scalar: try_merge_opt!(scalar: self, another), - r#dyn: try_merge_opt!(r#dyn: self, another), - asyncness: try_merge_opt!(asyncness: self, another), - }) - } - - /// Parses [`ImplAttr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a trait implementation block. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) - } -} - /// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces struct Definition { - /// Rust type that this [GraphQL interface][1] is represented with. + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ty: Type, + trait_generics: syn::Generics, + + /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + vis: syn::Visibility, + + /// Name of the generic enum describing all [`implementers`]. It's generic + /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. + /// + /// [`implementers`]: Self::implementers + /// [`Debug`]: std::fmt::Debug + enum_ident: syn::Ident, + + /// Name of the type alias for [`enum_ident`] with [`implementers`]. + /// + /// [`enum_ident`]: Self::enum_ident + /// [`implementers`]: Self::implementers + enum_alias_ident: syn::Ident, /// Name of this [GraphQL interface][1] in GraphQL schema. /// @@ -430,21 +295,148 @@ struct Definition { /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + implementers: Vec, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { - self.ty.to_token_stream().to_tokens(into); + self.generate_enum_tokens().to_tokens(into); self.impl_graphql_interface_tokens().to_tokens(into); self.impl_output_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_async_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); } } impl Definition { + /// Generates enum describing all the [`implementers`]. + /// + /// [`implementers`]: Self::implementers + #[must_use] + fn generate_enum_tokens(&self) -> TokenStream { + let vis = &self.vis; + let enum_ident = &self.enum_ident; + let alias_ident = &self.enum_alias_ident; + + let variant_gens_pars = self + .implementers + .iter() + .enumerate() + .map::(|(id, _)| { + let par = format_ident!("__I{}", id); + parse_quote! { #par } + }); + + let variants_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); + + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); + + let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + .params + .clone() + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let enum_gens = { + let mut enum_gens = trait_gens.clone(); + enum_gens.params = trait_gens_lifetimes.clone(); + enum_gens.params.extend(variant_gens_pars.clone()); + enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens + }; + + let enum_alias_gens = { + let mut enum_alias_gens = trait_gens.clone(); + enum_alias_gens.move_bounds_to_where_clause(); + enum_alias_gens + }; + + let enum_to_alias_gens = { + trait_gens_lifetimes + .into_iter() + .map(|par| match par { + syn::GenericParam::Lifetime(def) => { + let lifetime = &def.lifetime; + quote! { #lifetime } + } + rest => quote! { #rest }, + }) + .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(trait_gens_tys.into_iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })) + }; + + let phantom_variant = self.has_phantom_variant().then(|| { + let phantom_params = trait_gens.params.iter().filter_map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => return None, + }; + Some(quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + }) + }); + quote! { __Phantom(#(#phantom_params),*) } + }); + + let from_impls = + self.implementers + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote! { + #[automatically_derived] + impl#trait_impl_gens ::std::convert::From<#ty> + for #alias_ident#trait_ty_gens + #trait_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) + } + } + } + }); + + quote! { + #[automatically_derived] + #[derive(Clone, Copy, Debug)] + #vis enum #enum_ident#enum_gens { + #(#variants_idents(#variant_gens_pars),)* + #phantom_variant + } + + #[automatically_derived] + #vis type #alias_ident#enum_alias_gens = + #enum_ident<#(#enum_to_alias_gens),*>; + + #(#from_impls)* + } + } + /// Returns generated code implementing [`GraphQLInterface`] trait for this /// [GraphQL interface][1]. /// @@ -452,19 +444,23 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let gens = self.impl_generics(false); + let (impl_generics, _, where_clause) = gens.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let impler_tys = &self.implementers; let all_implers_unique = (impler_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> + for #ty#ty_generics + #where_clause { fn mark() { #all_implers_unique @@ -481,25 +477,34 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; + let const_scalar = &self.scalar.default_ty(); - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty_const_generics = self.const_trait_generics(); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter().map(|impler| &impler.ty); + let impler_tys = self.implementers.iter().collect::>(); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> + for #ty#ty_generics + #where_clause { fn mark() { #( #fields_marks )* #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + ::juniper::assert_interfaces_impls!( + #const_scalar, #ty#ty_const_generics, #(#impler_tys),* + ); } } } @@ -512,10 +517,12 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let name = &self.name; let description = self @@ -524,7 +531,7 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let mut impler_tys = self.implementers.clone(); impler_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) @@ -534,7 +541,9 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLType<#scalar> + for #ty#ty_generics + #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -552,7 +561,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_interface_type::<#ty>(info, &fields) + registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description .into_meta() } @@ -567,49 +576,39 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let context = &self.context; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); - let ty_name = ty.to_string(); - let trait_ty = self.ty.trait_ty(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(scalar, Some(&trait_ty))); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, &ty_name, - ) + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflect::Field::< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#name) } + >::call(self, info, args, executor) + } }) - }; + }); + let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - let custom_downcast_checks = self - .implementers - .iter() - .filter_map(|i| i.method_concrete_type_name_tokens(&trait_ty)); - let regular_downcast_check = self.ty.method_concrete_type_name_tokens(); + let downcast_check = self.method_concrete_type_name_tokens(); - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.method_resolve_into_type_tokens(&trait_ty)); - let regular_downcast = self.ty.method_resolve_into_type_tokens(); + let downcast = self.method_resolve_into_type_tokens(); quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics + #where_clause { type Context = #context; type TypeInfo = (); @@ -627,7 +626,6 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* - #async_fields_err _ => #no_field_err, } } @@ -637,8 +635,7 @@ impl Definition { context: &Self::Context, info: &Self::TypeInfo, ) -> String { - #( #custom_downcast_checks )* - #regular_downcast_check + #downcast_check } fn resolve_into_type( @@ -648,8 +645,7 @@ impl Definition { _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - #( #custom_downcasts )* - #regular_downcast + #downcast } } } @@ -662,30 +658,35 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(true); - let ty = self.ty.ty_tokens(); - let ty_name = ty.to_string(); - let trait_ty = self.ty.trait_ty(); + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self - .fields - .iter() - .map(|f| f.method_resolve_field_async_tokens(scalar, Some(&trait_ty))); + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::reflect::AsyncField::< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.method_resolve_into_type_async_tokens(&trait_ty)); - let regular_downcast = self.ty.method_resolve_into_type_async_tokens(); + let downcast = self.method_resolve_into_type_async_tokens(); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics + #where_clause { fn resolve_field_async<'b>( &'b self, @@ -707,348 +708,416 @@ impl Definition { _: Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - #( #custom_downcasts )* - #regular_downcast + #downcast } } } } -} -/// Representation of custom downcast into an [`Implementer`] from a -/// [GraphQL interface][1] type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Clone, Debug)] -enum ImplementerDowncast { - /// Downcast is performed via a method of trait describing a + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][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-Interfaces + #[must_use] + fn impl_reflection_traits_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let implementers = &self.implementers; + let scalar = &self.scalar; + let name = &self.name; + let fields = self.fields.iter().map(|f| &f.name); + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ty#ty_generics + #where_clause + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = &[ + >::NAME, + #(<#implementers as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* + ]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + for #ty#ty_generics + #where_clause + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::Fields<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; + } + } + } + + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// + /// [`FieldMeta`]: juniper::macros::reflect::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Method { - /// Name of trait method which performs this [`ImplementerDowncast`]. - name: syn::Ident, + fn impl_field_meta_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let context = &self.context; + let scalar = &self.scalar; - /// Indicator whether the trait method accepts a [`Context`] as its - /// second argument. - /// - /// [`Context`]: juniper::Context - with_context: bool, - }, + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - /// Downcast is performed via some external function. - External { - /// Path of the external function to be called with. - path: syn::ExprPath, - }, -} + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); -/// Representation of [GraphQL interface][1] implementer for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Clone, Debug)] -struct Implementer { - /// Rust type that this [GraphQL interface][1] [`Implementer`] is - /// represented by. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ty: syn::Type, + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), + _ => None, + }) + .unzip(); - /// Custom [`ImplementerDowncast`] for this [`Implementer`]. - /// - /// If absent, then [`Implementer`] is downcast from an enum variant or a - /// trait object. - downcast: Option, - - /// Rust type of [`Context`] that this [GraphQL interface][1] - /// [`Implementer`] requires for downcasting. - /// - /// It's available only when code generation happens for Rust traits and a - /// trait method contains context argument. - /// - /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - context: Option, - - /// [`ScalarValue`] parametrization of this [`Implementer`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, -} - -impl Implementer { - /// Returns generated code of downcasting this [`Implementer`] via custom - /// [`ImplementerDowncast`]. - /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - #[must_use] - fn downcast_call_tokens( - &self, - trait_ty: &syn::Type, - ctx: Option, - ) -> Option { - let ctx = ctx.unwrap_or_else(|| parse_quote! { executor.context() }); - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(#ctx) }); - - let fn_path = match self.downcast.as_ref()? { - ImplementerDowncast::Method { name, with_context } => { - if !with_context { - ctx_arg = None; + quote! { + #[allow(non_snake_case)] + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::FieldMeta< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::reflect::Type = + <#return_ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflect::Types = + <#return_ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflect::WrappedValue = + <#return_ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::reflect::Name, + ::juniper::macros::reflect::Type, + ::juniper::macros::reflect::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, + )),*]; + } } - quote! { ::#name } - } - ImplementerDowncast::External { path } => { - quote! { #path } - } - }; - - Some(quote! { - #fn_path(self #ctx_arg) - }) + }) + .collect() } - /// Returns generated code for the [`GraphQLValue::concrete_type_name`] - /// method, which returns name of the GraphQL type represented by this - /// [`Implementer`]. + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL interface][1]. /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - /// - /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; - let scalar = &self.scalar; - - let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context })); - - // Doing this may be quite an expensive, because resolving may contain some heavy - // computation, so we're preforming it twice. Unfortunately, we have no other options here, - // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. - Some(quote! { - if (#downcast as ::std::option::Option<&#ty>).is_some() { - return <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap().to_string(); - } - }) - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts the [GraphQL interface][1] type into this - /// [`Implementer`] synchronously. - /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type + /// [`Field`]: juniper::macros::reflect::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; + fn impl_field_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); - let ty = &self.ty; - let ty_name = ty.to_token_stream().to_string(); + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + quote! { + #[allow(non_snake_case)] + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::Field< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::reflect::Field::< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + } + }) + .collect() + } + + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL interface][1]. + /// + /// [`AsyncField`]: juniper::macros::reflect::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_async_field_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); + + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + quote! { + #[allow(non_snake_case)] + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::AsyncField< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::reflect::AsyncField< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + } + }) + .collect() + } + + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`implementers`][1] GraphQL + /// type contained in this enum. + /// + /// [0]: juniper::GraphQLValue::concrete_type_name + /// [1]: Self::implementers + #[must_use] + fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(trait_ty, None); + let match_arms = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) + .map(|(ident, ty)| { + quote! { + Self::#ident(v) => < + #ty as ::juniper::GraphQLValue<#scalar> + >::concrete_type_name(v, context, info), + } + }); - let resolving_code = gen::sync_resolving_code(); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); - Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info) - .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))? - { - let res = #downcast; - return #resolving_code; + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm } - }) + } } /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts the [GraphQL interface][1] type into this [`Implementer`] + /// downcasts this enum into its underlying [`implementers`][1] type /// asynchronously. /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: Self::implementers #[must_use] - fn method_resolve_into_type_async_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; - let ty_name = ty.to_token_stream().to_string(); - let scalar = &self.scalar; - - let downcast = self.downcast_call_tokens(trait_ty, None); - + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - Some(quote! { - match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) { - Some(name) => { - if type_name == name { - let fut = ::juniper::futures::future::ready(#downcast); - return #resolving_code; + let match_arms = self.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(v) => { + let fut = ::juniper::futures::future::ready(v); + #resolving_code } } - None => return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name), + }) + }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm } - }) - } -} - -/// Representation of Rust enum implementing [GraphQL interface][1] type for -/// code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct EnumType { - /// Name of this [`EnumType`] to generate it with. - ident: syn::Ident, - - /// [`syn::Visibility`] of this [`EnumType`] to generate it with. - visibility: syn::Visibility, - - /// Rust types of all [GraphQL interface][1] implements to represent - /// variants of this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - variants: Vec, - - /// Name of the trait describing the [GraphQL interface][1] represented by - /// this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_ident: syn::Ident, - - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, - - /// Associated types of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - trait_types: Vec<(syn::Ident, syn::Generics)>, - - /// Associated constants of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_consts: Vec<(syn::Ident, syn::Type)>, - - /// Methods of the trait describing the [GraphQL interface][1] represented - /// by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_methods: Vec, - - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] - /// implementation with for this [`EnumType`]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, -} - -impl ToTokens for EnumType { - fn to_tokens(&self, into: &mut TokenStream) { - self.type_definition_tokens().to_tokens(into); - into.append_all(self.impl_from_tokens()); - self.impl_trait_tokens().to_tokens(into); - } -} - -impl EnumType { - /// Constructs a new [`EnumType`] out of the given parameters. - #[must_use] - fn new( - r#trait: &syn::ItemTrait, - meta: &TraitAttr, - implers: &[Implementer], - scalar: scalar::Type, - ) -> Self { - Self { - ident: meta - .r#enum - .as_ref() - .map(SpanContainer::as_ref) - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", r#trait.ident)), - visibility: r#trait.vis.clone(), - variants: implers.iter().map(|impler| impler.ty.clone()).collect(), - trait_ident: r#trait.ident.clone(), - trait_generics: r#trait.generics.clone(), - trait_types: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Type(ty) = i { - Some((ty.ident.clone(), ty.generics.clone())) - } else { - None - } - }) - .collect(), - trait_consts: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Const(cnst) = i { - Some((cnst.ident.clone(), cnst.ty.clone())) - } else { - None - } - }) - .collect(), - trait_methods: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(m.sig.clone()) - } else { - None - } - }) - .collect(), - scalar, } } - /// Returns name of a single variant of this [`EnumType`] by the given - /// underlying [`syn::Type`] of the variant. - #[must_use] - fn variant_ident(ty: &syn::Type) -> &syn::Ident { - if let syn::Type::Path(p) = ty { - &p.path.segments.last().unwrap().ident - } else { - unreachable!("GraphQL object has unexpected type `{}`", quote! { #ty }) - } - } - - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant - /// to hold type parameters. - #[must_use] - fn has_phantom_variant(&self) -> bool { - !self.trait_generics.params.is_empty() - } - - /// Returns generate code for dispatching non-exhaustive phantom variant of - /// this [`EnumType`] in `match` expressions. + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which resolves this enum into its underlying + /// [`implementers`][1] type synchronously. /// - /// Returns [`None`] if this [`EnumType`] is exhaustive. + /// [0]: juniper::GraphQLValue::resolve_into_type + /// [1]: Self::implementers #[must_use] - fn non_exhaustive_match_arm_tokens(&self) -> Option { - if self.has_phantom_variant() || self.variants.is_empty() { - Some(quote! { _ => unreachable!(), }) - } else { - None + fn method_resolve_into_type_tokens(&self) -> TokenStream { + let resolving_code = gen::sync_resolving_code(); + + let match_arms = self.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(res) => #resolving_code, + } + }) + }); + + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } } } + /// Returns trait generics replaced with the default values for usage in a + /// `const` context. + #[must_use] + fn const_trait_generics(&self) -> syn::PathArguments { + struct GenericsForConst(syn::AngleBracketedGenericArguments); + + impl Visit<'_> for GenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + let arg = match param { + syn::GenericParam::Lifetime(_) => parse_quote! { 'static }, + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + parse_quote! { ::juniper::DefaultScalarValue } + } else { + return; + } + } + syn::GenericParam::Const(c) => { + if c.default.is_none() { + // This hack works because only `min_const_generics` + // are enabled for now. + // TODO: Replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } else { + return; + } + } + }; + self.0.args.push(arg) + } + } + + let mut visitor = GenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + syn::PathArguments::AngleBracketed(visitor.0) + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. + /// similar) implementation of this enum. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. @@ -1084,7 +1153,7 @@ impl EnumType { } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.ident; + let ty = &self.enum_alias_ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty#ty_generics } @@ -1107,664 +1176,10 @@ impl EnumType { generics } - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// Indicates whether this enum has non-exhaustive phantom variant to hold + /// type parameters. #[must_use] - fn trait_ty(&self) -> syn::Type { - let ty = &self.trait_ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); - - parse_quote! { #ty#generics } - } - - /// Returns generated code of the full type signature of this [`EnumType`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - let ty = &self.ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); - - quote! { #ty#generics } - } - - /// Returns generate code of the Rust type definitions of this [`EnumType`]. - /// - /// If the [`EnumType::trait_generics`] are not empty, then they are - /// contained in the generated enum too. - #[must_use] - fn type_definition_tokens(&self) -> TokenStream { - let enum_ty = &self.ident; - let generics = &self.trait_generics; - let vis = &self.visibility; - - let doc = format!( - "Type implementing [GraphQL interface][1] represented by `{}` trait.\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces", - self.trait_ident, - ); - - let variants = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - let doc = format!( - "`{}` implementer of this GraphQL interface.", - quote! { #ty }, - ); - - quote! { - #[doc = #doc] - #variant(#ty), - } - }); - - let phantom_variant = if self.has_phantom_variant() { - let ty_params = generics.params.iter().map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => unimplemented!(), - }; - quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - } - }); - - Some(quote! { - #[doc(hidden)] - __Phantom(#( #ty_params ),*), - }) - } else { - None - }; - - quote! { - #[automatically_derived] - #[doc = #doc] - #vis enum #enum_ty#generics { - #( #variants )* - #phantom_variant - } - } - } - - /// Returns generated code implementing [`From`] trait for this [`EnumType`] - /// from its [`EnumType::variants`]. - fn impl_from_tokens(&self) -> impl Iterator + '_ { - let enum_ty = &self.ident; - let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - - self.variants.iter().map(move |ty| { - let variant = Self::variant_ident(ty); - - quote! { - #[automatically_derived] - impl#impl_generics From<#ty> for #enum_ty#generics #where_clause { - fn from(v: #ty) -> Self { - Self::#variant(v) - } - } - } - }) - } - - /// Returns generated code implementing the original trait describing the - /// [GraphQL interface][1] for this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_trait_tokens(&self) -> TokenStream { - let enum_ty = &self.ident; - - let trait_ident = &self.trait_ident; - let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - - let var_ty = self.variants.first(); - - let assoc_types = self.trait_types.iter().map(|(ty, ty_gen)| { - quote! { - type #ty#ty_gen = <#var_ty as #trait_ident#generics>::#ty#ty_gen; - } - }); - - let assoc_consts = self.trait_consts.iter().map(|(ident, ty)| { - quote! { - const #ident: #ty = <#var_ty as #trait_ident#generics>::#ident; - } - }); - - let methods = self.trait_methods.iter().map(|sig| { - let method = &sig.ident; - - let mut sig = sig.clone(); - let mut args = vec![]; - for (n, arg) in sig.inputs.iter_mut().enumerate() { - match arg { - syn::FnArg::Receiver(_) => {} - syn::FnArg::Typed(a) => { - if !matches!(&*a.pat, syn::Pat::Ident(_)) { - let ident = format_ident!("__arg{}", n); - a.pat = parse_quote! { #ident }; - } - args.push(a.pat.clone()); - } - } - } - - let and_await = if sig.asyncness.is_some() { - Some(quote! { .await }) - } else { - None - }; - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - let args = args.clone(); - - quote! { - Self::#variant(v) => - <#ty as #trait_ident#generics>::#method(v #( , #args )* )#and_await, - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - #sig { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - }); - - let mut impl_tokens = quote! { - #[allow(deprecated)] - #[automatically_derived] - impl#impl_generics #trait_ident#generics for #enum_ty#generics #where_clause { - #( #assoc_types )* - - #( #assoc_consts )* - - #( #methods )* - } - }; - - if self.trait_methods.iter().any(|sig| sig.asyncness.is_some()) { - let mut ast: syn::ItemImpl = parse_quote! { #impl_tokens }; - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::ImplItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - impl_tokens = quote! { #ast }; - } - - impl_tokens - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(v) => < - #ty as ::juniper::GraphQLValue<#scalar> - >::concrete_type_name(v, context, info), - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`EnumType`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - let resolving_code = gen::sync_resolving_code(); - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(res) => #resolving_code, - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`EnumType`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(v) => { - let fut = ::juniper::futures::future::ready(v); - #resolving_code - } - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } -} - -/// Representation of Rust [trait object][2] implementing [GraphQL interface][1] -/// type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://doc.rust-lang.org/reference/types/trait-object.html -struct TraitObjectType { - /// Name of this [`TraitObjectType`] to generate it with. - ident: syn::Ident, - - /// [`syn::Visibility`] of this [`TraitObjectType`] to generate it with. - visibility: syn::Visibility, - - /// Name of the trait describing the [GraphQL interface][1] represented by - /// this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_ident: syn::Ident, - - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] - /// represented by this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, - - /// [`ScalarValue`] parametrization of this [`TraitObjectType`] to generate - /// it with. - /// - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, - - /// Rust type of [`Context`] to generate this [`TraitObjectType`] with. - /// - /// [`Context`]: juniper::Context - context: syn::Type, -} - -impl TraitObjectType { - /// Constructs a new [`TraitObjectType`] out of the given parameters. - #[must_use] - fn new( - r#trait: &syn::ItemTrait, - meta: &TraitAttr, - scalar: scalar::Type, - context: syn::Type, - ) -> Self { - Self { - ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(), - visibility: r#trait.vis.clone(), - trait_ident: r#trait.ident.clone(), - trait_generics: r#trait.generics.clone(), - scalar, - context, - } - } - - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`TraitObjectType`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); - - generics.params.push(parse_quote! { '__obj }); - - let scalar = &self.scalar; - if scalar.is_implicit_generic() { - generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - if let Some(bound) = scalar.bounds() { - generics.make_where_clause().predicates.push(bound); - } - - if for_async { - generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: Sync }); - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - } - - generics - } - - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn trait_ty(&self) -> syn::Type { - let ty = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - if !self.scalar.is_explicit_generic() { - let scalar = &self.scalar; - generics.params.push(parse_quote! { #scalar }); - } - let (_, generics, _) = generics.split_for_impl(); - - parse_quote! { #ty#generics } - } - - /// Returns generated code of the full type signature of this - /// [`TraitObjectType`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - let ty = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - generics.remove_defaults(); - generics.move_bounds_to_where_clause(); - if !self.scalar.is_explicit_generic() { - let scalar = &self.scalar; - generics.params.push(parse_quote! { #scalar }); - } - let ty_params = &generics.params; - - let context = &self.context; - - quote! { - dyn #ty<#ty_params, Context = #context, TypeInfo = ()> + '__obj + Send + Sync - } - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`TraitObjectType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - quote! { - self.as_dyn_graphql_value().concrete_type_name(context, info) - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`TraitObjectType`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - let resolving_code = gen::sync_resolving_code(); - - quote! { - let res = self.as_dyn_graphql_value(); - #resolving_code - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`TraitObjectType`] into its underlying [`Implementer`] - /// type asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - quote! { - let fut = ::juniper::futures::future::ready(self.as_dyn_graphql_value_async()); - #resolving_code - } - } -} - -impl ToTokens for TraitObjectType { - fn to_tokens(&self, into: &mut TokenStream) { - let dyn_ty = &self.ident; - let vis = &self.visibility; - - let doc = format!( - "Helper alias for the `{}` [trait object][2] implementing [GraphQL interface][1].\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces\n\ - [2]: https://doc.rust-lang.org/reference/types/trait-object.html", - self.trait_ident, - ); - - let trait_ident = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - if !self.scalar.is_explicit_generic() { - let scalar_ty = self.scalar.generic_ty(); - let default_ty = self.scalar.default_ty(); - generics - .params - .push(parse_quote! { #scalar_ty = #default_ty }); - } - - let (mut ty_params_left, mut ty_params_right) = (None, None); - if !generics.params.is_empty() { - // We should preserve defaults for left side. - generics.move_bounds_to_where_clause(); - let params = &generics.params; - ty_params_left = Some(quote! { , #params }); - - generics.remove_defaults(); - let params = &generics.params; - ty_params_right = Some(quote! { #params, }); - }; - - let context = &self.context; - - let dyn_alias = quote! { - #[automatically_derived] - #[doc = #doc] - #vis type #dyn_ty<'a #ty_params_left> = - dyn #trait_ident<#ty_params_right Context = #context, TypeInfo = ()> + - 'a + Send + Sync; - }; - - into.append_all(&[dyn_alias]); - } -} - -/// Representation of possible Rust types implementing [GraphQL interface][1] -/// type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -enum Type { - /// [GraphQL interface][1] type implementation as Rust enum. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Enum(Box), - - /// [GraphQL interface][1] type implementation as Rust [trait object][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - TraitObject(Box), -} - -impl ToTokens for Type { - fn to_tokens(&self, into: &mut TokenStream) { - match self { - Self::Enum(e) => e.to_tokens(into), - Self::TraitObject(o) => o.to_tokens(into), - } - } -} - -impl Type { - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`Type`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> (TokenStream, Option) { - let generics = match self { - Self::Enum(e) => e.impl_generics(for_async), - Self::TraitObject(o) => o.impl_generics(for_async), - }; - let (impl_generics, _, where_clause) = generics.split_for_impl(); - (quote! { #impl_generics }, where_clause.cloned()) - } - - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`Type`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn trait_ty(&self) -> syn::Type { - match self { - Self::Enum(e) => e.trait_ty(), - Self::TraitObject(o) => o.trait_ty(), - } - } - - /// Returns generated code of the full type signature of this [`Type`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.ty_tokens(), - Self::TraitObject(o) => o.ty_tokens(), - } - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`Type`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_concrete_type_name_tokens(), - Self::TraitObject(o) => o.method_concrete_type_name_tokens(), - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`Type`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_resolve_into_type_tokens(), - Self::TraitObject(o) => o.method_resolve_into_type_tokens(), - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`Type`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_resolve_into_type_async_tokens(), - Self::TraitObject(o) => o.method_resolve_into_type_async_tokens(), - } - } -} - -/// Injects [`async_trait`] implementation into the given trait definition or -/// trait implementation block, correctly restricting type and lifetime -/// parameters with `'async_trait` lifetime, if required. -fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) -where - M: IntoIterator, -{ - attrs.push(parse_quote! { #[::juniper::async_trait] }); - - for method in methods.into_iter() { - if method.asyncness.is_some() { - let where_clause = &mut method.generics.make_where_clause().predicates; - for p in &generics.params { - let ty_param = match p { - syn::GenericParam::Type(t) => { - let ty_param = &t.ident; - quote! { #ty_param } - } - syn::GenericParam::Lifetime(l) => { - let ty_param = &l.lifetime; - quote! { #ty_param } - } - syn::GenericParam::Const(_) => continue, - }; - where_clause.push(parse_quote! { #ty_param: 'async_trait }); - } - } + fn has_phantom_variant(&self) -> bool { + !self.trait_generics.params.is_empty() } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 171e00a3..49459737 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -18,10 +18,10 @@ use syn::{ use crate::{ common::{ - field, + field, gen, parse::{ attr::{err, OptionExt as _}, - ParseBufferExt as _, TypeExt, + GenericsExt as _, ParseBufferExt as _, TypeExt, }, scalar, }, @@ -361,6 +361,68 @@ impl Definition { } } + /// 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 = + &[>::NAME]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::Implements<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[#(<#interfaces as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + for #ty + #where_clause + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::Fields<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -439,6 +501,10 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_as_dyn_graphql_value_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } @@ -451,11 +517,25 @@ impl Definition { #[must_use] fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let interface_tys = self.interfaces.iter(); + + let generics = { + let mut generics = self.generics.clone(); + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }) + } + generics + }; + let const_interface_tys = interface_tys.clone().cloned().map(|mut ty| { + generics.replace_type_with_defaults(&mut ty); + ty + }); + // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, // but considering generics. //let interface_tys: Vec<_> = self.interfaces.iter().collect(); @@ -469,11 +549,210 @@ impl Definition { { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + ::juniper::assert_implemented_for!( + #const_scalar, #ty, #(#const_interface_tys),* + ); } } } } + /// Returns generated code implementing [`FieldMeta`] traits for each field + /// of this [GraphQL object][1]. + /// + /// [`FieldMeta`]: juniper::FieldMeta + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_field_meta_tokens(&self) -> TokenStream { + let impl_ty = &self.ty; + let scalar = &self.scalar; + let context = &self.context; + let (impl_generics, where_clause) = self.impl_generics(false); + + self.fields + .iter() + .map(|field| { + let (name, ty) = (&field.name, field.ty.clone()); + + let arguments = field + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + Some(quote! {( + #name, + <#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, + )}) + } + field::MethodArgument::Executor | field::MethodArgument::Context(_) => None, + }) + .collect::>(); + + 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, + >::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, + ) -> ::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, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let fut = #res; + #resolving_code + } + } + } + }) + .collect() + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL object][1]. /// @@ -490,22 +769,18 @@ impl Definition { let name = &self.name; - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(scalar, None)); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, &ty_name, - ) - }) - }; + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::reflect::Field::< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); + let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -530,7 +805,6 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* - #async_fields_err _ => #no_field_err, } } @@ -559,10 +833,18 @@ impl Definition { let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); - let fields_resolvers = self - .fields - .iter() - .map(|f| f.method_resolve_field_async_tokens(scalar, None)); + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::reflect::AsyncField::< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); + let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 00ba528e..4c3bafca 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -320,6 +320,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); } } @@ -609,6 +610,50 @@ impl Definition { } } } + + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL union][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`WrappedType`]: juniper::macros::reflect::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let variants = self.variants.iter().map(|var| &var.ty); + let (impl_generics, ty, where_clause) = self.impl_generics(false); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ty + #where_clause + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = &[ + >::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. diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index a790abac..e40aa41d 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -327,6 +327,25 @@ pub fn build_scalar( #from_str_body } } + + impl#generic_type_decl ::juniper::macros::reflect::BaseType<#generic_type> for #impl_for_type + #generic_type_bound + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + impl#generic_type_decl ::juniper::macros::reflect::BaseSubTypes<#generic_type> for #impl_for_type + #generic_type_bound + { + const NAMES: ::juniper::macros::reflect::Types = + &[>::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) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1e426af3..4aff89d1 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -271,70 +271,36 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// Specifying multiple `#[graphql_interface]` attributes on the same definition /// is totally okay. They all will be treated as a single attribute. /// -/// The main difference between [GraphQL interface][1] type and Rust trait is +/// [GraphQL interfaces][1] are more like structurally-typed interfaces, while +/// Rust's traits are more like type classes. Using `impl Trait` isn't an +/// option, so you have to cover all trait's methods with type's fields or +/// impl block. But no one is stopping you from additionally implementing trait +/// manually. +/// +/// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to /// concrete implementers_, while in Rust, a trait is an _abstraction only_ and /// you need a separate type to downcast into a concrete implementer, like enum /// or [trait object][3], because trait doesn't represent a type itself. -/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by -/// default, however [trait object][3] may be used too (use `dyn` attribute -/// argument for that). -/// -/// A __trait has to be [object safe][2]__ if its values are represented by -/// [trait object][3], because schema resolvers will need to return that -/// [trait object][3]. The [trait object][3] has to be [`Send`] and [`Sync`], -/// and the macro automatically generate a convenien type alias for such -/// [trait object][3]. +/// Macro uses Rust enums only to represent a value type of a +/// [GraphQL interface][1]. /// /// ``` /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this /// // GraphQL interface. -/// #[graphql_interface(for = [Human, Droid])] // enumerating all implementers is mandatory +/// #[graphql_interface(for = Human)] // enumerating all implementers is mandatory /// trait Character { /// fn id(&self) -> &str; /// } /// -/// // NOTICE: `dyn` attribute argument enables trait object usage to represent values of this -/// // GraphQL interface. Also, for trait objects a trait is slightly modified -/// // under-the-hood by adding a `ScalarValue` type parameter. -/// #[graphql_interface(dyn = DynSerial, for = Droid)] -/// trait Serial { -/// fn number(&self) -> i32; -/// } -/// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name /// struct Human { -/// id: String, +/// id: String, // this field is used to resolve Character::id /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = [CharacterValue, DynSerial<__S>])] // notice the trait object referred by alias -/// struct Droid { // and its parametrization by generic -/// id: String, // `ScalarValue` -/// primary_function: String, -/// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// #[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too -/// impl Serial for Droid { -/// fn number(&self) -> i32 { -/// 78953 -/// } -/// } /// ``` /// /// # Custom name, description, deprecation and argument defaults @@ -390,7 +356,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// `none` (disables any renaming). /// /// ``` -/// # use juniper::{graphql_interface, GraphQLObject}; +/// # use juniper::{graphql_interface, graphql_object}; /// # /// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming /// trait Character { @@ -399,19 +365,26 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// fn detailed_info(&self, info_kind: String) -> String; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn detailed_info(&self, info_kind: String) -> String { +/// +/// #[graphql_object(impl = CharacterValue, rename_all = "none")] +/// impl Human { +/// fn id(&self) -> &str { +/// &self.id +/// } +/// +/// fn home_planet(&self) -> &str { +/// &self.home_planet +/// } +/// +/// // You can return `&str` even if trait definition returns `String`. +/// fn detailed_info(&self, info_kind: String) -> &str { /// (info_kind == "planet") /// .then(|| &self.home_planet) /// .unwrap_or(&self.id) -/// .clone() /// } /// } /// ``` @@ -447,7 +420,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// /// ``` /// # use std::collections::HashMap; -/// # use juniper::{graphql_interface, GraphQLObject}; +/// # use juniper::{graphql_interface, graphql_object}; /// # /// struct Database { /// humans: HashMap, @@ -461,36 +434,38 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str>; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> { -/// db.humans.get(&self.id).map(|h| h.id.as_str()) +/// #[graphql_object(impl = CharacterValue, Context = Database)] +/// impl Human { +/// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> { +/// context.humans.get(&self.id).map(|h| h.id.as_str()) /// } -/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> { +/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.humans.get(&self.id).map(|h| h.home_planet.as_str()) /// } +/// fn home_planet(&self) -> &str { +/// &self.home_planet +/// } /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] /// struct Droid { /// id: String, /// primary_function: String, /// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> { -/// db.droids.get(&self.id).map(|h| h.id.as_str()) +/// #[graphql_object(impl = CharacterValue, Context = Database)] +/// impl Droid { +/// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str> { +/// ctx.droids.get(&self.id).map(|h| h.id.as_str()) /// } -/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> { +/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.droids.get(&self.id).map(|h| h.primary_function.as_str()) /// } +/// fn primary_function(&self) -> &str { +/// &self.primary_function +/// } /// } /// ``` /// @@ -503,42 +478,33 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// /// ``` -/// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +/// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; /// # /// // NOTICE: Specifying `ScalarValue` as existing type parameter. /// #[graphql_interface(for = Human, scalar = S)] /// trait Character { -/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str -/// where -/// S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ +/// fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; /// -/// async fn name<'b>( +/// fn name<'b>( /// &'b self, /// #[graphql(executor)] another: &Executor<'_, '_, (), S>, -/// ) -> &'b str -/// where -/// S: Send + Sync; +/// ) -> &'b str; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue<__S>)] /// struct Human { /// id: String, /// name: String, /// } -/// #[graphql_interface(scalar = S)] -/// impl Character for Human { -/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str +/// #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] +/// impl Human { +/// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where -/// S: Send + Sync, +/// S: ScalarValue, /// { /// executor.look_ahead().field_name() /// } /// -/// async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str -/// where -/// S: Send + Sync, -/// { +/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str { /// &self.name /// } /// } @@ -557,7 +523,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// // NOTICE: Removing `Scalar` argument will fail compilation. -/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)] +/// #[graphql_interface(for = Human, scalar = DefaultScalarValue)] /// trait Character { /// fn id(&self) -> &str; /// } @@ -568,93 +534,6 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface(scalar = DefaultScalarValue)] -/// impl Character for Human { -/// fn id(&self) -> &str{ -/// &self.id -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] -/// struct Droid { -/// id: String, -/// primary_function: String, -/// } -/// #[graphql_interface(scalar = DefaultScalarValue)] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// ``` -/// -/// # Downcasting -/// -/// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via -/// matching the enum variant or downcasting the trait object (if `dyn` attribute's argument is -/// used). -/// -/// To use a custom logic for downcasting a [GraphQL interface][1] into its implementer, there may -/// be specified: -/// - either a `downcast` attribute's argument directly on a trait method; -/// - or an `on` attribute's argument on aa trait definition referring an exteranl function. -/// -/// ``` -/// # use std::collections::HashMap; -/// # use juniper::{graphql_interface, GraphQLObject}; -/// # -/// struct Database { -/// humans: HashMap, -/// droids: HashMap, -/// } -/// 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 diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 4109ce7e..726fce44 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -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 = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty + #where_clause + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } ); if !self.no_async { @@ -1153,6 +1172,28 @@ impl GraphQLTypeDefiniton { ].into_iter().collect()) } } + + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[>::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 { diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs index f1fe9bd3..51c2e914 100644 --- a/juniper_graphql_ws/src/lib.rs +++ b/juniper_graphql_ws/src/lib.rs @@ -68,6 +68,7 @@ impl ConnectionConfig { /// Specifies the maximum number of in-flight operations that a connection can have. If this /// number is exceeded, attempting to start more will result in an error. By default, there is /// no limit to in-flight operations. + #[must_use] pub fn with_max_in_flight_operations(mut self, max: usize) -> Self { self.max_in_flight_operations = max; self @@ -75,6 +76,7 @@ impl ConnectionConfig { /// Specifies the interval at which to send keep-alives. Specifying a zero duration will /// disable keep-alives. By default, keep-alives are sent every 15 seconds. + #[must_use] pub fn with_keep_alive_interval(mut self, interval: Duration) -> Self { self.keep_alive_interval = interval; self diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 52e67a92..d46df452 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -286,24 +286,24 @@ enum GraphQLRequestError { } impl fmt::Display for GraphQLRequestError { - fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, &mut f), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, f), } } } impl Error for GraphQLRequestError { fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - GraphQLRequestError::BodyHyper(ref err) => Some(err), - GraphQLRequestError::BodyUtf8(ref err) => Some(err), - GraphQLRequestError::BodyJSONError(ref err) => Some(err), - GraphQLRequestError::Variables(ref err) => Some(err), + match self { + GraphQLRequestError::BodyHyper(err) => Some(err), + GraphQLRequestError::BodyUtf8(err) => Some(err), + GraphQLRequestError::BodyJSONError(err) => Some(err), + GraphQLRequestError::Variables(err) => Some(err), GraphQLRequestError::Invalid(_) => None, } } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 5a665187..cbef5ace 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -311,15 +311,15 @@ where Subscription: GraphQLType + Send + Sync + 'static, 'a: 'static, { - fn handle(&self, mut req: &mut Request) -> IronResult { + fn handle(&self, req: &mut Request) -> IronResult { let context = (self.context_factory)(req)?; let graphql_request = match req.method { - method::Get => self.handle_get(&mut req)?, + method::Get => self.handle_get(req)?, method::Post => match req.headers.get::().map(ContentType::deref) { Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() { - "json" => self.handle_post_json(&mut req)?, - "graphql" => self.handle_post_graphql(&mut req)?, + "json" => self.handle_post_json(req)?, + "graphql" => self.handle_post_graphql(req)?, _ => return Ok(Response::with(status::BadRequest)), }, _ => return Ok(Response::with(status::BadRequest)), @@ -369,11 +369,11 @@ enum GraphQLIronError { } impl fmt::Display for GraphQLIronError { - fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, &mut f), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + GraphQLIronError::Serde(err) => fmt::Display::fmt(err, f), + GraphQLIronError::Url(err) => fmt::Display::fmt(err, f), + GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f), } } } diff --git a/juniper_subscriptions/src/lib.rs b/juniper_subscriptions/src/lib.rs index 5e890b63..e09e1127 100644 --- a/juniper_subscriptions/src/lib.rs +++ b/juniper_subscriptions/src/lib.rs @@ -183,7 +183,7 @@ where ready_vec.push(None); } - let stream = stream::poll_fn(move |mut ctx| -> Poll>> { + let stream = stream::poll_fn(move |ctx| -> Poll>> { let mut obj_iterator = object.iter_mut(); // Due to having to modify `ready_vec` contents (by-move pattern) @@ -204,7 +204,7 @@ where match val { Value::Scalar(stream) => { - match Pin::new(stream).poll_next(&mut ctx) { + match Pin::new(stream).poll_next(ctx) { Poll::Ready(None) => return Poll::Ready(None), Poll::Ready(Some(value)) => { *ready = Some((field_name.clone(), value));