From d0b56f9222d0e907fe280d86c1f71c4e8453be38 Mon Sep 17 00:00:00 2001 From: ilslv <47687266+ilslv@users.noreply.github.com> Date: Fri, 1 Apr 2022 21:10:45 +0300 Subject: [PATCH] Implement `#[derive(GraphQLInterface)]` to use structs as GraphQL interfaces (#1026) - support `#[graphql_interface]` on structs --- .../derive_incompatible_object.stderr | 8 +- .../additional_non_nullable_argument.stderr | 7 - ...{wrong_item_enum.rs => attr_wrong_item.rs} | 0 .../fail/interface/attr_wrong_item.stderr | 5 + .../fail/interface/derive_wrong_item.rs | 12 + .../fail/interface/derive_wrong_item.stderr | 6 + .../field_non_output_return_type.stderr | 7 - .../fail/interface/missing_field.stderr | 7 - .../interface/missing_field_argument.stderr | 7 - .../fail/interface/non_subtype_return.stderr | 7 - .../attr_additional_non_nullable_argument.rs | 19 + ...tr_additional_non_nullable_argument.stderr | 7 + .../struct/attr_field_double_underscored.rs | 8 + .../attr_field_double_underscored.stderr | 7 + .../attr_field_non_output_return_type.rs | 13 + .../attr_field_non_output_return_type.stderr | 5 + .../interface/struct/attr_fields_duplicate.rs | 11 + .../struct/attr_fields_duplicate.stderr | 12 + .../attr_implementers_duplicate_pretty.rs | 14 + .../attr_implementers_duplicate_pretty.stderr | 11 + .../attr_implementers_duplicate_ugly.rs | 16 + .../attr_implementers_duplicate_ugly.stderr | 21 + .../interface/struct/attr_missing_field.rs | 14 + .../struct/attr_missing_field.stderr | 7 + .../interface/struct/attr_missing_for_attr.rs | 14 + .../struct/attr_missing_for_attr.stderr | 7 + .../attr_missing_impl_attr.rs} | 6 +- .../struct/attr_missing_impl_attr.stderr | 7 + .../struct/attr_name_double_underscored.rs | 8 + .../attr_name_double_underscored.stderr | 7 + .../fail/interface/struct/attr_no_fields.rs | 6 + .../interface/struct/attr_no_fields.stderr | 7 + .../struct/attr_non_subtype_return.rs | 14 + .../struct/attr_non_subtype_return.stderr | 7 + .../interface/struct/attr_unnamed_field.rs | 6 + .../struct/attr_unnamed_field.stderr | 7 + ...derive_additional_non_nullable_argument.rs | 20 + ...ve_additional_non_nullable_argument.stderr | 7 + .../struct/derive_field_double_underscored.rs | 8 + .../derive_field_double_underscored.stderr | 7 + .../derive_field_non_output_return_type.rs | 13 + ...derive_field_non_output_return_type.stderr | 5 + .../struct/derive_fields_duplicate.rs | 11 + .../struct/derive_fields_duplicate.stderr | 12 + .../derive_implementers_duplicate_pretty.rs | 15 + ...erive_implementers_duplicate_pretty.stderr | 11 + .../derive_implementers_duplicate_ugly.rs | 17 + .../derive_implementers_duplicate_ugly.stderr | 21 + .../interface/struct/derive_missing_field.rs | 15 + .../struct/derive_missing_field.stderr | 7 + .../struct/derive_missing_for_attr.rs | 14 + .../struct/derive_missing_for_attr.stderr | 7 + .../struct/derive_missing_impl_attr.rs | 14 + .../struct/derive_missing_impl_attr.stderr | 7 + .../struct/derive_name_double_underscored.rs | 8 + .../derive_name_double_underscored.stderr | 7 + .../fail/interface/struct/derive_no_fields.rs | 6 + .../interface/struct/derive_no_fields.stderr | 7 + .../struct/derive_non_subtype_return.rs | 15 + .../struct/derive_non_subtype_return.stderr | 7 + .../interface/struct/derive_unnamed_field.rs | 6 + .../struct/derive_unnamed_field.stderr | 7 + .../additional_non_nullable_argument.rs | 0 .../additional_non_nullable_argument.stderr | 7 + .../argument_double_underscored.rs | 0 .../argument_double_underscored.stderr | 2 +- .../{ => trait}/argument_non_input_type.rs | 0 .../argument_non_input_type.stderr | 12 +- .../argument_wrong_default_array.rs | 0 .../argument_wrong_default_array.stderr | 6 +- .../{ => trait}/field_double_underscored.rs | 0 .../field_double_underscored.stderr | 2 +- .../field_non_output_return_type.rs | 0 .../trait/field_non_output_return_type.stderr | 5 + .../interface/{ => trait}/fields_duplicate.rs | 0 .../{ => trait}/fields_duplicate.stderr | 2 +- .../implementers_duplicate_pretty.rs | 0 .../implementers_duplicate_pretty.stderr | 4 +- .../implementers_duplicate_ugly.rs | 0 .../implementers_duplicate_ugly.stderr | 4 +- .../{ => trait}/method_default_impl.rs | 0 .../{ => trait}/method_default_impl.stderr | 2 +- .../interface/{ => trait}/missing_field.rs | 0 .../fail/interface/trait/missing_field.stderr | 7 + .../{ => trait}/missing_field_argument.rs | 0 .../trait/missing_field_argument.stderr | 7 + .../interface/{ => trait}/missing_for_attr.rs | 0 .../{ => trait}/missing_for_attr.stderr | 4 +- .../{ => trait}/missing_impl_attr.rs | 0 .../{ => trait}/missing_impl_attr.stderr | 4 +- .../{ => trait}/name_double_underscored.rs | 0 .../name_double_underscored.stderr | 2 +- .../fail/interface/{ => trait}/no_fields.rs | 0 .../interface/{ => trait}/no_fields.stderr | 2 +- .../{ => trait}/non_subtype_return.rs | 0 .../interface/trait/non_subtype_return.stderr | 7 + .../{ => trait}/wrong_argument_type.rs | 0 .../trait/wrong_argument_type.stderr | 7 + .../fail/interface/wrong_argument_type.stderr | 7 - .../fail/interface/wrong_item_enum.stderr | 7 - .../interface/wrong_item_impl_block.stderr | 7 - .../object/argument_non_input_type.stderr | 8 +- .../argument_wrong_default_array.stderr | 4 +- .../attr_field_non_output_return_type.stderr | 8 +- ...derive_field_non_output_return_type.stderr | 10 +- .../argument_non_input_type.stderr | 8 +- .../argument_wrong_default_array.stderr | 4 +- .../field_non_output_return_type.stderr | 8 +- .../src/codegen/interface_attr_struct.rs | 2540 ++++++++++++++++ ...erface_attr.rs => interface_attr_trait.rs} | 140 +- .../src/codegen/interface_derive.rs | 2560 +++++++++++++++++ .../juniper_tests/src/codegen/mod.rs | 4 +- juniper/CHANGELOG.md | 1 + juniper/src/lib.rs | 2 +- juniper/src/macros/reflect.rs | 2 +- juniper/src/types/marker.rs | 3 +- juniper_codegen/src/common/field/arg.rs | 8 +- juniper_codegen/src/common/field/mod.rs | 4 +- juniper_codegen/src/common/parse/mod.rs | 77 +- juniper_codegen/src/graphql_interface/attr.rs | 271 +- .../src/graphql_interface/derive.rs | 159 + juniper_codegen/src/graphql_interface/mod.rs | 301 +- juniper_codegen/src/lib.rs | 80 +- juniper_codegen/src/result.rs | 5 +- juniper_codegen/src/util/mod.rs | 6 +- 125 files changed, 6537 insertions(+), 384 deletions(-) delete mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr rename integration_tests/codegen_fail/fail/interface/{wrong_item_enum.rs => attr_wrong_item.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs create mode 100644 integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr rename integration_tests/codegen_fail/fail/interface/{wrong_item_impl_block.rs => struct/attr_missing_impl_attr.rs} (72%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/additional_non_nullable_argument.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_double_underscored.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_double_underscored.stderr (83%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_non_input_type.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_non_input_type.stderr (65%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_wrong_default_array.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_wrong_default_array.stderr (81%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/field_double_underscored.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/field_double_underscored.stderr (83%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/field_non_output_return_type.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/fields_duplicate.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/fields_duplicate.stderr (84%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_pretty.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_pretty.stderr (70%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_ugly.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_ugly.stderr (88%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/method_default_impl.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/method_default_impl.stderr (82%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_field.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_field_argument.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_for_attr.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_for_attr.stderr (82%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_impl_attr.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_impl_attr.stderr (80%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/name_double_underscored.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/name_double_underscored.stderr (83%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/no_fields.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/no_fields.stderr (79%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/non_subtype_return.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/wrong_argument_type.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr create mode 100644 integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs rename integration_tests/juniper_tests/src/codegen/{interface_attr.rs => interface_attr_trait.rs} (94%) create mode 100644 integration_tests/juniper_tests/src/codegen/interface_derive.rs create mode 100644 juniper_codegen/src/graphql_interface/derive.rs diff --git a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr b/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr index fd21efcca..53ac2dee6 100644 --- a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr +++ b/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr @@ -1,10 +1,8 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied - --> fail/input-object/derive_incompatible_object.rs:6:10 - | -6 | #[derive(juniper::GraphQLInputObject)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` + --> fail/input-object/derive_incompatible_object.rs:8:12 | - = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) +8 | field: ObjectA, + | ^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_object.rs:6:10 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 deleted file mode 100644 index 589ed21b5..000000000 --- a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -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/wrong_item_enum.rs b/integration_tests/codegen_fail/fail/interface/attr_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs rename to integration_tests/codegen_fail/fail/interface/attr_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr new file mode 100644 index 000000000..410f72bd2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr @@ -0,0 +1,5 @@ +error: GraphQL interface #[graphql_interface] attribute is applicable to trait and struct definitions only + --> fail/interface/attr_wrong_item.rs:9:1 + | +9 | enum Character {} + | ^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs new file mode 100644 index 000000000..2ba9b2923 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs @@ -0,0 +1,12 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + test: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +enum Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr new file mode 100644 index 000000000..9b9be446f --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr @@ -0,0 +1,6 @@ +error: GraphQL interface can only be derived on structs + --> fail/interface/derive_wrong_item.rs:9:1 + | +9 | / #[graphql(for = ObjA)] +10 | | enum Character {} + | |_________________^ 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 deleted file mode 100644 index 5bd7c8c9c..000000000 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> 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/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/missing_field.stderr deleted file mode 100644 index d0efa3518..000000000 --- a/integration_tests/codegen_fail/fail/interface/missing_field.stderr +++ /dev/null @@ -1,7 +0,0 @@ -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.stderr b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr deleted file mode 100644 index 719cfd1a8..000000000 --- a/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -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/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr deleted file mode 100644 index ca48f8464..000000000 --- a/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr +++ /dev/null @@ -1,7 +0,0 @@ -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/struct/attr_additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs new file mode 100644 index 000000000..dfe2d7b55 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_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)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr new file mode 100644 index 000000000..60a8f73cd --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | +16 | id: String, + | ^^ 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/struct/attr_additional_non_nullable_argument.rs:16:5 + | + = 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/struct/attr_field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs new file mode 100644 index 000000000..0fb8bf91e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character { + __id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr new file mode 100644 index 000000000..31648f4e1 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/attr_field_double_underscored.rs:5:5 + | +5 | __id: String, + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs new file mode 100644 index 000000000..29be71aa0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs @@ -0,0 +1,13 @@ +use juniper::{graphql_interface, GraphQLInputObject}; + +#[derive(GraphQLInputObject)] +pub struct ObjB { + id: i32, +} + +#[graphql_interface] +struct Character { + id: ObjB, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr new file mode 100644 index 000000000..ecd251f1a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/struct/attr_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs new file mode 100644 index 000000000..f5cee2669 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs @@ -0,0 +1,11 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character { + id: String, + + #[graphql(name = "id")] + id2: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr new file mode 100644 index 000000000..b6a25f246 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr @@ -0,0 +1,12 @@ +error: GraphQL interface must have a different name for each field + --> fail/interface/struct/attr_fields_duplicate.rs:4:1 + | +4 | / struct Character { +5 | | id: String, +6 | | +7 | | #[graphql(name = "id")] +8 | | id2: String, +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs new file mode 100644 index 000000000..fd367d499 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = [ObjA, ObjA])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..c87ad2e06 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr @@ -0,0 +1,11 @@ +error: duplicated attribute argument found + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs new file mode 100644 index 000000000..f119eeaf4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs @@ -0,0 +1,16 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +type ObjAlias = ObjA; + +#[graphql_interface(for = [ObjA, ObjAlias])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr new file mode 100644 index 000000000..da24362cf --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr @@ -0,0 +1,21 @@ +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 + | +11 | #[graphql_interface(for = [ObjA, ObjAlias])] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | 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) + +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 + | +11 | #[graphql_interface(for = [ObjA, ObjAlias])] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `ObjA` + | + = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs new file mode 100644 index 000000000..b835e486b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_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)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr new file mode 100644 index 000000000..47856d5d4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = 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/struct/attr_missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs new file mode 100644 index 000000000..b7480d649 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_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] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr new file mode 100644 index 000000000..b7008d176 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_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/struct/attr_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/wrong_item_impl_block.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs similarity index 72% rename from integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs rename to integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs index 61e142410..5d822f716 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs @@ -2,10 +2,12 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] pub struct ObjA { - test: String, + id: String, } #[graphql_interface(for = ObjA)] -impl ObjA {} +struct Character { + id: String, +} fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr new file mode 100644 index 000000000..f03491d39 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_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/struct/attr_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/struct/attr_name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs new file mode 100644 index 000000000..ac0613b97 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct __Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr new file mode 100644 index 000000000..15db17f68 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/attr_name_double_underscored.rs:4:8 + | +4 | struct __Character { + | ^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs new file mode 100644 index 000000000..7dd3ab857 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs @@ -0,0 +1,6 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr new file mode 100644 index 000000000..6920674e2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface must have at least one field + --> fail/interface/struct/attr_no_fields.rs:4:1 + | +4 | struct Character {} + | ^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs new file mode 100644 index 000000000..8a931d1d4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_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)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr new file mode 100644 index 000000000..213ecb20d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_non_subtype_return.rs:11:5 + | +11 | id: String, + | ^^ 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/struct/attr_non_subtype_return.rs:11:5 + | + = 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/struct/attr_unnamed_field.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs new file mode 100644 index 000000000..479efd00c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs @@ -0,0 +1,6 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character(i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr new file mode 100644 index 000000000..6fa918e25 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface expected named struct field + --> fail/interface/struct/attr_unnamed_field.rs:4:18 + | +4 | struct Character(i32); + | ^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs new file mode 100644 index 000000000..5636cc177 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs @@ -0,0 +1,20 @@ +use juniper::{graphql_object, GraphQLInterface}; + +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") + } +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr new file mode 100644 index 000000000..cfe81a29b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 + | +17 | id: String, + | ^^ 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/struct/derive_additional_non_nullable_argument.rs:17:5 + | + = 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/struct/derive_field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs new file mode 100644 index 000000000..5be56fff9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character { + __id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr new file mode 100644 index 000000000..37b88b689 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/derive_field_double_underscored.rs:5:5 + | +5 | __id: String, + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs new file mode 100644 index 000000000..6e590c9c6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs @@ -0,0 +1,13 @@ +use juniper::{GraphQLInputObject, GraphQLInterface}; + +#[derive(GraphQLInputObject)] +pub struct ObjB { + id: i32, +} + +#[derive(GraphQLInterface)] +struct Character { + id: ObjB, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr new file mode 100644 index 000000000..fc418f480 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/struct/derive_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs new file mode 100644 index 000000000..1080e978a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs @@ -0,0 +1,11 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character { + id: String, + + #[graphql(name = "id")] + id2: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr new file mode 100644 index 000000000..a00789895 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr @@ -0,0 +1,12 @@ +error: GraphQL interface must have a different name for each field + --> fail/interface/struct/derive_fields_duplicate.rs:4:1 + | +4 | / struct Character { +5 | | id: String, +6 | | +7 | | #[graphql(name = "id")] +8 | | id2: String, +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs new file mode 100644 index 000000000..5dd70cfb6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs @@ -0,0 +1,15 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = [ObjA, ObjA])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..a4a803789 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr @@ -0,0 +1,11 @@ +error: duplicated attribute argument found + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:10:24 + | +10 | #[graphql(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs new file mode 100644 index 000000000..25f8d68c2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs @@ -0,0 +1,17 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +type ObjAlias = ObjA; + +#[derive(GraphQLInterface)] +#[graphql(for = [ObjA, ObjAlias])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr new file mode 100644 index 000000000..fde533c1b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr @@ -0,0 +1,21 @@ +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 + | +11 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` + | + = note: this error originates in the derive macro `GraphQLInterface` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 + | +11 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `ObjA` + | + = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs new file mode 100644 index 000000000..f5993e9ab --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs @@ -0,0 +1,15 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr new file mode 100644 index 000000000..a8570a6cf --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = 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/struct/derive_missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs new file mode 100644 index 000000000..536d7d6b4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs @@ -0,0 +1,14 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[derive(GraphQLInterface)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr new file mode 100644 index 000000000..9f394b974 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_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/struct/derive_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/struct/derive_missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs new file mode 100644 index 000000000..890012f7c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs @@ -0,0 +1,14 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr new file mode 100644 index 000000000..e092494a0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_impl_attr.rs:8:10 + | +8 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/derive_missing_impl_attr.rs:8: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/struct/derive_name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs new file mode 100644 index 000000000..a8ce1a690 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct __Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr new file mode 100644 index 000000000..0996adac9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/derive_name_double_underscored.rs:4:8 + | +4 | struct __Character { + | ^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs new file mode 100644 index 000000000..d06b8a6c0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr new file mode 100644 index 000000000..d0718a6de --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface must have at least one field + --> fail/interface/struct/derive_no_fields.rs:4:1 + | +4 | struct Character {} + | ^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs new file mode 100644 index 000000000..2bb1c0bf8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs @@ -0,0 +1,15 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr new file mode 100644 index 000000000..46c9de9de --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_non_subtype_return.rs:12:5 + | +12 | id: String, + | ^^ 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/struct/derive_non_subtype_return.rs:12:5 + | + = 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/struct/derive_unnamed_field.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs new file mode 100644 index 000000000..4ecd78c23 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character(i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr new file mode 100644 index 000000000..8a826c10c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface expected named struct field + --> fail/interface/struct/derive_unnamed_field.rs:4:18 + | +4 | struct Character(i32); + | ^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs rename to integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr new file mode 100644 index 000000000..f513ef68e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | +16 | fn id(&self) -> &str; + | ^^ 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/trait/additional_non_nullable_argument.rs:16:8 + | + = 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/trait/argument_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr similarity index 83% rename from integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr index 0a6e79eba..817231404 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr @@ -1,5 +1,5 @@ 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:5:18 + --> fail/interface/trait/argument_double_underscored.rs:5:18 | 5 | fn id(&self, __num: i32) -> &str; | ^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs rename to integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr similarity index 65% rename from integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr rename to integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr index e20698e5b..c532bab16 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr @@ -1,13 +1,11 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> 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) + --> fail/interface/trait/argument_non_input_type.rs:10:23 + | +10 | fn id(&self, obj: ObjA) -> &str; + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:8:1 + --> fail/interface/trait/argument_non_input_type.rs:8:1 | 8 | #[graphql_interface] | ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs rename to integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr similarity index 81% rename from integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr rename to integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr index c134913f8..d609ce029 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr @@ -1,14 +1,14 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:3:1 + --> fail/interface/trait/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: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - <[T; LANES] as From>> - <[bool; LANES] as From>> + and 11 others = 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/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/field_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr similarity index 83% rename from integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr index 6c683ed51..979daae6a 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr @@ -1,5 +1,5 @@ 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:5:8 + --> fail/interface/trait/field_double_underscored.rs:5:8 | 5 | fn __id(&self) -> &str; | ^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs rename to integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr new file mode 100644 index 000000000..2c6cf3a9f --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/trait/field_non_output_return_type.rs:10:21 + | +10 | fn id(&self) -> ObjB; + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/fields_duplicate.rs rename to integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr similarity index 84% rename from integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr rename to integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr index 143f85dab..e7fc50807 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr @@ -1,5 +1,5 @@ error: GraphQL interface must have a different name for each field - --> fail/interface/fields_duplicate.rs:4:1 + --> fail/interface/trait/fields_duplicate.rs:4:1 | 4 | / trait Character { 5 | | fn id(&self) -> &str; diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.rs diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr similarity index 70% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr index ce72fa2d7..5d5469027 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr @@ -1,11 +1,11 @@ error: duplicated attribute argument found - --> fail/interface/implementers_duplicate_pretty.rs:9:34 + --> fail/interface/trait/implementers_duplicate_pretty.rs:9:34 | 9 | #[graphql_interface(for = [ObjA, ObjA])] | ^^^^ error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/implementers_duplicate_pretty.rs:4:18 + --> fail/interface/trait/implementers_duplicate_pretty.rs:4:18 | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ 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/trait/implementers_duplicate_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.rs diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr similarity index 88% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 726e30c8d..74b695d8f 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,5 +1,5 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/implementers_duplicate_ugly.rs:11:1 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -10,7 +10,7 @@ error[E0119]: conflicting implementations of trait `std::convert::From` fo = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) 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 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.rs b/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/method_default_impl.rs rename to integration_tests/codegen_fail/fail/interface/trait/method_default_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr b/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/method_default_impl.stderr rename to integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr index 887107a68..b95687d48 100644 --- a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr @@ -1,5 +1,5 @@ error: GraphQL interface trait method can't have default implementation - --> fail/interface/method_default_impl.rs:5:26 + --> fail/interface/trait/method_default_impl.rs:5:26 | 5 | fn id(&self) -> &str { | __________________________^ diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.rs b/integration_tests/codegen_fail/fail/interface/trait/missing_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_field.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_field.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr new file mode 100644 index 000000000..d76c39ff6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = 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/trait/missing_field_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_field_argument.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr new file mode 100644 index 000000000..60ead166d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field_argument.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ 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/trait/missing_field_argument.rs:16:8 + | + = 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/trait/missing_for_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_for_attr.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr rename to integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr index 84072d87d..da011ad9d 100644 --- a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/missing_for_attr.rs:3:10 + --> fail/interface/trait/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 + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/trait/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/trait/missing_impl_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr rename to integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr index 767a53203..a82878d67 100644 --- a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/missing_impl_attr.rs:8:1 + --> fail/interface/trait/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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/trait/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/trait/name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/name_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr similarity index 83% rename from integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr index 2cc6aa61c..1fa999a49 100644 --- a/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr @@ -1,5 +1,5 @@ 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/name_double_underscored.rs:4:7 + --> fail/interface/trait/name_double_underscored.rs:4:7 | 4 | trait __Character { | ^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/trait/no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/no_fields.rs rename to integration_tests/codegen_fail/fail/interface/trait/no_fields.rs diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr similarity index 79% rename from integration_tests/codegen_fail/fail/interface/no_fields.stderr rename to integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr index 70ab57452..63c7d2968 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr @@ -1,5 +1,5 @@ error: GraphQL interface must have at least one field - --> fail/interface/no_fields.rs:4:1 + --> fail/interface/trait/no_fields.rs:4:1 | 4 | trait Character {} | ^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/non_subtype_return.rs rename to integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr new file mode 100644 index 000000000..ceefffb74 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/non_subtype_return.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ 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/trait/non_subtype_return.rs:11:8 + | + = 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/trait/wrong_argument_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs rename to integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr new file mode 100644 index 000000000..2921f2538 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/wrong_argument_type.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 + | + = 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.stderr b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr deleted file mode 100644 index 204bd1234..000000000 --- a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -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_enum.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr deleted file mode 100644 index 9c2607dd4..000000000 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: #[graphql_interface] attribute is applicable to trait definitions only - --> fail/interface/wrong_item_enum.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/codegen_fail/fail/interface/wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr deleted file mode 100644 index 712206c0b..000000000 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr +++ /dev/null @@ -1,7 +0,0 @@ -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/codegen_fail/fail/object/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr index 44e21e0a1..d4a3b19a8 100644 --- a/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr @@ -1,10 +1,8 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/object/argument_non_input_type.rs:10:1 - | -10 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + --> fail/object/argument_non_input_type.rs:12:23 | - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) +12 | fn id(&self, obj: ObjA) -> &str { + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied --> fail/object/argument_non_input_type.rs:10:1 diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr index 6992876c5..1e65f81c4 100644 --- a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr @@ -6,9 +6,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | = help: the following implementations were found: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - <[T; LANES] as From>> - <[bool; LANES] as From>> + and 11 others = 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_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr index 088f406e6..56c9587b8 100644 --- a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/object/attr_field_non_output_return_type.rs:10:1 + --> fail/object/attr_field_non_output_return_type.rs:12:21 | -10 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) +12 | fn id(&self) -> ObjB { + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr index 2a661f4c0..90a9be584 100644 --- a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/object/derive_field_non_output_return_type.rs:8:10 - | -8 | #[derive(GraphQLObject)] - | ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/object/derive_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr index 45c72bce6..43d9b36dd 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr @@ -1,10 +1,8 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/subscription/argument_non_input_type.rs:15:1 - | -15 | #[graphql_subscription] - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + --> fail/subscription/argument_non_input_type.rs:17:29 | - = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) +17 | async fn id(&self, obj: ObjA) -> Stream<'static, &'static str> { + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied --> fail/subscription/argument_non_input_type.rs:15:1 diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr index d6416583b..eafad0546 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr @@ -6,9 +6,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | = help: the following implementations were found: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - <[T; LANES] as From>> - <[bool; LANES] as From>> + and 11 others = 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_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr index 090a3123a..ed30b222e 100644 --- a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/subscription/field_non_output_return_type.rs:15:1 + --> fail/subscription/field_non_output_return_type.rs:17:27 | -15 | #[graphql_subscription] - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) +17 | async fn id(&self) -> Stream<'static, ObjB> { + | ^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs new file mode 100644 index 000000000..bb6c11106 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs @@ -0,0 +1,2540 @@ +//! Tests for `#[graphql_interface]` macro placed on a struct. + +use std::marker::PhantomData; + +use juniper::{ + execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, + FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, +}; + +use crate::util::{schema, schema_with_scalar}; + +mod no_implers { + use super::*; + + #[graphql_interface] + struct Character { + id: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_struct_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + 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); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: String, + } + + #[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#"{ + 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 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_struct_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 explicit_alias { + use super::*; + + #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterEnum)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterEnum)] + impl Droid { + 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) -> CharacterEnum { + 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#"{ + 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_struct_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])] + struct Character { + id: String, + } + + #[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#"{ + 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 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_struct_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 fallible_field { + use super::*; + + struct CustomError; + + impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + FieldError::new("Whatever", graphql_value!({"code": "some"})) + } + } + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: Result, + } + + #[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 { + fn id(&self) -> Result { + Ok(self.id.clone()) + } + + 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#"{ + 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 has_correct_graphql_type() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + 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![], + )), + ); + } +} + +mod generic { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: String, + + #[graphql(skip)] + _phantom: PhantomData<(A, B)>, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { + 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#"{ + 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 uses_struct_name_without_type_params() { + 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![])), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Rust docs. + #[graphql_interface(for = Human)] + struct Character { + /// Rust `id` docs. + /// Long. + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + fields { + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs.\nLong."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + #[graphql_interface(for = Human)] + struct Character { + id: String, + + #[deprecated] + a: String, + + #[deprecated(note = "Use `id`.")] + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[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; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_fields() { + const DOC: &str = r#"{ + character { + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": null}, + {"name": "a", "deprecationReason": null}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + /// Rust docs. + #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] + struct Character { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + id: String, + + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + a: String, + + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[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; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + myId + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "myId": "human-32", + "a": "a", + "b": "b", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + name + fields(includeDeprecated: true) { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyChar", + "fields": [ + {"name": "myId", "args": []}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + description + fields(includeDeprecated: true) { + name + description + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My character.", + "fields": [{ + "name": "myId", + "description": "My character ID.", + "args": [], + }, { + "name": "a", + "description": null, + "args": [], + }, { + "name": "b", + "description": null, + "args": [], + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": null, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": null, + }], + }}), + vec![], + )), + ); + } +} + +mod renamed_all_fields_and_args { + use super::*; + + #[graphql_interface(rename_all = "none", for = Human)] + struct Character { + id: String, + } + + struct Human; + + #[graphql_object(rename_all = "none", impl = CharacterValue)] + impl Human { + fn id() -> &'static str { + "human-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human.into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": "human-32", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_and_args_names() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + #[graphql_interface(scalar = DefaultScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = DefaultScalarValue)] + 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#"{ + 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![])), + ); + } + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + 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#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema_with_scalar::(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_with_scalar::(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_with_scalar::(*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 explicit_generic_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = S)] + struct Character { + id: FieldResult, + } + + #[derive(GraphQLObject)] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue)] + 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#"{ + 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![])), + ); + } + } +} + +mod bounded_generic_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { + 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#"{ + 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![])), + ); + } + } +} + +mod ignored_method { + use super::*; + + #[graphql_interface(for = Human)] + struct Character { + id: String, + + #[graphql(ignore)] + ignored: Option, + + #[graphql(skip)] + skipped: i32, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + +mod field_return_subtyping { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[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 { + 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#"{ + 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![])), + ); + } + } +} + +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])] + struct Character { + id: Option, + key_feature: 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 + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + "keyFeature": {"value": 10}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + "keyFeature": {"value": 42}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } + } + }"#; + + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_val = *expected_val; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "keyFeature": {"value": expected_val}, + }}), + vec![], + )), + ); + } + } +} + +mod nullable_argument_subtyping { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[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 { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") + } + + 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#"{ + 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(isPresent: true) + 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, "missing"), + ] { + 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![])), + ); + } + } +} diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs similarity index 94% rename from integration_tests/juniper_tests/src/codegen/interface_attr.rs rename to integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs index e5bdc4fce..c7c8ae156 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs @@ -1,4 +1,4 @@ -//! Tests for `#[graphql_interface]` macro. +//! Tests for `#[graphql_interface]` macro placed on a trait. use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, @@ -551,7 +551,10 @@ mod trivial_async { 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![], )), ); @@ -573,7 +576,10 @@ mod trivial_async { 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![], )), ); @@ -711,7 +717,7 @@ mod fallible_field { impl IntoFieldError for CustomError { fn into_field_error(self) -> FieldError { - juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + FieldError::new("Whatever", graphql_value!({"code": "some"})) } } @@ -783,7 +789,10 @@ mod fallible_field { 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![], )), ); @@ -805,7 +814,10 @@ mod fallible_field { 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![], )), ); @@ -861,7 +873,10 @@ mod fallible_field { "kind": "INTERFACE", "fields": [{ "name": "id", - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + "type": { + "kind": "NON_NULL", + "ofType": {"name": "String"}, + }, }], }}), vec![], @@ -963,7 +978,10 @@ mod generic { 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![], )), ); @@ -1580,7 +1598,11 @@ mod explicit_name_description_and_deprecation { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + graphql_value!({"character": { + "myId": "human-32", + "a": "a", + "b": "b", + }}), vec![], )), ); @@ -1861,7 +1883,10 @@ mod explicit_scalar { 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![], )), ); @@ -1883,7 +1908,10 @@ mod explicit_scalar { 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![], )), ); @@ -1985,7 +2013,10 @@ mod custom_scalar { 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![], )), ); @@ -2007,7 +2038,10 @@ mod custom_scalar { 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![], )), ); @@ -2107,7 +2141,10 @@ mod explicit_generic_scalar { 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![], )), ); @@ -2129,7 +2166,10 @@ mod explicit_generic_scalar { 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![], )), ); @@ -2229,7 +2269,10 @@ mod bounded_generic_scalar { 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![], )), ); @@ -2251,7 +2294,10 @@ mod bounded_generic_scalar { 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![], )), ); @@ -2384,7 +2430,10 @@ mod explicit_custom_context { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2406,7 +2455,10 @@ mod explicit_custom_context { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2537,7 +2589,10 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"humanId": "in-ctx", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "in-ctx", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2560,7 +2615,10 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"droidId": "in-droid", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "in-droid", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2694,7 +2752,10 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "humanId", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "humanId", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2716,7 +2777,10 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droidId", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droidId", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2738,7 +2802,10 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"id": "id", "info": expected_info}}), + graphql_value!({"character": { + "id": "id", + "info": expected_info, + }}), vec![], )), ); @@ -2825,7 +2892,10 @@ mod ignored_method { 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![], )), ); @@ -2940,7 +3010,10 @@ mod field_return_subtyping { 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![], )), ); @@ -2962,7 +3035,10 @@ mod field_return_subtyping { 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![], )), ); @@ -3245,7 +3321,10 @@ mod nullable_argument_subtyping { 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![], )), ); @@ -3267,7 +3346,10 @@ mod nullable_argument_subtyping { 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![], )), ); diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs new file mode 100644 index 000000000..38a8bbe14 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -0,0 +1,2560 @@ +//! Tests for `#[derive(GraphQLInterface)]` macro. + +use std::marker::PhantomData; + +use juniper::{ + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, FieldError, + FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, +}; + +use crate::util::{schema, schema_with_scalar}; + +mod no_implers { + use super::*; + + #[derive(GraphQLInterface)] + struct Character { + id: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_struct_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + 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); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: String, + } + + #[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#"{ + 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 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_struct_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 explicit_alias { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(enum = CharacterEnum, for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterEnum)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterEnum)] + impl Droid { + 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) -> CharacterEnum { + 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#"{ + 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_struct_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::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: String, + } + + #[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#"{ + 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 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_struct_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 fallible_field { + use super::*; + + struct CustomError; + + impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + FieldError::new("Whatever", graphql_value!({"code": "some"})) + } + } + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Result, + } + + #[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 { + fn id(&self) -> Result { + Ok(self.id.clone()) + } + + 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#"{ + 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 has_correct_graphql_type() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + 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![], + )), + ); + } +} + +mod generic { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: String, + + #[graphql(skip)] + _phantom: PhantomData<(A, B)>, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { + 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#"{ + 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 uses_struct_name_without_type_params() { + 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![])), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Rust docs. + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + struct Character { + /// Rust `id` docs. + /// Long. + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + fields { + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs.\nLong."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + struct Character { + id: String, + + #[deprecated] + a: String, + + #[deprecated(note = "Use `id`.")] + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[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; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_fields() { + const DOC: &str = r#"{ + character { + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": null}, + {"name": "a", "deprecationReason": null}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + /// Rust docs. + #[derive(GraphQLInterface)] + #[graphql(name = "MyChar", desc = "My character.", for = Human)] + struct Character { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + id: String, + + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + a: String, + + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[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; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + myId + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "myId": "human-32", + "a": "a", + "b": "b", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + name + fields(includeDeprecated: true) { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyChar", + "fields": [ + {"name": "myId", "args": []}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + description + fields(includeDeprecated: true) { + name + description + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My character.", + "fields": [{ + "name": "myId", + "description": "My character ID.", + "args": [], + }, { + "name": "a", + "description": null, + "args": [], + }, { + "name": "b", + "description": null, + "args": [], + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": null, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": null, + }], + }}), + vec![], + )), + ); + } +} + +mod renamed_all_fields_and_args { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(rename_all = "none", for = Human)] + struct Character { + id: String, + } + + struct Human; + + #[graphql_object(rename_all = "none", impl = CharacterValue)] + impl Human { + fn id() -> &'static str { + "human-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human.into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": "human-32", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_and_args_names() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + #[graphql(scalar = DefaultScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = DefaultScalarValue)] + 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#"{ + 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![])), + ); + } + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid], scalar = MyScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + 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#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema_with_scalar::(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_with_scalar::(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_with_scalar::(*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 explicit_generic_scalar { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid], scalar = S)] + struct Character { + id: FieldResult, + } + + #[derive(GraphQLObject)] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue)] + 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#"{ + 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![])), + ); + } + } +} + +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { + 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#"{ + 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![])), + ); + } + } +} + +mod ignored_method { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + struct Character { + id: String, + + #[graphql(ignore)] + ignored: Option, + + #[graphql(skip)] + skipped: i32, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + +mod field_return_subtyping { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[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 { + 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#"{ + 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![])), + ); + } + } +} + +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), + } + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Option, + key_feature: 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 + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + "keyFeature": {"value": 10}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + "keyFeature": {"value": 42}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } + } + }"#; + + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_val = *expected_val; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "keyFeature": {"value": expected_val}, + }}), + vec![], + )), + ); + } + } +} + +mod nullable_argument_subtyping { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[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 { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") + } + + 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#"{ + 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(isPresent: true) + 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, "missing"), + ] { + 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![])), + ); + } + } +} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index ba6bfd726..3a709c66a 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -1,7 +1,9 @@ mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; -mod interface_attr; +mod interface_attr_struct; +mod interface_attr_trait; +mod interface_derive; mod object_attr; mod object_derive; mod scalar_attr_derive_input; diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 700175119..e8604ddf9 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -47,6 +47,7 @@ - Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005)) - Implement `#[derive(ScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) +- Implement `#[derive(GraphQLInterface)]` macro to use structs as GraphQL interfaces. ([#1026](https://github.com/graphql-rust/juniper/pull/1026)) ## Fixes diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index d26f8dcb7..817206181 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -117,7 +117,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // functionality automatically. pub use juniper_codegen::{ graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalar, GraphQLUnion, + GraphQLEnum, GraphQLInputObject, GraphQLInterface, GraphQLObject, GraphQLScalar, GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 64b04c2c2..4cba097f8 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -194,7 +194,7 @@ pub type WrappedValue = u128; /// /// 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]"); +/// assert_eq!(format_type!(TYPE_STR, WRAP_VAL_STR), "[String]"); /// ``` /// /// [`VALUE`]: Self::VALUE diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index b04a41906..2dd22789e 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -186,8 +186,7 @@ where /// types. Each type which can be used as an output type should /// implement this trait. The specification defines enum, scalar, /// object, union, and interface as output types. -// TODO: Re-enable GraphQLType requirement in #682 -pub trait IsOutputType /*: GraphQLType*/ { +pub trait IsOutputType: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 0534432c2..fdd8f6480 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -6,7 +6,7 @@ use std::mem; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, @@ -306,7 +306,7 @@ impl OnMethod { #[must_use] pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> Option { let ty = &self.as_regular()?.ty; - Some(quote! { + Some(quote_spanned! { ty.span() => <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) } @@ -333,7 +333,9 @@ impl OnMethod { .as_ref() .map(|v| quote! { (#v).into() }) .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote! { .arg_with_default::<#ty>(#name, &#val, info) } + quote_spanned! { val.span() => + .arg_with_default::<#ty>(#name, &#val, info) + } } else { quote! { .arg::<#ty>(#name, info) } }; diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index bf299b458..2efe969f6 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -6,7 +6,7 @@ pub(crate) mod arg; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::{ parse::{Parse, ParseStream}, parse_quote, @@ -280,7 +280,7 @@ impl Definition { >>::Type }; - quote! { + quote_spanned! { self.ty.span() => #( #args_marks )* <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 05e4b8604..c9c79dc72 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -254,6 +254,10 @@ pub(crate) trait GenericsExt { /// 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); + + /// Replaces generic parameters in the given [`syn::TypePath`] with default + /// ones, provided by these [`syn::Generics`]. + fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath); } impl GenericsExt for syn::Generics { @@ -305,40 +309,51 @@ impl GenericsExt for syn::Generics { } fn replace_type_with_defaults(&self, ty: &mut syn::Type) { - struct Replace<'a>(&'a syn::Generics); + ReplaceWithDefaults(self).visit_type_mut(ty) + } - 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); - } - } - _ => {} + fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath) { + ReplaceWithDefaults(self).visit_type_path_mut(ty) + } +} + +/// Replaces [`Generics`] with default values: +/// - `'static` for [`Lifetime`]s; +/// - `::juniper::DefaultScalarValue` for [`Type`]s. +/// +/// [`Generics`]: syn::Generics +/// [`Lifetime`]: syn::Lifetime +/// [`Type`]: syn::Type +struct ReplaceWithDefaults<'a>(&'a syn::Generics); + +impl<'a> VisitMut for ReplaceWithDefaults<'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/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 88f8b0d61..cea1c653e 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::{format_ident, quote}; +use quote::quote; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -16,31 +16,37 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{Definition, TraitAttr}; +use super::{enum_idents, Attr, Definition}; /// [`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) { + if let Ok(mut ast) = syn::parse2::(body.clone()) { 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); } + 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_derive_input(trait_attrs, ast); + } Err(syn::Error::new( Span::call_site(), - "#[graphql_interface] attribute is applicable to trait definitions only", + "#[graphql_interface] attribute is applicable to trait and struct \ + definitions only", )) } -/// Expands `#[graphql_interface]` macro placed on trait definition. +/// Expands `#[graphql_interface]` macro placed on the given trait definition. fn expand_on_trait( attrs: Vec, mut ast: syn::ItemTrait, ) -> syn::Result { - let attr = TraitAttr::from_attrs("graphql_interface", &attrs)?; + let attr = Attr::from_attrs("graphql_interface", &attrs)?; let trait_ident = &ast.ident; let trait_span = ast.span(); @@ -69,14 +75,16 @@ fn expand_on_trait( .copied() .unwrap_or(RenameRule::CamelCase); - let mut fields = vec![]; - for item in &mut ast.items { - if let syn::TraitItem::Method(m) = item { - if let Some(f) = parse_field(m, &renaming) { - fields.push(f) + let fields = ast + .items + .iter_mut() + .filter_map(|item| { + if let syn::TraitItem::Method(m) = item { + return parse_trait_method(m, &renaming); } - } - } + None + }) + .collect::>(); proc_macro_error::abort_if_dirty(); @@ -104,18 +112,10 @@ fn expand_on_trait( }) .unwrap_or_else(|| parse_quote! { () }); - 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 (enum_ident, enum_alias_ident) = enum_idents(trait_ident, attr.r#enum.as_deref()); let generated_code = Definition { - trait_generics: ast.generics.clone(), + generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, enum_alias_ident, @@ -124,11 +124,12 @@ fn expand_on_trait( context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), + suppress_dead_code: None, }; Ok(quote! { @@ -139,9 +140,9 @@ fn expand_on_trait( /// Parses a [`field::Definition`] from the given trait method definition. /// -/// Returns [`None`] if parsing fails, or the method field is ignored. +/// Returns [`None`] if the parsing fails, or the method field is ignored. #[must_use] -fn parse_field( +fn parse_trait_method( method: &mut syn::TraitItemMethod, renaming: &RenameRule, ) -> Option { @@ -181,33 +182,15 @@ fn parse_field( 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); - } - } - 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() - }; + let arguments = method + .sig + .inputs + .iter_mut() + .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! { () }, @@ -233,6 +216,167 @@ fn parse_field( }) } +/// Expands `#[graphql_interface]` macro placed on the given struct. +fn expand_on_derive_input( + attrs: Vec, + mut ast: syn::DeriveInput, +) -> syn::Result { + let attr = Attr::from_attrs("graphql_interface", &attrs)?; + + let trait_ident = &ast.ident; + let trait_span = ast.span(); + + let data = match &mut ast.data { + syn::Data::Struct(data) => data, + syn::Data::Enum(_) | syn::Data::Union(_) => { + return Err(ERR.custom_error( + ast.span(), + "#[graphql_interface] attribute is applicable to trait and \ + struct definitions only", + )); + } + }; + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| trait_ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| trait_ident.span()), + ); + } + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let renaming = attr + .rename_fields + .as_deref() + .copied() + .unwrap_or(RenameRule::CamelCase); + + let fields = data + .fields + .iter_mut() + .filter_map(|f| parse_struct_field(f, &renaming)) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(trait_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(trait_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + + let context = attr + .context + .as_deref() + .cloned() + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments.as_ref().and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) + }) + }) + .unwrap_or_else(|| parse_quote! { () }); + + let (enum_ident, enum_alias_ident) = enum_idents(trait_ident, attr.r#enum.as_deref()); + let generated_code = Definition { + generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, + name, + description: attr.description.as_deref().cloned(), + context, + scalar, + fields, + implemented_for: attr + .implemented_for + .iter() + .map(|c| c.inner().clone()) + .collect(), + suppress_dead_code: None, + }; + + Ok(quote! { + #[allow(dead_code)] + #ast + #generated_code + }) +} + +/// Parses a [`field::Definition`] from the given struct field definition. +/// +/// Returns [`None`] if the parsing fails, or the struct field is ignored. +#[must_use] +fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option { + let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; + let field_attrs = field.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + field.attrs = mem::take(&mut field.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); + + let attr = field::Attr::from_attrs("graphql", &field_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.ignore.is_some() { + return None; + } + + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| field_ident.span()), + ); + return None; + } + + let mut ty = field.ty.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)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: field_ident.clone(), + arguments: None, + has_receiver: false, + is_async: false, + }) +} + /// Emits "trait method can't have default implementation" [`syn::Error`] /// pointing to the given `span`. fn err_default_impl_block(span: &S) -> Option { @@ -243,22 +387,9 @@ fn err_default_impl_block(span: &S) -> Option { None } -/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given -/// `span`. -fn err_invalid_method_receiver(span: &S) -> Option { - ERR.emit_custom( - span.span(), - "trait method receiver can only be a shared reference `&self`", - ); - None -} - -/// Emits "no trait method receiver" [`syn::Error`] pointing to the given +/// Emits "expected named struct field" [`syn::Error`] pointing to the given /// `span`. -fn err_no_method_receiver(span: &S) -> Option { - ERR.emit_custom( - span.span(), - "trait method should have a shared reference receiver `&self`", - ); +pub(crate) fn err_unnamed_field(span: &S) -> Option { + ERR.emit_custom(span.span(), "expected named struct field"); None } diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs new file mode 100644 index 000000000..fe55a4ff8 --- /dev/null +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -0,0 +1,159 @@ +//! Code generation for `#[derive(GraphQLInterface)]` macro. + +use proc_macro2::TokenStream; +use quote::ToTokens as _; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; + +use crate::{ + common::{field, parse::TypeExt as _, scalar}, + result::GraphQLScope, + util::{span_container::SpanContainer, RenameRule}, +}; + +use super::{attr::err_unnamed_field, enum_idents, Attr, Definition}; + +/// [`GraphQLScope`] of errors for `#[derive(GraphQLInterface)]` macro. +const ERR: GraphQLScope = GraphQLScope::InterfaceDerive; + +/// Expands `#[derive(GraphQLInterface)]` macro into generated code. +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + let attr = Attr::from_attrs("graphql", &ast.attrs)?; + + let data = if let syn::Data::Struct(data) = &ast.data { + data + } else { + return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); + }; + + let struct_ident = &ast.ident; + let struct_span = ast.span(); + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| struct_ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| struct_ident.span()), + ); + } + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let renaming = attr + .rename_fields + .as_deref() + .copied() + .unwrap_or(RenameRule::CamelCase); + + let fields = data + .fields + .iter() + .filter_map(|f| parse_field(f, &renaming)) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(struct_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(struct_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + + let context = attr + .context + .as_deref() + .cloned() + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments.as_ref().and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) + }) + }) + .unwrap_or_else(|| parse_quote! { () }); + + let (enum_ident, enum_alias_ident) = enum_idents(struct_ident, attr.r#enum.as_deref()); + + Ok(Definition { + generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, + name, + description: attr.description.as_deref().cloned(), + context, + scalar, + fields, + implemented_for: attr + .implemented_for + .iter() + .map(|c| c.inner().clone()) + .collect(), + suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())), + } + .into_token_stream()) +} + +/// Parses a [`field::Definition`] from the given struct field definition. +/// +/// Returns [`None`] if the parsing fails, or the struct field is ignored. +#[must_use] +fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { + let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; + + let attr = field::Attr::from_attrs("graphql", &field.attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.ignore.is_some() { + return None; + } + + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| field_ident.span()), + ); + return None; + } + + let mut ty = field.ty.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)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: field_ident.clone(), + arguments: None, + has_receiver: false, + is_async: false, + }) +} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 263d1c5f7..018d22534 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,17 +3,18 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; +pub mod derive; use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, - spanned::Spanned as _, + spanned::Spanned, token, visit::Visit, }; @@ -30,12 +31,32 @@ use crate::{ util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; +/// Returns [`syn::Ident`]s for a generic enum deriving [`Clone`] and [`Copy`] +/// on it and enum alias which generic arguments are filled with +/// [GraphQL interface][1] implementers. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +fn enum_idents( + trait_ident: &syn::Ident, + alias_ident: Option<&syn::Ident>, +) -> (syn::Ident, syn::Ident) { + let enum_alias_ident = alias_ident + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + let enum_ident = alias_ident.map_or_else( + || format_ident!("{}ValueEnum", trait_ident.to_string()), + |c| format_ident!("{}Enum", c.to_string()), + ); + (enum_ident, enum_alias_ident) +} + /// Available arguments behind `#[graphql_interface]` attribute placed on a -/// trait definition, when generating code for [GraphQL interface][1] type. +/// trait or struct definition, when generating code for [GraphQL interface][1] +/// type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] -struct TraitAttr { +struct Attr { /// Explicitly specified name of [GraphQL interface][1] type. /// /// If [`None`], then Rust trait name is used by default. @@ -52,7 +73,7 @@ struct TraitAttr { description: Option>, /// Explicitly specified identifier of the type alias of Rust enum type - /// behind the trait, being an actual implementation of a + /// behind the trait or struct, being an actual implementation of a /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. @@ -65,7 +86,7 @@ struct TraitAttr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - implementers: HashSet>, + implemented_for: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -110,7 +131,7 @@ struct TraitAttr { is_internal: bool, } -impl Parse for TraitAttr { +impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { @@ -159,7 +180,7 @@ impl Parse for TraitAttr { >()? { let impler_span = impler.span(); out - .implementers + .implemented_for .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) .none_or_else(|_| err::dup_arg(impler_span))?; } @@ -201,7 +222,7 @@ impl Parse for TraitAttr { } } -impl TraitAttr { +impl Attr { /// Tries to merge two [`TraitAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { @@ -210,7 +231,7 @@ impl TraitAttr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), - implementers: try_merge_hashset!(implementers: self, another => span_joined), + implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), @@ -237,12 +258,14 @@ impl TraitAttr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces struct Definition { - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. + /// [`syn::Generics`] of the trait or struct describing the + /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, + generics: syn::Generics, - /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. + /// [`syn::Visibility`] of the trait or struct describing the + /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces vis: syn::Visibility, @@ -295,7 +318,15 @@ struct Definition { /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + implemented_for: Vec, + + /// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't + /// append `#[allow(dead_code)]` to the unused struct, representing + /// [GraphQL interface][1]. We generate hacky `const` which doesn't actually + /// use it, but suppresses this warning. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + suppress_dead_code: Option<(syn::Ident, syn::Fields)>, } impl ToTokens for Definition { @@ -323,48 +354,42 @@ impl Definition { 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 variant_gens_pars = (0..self.implemented_for.len()).map::(|id| { + let par = format_ident!("__I{}", id); + parse_quote! { #par } + }); let variants_idents = self - .implementers + .implemented_for .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 interface_gens = &self.generics; + let (interface_impl_gens, interface_ty_gens, interface_where_clause) = + self.generics.split_for_impl(); - let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + let (interface_gens_lifetimes, interface_gens_tys) = interface_gens .params .clone() .into_iter() - .partition::, _>(|par| { - matches!(par, syn::GenericParam::Lifetime(_)) - }); + .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); let enum_gens = { - let mut enum_gens = trait_gens.clone(); - enum_gens.params = trait_gens_lifetimes.clone(); + let mut enum_gens = interface_gens.clone(); + enum_gens.params = interface_gens_lifetimes.clone(); enum_gens.params.extend(variant_gens_pars.clone()); - enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens.params.extend(interface_gens_tys.clone()); enum_gens }; let enum_alias_gens = { - let mut enum_alias_gens = trait_gens.clone(); + let mut enum_alias_gens = interface_gens.clone(); enum_alias_gens.move_bounds_to_where_clause(); enum_alias_gens }; let enum_to_alias_gens = { - trait_gens_lifetimes + interface_gens_lifetimes .into_iter() .map(|par| match par { syn::GenericParam::Lifetime(def) => { @@ -373,8 +398,8 @@ impl Definition { } rest => quote! { #rest }, }) - .chain(self.implementers.iter().map(ToTokens::to_token_stream)) - .chain(trait_gens_tys.into_iter().map(|par| match par { + .chain(self.implemented_for.iter().map(ToTokens::to_token_stream)) + .chain(interface_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; quote! { #par_ident } @@ -384,7 +409,7 @@ impl Definition { }; let phantom_variant = self.has_phantom_variant().then(|| { - let phantom_params = trait_gens.params.iter().filter_map(|p| { + let phantom_params = interface_gens.params.iter().filter_map(|p| { let ty = match p { syn::GenericParam::Type(ty) => { let ident = &ty.ident; @@ -403,37 +428,37 @@ impl Definition { 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) - } + let from_impls = self + .implemented_for + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote! { + #[automatically_derived] + impl#interface_impl_gens ::std::convert::From<#ty> + for #alias_ident#interface_ty_gens + #interface_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),)* + #( #variants_idents(#variant_gens_pars), )* #phantom_variant } #[automatically_derived] #vis type #alias_ident#enum_alias_gens = - #enum_ident<#(#enum_to_alias_gens),*>; + #enum_ident<#( #enum_to_alias_gens ),*>; - #(#from_impls)* + #( #from_impls )* } } @@ -449,11 +474,29 @@ impl Definition { 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 (_, ty_generics, _) = self.generics.split_for_impl(); + + let implemented_for = &self.implemented_for; + let all_impled_for_unique = (implemented_for.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } + }); - let impler_tys = &self.implementers; - let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } + let suppress_dead_code = self.suppress_dead_code.as_ref().map(|(ident, fields)| { + let const_gens = self.const_trait_generics(); + let fields = fields.iter().map(|f| &f.ident); + + quote! {{ + const SUPPRESS_DEAD_CODE: () = { + let none = Option::<#ident#const_gens>::None; + match none { + Some(unreachable) => { + #( let _ = unreachable.#fields; )* + } + None => {} + } + }; + let _ = SUPPRESS_DEAD_CODE; + }} }); quote! { @@ -463,8 +506,9 @@ impl Definition { #where_clause { fn mark() { - #all_implers_unique - #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + #suppress_dead_code + #all_impled_for_unique + #( <#implemented_for as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } } @@ -483,7 +527,7 @@ impl Definition { 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_generics, _) = self.generics.split_for_impl(); let ty_const_generics = self.const_trait_generics(); let fields_marks = self @@ -491,7 +535,16 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter().collect::>(); + let is_output = self.implemented_for.iter().map(|impler| { + quote_spanned! { impler.span() => + <#impler as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); + + let const_impl_for = self.implemented_for.iter().cloned().map(|mut ty| { + generics.replace_type_path_with_defaults(&mut ty); + ty + }); quote! { #[automatically_derived] @@ -501,9 +554,11 @@ impl Definition { { fn mark() { #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + #( #is_output )* ::juniper::assert_interfaces_impls!( - #const_scalar, #ty#ty_const_generics, #(#impler_tys),* + #const_scalar, + #ty#ty_const_generics, + #( #const_impl_for ),* ); } } @@ -522,7 +577,7 @@ impl Definition { 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_generics, _) = self.generics.split_for_impl(); let name = &self.name; let description = self @@ -531,8 +586,8 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.implementers.clone(); - impler_tys.sort_unstable_by(|a, b| { + let mut implemented_for = self.implemented_for.clone(); + implemented_for.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); @@ -556,7 +611,7 @@ impl Definition { where #scalar: 'r, { // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_tys>(info); )* + #( let _ = registry.get_type::<#implemented_for>(info); )* let fields = [ #( #fields_meta, )* @@ -583,7 +638,7 @@ impl Definition { 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_generics, _) = self.generics.split_for_impl(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; @@ -664,7 +719,7 @@ impl Definition { 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 (_, ty_generics, _) = self.generics.split_for_impl(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; @@ -725,14 +780,14 @@ impl Definition { #[must_use] fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; - let implementers = &self.implementers; + let implemented_for = &self.implemented_for; 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(); + let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] @@ -750,7 +805,7 @@ impl Definition { { const NAMES: ::juniper::macros::reflect::Types = &[ >::NAME, - #(<#implementers as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* + #( <#implemented_for as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),* ]; } @@ -784,7 +839,7 @@ impl Definition { 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_generics, _) = self.generics.split_for_impl(); self.fields .iter() @@ -822,11 +877,11 @@ impl Definition { ::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, - )),*]; + ) ),*]; } } }) @@ -843,17 +898,25 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let const_implemented_for = self + .implemented_for + .iter() + .cloned() + .map(|mut impl_for| { + generics.replace_type_path_with_defaults(&mut impl_for); + impl_for + }) + .collect::>(); + let implemented_for_idents = self + .implemented_for .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| { @@ -863,13 +926,13 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) + let unreachable_arm = (self.implemented_for.is_empty() + || !self.generics.params.is_empty()) .then(|| { quote! { _ => unreachable!() } }); - quote! { + quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::macros::reflect::Field< @@ -883,10 +946,10 @@ impl Definition { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { match self { - #(#ty::#impl_idents(v) => { + #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, - #impl_tys, + #const_implemented_for, #const_scalar, #field_name, ); @@ -895,7 +958,7 @@ impl Definition { #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) - })* + } )* #unreachable_arm } } @@ -915,17 +978,25 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let const_implemented_for = self + .implemented_for + .iter() + .cloned() + .map(|mut impl_for| { + generics.replace_type_path_with_defaults(&mut impl_for); + impl_for + }) + .collect::>(); + let implemented_for_idents = self + .implemented_for .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| { @@ -935,13 +1006,13 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) + let unreachable_arm = (self.implemented_for.is_empty() + || !self.generics.params.is_empty()) .then(|| { quote! { _ => unreachable!() } }); - quote! { + quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::macros::reflect::AsyncField< @@ -955,10 +1026,10 @@ impl Definition { executor: &'b ::juniper::Executor, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match self { - #(#ty::#impl_idents(v) => { + #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, - #impl_tys, + #const_implemented_for, #const_scalar, #field_name, ); @@ -967,7 +1038,7 @@ impl Definition { #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) - })* + } )* #unreachable_arm } } @@ -988,7 +1059,7 @@ impl Definition { let scalar = &self.scalar; let match_arms = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) .map(|(ident, ty)| { @@ -1000,7 +1071,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1023,7 +1094,7 @@ impl Definition { fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - let match_arms = self.implementers.iter().filter_map(|ty| { + let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(v) => { @@ -1034,7 +1105,7 @@ impl Definition { }) }); let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1056,7 +1127,7 @@ impl Definition { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.implementers.iter().filter_map(|ty| { + let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(res) => #resolving_code, @@ -1065,7 +1136,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1112,7 +1183,7 @@ impl Definition { } let mut visitor = GenericsForConst(parse_quote!( <> )); - visitor.visit_generics(&self.trait_generics); + visitor.visit_generics(&self.generics); syn::PathArguments::AngleBracketed(visitor.0) } @@ -1126,7 +1197,7 @@ impl Definition { /// [`GraphQLType`]: juniper::GraphQLType #[must_use] fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); + let mut generics = self.generics.clone(); let scalar = &self.scalar; if scalar.is_implicit_generic() { @@ -1143,10 +1214,10 @@ impl Definition { } if for_async { - let self_ty = if self.trait_generics.lifetimes().next().is_some() { + let self_ty = if self.generics.lifetimes().next().is_some() { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. - let mut generics = self.trait_generics.clone(); + let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); lt.lifetime.ident = format_ident!("__fa__{}", ident); @@ -1180,6 +1251,6 @@ impl Definition { /// type parameters. #[must_use] fn has_phantom_variant(&self) -> bool { - !self.trait_generics.params.is_empty() + !self.generics.params.is_empty() } } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index cb9a0ba42..052ca188e 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -667,8 +667,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// [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. +/// impl block. /// /// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to @@ -678,7 +677,30 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// Macro uses Rust enums only to represent a value type of a /// [GraphQL interface][1]. /// +/// [GraphQL interface][1] can be represented with struct in case methods don't +/// have any arguments: +/// +/// ```rust +/// 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)] // enumerating all implementers is mandatory +/// struct Character { +/// id: String, +/// } +/// +/// #[derive(GraphQLObject)] +/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name +/// struct Human { +/// id: String, // this field is used to resolve Character::id +/// home_planet: String, +/// } /// ``` +/// +/// Also [GraphQL interface][1] can be represented with trait: +/// +/// ```rust /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this @@ -696,6 +718,11 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// } /// ``` /// +/// > __NOTE:__ Struct or trait representing interface acts only as a blueprint +/// > for names of methods, their arguments and return type, so isn't +/// > actually used at a runtime. But no-one is stopping you from +/// > implementing trait manually for your own usage. +/// /// # Custom name, description, deprecation and argument defaults /// /// The name of [GraphQL interface][1], its field, or a field argument may be overridden with a @@ -711,7 +738,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// The default value of a field argument may be specified with a `default` attribute argument (if /// no exact value is specified then [`Default::default`] is used). /// -/// ``` +/// ```rust /// # use juniper::graphql_interface; /// # /// #[graphql_interface(name = "Character", desc = "Possible episode characters.")] @@ -748,7 +775,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`, /// `none` (disables any renaming). /// -/// ``` +/// ```rust /// # use juniper::{graphql_interface, graphql_object}; /// # /// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming @@ -787,7 +814,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// To omit some trait method to be assumed as a [GraphQL interface][1] field /// and ignore it, use an `ignore` attribute's argument directly on that method. /// -/// ``` +/// ```rust /// # use juniper::graphql_interface; /// # /// #[graphql_interface] @@ -811,7 +838,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// or `ctx` then this argument is assumed as [`Context`] and will be omitted in GraphQL schema. /// Additionally, any argument may be marked as [`Context`] with a `context` attribute's argument. /// -/// ``` +/// ```rust /// # use std::collections::HashMap; /// # use juniper::{graphql_interface, graphql_object}; /// # @@ -870,7 +897,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// -/// ``` +/// ```rust /// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; /// # /// // NOTICE: Specifying `ScalarValue` as existing type parameter. @@ -912,7 +939,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// concrete [`ScalarValue`] type should be specified with a `scalar` /// attribute's argument. /// -/// ``` +/// ```rust /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// // NOTICE: Removing `Scalar` argument will fail compilation. @@ -945,6 +972,43 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +/// `#[derive(GraphQLInterface)]` macro for generating a [GraphQL interface][1] +/// implementation for traits and its implementers. +/// +/// This macro is applicable only to structs and useful in case [interface][1] +/// fields don't have any arguments: +/// +/// ```rust +/// use juniper::{GraphQLInterface, GraphQLObject}; +/// +/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this +/// // GraphQL interface. +/// #[derive(GraphQLInterface)] +/// #[graphql(for = Human)] // enumerating all implementers is mandatory +/// struct Character { +/// id: String, +/// } +/// +/// #[derive(GraphQLObject)] +/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name +/// struct Human { +/// id: String, // this field is used to resolve Character::id +/// home_planet: String, +/// } +/// ``` +/// +/// For more info and possibilities see [`#[graphql_interface]`] macro. +/// +/// [`#[graphql_interface]`]: crate::graphql_interface +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[proc_macro_error] +#[proc_macro_derive(GraphQLInterface, attributes(graphql))] +pub fn derive_interface(body: TokenStream) -> TokenStream { + self::graphql_interface::derive::expand(body.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index f3a06f426..8b58339a5 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -10,6 +10,7 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; pub enum GraphQLScope { InterfaceAttr, + InterfaceDerive, ObjectAttr, ObjectDerive, ScalarAttr, @@ -24,7 +25,7 @@ pub enum GraphQLScope { impl GraphQLScope { pub fn spec_section(&self) -> &str { match self { - Self::InterfaceAttr => "#sec-Interfaces", + Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars", Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars", @@ -38,7 +39,7 @@ impl GraphQLScope { impl fmt::Display for GraphQLScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { - Self::InterfaceAttr => "interface", + Self::InterfaceAttr | Self::InterfaceDerive => "interface", Self::ObjectAttr | Self::ObjectDerive => "object", Self::ScalarAttr | Self::ScalarDerive => "scalar", Self::ScalarValueDerive => "built-in scalars", diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 6c2a31bc6..510c3aeb2 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -7,7 +7,7 @@ use std::{collections::HashMap, convert::TryFrom, str::FromStr}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; -use quote::quote; +use quote::{quote, quote_spanned}; use span_container::SpanContainer; use syn::{ ext::IdentExt as _, @@ -1075,7 +1075,9 @@ impl GraphQLTypeDefiniton { let marks = self.fields.iter().map(|field| { let field_ty = &field._type; - quote! { <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + quote_spanned! { field_ty.span() => + <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); + } }); let mut body = quote!(