From 0909a0dcb1868cb0bb065537a7078d96ddb35204 Mon Sep 17 00:00:00 2001 From: tyranron Date: Sun, 14 Jun 2020 12:40:39 +0300 Subject: [PATCH 01/79] Bootstrap --- .../juniper_tests/src/codegen/derive_enum.rs | 4 +- .../src/codegen/derive_input_object.rs | 10 +- .../src/codegen/derive_object.rs | 4 +- .../src/codegen/interface_attr.rs | 415 ++++++++++++++++++ .../juniper_tests/src/codegen/mod.rs | 1 + .../src/codegen/scalar_value_transparent.rs | 6 +- .../juniper_tests/src/codegen/union_attr.rs | 4 +- .../juniper_tests/src/codegen/union_derive.rs | 4 +- juniper/Cargo.toml | 32 +- juniper/src/executor/mod.rs | 28 +- juniper/src/lib.rs | 9 +- juniper/src/macros/interface.rs | 91 ++-- juniper/src/macros/tests/util.rs | 12 +- juniper/src/schema/model.rs | 20 +- juniper/src/schema/schema.rs | 33 +- .../src/schema/translate/graphql_parser.rs | 40 +- juniper/src/tests/model.rs | 11 +- juniper/src/tests/type_info_tests.rs | 31 +- juniper/src/types/async_await.rs | 12 +- juniper/src/types/base.rs | 261 ++++++----- juniper/src/types/containers.rs | 81 ++-- juniper/src/types/marker.rs | 34 +- juniper/src/types/pointers.rs | 87 ++-- juniper/src/types/scalars.rs | 45 +- juniper/src/types/subscriptions.rs | 4 +- .../rules/overlapping_fields_can_be_merged.rs | 101 ++++- juniper/src/validation/test_harness.rs | 180 +++++++- juniper_codegen/src/derive_scalar_value.rs | 18 +- juniper_codegen/src/graphql_union/mod.rs | 51 ++- juniper_codegen/src/impl_scalar.rs | 26 +- juniper_codegen/src/util/mod.rs | 129 ++++-- 31 files changed, 1361 insertions(+), 423 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/interface_attr.rs diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index 88a54a08b..a4879f4b5 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -2,7 +2,7 @@ use fnv::FnvHashMap; #[cfg(test)] -use juniper::{self, DefaultScalarValue, FromInputValue, GraphQLType, InputValue, ToInputValue}; +use juniper::{self, DefaultScalarValue, FromInputValue, GraphQLType, GraphQLTypeMeta, InputValue, ToInputValue}; pub struct CustomContext {} @@ -53,7 +53,7 @@ enum ContextEnum { fn test_derived_enum() { // Ensure that rename works. assert_eq!( - >::name(&()), + >::name(&()), Some("Some") ); diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index 664473cba..70d77d934 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -1,7 +1,7 @@ use fnv::FnvHashMap; use juniper::{ - self, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, InputValue, + self, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLTypeMeta, InputValue, ToInputValue, }; @@ -66,6 +66,12 @@ impl<'a> GraphQLType for &'a Fake { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + None + } +} + +impl<'a> GraphQLTypeMeta for &'a Fake { fn name(_: &()) -> Option<&'static str> { None } @@ -94,7 +100,7 @@ struct WithLifetime<'a> { #[test] fn test_derived_input_object() { assert_eq!( - >::name(&()), + >::name(&()), Some("MyInput") ); diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index b7dd51ca0..f8114be81 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -6,7 +6,7 @@ use juniper::{DefaultScalarValue, GraphQLObject}; #[cfg(test)] use juniper::{ - self, execute, EmptyMutation, EmptySubscription, GraphQLType, RootNode, Value, Variables, + self, execute, EmptyMutation, EmptySubscription, GraphQLTypeMeta, RootNode, Value, Variables, }; #[derive(GraphQLObject, Debug, PartialEq)] @@ -176,7 +176,7 @@ async fn test_doc_comment_override() { #[tokio::test] async fn test_derived_object() { assert_eq!( - >::name(&()), + >::name(&()), Some("MyObj") ); diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs new file mode 100644 index 000000000..af8c9a59b --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -0,0 +1,415 @@ +//! Tests for `#[graphql_interface]` macro. + +use juniper::{execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, GraphQLTypeMeta}; + +/* SUGARED +#[derive(GraphQLObject)] +#[graphql(implements Character)] +struct Human { + id: String, + home_planet: String, +} + DESUGARS INTO: */ +#[derive(GraphQLObject)] +struct Human { + id: String, + home_planet: String, +} +#[automatically_derived] +::juniper::inventory::submit! { + #![crate = juniper] + ::juniper::GraphQLInterfaceTypeImplementor { + interface_name: "Character", + object: ::juniper::GraphQLObjectTypeInfo { + name: "Human", + mark_fn: >::mark, + } + } +} +impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLType<__S> for Human { + type Context = >::Context; + type TypeInfo = >::TypeInfo; + + fn as_dyn_graphql_type(&self) -> &(dyn GraphQLType<__S, Context = Self::Context, TypeInfo = Self::TypeInfo> + 'static + Send + Sync) { + self + } +} + +/* SUGARED +#[graphql_interface] +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} + DESUGARS INTO: */ +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} + +// ------------------------------------------ + +/* SUGARED +#[derive(GraphQLObject)] +#[graphql(implements Character)] +struct Droid { + id: String, + primary_function: String, +} + DESUGARS INTO: */ +#[derive(GraphQLObject)] +struct Droid { + id: String, + primary_function: String, +} +::juniper::inventory::submit! { + #![crate = juniper] + ::juniper::GraphQLInterfaceTypeImplementor { + interface_name: "Character", + object: ::juniper::GraphQLObjectTypeInfo { + name: "Droid", + mark_fn: >::mark, + reg_fn: + } + } +} +#[automatically_derived] +impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLType<__S> for Droid { + type Context = >::Context; + type TypeInfo = >::TypeInfo; + + fn as_dyn_graphql_type(&self) -> &(dyn GraphQLType<__S, Context = Self::Context, TypeInfo = Self::TypeInfo> + 'static + Send + Sync) { + self + } +} + +/* SUGARED +#[graphql_interface] +impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + + fn as_droid(&self) -> Option<&Droid> { + Some(self) + } +} + DESUGARS INTO: */ +impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + + fn as_droid(&self) -> Option<&Droid> { + Some(self) + } +} + +// ------------------------------------------ + +/* SUGARED +#[graphql_interface] +trait Character { + fn id(&self) -> &str; + + #[graphql_interface(downcast)] + fn as_droid(&self) -> Option<&Droid> { None } +} + DESUGARS INTO: */ +trait Character: ::juniper::AsDynGraphQLType { + fn id(&self) -> &str; + + fn as_droid(&self) -> Option<&Droid> { None } +} +#[automatically_derived] +impl<'__obj> ::juniper::marker::GraphQLInterface for dyn Character + '__obj + Send + Sync +{ + fn mark() { + if let Some(objects) = ::juniper::GRAPHQL_IFACE_TYPES.get("Character") { + for obj in objects { + (obj.mark_fn)(); + } + } + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::marker::IsOutputType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + fn mark() { + if let Some(objects) = ::juniper::GRAPHQL_IFACE_TYPES.get("Character") { + for obj in objects { + (obj.mark_fn)(); + } + } + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::GraphQLType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + type Context = (); + type TypeInfo = (); + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + fn resolve_field( + &self, + _: &Self::TypeInfo, + field: &str, + _: &juniper::Arguments<__S>, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult<__S> { + match field { + "id" => { + let res = self.id(); + ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(juniper::Value::null()), + }) + } + _ => { + panic!( + "Field {} not found on GraphQL interface {}", + field, "Character", + ); + } + } + } + + fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { + + // First, check custom downcaster to be used. + if ({ Character::as_droid(self) } as ::std::option::Option<&Droid>).is_some() { + return >::name(info) + .unwrap() + .to_string(); + } + + // Otherwise, get concrete type name as dyn object. + self.as_dyn_graphql_type().concrete_type_name(context, info) + } + fn resolve_into_type( + &self, + ti: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<__S>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<__S> { + let context = executor.context(); + + // First, check custom downcaster to be used. + if type_name == (>::name(ti)).unwrap() { + return ::juniper::IntoResolvable::into( + Character::as_droid(self), + executor.context(), + ) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(::juniper::Value::null()), + }); + } + + // Otherwise, resolve inner type as dyn object. + return ::juniper::IntoResolvable::into( + self.as_dyn_graphql_type(), + executor.context(), + ) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(::juniper::Value::null()), + }); + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::GraphQLTypeMeta<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + fn name(_: &Self::TypeInfo) -> Option<&str> { + Some("Character") + } + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, __S>, + ) -> ::juniper::meta::MetaType<'r, __S> + where + __S: 'r, + { + // Ensure custom downcaster type is registered + let _ = registry.get_type::<&Droid>(info); + + // Ensure all child types are registered + // TODO: how? + // TODO: get_type_by_name and iter + //let _ = registry.get_type::<&Human>(info); + + let fields = vec![ + // TODO: try array + registry.field_convert::<&str, _, Self::Context>("id", info), + ]; + + registry + .build_interface_type:: + '__obj + Send + Sync>(info, &fields) + .into_meta() + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::GraphQLTypeAsync<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, + Self: Send + Sync, + __S: Send + Sync, +{ + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field_name: &'b str, + arguments: &'b ::juniper::Arguments<__S>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + // TODO: similar to what happens in GraphQLType impl + let res = self.resolve_field(info, field_name, arguments, executor); + ::juniper::futures::future::FutureExt::boxed(async move { res }) + } + + fn resolve_into_type_async<'b>( + &'b self, + ti: &'b Self::TypeInfo, + type_name: &str, + se: Option<&'b [::juniper::Selection<'b, __S>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + // TODO: similar to what happens in GraphQLType impl + let res = self.resolve_into_type(ti, type_name, se, executor); + ::juniper::futures::future::FutureExt::boxed(async move { res }) + } +} + +// ------------------------------------------ + +fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> + where + Q: GraphQLTypeMeta + 'q, + S: ScalarValue + 'q, +{ + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +mod poc { + use super::*; + + type DynCharacter<'a, S = DefaultScalarValue> = dyn Character + 'a + Send + Sync; + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_id_for_human() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"id": "human-32"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_for_droid() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"id": "droid-99"}}), + vec![], + )), + ); + } + + #[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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + humanId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} \ No newline at end of file diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 99d2b3651..37e12c31f 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -4,6 +4,7 @@ mod derive_object; mod derive_object_with_raw_idents; mod impl_object; mod impl_scalar; +mod interface_attr; mod scalar_value_transparent; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs index 6f52e5b64..511fe7c16 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs @@ -1,5 +1,5 @@ use fnv::FnvHashMap; -use juniper::{DefaultScalarValue, FromInputValue, GraphQLType, InputValue, ToInputValue}; +use juniper::{DefaultScalarValue, FromInputValue, GraphQLTypeMeta, InputValue, ToInputValue}; #[derive(juniper::GraphQLScalarValue, PartialEq, Eq, Debug)] #[graphql(transparent)] @@ -32,7 +32,7 @@ impl User2 { #[test] fn test_scalar_value_simple() { assert_eq!( - >::name(&()), + >::name(&()), Some("UserId") ); @@ -53,7 +53,7 @@ fn test_scalar_value_simple() { #[test] fn test_scalar_value_custom() { assert_eq!( - >::name(&()), + >::name(&()), Some("MyUserId") ); diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index 7377dbea3..185e5b055 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -2,7 +2,7 @@ use juniper::{ execute, graphql_object, graphql_union, graphql_value, DefaultScalarValue, EmptyMutation, - EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, + EmptySubscription, GraphQLObject, GraphQLTypeMeta, RootNode, ScalarValue, Variables, }; #[derive(GraphQLObject)] @@ -53,7 +53,7 @@ struct EwokCustomContext { fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> where - Q: GraphQLType + 'q, + Q: GraphQLTypeMeta + 'q, S: ScalarValue + 'q, { RootNode::new( diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index 55d4c047a..2fcef8140 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use juniper::{ execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, - GraphQLObject, GraphQLType, GraphQLUnion, RootNode, ScalarValue, Variables, + GraphQLObject, GraphQLTypeMeta, GraphQLUnion, RootNode, ScalarValue, Variables, }; #[derive(GraphQLObject)] @@ -55,7 +55,7 @@ struct EwokCustomContext { fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> where - Q: GraphQLType + 'q, + Q: GraphQLTypeMeta + 'q, S: ScalarValue + 'q, { RootNode::new( diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 431b30e2c..ac2f5eaa0 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -18,40 +18,42 @@ edition = "2018" [badges] travis-ci = { repository = "graphql-rust/juniper" } -[[bench]] -name = "bench" -harness = false -path = "benches/bench.rs" - [features] -expose-test-schema = ["serde_json"] -schema-language = ["graphql-parser-integration"] -graphql-parser-integration = ["graphql-parser"] default = [ "bson", "chrono", + "schema-language", "url", "uuid", - "schema-language", ] +expose-test-schema = ["serde_json"] +graphql-parser-integration = ["graphql-parser"] scalar-naivetime = [] +schema-language = ["graphql-parser-integration"] [dependencies] juniper_codegen = { version = "0.14.2", path = "../juniper_codegen" } -bson = { version = "1.0.0", optional = true } -chrono = { version = "0.4.0", optional = true } +bson = { version = "1.0", optional = true } +chrono = { version = "0.4", optional = true } fnv = "1.0.3" futures = "0.3.1" -indexmap = { version = "1.0.0", features = ["serde-1"] } +graphql-parser = { version = "0.3", optional = true } +indexmap = { version = "1.0", features = ["serde-1"] } +inventory = "0.1.6" +once_cell = "1.4" serde = { version = "1.0.8", features = ["derive"] } -serde_json = { version="1.0.2", optional = true } +serde_json = { version = "1.0.2", optional = true } static_assertions = "1.1" -url = { version = "2", optional = true } +url = { version = "2.0", optional = true } uuid = { version = "0.8", optional = true } -graphql-parser = {version = "0.3.0", optional = true } [dev-dependencies] bencher = "0.1.2" serde_json = { version = "1.0.2" } tokio = { version = "0.2", features = ["macros", "rt-core", "time"] } + +[[bench]] +name = "bench" +harness = false +path = "benches/bench.rs" diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 998c7d50f..901a5dae1 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -22,7 +22,7 @@ use crate::{ }, model::{RootNode, SchemaType, TypeType}, }, - types::{base::GraphQLType, name::Name}, + types::{base::{GraphQLType, GraphQLTypeMeta}, name::Name}, value::{DefaultScalarValue, ParseScalarValue, ScalarValue, Value}, GraphQLError, }; @@ -1101,7 +1101,7 @@ where /// construct its metadata and store it. pub fn get_type(&mut self, info: &T::TypeInfo) -> Type<'r> where - T: GraphQLType + ?Sized, + T: GraphQLTypeMeta + ?Sized, { if let Some(name) = T::name(info) { let validated_name = name.parse::().unwrap(); @@ -1122,7 +1122,7 @@ where /// Create a field with the provided name pub fn field(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S> where - T: GraphQLType + ?Sized, + T: GraphQLTypeMeta + ?Sized, { Field { name: name.to_owned(), @@ -1140,7 +1140,7 @@ where info: &I::TypeInfo, ) -> Field<'r, S> where - I: GraphQLType, + I: GraphQLTypeMeta, { Field { name: name.to_owned(), @@ -1154,7 +1154,7 @@ where /// Create an argument with the provided name pub fn arg(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S> where - T: GraphQLType + FromInputValue + ?Sized, + T: GraphQLTypeMeta + FromInputValue + ?Sized, { Argument::new(name, self.get_type::(info)) } @@ -1170,7 +1170,7 @@ where info: &T::TypeInfo, ) -> Argument<'r, S> where - T: GraphQLType + ToInputValue + FromInputValue + ?Sized, + T: GraphQLTypeMeta + ToInputValue + FromInputValue + ?Sized, { Argument::new(name, self.get_type::>(info)).default_value(value.to_input_value()) } @@ -1186,14 +1186,14 @@ where /// This expects the type to implement `FromInputValue`. pub fn build_scalar_type(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S> where - T: FromInputValue + GraphQLType + ParseScalarValue + ?Sized + 'r, + T: FromInputValue + GraphQLTypeMeta + ParseScalarValue + ?Sized + 'r, { let name = T::name(info).expect("Scalar types must be named. Implement name()"); ScalarMeta::new::(Cow::Owned(name.to_string())) } /// Create a list meta type - pub fn build_list_type + ?Sized>( + pub fn build_list_type + ?Sized>( &mut self, info: &T::TypeInfo, ) -> ListMeta<'r> { @@ -1202,7 +1202,7 @@ where } /// Create a nullable meta type - pub fn build_nullable_type + ?Sized>( + pub fn build_nullable_type + ?Sized>( &mut self, info: &T::TypeInfo, ) -> NullableMeta<'r> { @@ -1220,7 +1220,7 @@ where fields: &[Field<'r, S>], ) -> ObjectMeta<'r, S> where - T: GraphQLType + ?Sized, + T: GraphQLTypeMeta + ?Sized, { let name = T::name(info).expect("Object types must be named. Implement name()"); @@ -1236,7 +1236,7 @@ where values: &[EnumValue], ) -> EnumMeta<'r, S> where - T: FromInputValue + GraphQLType + ?Sized, + T: FromInputValue + GraphQLTypeMeta + ?Sized, { let name = T::name(info).expect("Enum types must be named. Implement name()"); @@ -1251,7 +1251,7 @@ where fields: &[Field<'r, S>], ) -> InterfaceMeta<'r, S> where - T: GraphQLType + ?Sized, + T: GraphQLTypeMeta + ?Sized, { let name = T::name(info).expect("Interface types must be named. Implement name()"); @@ -1263,7 +1263,7 @@ where /// Create a union meta type pub fn build_union_type(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r> where - T: GraphQLType + ?Sized, + T: GraphQLTypeMeta + ?Sized, { let name = T::name(info).expect("Union types must be named. Implement name()"); @@ -1277,7 +1277,7 @@ where args: &[Argument<'r, S>], ) -> InputObjectMeta<'r, S> where - T: FromInputValue + GraphQLType + ?Sized, + T: FromInputValue + GraphQLTypeMeta + ?Sized, { let name = T::name(info).expect("Input object types must be named. Implement name()"); diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 14bf1fa52..b26151df2 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,7 +115,7 @@ extern crate bson; // These are required by the code generated via the `juniper_codegen` macros. #[doc(hidden)] -pub use {futures, static_assertions as sa}; +pub use {futures, inventory, static_assertions as sa}; #[doc(inline)] pub use futures::future::BoxFuture; @@ -186,8 +186,11 @@ pub use crate::{ }, types::{ async_await::GraphQLTypeAsync, - base::{Arguments, GraphQLType, TypeKind}, - marker::{self, GraphQLUnion}, + base::{ + Arguments, GraphQLInterfaceTypeImplementor, GraphQLObjectTypeInfo, GraphQLType, + TypeKind, GRAPHQL_IFACE_TYPES, GraphQLTypeMeta, AsDynGraphQLType, + }, + marker::{self, GraphQLInterface, GraphQLUnion}, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator}, }, diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs index e0c399f54..b9213bdcf 100644 --- a/juniper/src/macros/interface.rs +++ b/juniper/src/macros/interface.rs @@ -137,49 +137,10 @@ macro_rules! graphql_interface { type Context = $ctx; type TypeInfo = (); - fn name(_ : &Self::TypeInfo) -> Option<&str> { + fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> { Some($($outname)*) } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> - ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> - where - $crate::__juniper_insert_generic!($($scalar)+): 'r - { - // Ensure all child types are registered - $( - let _ = registry.get_type::<$resolver_src>(info); - )* - let fields = &[$( - registry.field_convert::<$return_ty, _, Self::Context>( - &$crate::to_camel_case(stringify!($fn_name)), - info - ) - $(.description($fn_description))* - .push_docstring(&[$($docstring,)*]) - $(.deprecated($deprecated))* - $(.argument( - $crate::__juniper_create_arg!( - registry = registry, - info = info, - arg_ty = $arg_ty, - arg_name = $arg_name, - $(default = $arg_default,)* - $(description = $arg_description,)* - $(docstring = $arg_docstring,)* - ) - ))*, - )*]; - registry.build_interface_type::<$name>( - info, fields - ) - $(.description($desciption))* - .into_meta() - } - - #[allow(unused_variables)] fn resolve_field( &$main_self, @@ -229,7 +190,7 @@ macro_rules! graphql_interface { $( if ($resolver_expr as ::std::option::Option<$resolver_src>).is_some() { return - <$resolver_src as $crate::GraphQLType<_>>::name(&()).unwrap().to_owned(); + <$resolver_src as $crate::GraphQLTypeMeta<_>>::name(&()).unwrap().to_owned(); } )* @@ -246,7 +207,7 @@ macro_rules! graphql_interface { $(let $resolver_ctx = &executor.context();)* $( - if type_name == (<$resolver_src as $crate::GraphQLType<_>>::name(&())).unwrap() { + if type_name == (<$resolver_src as $crate::GraphQLTypeMeta<_>>::name(&())).unwrap() { return executor.resolve(&(), &$resolver_expr); } )* @@ -255,6 +216,52 @@ macro_rules! graphql_interface { } } ); + + $crate::__juniper_impl_trait!( + impl<$($scalar)* $(, $lifetimes)* > GraphQLTypeMeta for $name { + fn name(_ : &Self::TypeInfo) -> Option<&str> { + Some($($outname)*) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> + ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> + where + $crate::__juniper_insert_generic!($($scalar)+): 'r + { + // Ensure all child types are registered + $( + let _ = registry.get_type::<$resolver_src>(info); + )* + let fields = &[$( + registry.field_convert::<$return_ty, _, Self::Context>( + &$crate::to_camel_case(stringify!($fn_name)), + info + ) + $(.description($fn_description))* + .push_docstring(&[$($docstring,)*]) + $(.deprecated($deprecated))* + $(.argument( + $crate::__juniper_create_arg!( + registry = registry, + info = info, + arg_ty = $arg_ty, + arg_name = $arg_name, + $(default = $arg_default,)* + $(description = $arg_description,)* + $(docstring = $arg_docstring,)* + ) + ))*, + )*]; + registry.build_interface_type::<$name>( + info, fields + ) + $(.description($desciption))* + .into_meta() + } + } + ); }; ( diff --git a/juniper/src/macros/tests/util.rs b/juniper/src/macros/tests/util.rs index 058317285..09abe867d 100644 --- a/juniper/src/macros/tests/util.rs +++ b/juniper/src/macros/tests/util.rs @@ -3,9 +3,9 @@ use std::default::Default; pub async fn run_query(query: &str) -> Value where - Query: GraphQLTypeAsync + Default, - Mutation: GraphQLTypeAsync + Default, - Subscription: crate::GraphQLType + Query: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, + Mutation: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, + Subscription: crate::GraphQLTypeMeta + Default + Sync + Send, @@ -27,9 +27,9 @@ where pub async fn run_info_query(type_name: &str) -> Value where - Query: GraphQLTypeAsync + Default, - Mutation: GraphQLTypeAsync + Default, - Subscription: crate::GraphQLType + Query: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, + Mutation: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, + Subscription: crate::GraphQLTypeMeta + Default + Sync + Send, diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 2a0d5c021..0d2265ec4 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -10,7 +10,7 @@ use crate::{ ast::Type, executor::{Context, Registry}, schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta}, - types::{base::GraphQLType, name::Name}, + types::{base::{GraphQLType, GraphQLTypeMeta}, name::Name}, value::{DefaultScalarValue, ScalarValue}, }; @@ -92,9 +92,9 @@ pub enum DirectiveLocation { impl<'a, QueryT, MutationT, SubscriptionT, S> RootNode<'a, QueryT, MutationT, SubscriptionT, S> where S: ScalarValue + 'a, - QueryT: GraphQLType, - MutationT: GraphQLType, - SubscriptionT: GraphQLType, + QueryT: GraphQLTypeMeta, + MutationT: GraphQLTypeMeta, + SubscriptionT: GraphQLTypeMeta, { /// Construct a new root node from query, mutation, and subscription nodes /// @@ -127,9 +127,9 @@ where impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S> where - QueryT: GraphQLType, - MutationT: GraphQLType, - SubscriptionT: GraphQLType, + QueryT: GraphQLTypeMeta, + MutationT: GraphQLTypeMeta, + SubscriptionT: GraphQLTypeMeta, S: ScalarValue + 'a, { /// Construct a new root node from query and mutation nodes, @@ -168,9 +168,9 @@ impl<'a, S> SchemaType<'a, S> { ) -> Self where S: ScalarValue + 'a, - QueryT: GraphQLType, - MutationT: GraphQLType, - SubscriptionT: GraphQLType, + QueryT: GraphQLTypeMeta, + MutationT: GraphQLTypeMeta, + SubscriptionT: GraphQLTypeMeta, { let mut directives = FnvHashMap::default(); let query_type_name: String; diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 39a155732..04af6bfef 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -1,7 +1,7 @@ use crate::{ ast::Selection, executor::{ExecutionResult, Executor, Registry}, - types::base::{Arguments, GraphQLType, TypeKind}, + types::base::{Arguments, GraphQLType, GraphQLTypeMeta, TypeKind}, value::{ScalarValue, Value}, }; @@ -24,15 +24,8 @@ where type Context = CtxT; type TypeInfo = QueryT::TypeInfo; - fn name(info: &QueryT::TypeInfo) -> Option<&str> { - QueryT::name(info) - } - - fn meta<'r>(info: &QueryT::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - QueryT::meta(info, registry) + fn type_name<'i>(&self, info: &'i QueryT::TypeInfo) -> Option<&'i str> { + self.query_type.type_name(info) } fn resolve_field( @@ -77,6 +70,26 @@ where } } +impl<'a, CtxT, S, QueryT, MutationT, SubscriptionT> GraphQLTypeMeta + for RootNode<'a, QueryT, MutationT, SubscriptionT, S> +where + S: ScalarValue, + QueryT: GraphQLTypeMeta, + MutationT: GraphQLTypeMeta, + SubscriptionT: GraphQLTypeMeta, +{ + fn name(info: &QueryT::TypeInfo) -> Option<&str> { + QueryT::name(info) + } + + fn meta<'r>(info: &QueryT::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + QueryT::meta(info, registry) + } +} + impl<'a, CtxT, S, QueryT, MutationT, SubscriptionT> crate::GraphQLTypeAsync for RootNode<'a, QueryT, MutationT, SubscriptionT, S> where diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index de7da1d9f..67f0ae848 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -1,25 +1,27 @@ -use std::boxed::Box; -use std::collections::BTreeMap; +use std::{boxed::Box, collections::BTreeMap}; -use graphql_parser::query::{ - Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType, +use graphql_parser::{ + query::{Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType}, + schema::{ + Definition, Document, EnumType as ExternalEnum, EnumValue as ExternalEnumValue, + Field as ExternalField, InputObjectType as ExternalInputObjectType, + InputValue as ExternalInputValue, InterfaceType as ExternalInterfaceType, + ObjectType as ExternalObjectType, ScalarType as ExternalScalarType, SchemaDefinition, Text, + TypeDefinition as ExternalTypeDefinition, UnionType as ExternalUnionType, + Value as ExternalValue, + }, + Pos, }; -use graphql_parser::schema::{Definition, Document, SchemaDefinition, Text}; -use graphql_parser::schema::{ - EnumType as ExternalEnum, EnumValue as ExternalEnumValue, Field as ExternalField, - InputObjectType as ExternalInputObjectType, InputValue as ExternalInputValue, - InterfaceType as ExternalInterfaceType, ObjectType as ExternalObjectType, - ScalarType as ExternalScalarType, TypeDefinition as ExternalTypeDefinition, - UnionType as ExternalUnionType, Value as ExternalValue, -}; -use graphql_parser::Pos; -use crate::ast::{InputValue, Type}; -use crate::schema::meta::DeprecationStatus; -use crate::schema::meta::{Argument, EnumValue, Field, MetaType}; -use crate::schema::model::SchemaType; -use crate::schema::translate::SchemaTranslator; -use crate::value::ScalarValue; +use crate::{ + ast::{InputValue, Type}, + schema::{ + meta::{Argument, DeprecationStatus, EnumValue, Field, MetaType}, + model::SchemaType, + translate::SchemaTranslator, + }, + value::ScalarValue, +}; pub struct GraphQLParserTranslator; diff --git a/juniper/src/tests/model.rs b/juniper/src/tests/model.rs index 645ede185..b133819b7 100644 --- a/juniper/src/tests/model.rs +++ b/juniper/src/tests/model.rs @@ -107,7 +107,7 @@ pub struct Database { } use crate::{ - executor::Registry, schema::meta::MetaType, types::base::GraphQLType, value::ScalarValue, + executor::Registry, schema::meta::MetaType, types::base::{GraphQLType,GraphQLTypeMeta}, value::ScalarValue, }; impl GraphQLType for Database @@ -117,6 +117,15 @@ where type Context = Self; type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("_Database") + } +} + +impl GraphQLTypeMeta for Database +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&str> { Some("_Database") } diff --git a/juniper/src/tests/type_info_tests.rs b/juniper/src/tests/type_info_tests.rs index 87208fd8c..5ea9823d7 100644 --- a/juniper/src/tests/type_info_tests.rs +++ b/juniper/src/tests/type_info_tests.rs @@ -4,7 +4,7 @@ use crate::{ executor::{ExecutionResult, Executor, Registry, Variables}, schema::{meta::MetaType, model::RootNode}, types::{ - base::{Arguments, GraphQLType}, + base::{Arguments, GraphQLType, GraphQLTypeMeta}, scalars::{EmptyMutation, EmptySubscription}, }, value::{ScalarValue, Value}, @@ -26,6 +26,25 @@ where type Context = (); type TypeInfo = NodeTypeInfo; + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { + Some(&info.name) + } + + fn resolve_field( + &self, + _: &Self::TypeInfo, + field_name: &str, + _: &Arguments, + executor: &Executor, + ) -> ExecutionResult { + executor.resolve(&(), &self.attributes.get(field_name).unwrap()) + } +} + +impl GraphQLTypeMeta for Node +where + S: ScalarValue, +{ fn name(info: &Self::TypeInfo) -> Option<&str> { Some(&info.name) } @@ -44,16 +63,6 @@ where .build_object_type::(info, &fields) .into_meta() } - - fn resolve_field( - &self, - _: &Self::TypeInfo, - field_name: &str, - _: &Arguments, - executor: &Executor, - ) -> ExecutionResult { - executor.resolve(&(), &self.attributes.get(field_name).unwrap()) - } } #[test] diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 156c2ab52..8802976c7 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -2,7 +2,7 @@ use crate::{ ast::Selection, executor::{ExecutionResult, Executor}, parser::Spanning, - value::{Object, ScalarValue, Value}, + value::{Object, ScalarValue, Value, DefaultScalarValue}, }; use crate::BoxFuture; @@ -15,7 +15,7 @@ This trait extends `GraphQLType` with asynchronous queries/mutations resolvers. Convenience macros related to asynchronous queries/mutations expand into an implementation of this trait and `GraphQLType` for the given type. */ -pub trait GraphQLTypeAsync: GraphQLType + Send + Sync +pub trait GraphQLTypeAsync: GraphQLType + Send + Sync where Self::Context: Send + Sync, Self::TypeInfo: Send + Sync, @@ -36,7 +36,7 @@ where _arguments: &'a Arguments, _executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { - panic!("resolve_field must be implemented by object types"); + panic!("resolve_field_async must be implemented by object types"); } /// Resolve the provided selection set against the current object. @@ -81,7 +81,7 @@ where selection_set: Option<&'a [Selection<'a, S>]>, executor: &'a Executor<'a, 'a, Self::Context, S>, ) -> BoxFuture<'a, ExecutionResult> { - if Self::name(info).unwrap() == type_name { + if self.type_name(info).unwrap() == type_name { self.resolve_async(info, selection_set, executor) } else { panic!("resolve_into_type_async must be implemented by unions and interfaces"); @@ -89,6 +89,8 @@ where } } +crate::sa::assert_obj_safe!(GraphQLTypeAsync); + // Wrapper function around resolve_selection_set_into_async_recursive. // This wrapper is necessary because async fns can not be recursive. fn resolve_selection_set_into_async<'a, 'e, T, CtxT, S>( @@ -143,7 +145,7 @@ where let meta_type = executor .schema() .concrete_type_by_name( - T::name(info) + instance.type_name(info) .expect("Resolving named type's selection set") .as_ref(), ) diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index c80188ed0..6fb3c02c8 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -124,111 +124,95 @@ where } /** -Primary trait used to expose Rust types in a GraphQL schema - -All of the convenience macros ultimately expand into an implementation of -this trait for the given type. The macros remove duplicated definitions of -fields and arguments, and add type checks on all resolve functions -automatically. This can all be done manually. - -`GraphQLType` provides _some_ convenience methods for you, in the form of -optional trait methods. The `name` and `meta` methods are mandatory, but -other than that, it depends on what type you're exposing: - +* Primary trait used to expose Rust types in a GraphQL schema +* +* All of the convenience macros ultimately expand into an implementation of +* this trait for the given type. The macros remove duplicated definitions of +* fields and arguments, and add type checks on all resolve functions +* automatically. This can all be done manually. +* +* `GraphQLType` provides _some_ convenience methods for you, in the form of +* optional trait methods. The `name` and `meta` methods are mandatory, but +* other than that, it depends on what type you're exposing: +* * Scalars, enums, lists and non null wrappers only require `resolve`, * Interfaces and objects require `resolve_field` _or_ `resolve` if you want - to implement custom resolution logic (probably not), +* to implement custom resolution logic (probably not), * Interfaces and unions require `resolve_into_type` and `concrete_type_name`. * Input objects do not require anything - -## Example - -Manually deriving an object is straightforward but tedious. This is the -equivalent of the `User` object as shown in the example in the documentation -root: - -```rust -use juniper::{GraphQLType, Registry, FieldResult, Context, - Arguments, Executor, ExecutionResult, - DefaultScalarValue}; -use juniper::meta::MetaType; -# use std::collections::HashMap; - -#[derive(Debug)] -struct User { id: String, name: String, friend_ids: Vec } -#[derive(Debug)] -struct Database { users: HashMap } - -impl Context for Database {} - -impl GraphQLType for User -{ - type Context = Database; - type TypeInfo = (); - - fn name(_: &()) -> Option<&'static str> { - Some("User") - } - - fn meta<'r>(_: &(), registry: &mut Registry<'r>) -> MetaType<'r> - where DefaultScalarValue: 'r, - { - // First, we need to define all fields and their types on this type. - // - // If we need arguments, want to implement interfaces, or want to add - // documentation strings, we can do it here. - let fields = &[ - registry.field::<&String>("id", &()), - registry.field::<&String>("name", &()), - registry.field::>("friends", &()), - ]; - - registry.build_object_type::(&(), fields).into_meta() - } - - fn resolve_field( - &self, - info: &(), - field_name: &str, - args: &Arguments, - executor: &Executor - ) - -> ExecutionResult - { - // Next, we need to match the queried field name. All arms of this - // match statement return `ExecutionResult`, which makes it hard to - // statically verify that the type you pass on to `executor.resolve*` - // actually matches the one that you defined in `meta()` above. - let database = executor.context(); - match field_name { - // Because scalars are defined with another `Context` associated - // type, you must use resolve_with_ctx here to make the executor - // perform automatic type conversion of its argument. - "id" => executor.resolve_with_ctx(info, &self.id), - "name" => executor.resolve_with_ctx(info, &self.name), - - // You pass a vector of User objects to `executor.resolve`, and it - // will determine which fields of the sub-objects to actually - // resolve based on the query. The executor instance keeps track - // of its current position in the query. - "friends" => executor.resolve(info, - &self.friend_ids.iter() - .filter_map(|id| database.users.get(id)) - .collect::>() - ), - - // We can only reach this panic in two cases; either a mismatch - // between the defined schema in `meta()` above, or a validation - // in this library failed because of a bug. - // - // In either of those two cases, the only reasonable way out is - // to panic the thread. - _ => panic!("Field {} not found on type User", field_name), - } - } -} -``` - +* +* ## Example +* +* Manually deriving an object is straightforward but tedious. This is the +* equivalent of the `User` object as shown in the example in the documentation +* root: +* +* ```rust +* use juniper::{GraphQLType, Registry, FieldResult, Context, +* Arguments, Executor, ExecutionResult, +* DefaultScalarValue}; +* use juniper::meta::MetaType; +* # use std::collections::HashMap; +* +* #[derive(Debug)] +* struct User { id: String, name: String, friend_ids: Vec } +* #[derive(Debug)] +* struct Database { users: HashMap } +* +* impl Context for Database {} +* +* impl GraphQLType for User +* { +* type Context = Database; +* type TypeInfo = (); +* +* fn type_name(&self, _: &()) -> Option<&'static str> { +* Some("User") +* } +* +* fn resolve_field( +* &self, +* info: &(), +* field_name: &str, +* args: &Arguments, +* executor: &Executor +* ) +* -> ExecutionResult +* { +* // Next, we need to match the queried field name. All arms of this +* // match statement return `ExecutionResult`, which makes it hard to +* // statically verify that the type you pass on to `executor.resolve*` +* // actually matches the one that you defined in `meta()` above. +* let database = executor.context(); +* match field_name { +* // Because scalars are defined with another `Context` associated +* // type, you must use resolve_with_ctx here to make the executor +* // perform automatic type conversion of its argument. +* "id" => executor.resolve_with_ctx(info, &self.id), +* "name" => executor.resolve_with_ctx(info, &self.name), +* +* // You pass a vector of User objects to `executor.resolve`, and it +* // will determine which fields of the sub-objects to actually +* // resolve based on the query. The executor instance keeps track +* // of its current position in the query. +* "friends" => executor.resolve(info, +* &self.friend_ids.iter() +* .filter_map(|id| database.users.get(id)) +* .collect::>() +* ), +* +* // We can only reach this panic in two cases; either a mismatch +* // between the defined schema in `meta()` above, or a validation +* // in this library failed because of a bug. +* // +* // In either of those two cases, the only reasonable way out is +* // to panic the thread. +* _ => panic!("Field {} not found on type User", field_name), +* } +* } +* } +* ``` +* */ pub trait GraphQLType where @@ -250,15 +234,10 @@ where /// The name of the GraphQL type to expose. /// - /// This function will be called multiple times during schema construction. + /// This function will be called multiple times during type resolution. /// It must _not_ perform any calculation and _always_ return the same /// value. - fn name(info: &Self::TypeInfo) -> Option<&str>; - - /// The meta type representing this GraphQL type. - fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r; + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str>; /// Resolve the value of a single field on this type. /// @@ -293,7 +272,7 @@ where selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { - if Self::name(info).unwrap() == type_name { + if self.type_name(info).unwrap() == type_name { self.resolve(info, selection_set, executor) } else { panic!("resolve_into_type must be implemented by unions and interfaces"); @@ -305,7 +284,7 @@ where /// The default implementation panics. #[allow(unused_variables)] fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - panic!("concrete_type_name must be implemented by unions and interfaces"); + panic!("concrete_type_name must be implemented by objects, unions and interfaces"); } /// Resolve the provided selection set against the current object. @@ -341,6 +320,34 @@ where } } +crate::sa::assert_obj_safe!(GraphQLType); + +/// TODO: blah-blah doc +pub trait GraphQLTypeMeta: GraphQLType { + /// The name of the GraphQL type to expose. + /// + /// This function will be called multiple times during schema construction. + /// It must _not_ perform any calculation and _always_ return the same + /// value. + fn name(info: &Self::TypeInfo) -> Option<&str>; + + /// The meta type representing this GraphQL type. + fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r; +} + +/// TODO: blah-blah doc +pub trait AsDynGraphQLType { + /// TODO: blah-blah doc + type Context; + /// TODO: blah-blah doc + type TypeInfo; + + /// TODO: blah-blah doc + fn as_dyn_graphql_type(&self) -> &(dyn GraphQLType + 'static + Send + Sync); +} + /// Resolver logic for queries'/mutations' selection set. /// Calls appropriate resolver method for each field or fragment found /// and then merges returned values into `result` or pushes errors to @@ -361,7 +368,7 @@ where let meta_type = executor .schema() .concrete_type_by_name( - T::name(info) + instance.type_name(info) .expect("Resolving named type's selection set") .as_ref(), ) @@ -576,3 +583,35 @@ fn merge_maps(dest: &mut Object, src: Object) { } } } + +// ---------------------------------- + +use std::collections::HashMap; + +use once_cell::sync::Lazy; + +#[doc(hidden)] +pub struct GraphQLInterfaceTypeImplementor { + pub interface_name: &'static str, + pub object: GraphQLObjectTypeInfo, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +pub struct GraphQLObjectTypeInfo { + pub name: &'static str, + pub mark_fn: fn(), + pub reg_fn: fn(), +} + +inventory::collect!(GraphQLInterfaceTypeImplementor); + +#[doc(hidden)] +pub static GRAPHQL_IFACE_TYPES: Lazy>> = + Lazy::new(|| { + let mut m = >>::new(); + for i in inventory::iter:: { + m.entry(i.interface_name).or_default().push(i.object); + } + m + }); diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 589624028..5f3d7bcc9 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -2,7 +2,7 @@ use crate::{ ast::{FromInputValue, InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, schema::meta::MetaType, - types::{async_await::GraphQLTypeAsync, base::GraphQLType}, + types::{async_await::GraphQLTypeAsync, base::{GraphQLType, GraphQLTypeMeta}}, value::{ScalarValue, Value}, }; @@ -14,17 +14,10 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn name(_: &T::TypeInfo) -> Option<&str> { + fn type_name(&self, _: &T::TypeInfo) -> Option<&'static str> { None } - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_nullable_type::(info).into_meta() - } - fn resolve( &self, info: &T::TypeInfo, @@ -38,6 +31,23 @@ where } } +impl GraphQLTypeMeta for Option +where + S: ScalarValue, + T: GraphQLTypeMeta, +{ + fn name(_: &T::TypeInfo) -> Option<&str> { + None + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_nullable_type::(info).into_meta() + } +} + impl GraphQLTypeAsync for Option where T: GraphQLTypeAsync, @@ -96,17 +106,10 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn name(_: &T::TypeInfo) -> Option<&str> { + fn type_name(&self, _: &T::TypeInfo) -> Option<&'static str> { None } - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_list_type::(info).into_meta() - } - fn resolve( &self, info: &T::TypeInfo, @@ -117,6 +120,23 @@ where } } +impl GraphQLTypeMeta for Vec + where + T: GraphQLTypeMeta, + S: ScalarValue, +{ + fn name(_: &T::TypeInfo) -> Option<&str> { + None + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_list_type::(info).into_meta() + } +} + impl GraphQLTypeAsync for Vec where T: GraphQLTypeAsync, @@ -175,17 +195,10 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn name(_: &T::TypeInfo) -> Option<&str> { + fn type_name(&self, _: &T::TypeInfo) -> Option<&'static str> { None } - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_list_type::(info).into_meta() - } - fn resolve( &self, info: &T::TypeInfo, @@ -196,6 +209,24 @@ where } } +impl GraphQLTypeMeta for [T] + where + S: ScalarValue, + T: GraphQLTypeMeta, +{ + + fn name(_: &T::TypeInfo) -> Option<&str> { + None + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_list_type::(info).into_meta() + } +} + impl GraphQLTypeAsync for [T] where T: GraphQLTypeAsync, diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 5e2fc909e..9d6503250 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -23,13 +23,37 @@ pub trait GraphQLObjectType: GraphQLType { fn mark() {} } +/// Maker trait for [GraphQL interfaces][1]. +/// +/// This trait extends the [`GraphQLType`] and is only used to mark an [interface][1]. During +/// compile this addition information is required to prevent unwanted structure compiling. If an +/// object requires this trait instead of the [`GraphQLType`], then it explicitly requires +/// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] +/// and [unions][6]) are not allowed. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [2]: https://spec.graphql.org/June2018/#sec-Scalars +/// [3]: https://spec.graphql.org/June2018/#sec-Enums +/// [4]: https://spec.graphql.org/June2018/#sec-Objects +/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects +/// [6]: https://spec.graphql.org/June2018/#sec-Unions +pub trait GraphQLInterface: GraphQLType { + /// An arbitrary function without meaning. + /// + /// May contain compile timed check logic which ensures that types are used correctly according + /// to the [GraphQL specification][1]. + /// + /// [1]: https://spec.graphql.org/June2018/ + fn mark() {} +} + /// Maker trait for [GraphQL unions][1]. /// -/// This trait extends the [`GraphQLType`] and is only used to mark [union][1]. During compile this -/// addition information is required to prevent unwanted structure compiling. If an object requires -/// this trait instead of the [`GraphQLType`], then it explicitly requires [GraphQL unions][1]. -/// Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and [interfaces][6]) are -/// not allowed. +/// This trait extends the [`GraphQLType`] and is only used to mark an [union][1]. During compile +/// this addition information is required to prevent unwanted structure compiling. If an object +/// requires this trait instead of the [`GraphQLType`], then it explicitly requires +/// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and +/// [interfaces][6]) are not allowed. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions /// [2]: https://spec.graphql.org/June2018/#sec-Scalars diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 68274fad3..71c791346 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -6,7 +6,7 @@ use crate::{ schema::meta::MetaType, types::{ async_await::GraphQLTypeAsync, - base::{Arguments, GraphQLType}, + base::{Arguments, GraphQLType, GraphQLTypeMeta}, }, value::ScalarValue, BoxFuture, @@ -20,15 +20,8 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn name(info: &T::TypeInfo) -> Option<&str> { - T::name(info) - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - T::meta(info, registry) + fn type_name<'i>(&self, info: &'i T::TypeInfo) -> Option<&'i str> { + T::type_name(&**self, info) } fn resolve_into_type( @@ -61,6 +54,23 @@ where } } +impl GraphQLTypeMeta for Box + where + S: ScalarValue, + T: GraphQLTypeMeta + ?Sized, +{ + fn name(info: &T::TypeInfo) -> Option<&str> { + T::name(info) + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + T::meta(info, registry) + } +} + impl crate::GraphQLTypeAsync for Box where T: GraphQLTypeAsync + ?Sized, @@ -109,15 +119,8 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn name(info: &T::TypeInfo) -> Option<&str> { - T::name(info) - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - T::meta(info, registry) + fn type_name<'i>(&self, info: &'i T::TypeInfo) -> Option<&'i str> { + T::type_name(&**self, info) } fn resolve_into_type( @@ -150,6 +153,23 @@ where } } +impl<'e, S, T, CtxT> GraphQLTypeMeta for &'e T + where + S: ScalarValue, + T: GraphQLTypeMeta + ?Sized, +{ + fn name(info: &T::TypeInfo) -> Option<&str> { + T::name(info) + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + T::meta(info, registry) + } +} + impl<'e, S, T> GraphQLTypeAsync for &'e T where S: ScalarValue + Send + Sync, @@ -195,15 +215,8 @@ where type Context = T::Context; type TypeInfo = T::TypeInfo; - fn name(info: &T::TypeInfo) -> Option<&str> { - T::name(info) - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - T::meta(info, registry) + fn type_name<'i>(&self, info: &'i T::TypeInfo) -> Option<&'i str> { + T::type_name(&**self, info) } fn resolve_into_type( @@ -236,6 +249,24 @@ where } } +impl GraphQLTypeMeta for Arc + where + S: ScalarValue, + T: GraphQLTypeMeta + ?Sized, +{ + fn name(info: &T::TypeInfo) -> Option<&str> { + T::name(info) + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + T::meta(info, registry) + } +} + + impl<'e, S, T> GraphQLTypeAsync for Arc where S: ScalarValue + Send + Sync, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index a2963c935..0f8670aa6 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -6,7 +6,7 @@ use crate::{ executor::{ExecutionResult, Executor, Registry}, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, - types::base::GraphQLType, + types::base::{GraphQLType, GraphQLTypeMeta}, value::{ParseScalarResult, ScalarValue, Value}, }; @@ -199,17 +199,10 @@ where type Context = (); type TypeInfo = (); - fn name(_: &()) -> Option<&str> { + fn type_name(&self, _: &()) -> Option<&'static str> { Some("String") } - fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_scalar_type::(&()).into_meta() - } - fn resolve( &self, _: &(), @@ -220,6 +213,22 @@ where } } +impl GraphQLTypeMeta for str + where + S: ScalarValue, +{ + fn name(_: &()) -> Option<&str> { + Some("String") + } + + fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_scalar_type::(&()).into_meta() + } +} + impl crate::GraphQLTypeAsync for str where S: ScalarValue + Send + Sync, @@ -348,6 +357,15 @@ where type Context = T; type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("_EmptyMutation") + } +} + +impl GraphQLTypeMeta for EmptyMutation +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&str> { Some("_EmptyMutation") } @@ -405,6 +423,15 @@ where type Context = T; type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("_EmptySubscription") + } +} + +impl GraphQLTypeMeta for EmptySubscription +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&str> { Some("_EmptySubscription") } diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs index 050769cd9..6f8f47c88 100644 --- a/juniper/src/types/subscriptions.rs +++ b/juniper/src/types/subscriptions.rs @@ -160,7 +160,7 @@ where 'res: 'f, { Box::pin(async move { - if Self::name(info) == Some(type_name) { + if self.type_name(info) == Some(type_name) { self.resolve_into_stream(info, executor).await } else { panic!("resolve_into_type_stream must be implemented"); @@ -218,7 +218,7 @@ where let meta_type = executor .schema() .concrete_type_by_name( - T::name(info) + instance.type_name(info) .expect("Resolving named type's selection set") .as_ref(), ) diff --git a/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs b/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs index 549375513..2bb1a2cb8 100644 --- a/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs +++ b/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs @@ -741,7 +741,7 @@ mod tests { executor::Registry, schema::meta::MetaType, types::{ - base::GraphQLType, + base::{GraphQLType, GraphQLTypeMeta}, scalars::{EmptyMutation, EmptySubscription, ID}, }, }; @@ -1383,6 +1383,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("SomeBox") + } + } + + impl GraphQLTypeMeta for SomeBox + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("SomeBox") } @@ -1408,6 +1417,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("StringBox") + } + } + + impl GraphQLTypeMeta for StringBox + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("StringBox") } @@ -1439,6 +1457,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("IntBox") + } + } + + impl GraphQLTypeMeta for IntBox + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("IntBox") } @@ -1470,6 +1497,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("NonNullStringBox1") + } + } + + impl GraphQLTypeMeta for NonNullStringBox1 + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox1") } @@ -1491,6 +1527,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("NonNullStringBox1Impl") + } + } + + impl GraphQLTypeMeta for NonNullStringBox1Impl + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox1Impl") } @@ -1522,6 +1567,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("NonNullStringBox2") + } + } + + impl GraphQLTypeMeta for NonNullStringBox2 + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox2") } @@ -1543,6 +1597,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("NonNullStringBox2Impl") + } + } + + impl GraphQLTypeMeta for NonNullStringBox2Impl + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox2Impl") } @@ -1574,6 +1637,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Node") + } + } + + impl GraphQLTypeMeta for Node + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("Node") } @@ -1598,6 +1670,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Edge") + } + } + + impl GraphQLTypeMeta for Edge + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("Edge") } @@ -1619,6 +1700,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Connection") + } + } + + impl GraphQLTypeMeta for Connection + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("Connection") } @@ -1640,6 +1730,15 @@ mod tests { type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("QueryRoot") + } + } + + impl GraphQLTypeMeta for QueryRoot + where + S: ScalarValue, + { fn name(_: &()) -> Option<&'static str> { Some("QueryRoot") } diff --git a/juniper/src/validation/test_harness.rs b/juniper/src/validation/test_harness.rs index 07e87d781..af2c2a7d4 100644 --- a/juniper/src/validation/test_harness.rs +++ b/juniper/src/validation/test_harness.rs @@ -8,7 +8,7 @@ use crate::{ meta::{EnumValue, MetaType}, model::{DirectiveLocation, DirectiveType, RootNode}, }, - types::{base::GraphQLType, scalars::ID}, + types::{base::{GraphQLType, GraphQLTypeMeta}, scalars::ID}, validation::{visit, MultiVisitorNil, RuleError, ValidatorContext, Visitor}, value::ScalarValue, }; @@ -74,6 +74,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Being") + } +} + +impl GraphQLTypeMeta for Being +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Being") } @@ -97,6 +106,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Pet") + } +} + +impl GraphQLTypeMeta for Pet +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Pet") } @@ -120,6 +138,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Canine") + } +} + +impl GraphQLTypeMeta for Canine +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Canine") } @@ -143,6 +170,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("DogCommand") + } +} + +impl GraphQLTypeMeta for DogCommand +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("DogCommand") } @@ -185,6 +221,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Dog") + } +} + +impl GraphQLTypeMeta for Dog +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Dog") } @@ -230,6 +275,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("FurColor") + } +} + +impl GraphQLTypeMeta for FurColor +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("FurColor") } @@ -274,6 +328,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Cat") + } +} + +impl GraphQLTypeMeta for Cat +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Cat") } @@ -306,6 +369,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("CatOrDog") + } +} + +impl GraphQLTypeMeta for CatOrDog +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("CatOrDog") } @@ -327,6 +399,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Intelligent") + } +} + +impl GraphQLTypeMeta for Intelligent +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Intelligent") } @@ -348,6 +429,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Human") + } +} + +impl GraphQLTypeMeta for Human +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Human") } @@ -381,6 +471,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("Alien") + } +} + +impl GraphQLTypeMeta for Alien +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("Alien") } @@ -414,6 +513,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("DogOrHuman") + } +} + +impl GraphQLTypeMeta for DogOrHuman +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("DogOrHuman") } @@ -435,6 +543,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("HumanOrAlien") + } +} + +impl GraphQLTypeMeta for HumanOrAlien +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("HumanOrAlien") } @@ -456,6 +573,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("ComplexInput") + } +} + +impl GraphQLTypeMeta for ComplexInput +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("ComplexInput") } @@ -508,6 +634,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("ComplicatedArgs") + } +} + +impl GraphQLTypeMeta for ComplicatedArgs +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("ComplicatedArgs") } @@ -571,6 +706,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("QueryRoot") + } +} + +impl GraphQLTypeMeta for QueryRoot +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&'static str> { Some("QueryRoot") } @@ -604,6 +748,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("MutationRoot") + } +} + +impl GraphQLTypeMeta for MutationRoot +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&str> { Some("MutationRoot") } @@ -634,6 +787,15 @@ where type Context = (); type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some("SubscriptionRoot") + } +} + +impl GraphQLTypeMeta for SubscriptionRoot +where + S: ScalarValue, +{ fn name(_: &()) -> Option<&str> { Some("SubscriptionRoot") } @@ -657,9 +819,9 @@ pub fn validate<'a, Q, M, Sub, V, F, S>( ) -> Vec where S: ScalarValue + 'a, - Q: GraphQLType, - M: GraphQLType, - Sub: GraphQLType, + Q: GraphQLTypeMeta, + M: GraphQLTypeMeta, + Sub: GraphQLTypeMeta, V: Visitor<'a, S> + 'a, F: Fn() -> V, { @@ -723,9 +885,9 @@ pub fn expect_passes_rule_with_schema<'a, Q, M, Sub, V, F, S>( q: &'a str, ) where S: ScalarValue + 'a, - Q: GraphQLType, - M: GraphQLType, - Sub: GraphQLType, + Q: GraphQLTypeMeta, + M: GraphQLTypeMeta, + Sub: GraphQLTypeMeta, V: Visitor<'a, S> + 'a, F: Fn() -> V, { @@ -754,8 +916,8 @@ pub fn expect_fails_rule_with_schema<'a, Q, M, V, F, S>( expected_errors: &[RuleError], ) where S: ScalarValue + 'a, - Q: GraphQLType, - M: GraphQLType, + Q: GraphQLTypeMeta, + M: GraphQLTypeMeta, V: Visitor<'a, S> + 'a, F: Fn() -> V, { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index c951b1e5c..7490b7f26 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -144,6 +144,15 @@ fn impl_scalar_struct( type Context = (); type TypeInfo = (); + fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + } + + impl #crate_name::GraphQLTypeMeta for #ident + where + S: #crate_name::ScalarValue, + { fn name(_: &Self::TypeInfo) -> Option<&str> { Some(#name) } @@ -159,15 +168,6 @@ fn impl_scalar_struct( #description .into_meta() } - - fn resolve( - &self, - info: &(), - selection: Option<&[#crate_name::Selection]>, - executor: &#crate_name::Executor, - ) -> #crate_name::ExecutionResult { - #crate_name::GraphQLType::resolve(&self.0, info, selection, executor) - } } impl #crate_name::ToInputValue for #ident diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 2d8132c28..54b3f943e 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -438,7 +438,7 @@ impl ToTokens for UnionDefinition { let var_check = &var.resolver_check; quote! { if #var_check { - return <#var_ty as #crate_path::GraphQLType<#scalar>>::name(&()) + return <#var_ty as #crate_path::GraphQLTypeMeta<#scalar>>::name(&()) .unwrap().to_string(); } } @@ -448,7 +448,9 @@ impl ToTokens for UnionDefinition { let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| { let var_ty = &var.ty; - let get_name = quote! { (<#var_ty as #crate_path::GraphQLType<#scalar>>::name(&())) }; + let get_name = quote! { + (<#var_ty as #crate_path::GraphQLTypeMeta<#scalar>>::name(&())) + }; quote! { if type_name == #get_name.unwrap() { return #crate_path::IntoResolvable::into( @@ -470,7 +472,7 @@ impl ToTokens for UnionDefinition { let var_ty = &var.ty; let get_name = quote! { - (<#var_ty as #crate_path::GraphQLType<#scalar>>::name(&())) + (<#var_ty as #crate_path::GraphQLTypeMeta<#scalar>>::name(&())) }; quote! { if type_name == #get_name.unwrap() { @@ -535,24 +537,10 @@ impl ToTokens for UnionDefinition { type Context = #context; type TypeInfo = (); - fn name(_ : &Self::TypeInfo) -> Option<&str> { + fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut #crate_path::Registry<'r, #scalar> - ) -> #crate_path::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - let types = &[ - #( registry.get_type::<&#var_types>(&(())), )* - ]; - registry.build_union_type::<#ty_full>(info, types) - #description - .into_meta() - } - fn concrete_type_name( &self, context: &Self::Context, @@ -583,6 +571,31 @@ impl ToTokens for UnionDefinition { } }; + let meta_type_impl = quote! { + #[automatically_derived] + impl#ext_impl_generics #crate_path::GraphQLTypeMeta<#scalar> for #ty_full + #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #crate_path::Registry<'r, #scalar> + ) -> #crate_path::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + let types = &[ + #( registry.get_type::<&#var_types>(&(())), )* + ]; + registry.build_union_type::<#ty_full>(info, types) + #description + .into_meta() + } + } + }; + let async_type_impl = quote! { #[automatically_derived] impl#ext_impl_generics #crate_path::GraphQLTypeAsync<#scalar> for #ty_full @@ -629,7 +642,7 @@ impl ToTokens for UnionDefinition { } }; - into.append_all(&[union_impl, output_type_impl, type_impl, async_type_impl]); + into.append_all(&[union_impl, output_type_impl, type_impl, async_type_impl, meta_type_impl]); } } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index fc449aa8a..2aa7ff9dc 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -286,6 +286,23 @@ pub fn build_scalar( type Context = (); type TypeInfo = (); + fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn resolve( + &self, + info: &(), + selection: Option<&[#crate_name::Selection<#generic_type>]>, + executor: &#crate_name::Executor, + ) -> #crate_name::ExecutionResult<#generic_type> { + Ok(#resolve_body) + } + } + + impl#generic_type_decl #crate_name::GraphQLTypeMeta<#generic_type> for #impl_for_type + #generic_type_bound + { fn name(_: &Self::TypeInfo) -> Option<&str> { Some(#name) } @@ -301,15 +318,6 @@ pub fn build_scalar( #description .into_meta() } - - fn resolve( - &self, - info: &(), - selection: Option<&[#crate_name::Selection<#generic_type>]>, - executor: &#crate_name::Executor, - ) -> #crate_name::ExecutionResult<#generic_type> { - Ok(#resolve_body) - } } impl#generic_type_decl #crate_name::ToInputValue<#generic_type> for #impl_for_type diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 82351e616..776ce6274 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -787,7 +787,7 @@ impl GraphQLTypeDefiniton { #name => { panic!("Tried to resolve async field {} on type {:?} with a sync resolver", #name, - >::name(_info) + >::name(_info) ); }, ) @@ -962,7 +962,7 @@ impl GraphQLTypeDefiniton { _ => { panic!("Field {} not found on type {:?}", field, - >::name(info) + >::name(info) ); } } @@ -1003,25 +1003,10 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); - fn name(_: &Self::TypeInfo) -> Option<&str> { + fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { Some(#name) } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut #juniper_crate_name::Registry<'r, #scalar> - ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> - where #scalar : 'r, - { - let fields = vec![ - #( #field_definitions ),* - ]; - let meta = registry.build_object_type::<#ty>( info, &fields ) - #description - #interfaces; - meta.into_meta() - } - #[allow(unused_variables)] #[allow(unused_mut)] fn resolve_field( @@ -1036,7 +1021,7 @@ impl GraphQLTypeDefiniton { _ => { panic!("Field {} not found on type {:?}", field, - >::name(_info) + >::name(_info) ); } } @@ -1049,6 +1034,29 @@ impl GraphQLTypeDefiniton { } + impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty #type_generics_tokens + #where_clause + { + fn name(_: &Self::TypeInfo) -> Option<&str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #juniper_crate_name::Registry<'r, #scalar> + ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> + where #scalar : 'r, + { + let fields = vec![ + #( #field_definitions ),* + ]; + let meta = registry.build_object_type::<#ty>( info, &fields ) + #description + #interfaces; + meta.into_meta() + } + } + #resolve_field_async ); output @@ -1238,25 +1246,10 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); - fn name(_: &Self::TypeInfo) -> Option<&str> { + fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { Some(#name) } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut #juniper_crate_name::Registry<'r, #scalar> - ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> - where #scalar : 'r, - { - let fields = vec![ - #( #field_definitions ),* - ]; - let meta = registry.build_object_type::<#ty>( info, &fields ) - #description - #interfaces; - meta.into_meta() - } - fn resolve_field( &self, _: &(), @@ -1274,6 +1267,31 @@ impl GraphQLTypeDefiniton { } ); + let graphql_meta_implementation = quote!( + impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty #type_generics_tokens + #where_clause + { + fn name(_: &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #juniper_crate_name::Registry<'r, #scalar> + ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> + where #scalar : 'r, + { + let fields = vec![ + #( #field_definitions ),* + ]; + let meta = registry.build_object_type::<#ty>( info, &fields ) + #description + #interfaces; + meta.into_meta() + } + } + ); + let subscription_implementation = quote!( impl#impl_generics #juniper_crate_name::GraphQLSubscriptionType<#scalar> for #ty #type_generics_tokens #where_clause @@ -1319,6 +1337,7 @@ impl GraphQLTypeDefiniton { quote!( #graphql_implementation + #graphql_meta_implementation #subscription_implementation ) } @@ -1462,6 +1481,26 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some(#name) + } + + fn resolve( + &self, + _: &(), + _: Option<&[#juniper_crate_name::Selection<#scalar>]>, + _: &#juniper_crate_name::Executor + ) -> #juniper_crate_name::ExecutionResult<#scalar> { + let v = match self { + #( #resolves )* + }; + Ok(v) + } + } + + impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty + #where_clause + { fn name(_: &()) -> Option<&'static str> { Some(#name) } @@ -1478,18 +1517,6 @@ impl GraphQLTypeDefiniton { #description .into_meta() } - - fn resolve( - &self, - _: &(), - _: Option<&[#juniper_crate_name::Selection<#scalar>]>, - _: &#juniper_crate_name::Executor - ) -> #juniper_crate_name::ExecutionResult<#scalar> { - let v = match self { - #( #resolves )* - }; - Ok(v) - } } impl#impl_generics #juniper_crate_name::FromInputValue<#scalar> for #ty @@ -1705,6 +1732,14 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); + fn type_name(&self, _: &()) -> Option<&'static str> { + Some(#name) + } + } + + impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty #type_generics_tokens + #where_clause + { fn name(_: &()) -> Option<&'static str> { Some(#name) } From c4d1eb0c8b03680e47d32367da699ea3971ccbef Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 16 Jun 2020 17:42:17 +0300 Subject: [PATCH 02/79] Upd --- .../src/codegen/interface_attr.rs | 18 +++++++++++++++--- juniper/src/schema/meta.rs | 2 +- juniper/src/types/base.rs | 1 - 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index af8c9a59b..0ea1d064d 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -71,7 +71,6 @@ struct Droid { object: ::juniper::GraphQLObjectTypeInfo { name: "Droid", mark_fn: >::mark, - reg_fn: } } } @@ -240,14 +239,16 @@ where where __S: 'r, { + //panic!("🔬 {:#?}", registry.types); // Ensure custom downcaster type is registered - let _ = registry.get_type::<&Droid>(info); + //let _ = registry.get_type::<&Droid>(info); // Ensure all child types are registered // TODO: how? // TODO: get_type_by_name and iter //let _ = registry.get_type::<&Human>(info); + let fields = vec![ // TODO: try array registry.field_convert::<&str, _, Self::Context>("id", info), @@ -381,6 +382,7 @@ mod poc { }"#; let schema = schema(QueryRoot::Human); + panic!("🔬 {:#?}", schema.schema); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -412,4 +414,14 @@ mod poc { )), ); } -} \ No newline at end of file +} + +/* +struct Woop; + +impl Woop { + fn get(&self) -> for<'r> fn(info: &TI, registry: &mut Registry<'r, S>) -> MetaType<'r, S> { + unimplemented!() + } +} +*/ \ No newline at end of file diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 1f610ed6f..9cebd29d0 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -729,7 +729,7 @@ impl<'a, S> Argument<'a, S> { /// Set the default value of the argument /// - /// This overwrites the description if any was previously set. + /// This overwrites the default value if any was previously set. pub fn default_value(mut self, default_value: InputValue) -> Self { self.default_value = Some(default_value); self diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 6fb3c02c8..1a6309e08 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -601,7 +601,6 @@ pub struct GraphQLInterfaceTypeImplementor { pub struct GraphQLObjectTypeInfo { pub name: &'static str, pub mark_fn: fn(), - pub reg_fn: fn(), } inventory::collect!(GraphQLInterfaceTypeImplementor); From 7789312dc990b4b64d62f6308f70df6014334d5c Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 29 Jun 2020 12:46:17 +0300 Subject: [PATCH 03/79] Bootstrap macro --- juniper_codegen/src/graphql_interface/attr.rs | 1 + juniper_codegen/src/graphql_interface/mod.rs | 5 +++++ juniper_codegen/src/lib.rs | 9 +++++++++ 3 files changed, 15 insertions(+) create mode 100644 juniper_codegen/src/graphql_interface/attr.rs create mode 100644 juniper_codegen/src/graphql_interface/mod.rs diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs new file mode 100644 index 000000000..0b0cfdd0b --- /dev/null +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -0,0 +1 @@ +//! Code generation for `#[graphql_interface]` macro. \ No newline at end of file diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs new file mode 100644 index 000000000..c98f0efbf --- /dev/null +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -0,0 +1,5 @@ +//! Code generation for [GraphQL interface][1]. +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces + +pub mod attr; \ No newline at end of file diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 9e6578910..d01d74fcb 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -20,6 +20,7 @@ mod impl_object; mod impl_scalar; mod graphql_union; +mod graphql_interface; use proc_macro::TokenStream; use proc_macro_error::{proc_macro_error, ResultExt as _}; @@ -546,6 +547,14 @@ pub fn graphql_subscription_internal(args: TokenStream, input: TokenStream) -> T )) } +#[proc_macro_error] +#[proc_macro_attribute] +pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_interface::attr::expand(attr.into(), body.into(), Mode::Public) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and /// structs. /// From cbb168b0c77704be68b5da7ce46bccb3d6632b90 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 30 Jun 2020 17:47:45 +0300 Subject: [PATCH 04/79] Revert stuff --- .../juniper_tests/src/codegen/derive_enum.rs | 4 +- .../src/codegen/derive_input_object.rs | 10 +- .../src/codegen/derive_object.rs | 4 +- .../src/codegen/scalar_value_transparent.rs | 6 +- .../juniper_tests/src/codegen/union_attr.rs | 4 +- .../juniper_tests/src/codegen/union_derive.rs | 4 +- juniper/src/executor/mod.rs | 28 +-- juniper/src/lib.rs | 9 +- juniper/src/macros/interface.rs | 91 ++++--- juniper/src/macros/tests/util.rs | 12 +- juniper/src/schema/model.rs | 20 +- juniper/src/schema/schema.rs | 33 +-- .../src/schema/translate/graphql_parser.rs | 40 ++- juniper/src/tests/model.rs | 11 +- juniper/src/tests/type_info_tests.rs | 31 +-- juniper/src/types/async_await.rs | 12 +- juniper/src/types/base.rs | 229 +++++++++--------- juniper/src/types/containers.rs | 81 ++----- juniper/src/types/pointers.rs | 87 +++---- juniper/src/types/scalars.rs | 45 +--- juniper/src/types/subscriptions.rs | 4 +- .../rules/overlapping_fields_can_be_merged.rs | 101 +------- juniper/src/validation/test_harness.rs | 180 +------------- juniper_codegen/src/derive_scalar_value.rs | 18 +- juniper_codegen/src/graphql_union/mod.rs | 51 ++-- juniper_codegen/src/impl_scalar.rs | 26 +- juniper_codegen/src/util/mod.rs | 129 ++++------ 27 files changed, 403 insertions(+), 867 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index a4879f4b5..88a54a08b 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -2,7 +2,7 @@ use fnv::FnvHashMap; #[cfg(test)] -use juniper::{self, DefaultScalarValue, FromInputValue, GraphQLType, GraphQLTypeMeta, InputValue, ToInputValue}; +use juniper::{self, DefaultScalarValue, FromInputValue, GraphQLType, InputValue, ToInputValue}; pub struct CustomContext {} @@ -53,7 +53,7 @@ enum ContextEnum { fn test_derived_enum() { // Ensure that rename works. assert_eq!( - >::name(&()), + >::name(&()), Some("Some") ); diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index 70d77d934..664473cba 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -1,7 +1,7 @@ use fnv::FnvHashMap; use juniper::{ - self, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLTypeMeta, InputValue, + self, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, InputValue, ToInputValue, }; @@ -66,12 +66,6 @@ impl<'a> GraphQLType for &'a Fake { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - None - } -} - -impl<'a> GraphQLTypeMeta for &'a Fake { fn name(_: &()) -> Option<&'static str> { None } @@ -100,7 +94,7 @@ struct WithLifetime<'a> { #[test] fn test_derived_input_object() { assert_eq!( - >::name(&()), + >::name(&()), Some("MyInput") ); diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index f8114be81..b7dd51ca0 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -6,7 +6,7 @@ use juniper::{DefaultScalarValue, GraphQLObject}; #[cfg(test)] use juniper::{ - self, execute, EmptyMutation, EmptySubscription, GraphQLTypeMeta, RootNode, Value, Variables, + self, execute, EmptyMutation, EmptySubscription, GraphQLType, RootNode, Value, Variables, }; #[derive(GraphQLObject, Debug, PartialEq)] @@ -176,7 +176,7 @@ async fn test_doc_comment_override() { #[tokio::test] async fn test_derived_object() { assert_eq!( - >::name(&()), + >::name(&()), Some("MyObj") ); diff --git a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs index 511fe7c16..6f52e5b64 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs @@ -1,5 +1,5 @@ use fnv::FnvHashMap; -use juniper::{DefaultScalarValue, FromInputValue, GraphQLTypeMeta, InputValue, ToInputValue}; +use juniper::{DefaultScalarValue, FromInputValue, GraphQLType, InputValue, ToInputValue}; #[derive(juniper::GraphQLScalarValue, PartialEq, Eq, Debug)] #[graphql(transparent)] @@ -32,7 +32,7 @@ impl User2 { #[test] fn test_scalar_value_simple() { assert_eq!( - >::name(&()), + >::name(&()), Some("UserId") ); @@ -53,7 +53,7 @@ fn test_scalar_value_simple() { #[test] fn test_scalar_value_custom() { assert_eq!( - >::name(&()), + >::name(&()), Some("MyUserId") ); diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index b24cc3c87..2581512cd 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -2,7 +2,7 @@ use juniper::{ execute, graphql_object, graphql_union, graphql_value, DefaultScalarValue, EmptyMutation, - EmptySubscription, GraphQLObject, GraphQLTypeMeta, RootNode, ScalarValue, Variables, + EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, }; #[derive(GraphQLObject)] @@ -53,7 +53,7 @@ struct EwokCustomContext { fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> where - Q: GraphQLTypeMeta + 'q, + Q: GraphQLType + 'q, S: ScalarValue + 'q, { RootNode::new( diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index 31bed7598..655ca9954 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use juniper::{ execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, - GraphQLObject, GraphQLTypeMeta, GraphQLUnion, RootNode, ScalarValue, Variables, + GraphQLObject, GraphQLType, GraphQLUnion, RootNode, ScalarValue, Variables, }; #[derive(GraphQLObject)] @@ -55,7 +55,7 @@ struct EwokCustomContext { fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> where - Q: GraphQLTypeMeta + 'q, + Q: GraphQLType + 'q, S: ScalarValue + 'q, { RootNode::new( diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 901a5dae1..998c7d50f 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -22,7 +22,7 @@ use crate::{ }, model::{RootNode, SchemaType, TypeType}, }, - types::{base::{GraphQLType, GraphQLTypeMeta}, name::Name}, + types::{base::GraphQLType, name::Name}, value::{DefaultScalarValue, ParseScalarValue, ScalarValue, Value}, GraphQLError, }; @@ -1101,7 +1101,7 @@ where /// construct its metadata and store it. pub fn get_type(&mut self, info: &T::TypeInfo) -> Type<'r> where - T: GraphQLTypeMeta + ?Sized, + T: GraphQLType + ?Sized, { if let Some(name) = T::name(info) { let validated_name = name.parse::().unwrap(); @@ -1122,7 +1122,7 @@ where /// Create a field with the provided name pub fn field(&mut self, name: &str, info: &T::TypeInfo) -> Field<'r, S> where - T: GraphQLTypeMeta + ?Sized, + T: GraphQLType + ?Sized, { Field { name: name.to_owned(), @@ -1140,7 +1140,7 @@ where info: &I::TypeInfo, ) -> Field<'r, S> where - I: GraphQLTypeMeta, + I: GraphQLType, { Field { name: name.to_owned(), @@ -1154,7 +1154,7 @@ where /// Create an argument with the provided name pub fn arg(&mut self, name: &str, info: &T::TypeInfo) -> Argument<'r, S> where - T: GraphQLTypeMeta + FromInputValue + ?Sized, + T: GraphQLType + FromInputValue + ?Sized, { Argument::new(name, self.get_type::(info)) } @@ -1170,7 +1170,7 @@ where info: &T::TypeInfo, ) -> Argument<'r, S> where - T: GraphQLTypeMeta + ToInputValue + FromInputValue + ?Sized, + T: GraphQLType + ToInputValue + FromInputValue + ?Sized, { Argument::new(name, self.get_type::>(info)).default_value(value.to_input_value()) } @@ -1186,14 +1186,14 @@ where /// This expects the type to implement `FromInputValue`. pub fn build_scalar_type(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S> where - T: FromInputValue + GraphQLTypeMeta + ParseScalarValue + ?Sized + 'r, + T: FromInputValue + GraphQLType + ParseScalarValue + ?Sized + 'r, { let name = T::name(info).expect("Scalar types must be named. Implement name()"); ScalarMeta::new::(Cow::Owned(name.to_string())) } /// Create a list meta type - pub fn build_list_type + ?Sized>( + pub fn build_list_type + ?Sized>( &mut self, info: &T::TypeInfo, ) -> ListMeta<'r> { @@ -1202,7 +1202,7 @@ where } /// Create a nullable meta type - pub fn build_nullable_type + ?Sized>( + pub fn build_nullable_type + ?Sized>( &mut self, info: &T::TypeInfo, ) -> NullableMeta<'r> { @@ -1220,7 +1220,7 @@ where fields: &[Field<'r, S>], ) -> ObjectMeta<'r, S> where - T: GraphQLTypeMeta + ?Sized, + T: GraphQLType + ?Sized, { let name = T::name(info).expect("Object types must be named. Implement name()"); @@ -1236,7 +1236,7 @@ where values: &[EnumValue], ) -> EnumMeta<'r, S> where - T: FromInputValue + GraphQLTypeMeta + ?Sized, + T: FromInputValue + GraphQLType + ?Sized, { let name = T::name(info).expect("Enum types must be named. Implement name()"); @@ -1251,7 +1251,7 @@ where fields: &[Field<'r, S>], ) -> InterfaceMeta<'r, S> where - T: GraphQLTypeMeta + ?Sized, + T: GraphQLType + ?Sized, { let name = T::name(info).expect("Interface types must be named. Implement name()"); @@ -1263,7 +1263,7 @@ where /// Create a union meta type pub fn build_union_type(&mut self, info: &T::TypeInfo, types: &[Type<'r>]) -> UnionMeta<'r> where - T: GraphQLTypeMeta + ?Sized, + T: GraphQLType + ?Sized, { let name = T::name(info).expect("Union types must be named. Implement name()"); @@ -1277,7 +1277,7 @@ where args: &[Argument<'r, S>], ) -> InputObjectMeta<'r, S> where - T: FromInputValue + GraphQLTypeMeta + ?Sized, + T: FromInputValue + GraphQLType + ?Sized, { let name = T::name(info).expect("Input object types must be named. Implement name()"); diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index b26151df2..14bf1fa52 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,7 +115,7 @@ extern crate bson; // These are required by the code generated via the `juniper_codegen` macros. #[doc(hidden)] -pub use {futures, inventory, static_assertions as sa}; +pub use {futures, static_assertions as sa}; #[doc(inline)] pub use futures::future::BoxFuture; @@ -186,11 +186,8 @@ pub use crate::{ }, types::{ async_await::GraphQLTypeAsync, - base::{ - Arguments, GraphQLInterfaceTypeImplementor, GraphQLObjectTypeInfo, GraphQLType, - TypeKind, GRAPHQL_IFACE_TYPES, GraphQLTypeMeta, AsDynGraphQLType, - }, - marker::{self, GraphQLInterface, GraphQLUnion}, + base::{Arguments, GraphQLType, TypeKind}, + marker::{self, GraphQLUnion}, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{GraphQLSubscriptionType, SubscriptionConnection, SubscriptionCoordinator}, }, diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs index b9213bdcf..e0c399f54 100644 --- a/juniper/src/macros/interface.rs +++ b/juniper/src/macros/interface.rs @@ -137,10 +137,49 @@ macro_rules! graphql_interface { type Context = $ctx; type TypeInfo = (); - fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> { + fn name(_ : &Self::TypeInfo) -> Option<&str> { Some($($outname)*) } + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> + ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> + where + $crate::__juniper_insert_generic!($($scalar)+): 'r + { + // Ensure all child types are registered + $( + let _ = registry.get_type::<$resolver_src>(info); + )* + let fields = &[$( + registry.field_convert::<$return_ty, _, Self::Context>( + &$crate::to_camel_case(stringify!($fn_name)), + info + ) + $(.description($fn_description))* + .push_docstring(&[$($docstring,)*]) + $(.deprecated($deprecated))* + $(.argument( + $crate::__juniper_create_arg!( + registry = registry, + info = info, + arg_ty = $arg_ty, + arg_name = $arg_name, + $(default = $arg_default,)* + $(description = $arg_description,)* + $(docstring = $arg_docstring,)* + ) + ))*, + )*]; + registry.build_interface_type::<$name>( + info, fields + ) + $(.description($desciption))* + .into_meta() + } + + #[allow(unused_variables)] fn resolve_field( &$main_self, @@ -190,7 +229,7 @@ macro_rules! graphql_interface { $( if ($resolver_expr as ::std::option::Option<$resolver_src>).is_some() { return - <$resolver_src as $crate::GraphQLTypeMeta<_>>::name(&()).unwrap().to_owned(); + <$resolver_src as $crate::GraphQLType<_>>::name(&()).unwrap().to_owned(); } )* @@ -207,7 +246,7 @@ macro_rules! graphql_interface { $(let $resolver_ctx = &executor.context();)* $( - if type_name == (<$resolver_src as $crate::GraphQLTypeMeta<_>>::name(&())).unwrap() { + if type_name == (<$resolver_src as $crate::GraphQLType<_>>::name(&())).unwrap() { return executor.resolve(&(), &$resolver_expr); } )* @@ -216,52 +255,6 @@ macro_rules! graphql_interface { } } ); - - $crate::__juniper_impl_trait!( - impl<$($scalar)* $(, $lifetimes)* > GraphQLTypeMeta for $name { - fn name(_ : &Self::TypeInfo) -> Option<&str> { - Some($($outname)*) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> - ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> - where - $crate::__juniper_insert_generic!($($scalar)+): 'r - { - // Ensure all child types are registered - $( - let _ = registry.get_type::<$resolver_src>(info); - )* - let fields = &[$( - registry.field_convert::<$return_ty, _, Self::Context>( - &$crate::to_camel_case(stringify!($fn_name)), - info - ) - $(.description($fn_description))* - .push_docstring(&[$($docstring,)*]) - $(.deprecated($deprecated))* - $(.argument( - $crate::__juniper_create_arg!( - registry = registry, - info = info, - arg_ty = $arg_ty, - arg_name = $arg_name, - $(default = $arg_default,)* - $(description = $arg_description,)* - $(docstring = $arg_docstring,)* - ) - ))*, - )*]; - registry.build_interface_type::<$name>( - info, fields - ) - $(.description($desciption))* - .into_meta() - } - } - ); }; ( diff --git a/juniper/src/macros/tests/util.rs b/juniper/src/macros/tests/util.rs index 09abe867d..058317285 100644 --- a/juniper/src/macros/tests/util.rs +++ b/juniper/src/macros/tests/util.rs @@ -3,9 +3,9 @@ use std::default::Default; pub async fn run_query(query: &str) -> Value where - Query: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, - Mutation: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, - Subscription: crate::GraphQLTypeMeta + Query: GraphQLTypeAsync + Default, + Mutation: GraphQLTypeAsync + Default, + Subscription: crate::GraphQLType + Default + Sync + Send, @@ -27,9 +27,9 @@ where pub async fn run_info_query(type_name: &str) -> Value where - Query: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, - Mutation: GraphQLTypeAsync + crate::GraphQLTypeMeta + Default, - Subscription: crate::GraphQLTypeMeta + Query: GraphQLTypeAsync + Default, + Mutation: GraphQLTypeAsync + Default, + Subscription: crate::GraphQLType + Default + Sync + Send, diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 0d2265ec4..2a0d5c021 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -10,7 +10,7 @@ use crate::{ ast::Type, executor::{Context, Registry}, schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta}, - types::{base::{GraphQLType, GraphQLTypeMeta}, name::Name}, + types::{base::GraphQLType, name::Name}, value::{DefaultScalarValue, ScalarValue}, }; @@ -92,9 +92,9 @@ pub enum DirectiveLocation { impl<'a, QueryT, MutationT, SubscriptionT, S> RootNode<'a, QueryT, MutationT, SubscriptionT, S> where S: ScalarValue + 'a, - QueryT: GraphQLTypeMeta, - MutationT: GraphQLTypeMeta, - SubscriptionT: GraphQLTypeMeta, + QueryT: GraphQLType, + MutationT: GraphQLType, + SubscriptionT: GraphQLType, { /// Construct a new root node from query, mutation, and subscription nodes /// @@ -127,9 +127,9 @@ where impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S> where - QueryT: GraphQLTypeMeta, - MutationT: GraphQLTypeMeta, - SubscriptionT: GraphQLTypeMeta, + QueryT: GraphQLType, + MutationT: GraphQLType, + SubscriptionT: GraphQLType, S: ScalarValue + 'a, { /// Construct a new root node from query and mutation nodes, @@ -168,9 +168,9 @@ impl<'a, S> SchemaType<'a, S> { ) -> Self where S: ScalarValue + 'a, - QueryT: GraphQLTypeMeta, - MutationT: GraphQLTypeMeta, - SubscriptionT: GraphQLTypeMeta, + QueryT: GraphQLType, + MutationT: GraphQLType, + SubscriptionT: GraphQLType, { let mut directives = FnvHashMap::default(); let query_type_name: String; diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 04af6bfef..39a155732 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -1,7 +1,7 @@ use crate::{ ast::Selection, executor::{ExecutionResult, Executor, Registry}, - types::base::{Arguments, GraphQLType, GraphQLTypeMeta, TypeKind}, + types::base::{Arguments, GraphQLType, TypeKind}, value::{ScalarValue, Value}, }; @@ -24,8 +24,15 @@ where type Context = CtxT; type TypeInfo = QueryT::TypeInfo; - fn type_name<'i>(&self, info: &'i QueryT::TypeInfo) -> Option<&'i str> { - self.query_type.type_name(info) + fn name(info: &QueryT::TypeInfo) -> Option<&str> { + QueryT::name(info) + } + + fn meta<'r>(info: &QueryT::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + QueryT::meta(info, registry) } fn resolve_field( @@ -70,26 +77,6 @@ where } } -impl<'a, CtxT, S, QueryT, MutationT, SubscriptionT> GraphQLTypeMeta - for RootNode<'a, QueryT, MutationT, SubscriptionT, S> -where - S: ScalarValue, - QueryT: GraphQLTypeMeta, - MutationT: GraphQLTypeMeta, - SubscriptionT: GraphQLTypeMeta, -{ - fn name(info: &QueryT::TypeInfo) -> Option<&str> { - QueryT::name(info) - } - - fn meta<'r>(info: &QueryT::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - QueryT::meta(info, registry) - } -} - impl<'a, CtxT, S, QueryT, MutationT, SubscriptionT> crate::GraphQLTypeAsync for RootNode<'a, QueryT, MutationT, SubscriptionT, S> where diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index 67f0ae848..de7da1d9f 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -1,27 +1,25 @@ -use std::{boxed::Box, collections::BTreeMap}; +use std::boxed::Box; +use std::collections::BTreeMap; -use graphql_parser::{ - query::{Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType}, - schema::{ - Definition, Document, EnumType as ExternalEnum, EnumValue as ExternalEnumValue, - Field as ExternalField, InputObjectType as ExternalInputObjectType, - InputValue as ExternalInputValue, InterfaceType as ExternalInterfaceType, - ObjectType as ExternalObjectType, ScalarType as ExternalScalarType, SchemaDefinition, Text, - TypeDefinition as ExternalTypeDefinition, UnionType as ExternalUnionType, - Value as ExternalValue, - }, - Pos, +use graphql_parser::query::{ + Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType, }; - -use crate::{ - ast::{InputValue, Type}, - schema::{ - meta::{Argument, DeprecationStatus, EnumValue, Field, MetaType}, - model::SchemaType, - translate::SchemaTranslator, - }, - value::ScalarValue, +use graphql_parser::schema::{Definition, Document, SchemaDefinition, Text}; +use graphql_parser::schema::{ + EnumType as ExternalEnum, EnumValue as ExternalEnumValue, Field as ExternalField, + InputObjectType as ExternalInputObjectType, InputValue as ExternalInputValue, + InterfaceType as ExternalInterfaceType, ObjectType as ExternalObjectType, + ScalarType as ExternalScalarType, TypeDefinition as ExternalTypeDefinition, + UnionType as ExternalUnionType, Value as ExternalValue, }; +use graphql_parser::Pos; + +use crate::ast::{InputValue, Type}; +use crate::schema::meta::DeprecationStatus; +use crate::schema::meta::{Argument, EnumValue, Field, MetaType}; +use crate::schema::model::SchemaType; +use crate::schema::translate::SchemaTranslator; +use crate::value::ScalarValue; pub struct GraphQLParserTranslator; diff --git a/juniper/src/tests/model.rs b/juniper/src/tests/model.rs index b133819b7..645ede185 100644 --- a/juniper/src/tests/model.rs +++ b/juniper/src/tests/model.rs @@ -107,7 +107,7 @@ pub struct Database { } use crate::{ - executor::Registry, schema::meta::MetaType, types::base::{GraphQLType,GraphQLTypeMeta}, value::ScalarValue, + executor::Registry, schema::meta::MetaType, types::base::GraphQLType, value::ScalarValue, }; impl GraphQLType for Database @@ -117,15 +117,6 @@ where type Context = Self; type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("_Database") - } -} - -impl GraphQLTypeMeta for Database -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&str> { Some("_Database") } diff --git a/juniper/src/tests/type_info_tests.rs b/juniper/src/tests/type_info_tests.rs index 5ea9823d7..87208fd8c 100644 --- a/juniper/src/tests/type_info_tests.rs +++ b/juniper/src/tests/type_info_tests.rs @@ -4,7 +4,7 @@ use crate::{ executor::{ExecutionResult, Executor, Registry, Variables}, schema::{meta::MetaType, model::RootNode}, types::{ - base::{Arguments, GraphQLType, GraphQLTypeMeta}, + base::{Arguments, GraphQLType}, scalars::{EmptyMutation, EmptySubscription}, }, value::{ScalarValue, Value}, @@ -26,25 +26,6 @@ where type Context = (); type TypeInfo = NodeTypeInfo; - fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { - Some(&info.name) - } - - fn resolve_field( - &self, - _: &Self::TypeInfo, - field_name: &str, - _: &Arguments, - executor: &Executor, - ) -> ExecutionResult { - executor.resolve(&(), &self.attributes.get(field_name).unwrap()) - } -} - -impl GraphQLTypeMeta for Node -where - S: ScalarValue, -{ fn name(info: &Self::TypeInfo) -> Option<&str> { Some(&info.name) } @@ -63,6 +44,16 @@ where .build_object_type::(info, &fields) .into_meta() } + + fn resolve_field( + &self, + _: &Self::TypeInfo, + field_name: &str, + _: &Arguments, + executor: &Executor, + ) -> ExecutionResult { + executor.resolve(&(), &self.attributes.get(field_name).unwrap()) + } } #[test] diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 8802976c7..156c2ab52 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -2,7 +2,7 @@ use crate::{ ast::Selection, executor::{ExecutionResult, Executor}, parser::Spanning, - value::{Object, ScalarValue, Value, DefaultScalarValue}, + value::{Object, ScalarValue, Value}, }; use crate::BoxFuture; @@ -15,7 +15,7 @@ This trait extends `GraphQLType` with asynchronous queries/mutations resolvers. Convenience macros related to asynchronous queries/mutations expand into an implementation of this trait and `GraphQLType` for the given type. */ -pub trait GraphQLTypeAsync: GraphQLType + Send + Sync +pub trait GraphQLTypeAsync: GraphQLType + Send + Sync where Self::Context: Send + Sync, Self::TypeInfo: Send + Sync, @@ -36,7 +36,7 @@ where _arguments: &'a Arguments, _executor: &'a Executor, ) -> BoxFuture<'a, ExecutionResult> { - panic!("resolve_field_async must be implemented by object types"); + panic!("resolve_field must be implemented by object types"); } /// Resolve the provided selection set against the current object. @@ -81,7 +81,7 @@ where selection_set: Option<&'a [Selection<'a, S>]>, executor: &'a Executor<'a, 'a, Self::Context, S>, ) -> BoxFuture<'a, ExecutionResult> { - if self.type_name(info).unwrap() == type_name { + if Self::name(info).unwrap() == type_name { self.resolve_async(info, selection_set, executor) } else { panic!("resolve_into_type_async must be implemented by unions and interfaces"); @@ -89,8 +89,6 @@ where } } -crate::sa::assert_obj_safe!(GraphQLTypeAsync); - // Wrapper function around resolve_selection_set_into_async_recursive. // This wrapper is necessary because async fns can not be recursive. fn resolve_selection_set_into_async<'a, 'e, T, CtxT, S>( @@ -145,7 +143,7 @@ where let meta_type = executor .schema() .concrete_type_by_name( - instance.type_name(info) + T::name(info) .expect("Resolving named type's selection set") .as_ref(), ) diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 1a6309e08..2faed6499 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -124,95 +124,111 @@ where } /** -* Primary trait used to expose Rust types in a GraphQL schema -* -* All of the convenience macros ultimately expand into an implementation of -* this trait for the given type. The macros remove duplicated definitions of -* fields and arguments, and add type checks on all resolve functions -* automatically. This can all be done manually. -* -* `GraphQLType` provides _some_ convenience methods for you, in the form of -* optional trait methods. The `name` and `meta` methods are mandatory, but -* other than that, it depends on what type you're exposing: -* +Primary trait used to expose Rust types in a GraphQL schema + +All of the convenience macros ultimately expand into an implementation of +this trait for the given type. The macros remove duplicated definitions of +fields and arguments, and add type checks on all resolve functions +automatically. This can all be done manually. + +`GraphQLType` provides _some_ convenience methods for you, in the form of +optional trait methods. The `name` and `meta` methods are mandatory, but +other than that, it depends on what type you're exposing: + * Scalars, enums, lists and non null wrappers only require `resolve`, * Interfaces and objects require `resolve_field` _or_ `resolve` if you want -* to implement custom resolution logic (probably not), + to implement custom resolution logic (probably not), * Interfaces and unions require `resolve_into_type` and `concrete_type_name`. * Input objects do not require anything -* -* ## Example -* -* Manually deriving an object is straightforward but tedious. This is the -* equivalent of the `User` object as shown in the example in the documentation -* root: -* -* ```rust -* use juniper::{GraphQLType, Registry, FieldResult, Context, -* Arguments, Executor, ExecutionResult, -* DefaultScalarValue}; -* use juniper::meta::MetaType; -* # use std::collections::HashMap; -* -* #[derive(Debug)] -* struct User { id: String, name: String, friend_ids: Vec } -* #[derive(Debug)] -* struct Database { users: HashMap } -* -* impl Context for Database {} -* -* impl GraphQLType for User -* { -* type Context = Database; -* type TypeInfo = (); -* -* fn type_name(&self, _: &()) -> Option<&'static str> { -* Some("User") -* } -* -* fn resolve_field( -* &self, -* info: &(), -* field_name: &str, -* args: &Arguments, -* executor: &Executor -* ) -* -> ExecutionResult -* { -* // Next, we need to match the queried field name. All arms of this -* // match statement return `ExecutionResult`, which makes it hard to -* // statically verify that the type you pass on to `executor.resolve*` -* // actually matches the one that you defined in `meta()` above. -* let database = executor.context(); -* match field_name { -* // Because scalars are defined with another `Context` associated -* // type, you must use resolve_with_ctx here to make the executor -* // perform automatic type conversion of its argument. -* "id" => executor.resolve_with_ctx(info, &self.id), -* "name" => executor.resolve_with_ctx(info, &self.name), -* -* // You pass a vector of User objects to `executor.resolve`, and it -* // will determine which fields of the sub-objects to actually -* // resolve based on the query. The executor instance keeps track -* // of its current position in the query. -* "friends" => executor.resolve(info, -* &self.friend_ids.iter() -* .filter_map(|id| database.users.get(id)) -* .collect::>() -* ), -* -* // We can only reach this panic in two cases; either a mismatch -* // between the defined schema in `meta()` above, or a validation -* // in this library failed because of a bug. -* // -* // In either of those two cases, the only reasonable way out is -* // to panic the thread. -* _ => panic!("Field {} not found on type User", field_name), -* } -* } -* } -* ``` -* + +## Example + +Manually deriving an object is straightforward but tedious. This is the +equivalent of the `User` object as shown in the example in the documentation +root: + +```rust +use juniper::{GraphQLType, Registry, FieldResult, Context, + Arguments, Executor, ExecutionResult, + DefaultScalarValue}; +use juniper::meta::MetaType; +# use std::collections::HashMap; + +#[derive(Debug)] +struct User { id: String, name: String, friend_ids: Vec } +#[derive(Debug)] +struct Database { users: HashMap } + +impl Context for Database {} + +impl GraphQLType for User +{ + type Context = Database; + type TypeInfo = (); + + fn name(_: &()) -> Option<&'static str> { + Some("User") + } + + fn meta<'r>(_: &(), registry: &mut Registry<'r>) -> MetaType<'r> + where DefaultScalarValue: 'r, + { + // First, we need to define all fields and their types on this type. + // + // If we need arguments, want to implement interfaces, or want to add + // documentation strings, we can do it here. + let fields = &[ + registry.field::<&String>("id", &()), + registry.field::<&String>("name", &()), + registry.field::>("friends", &()), + ]; + + registry.build_object_type::(&(), fields).into_meta() + } + + fn resolve_field( + &self, + info: &(), + field_name: &str, + args: &Arguments, + executor: &Executor + ) + -> ExecutionResult + { + // Next, we need to match the queried field name. All arms of this + // match statement return `ExecutionResult`, which makes it hard to + // statically verify that the type you pass on to `executor.resolve*` + // actually matches the one that you defined in `meta()` above. + let database = executor.context(); + match field_name { + // Because scalars are defined with another `Context` associated + // type, you must use resolve_with_ctx here to make the executor + // perform automatic type conversion of its argument. + "id" => executor.resolve_with_ctx(info, &self.id), + "name" => executor.resolve_with_ctx(info, &self.name), + + // You pass a vector of User objects to `executor.resolve`, and it + // will determine which fields of the sub-objects to actually + // resolve based on the query. The executor instance keeps track + // of its current position in the query. + "friends" => executor.resolve(info, + &self.friend_ids.iter() + .filter_map(|id| database.users.get(id)) + .collect::>() + ), + + // We can only reach this panic in two cases; either a mismatch + // between the defined schema in `meta()` above, or a validation + // in this library failed because of a bug. + // + // In either of those two cases, the only reasonable way out is + // to panic the thread. + _ => panic!("Field {} not found on type User", field_name), + } + } +} +``` + */ pub trait GraphQLType where @@ -234,10 +250,15 @@ where /// The name of the GraphQL type to expose. /// - /// This function will be called multiple times during type resolution. + /// This function will be called multiple times during schema construction. /// It must _not_ perform any calculation and _always_ return the same /// value. - fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str>; + fn name(info: &Self::TypeInfo) -> Option<&str>; + + /// The meta type representing this GraphQL type. + fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r; /// Resolve the value of a single field on this type. /// @@ -272,7 +293,7 @@ where selection_set: Option<&[Selection]>, executor: &Executor, ) -> ExecutionResult { - if self.type_name(info).unwrap() == type_name { + if Self::name(info).unwrap() == type_name { self.resolve(info, selection_set, executor) } else { panic!("resolve_into_type must be implemented by unions and interfaces"); @@ -284,7 +305,7 @@ where /// The default implementation panics. #[allow(unused_variables)] fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - panic!("concrete_type_name must be implemented by objects, unions and interfaces"); + panic!("concrete_type_name must be implemented by unions and interfaces"); } /// Resolve the provided selection set against the current object. @@ -320,34 +341,6 @@ where } } -crate::sa::assert_obj_safe!(GraphQLType); - -/// TODO: blah-blah doc -pub trait GraphQLTypeMeta: GraphQLType { - /// The name of the GraphQL type to expose. - /// - /// This function will be called multiple times during schema construction. - /// It must _not_ perform any calculation and _always_ return the same - /// value. - fn name(info: &Self::TypeInfo) -> Option<&str>; - - /// The meta type representing this GraphQL type. - fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r; -} - -/// TODO: blah-blah doc -pub trait AsDynGraphQLType { - /// TODO: blah-blah doc - type Context; - /// TODO: blah-blah doc - type TypeInfo; - - /// TODO: blah-blah doc - fn as_dyn_graphql_type(&self) -> &(dyn GraphQLType + 'static + Send + Sync); -} - /// Resolver logic for queries'/mutations' selection set. /// Calls appropriate resolver method for each field or fragment found /// and then merges returned values into `result` or pushes errors to @@ -368,7 +361,7 @@ where let meta_type = executor .schema() .concrete_type_by_name( - instance.type_name(info) + T::name(info) .expect("Resolving named type's selection set") .as_ref(), ) diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 5f3d7bcc9..589624028 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -2,7 +2,7 @@ use crate::{ ast::{FromInputValue, InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, schema::meta::MetaType, - types::{async_await::GraphQLTypeAsync, base::{GraphQLType, GraphQLTypeMeta}}, + types::{async_await::GraphQLTypeAsync, base::GraphQLType}, value::{ScalarValue, Value}, }; @@ -14,10 +14,17 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn type_name(&self, _: &T::TypeInfo) -> Option<&'static str> { + fn name(_: &T::TypeInfo) -> Option<&str> { None } + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_nullable_type::(info).into_meta() + } + fn resolve( &self, info: &T::TypeInfo, @@ -31,23 +38,6 @@ where } } -impl GraphQLTypeMeta for Option -where - S: ScalarValue, - T: GraphQLTypeMeta, -{ - fn name(_: &T::TypeInfo) -> Option<&str> { - None - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_nullable_type::(info).into_meta() - } -} - impl GraphQLTypeAsync for Option where T: GraphQLTypeAsync, @@ -106,10 +96,17 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn type_name(&self, _: &T::TypeInfo) -> Option<&'static str> { + fn name(_: &T::TypeInfo) -> Option<&str> { None } + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_list_type::(info).into_meta() + } + fn resolve( &self, info: &T::TypeInfo, @@ -120,23 +117,6 @@ where } } -impl GraphQLTypeMeta for Vec - where - T: GraphQLTypeMeta, - S: ScalarValue, -{ - fn name(_: &T::TypeInfo) -> Option<&str> { - None - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_list_type::(info).into_meta() - } -} - impl GraphQLTypeAsync for Vec where T: GraphQLTypeAsync, @@ -195,10 +175,17 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn type_name(&self, _: &T::TypeInfo) -> Option<&'static str> { + fn name(_: &T::TypeInfo) -> Option<&str> { None } + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_list_type::(info).into_meta() + } + fn resolve( &self, info: &T::TypeInfo, @@ -209,24 +196,6 @@ where } } -impl GraphQLTypeMeta for [T] - where - S: ScalarValue, - T: GraphQLTypeMeta, -{ - - fn name(_: &T::TypeInfo) -> Option<&str> { - None - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_list_type::(info).into_meta() - } -} - impl GraphQLTypeAsync for [T] where T: GraphQLTypeAsync, diff --git a/juniper/src/types/pointers.rs b/juniper/src/types/pointers.rs index 71c791346..68274fad3 100644 --- a/juniper/src/types/pointers.rs +++ b/juniper/src/types/pointers.rs @@ -6,7 +6,7 @@ use crate::{ schema::meta::MetaType, types::{ async_await::GraphQLTypeAsync, - base::{Arguments, GraphQLType, GraphQLTypeMeta}, + base::{Arguments, GraphQLType}, }, value::ScalarValue, BoxFuture, @@ -20,8 +20,15 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn type_name<'i>(&self, info: &'i T::TypeInfo) -> Option<&'i str> { - T::type_name(&**self, info) + fn name(info: &T::TypeInfo) -> Option<&str> { + T::name(info) + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + T::meta(info, registry) } fn resolve_into_type( @@ -54,23 +61,6 @@ where } } -impl GraphQLTypeMeta for Box - where - S: ScalarValue, - T: GraphQLTypeMeta + ?Sized, -{ - fn name(info: &T::TypeInfo) -> Option<&str> { - T::name(info) - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - T::meta(info, registry) - } -} - impl crate::GraphQLTypeAsync for Box where T: GraphQLTypeAsync + ?Sized, @@ -119,8 +109,15 @@ where type Context = CtxT; type TypeInfo = T::TypeInfo; - fn type_name<'i>(&self, info: &'i T::TypeInfo) -> Option<&'i str> { - T::type_name(&**self, info) + fn name(info: &T::TypeInfo) -> Option<&str> { + T::name(info) + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + T::meta(info, registry) } fn resolve_into_type( @@ -153,23 +150,6 @@ where } } -impl<'e, S, T, CtxT> GraphQLTypeMeta for &'e T - where - S: ScalarValue, - T: GraphQLTypeMeta + ?Sized, -{ - fn name(info: &T::TypeInfo) -> Option<&str> { - T::name(info) - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - T::meta(info, registry) - } -} - impl<'e, S, T> GraphQLTypeAsync for &'e T where S: ScalarValue + Send + Sync, @@ -215,8 +195,15 @@ where type Context = T::Context; type TypeInfo = T::TypeInfo; - fn type_name<'i>(&self, info: &'i T::TypeInfo) -> Option<&'i str> { - T::type_name(&**self, info) + fn name(info: &T::TypeInfo) -> Option<&str> { + T::name(info) + } + + fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + T::meta(info, registry) } fn resolve_into_type( @@ -249,24 +236,6 @@ where } } -impl GraphQLTypeMeta for Arc - where - S: ScalarValue, - T: GraphQLTypeMeta + ?Sized, -{ - fn name(info: &T::TypeInfo) -> Option<&str> { - T::name(info) - } - - fn meta<'r>(info: &T::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - T::meta(info, registry) - } -} - - impl<'e, S, T> GraphQLTypeAsync for Arc where S: ScalarValue + Send + Sync, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 0f8670aa6..a2963c935 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -6,7 +6,7 @@ use crate::{ executor::{ExecutionResult, Executor, Registry}, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, - types::base::{GraphQLType, GraphQLTypeMeta}, + types::base::GraphQLType, value::{ParseScalarResult, ScalarValue, Value}, }; @@ -199,10 +199,17 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { + fn name(_: &()) -> Option<&str> { Some("String") } + fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + registry.build_scalar_type::(&()).into_meta() + } + fn resolve( &self, _: &(), @@ -213,22 +220,6 @@ where } } -impl GraphQLTypeMeta for str - where - S: ScalarValue, -{ - fn name(_: &()) -> Option<&str> { - Some("String") - } - - fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_scalar_type::(&()).into_meta() - } -} - impl crate::GraphQLTypeAsync for str where S: ScalarValue + Send + Sync, @@ -357,15 +348,6 @@ where type Context = T; type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("_EmptyMutation") - } -} - -impl GraphQLTypeMeta for EmptyMutation -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&str> { Some("_EmptyMutation") } @@ -423,15 +405,6 @@ where type Context = T; type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("_EmptySubscription") - } -} - -impl GraphQLTypeMeta for EmptySubscription -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&str> { Some("_EmptySubscription") } diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs index 6f8f47c88..050769cd9 100644 --- a/juniper/src/types/subscriptions.rs +++ b/juniper/src/types/subscriptions.rs @@ -160,7 +160,7 @@ where 'res: 'f, { Box::pin(async move { - if self.type_name(info) == Some(type_name) { + if Self::name(info) == Some(type_name) { self.resolve_into_stream(info, executor).await } else { panic!("resolve_into_type_stream must be implemented"); @@ -218,7 +218,7 @@ where let meta_type = executor .schema() .concrete_type_by_name( - instance.type_name(info) + T::name(info) .expect("Resolving named type's selection set") .as_ref(), ) diff --git a/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs b/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs index 2bb1a2cb8..549375513 100644 --- a/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs +++ b/juniper/src/validation/rules/overlapping_fields_can_be_merged.rs @@ -741,7 +741,7 @@ mod tests { executor::Registry, schema::meta::MetaType, types::{ - base::{GraphQLType, GraphQLTypeMeta}, + base::GraphQLType, scalars::{EmptyMutation, EmptySubscription, ID}, }, }; @@ -1383,15 +1383,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("SomeBox") - } - } - - impl GraphQLTypeMeta for SomeBox - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("SomeBox") } @@ -1417,15 +1408,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("StringBox") - } - } - - impl GraphQLTypeMeta for StringBox - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("StringBox") } @@ -1457,15 +1439,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("IntBox") - } - } - - impl GraphQLTypeMeta for IntBox - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("IntBox") } @@ -1497,15 +1470,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("NonNullStringBox1") - } - } - - impl GraphQLTypeMeta for NonNullStringBox1 - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox1") } @@ -1527,15 +1491,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("NonNullStringBox1Impl") - } - } - - impl GraphQLTypeMeta for NonNullStringBox1Impl - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox1Impl") } @@ -1567,15 +1522,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("NonNullStringBox2") - } - } - - impl GraphQLTypeMeta for NonNullStringBox2 - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox2") } @@ -1597,15 +1543,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("NonNullStringBox2Impl") - } - } - - impl GraphQLTypeMeta for NonNullStringBox2Impl - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("NonNullStringBox2Impl") } @@ -1637,15 +1574,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Node") - } - } - - impl GraphQLTypeMeta for Node - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("Node") } @@ -1670,15 +1598,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Edge") - } - } - - impl GraphQLTypeMeta for Edge - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("Edge") } @@ -1700,15 +1619,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Connection") - } - } - - impl GraphQLTypeMeta for Connection - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("Connection") } @@ -1730,15 +1640,6 @@ mod tests { type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("QueryRoot") - } - } - - impl GraphQLTypeMeta for QueryRoot - where - S: ScalarValue, - { fn name(_: &()) -> Option<&'static str> { Some("QueryRoot") } diff --git a/juniper/src/validation/test_harness.rs b/juniper/src/validation/test_harness.rs index af2c2a7d4..07e87d781 100644 --- a/juniper/src/validation/test_harness.rs +++ b/juniper/src/validation/test_harness.rs @@ -8,7 +8,7 @@ use crate::{ meta::{EnumValue, MetaType}, model::{DirectiveLocation, DirectiveType, RootNode}, }, - types::{base::{GraphQLType, GraphQLTypeMeta}, scalars::ID}, + types::{base::GraphQLType, scalars::ID}, validation::{visit, MultiVisitorNil, RuleError, ValidatorContext, Visitor}, value::ScalarValue, }; @@ -74,15 +74,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Being") - } -} - -impl GraphQLTypeMeta for Being -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Being") } @@ -106,15 +97,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Pet") - } -} - -impl GraphQLTypeMeta for Pet -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Pet") } @@ -138,15 +120,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Canine") - } -} - -impl GraphQLTypeMeta for Canine -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Canine") } @@ -170,15 +143,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("DogCommand") - } -} - -impl GraphQLTypeMeta for DogCommand -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("DogCommand") } @@ -221,15 +185,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Dog") - } -} - -impl GraphQLTypeMeta for Dog -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Dog") } @@ -275,15 +230,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("FurColor") - } -} - -impl GraphQLTypeMeta for FurColor -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("FurColor") } @@ -328,15 +274,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Cat") - } -} - -impl GraphQLTypeMeta for Cat -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Cat") } @@ -369,15 +306,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("CatOrDog") - } -} - -impl GraphQLTypeMeta for CatOrDog -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("CatOrDog") } @@ -399,15 +327,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Intelligent") - } -} - -impl GraphQLTypeMeta for Intelligent -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Intelligent") } @@ -429,15 +348,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Human") - } -} - -impl GraphQLTypeMeta for Human -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Human") } @@ -471,15 +381,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("Alien") - } -} - -impl GraphQLTypeMeta for Alien -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("Alien") } @@ -513,15 +414,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("DogOrHuman") - } -} - -impl GraphQLTypeMeta for DogOrHuman -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("DogOrHuman") } @@ -543,15 +435,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("HumanOrAlien") - } -} - -impl GraphQLTypeMeta for HumanOrAlien -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("HumanOrAlien") } @@ -573,15 +456,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("ComplexInput") - } -} - -impl GraphQLTypeMeta for ComplexInput -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("ComplexInput") } @@ -634,15 +508,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("ComplicatedArgs") - } -} - -impl GraphQLTypeMeta for ComplicatedArgs -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("ComplicatedArgs") } @@ -706,15 +571,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("QueryRoot") - } -} - -impl GraphQLTypeMeta for QueryRoot -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&'static str> { Some("QueryRoot") } @@ -748,15 +604,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("MutationRoot") - } -} - -impl GraphQLTypeMeta for MutationRoot -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&str> { Some("MutationRoot") } @@ -787,15 +634,6 @@ where type Context = (); type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some("SubscriptionRoot") - } -} - -impl GraphQLTypeMeta for SubscriptionRoot -where - S: ScalarValue, -{ fn name(_: &()) -> Option<&str> { Some("SubscriptionRoot") } @@ -819,9 +657,9 @@ pub fn validate<'a, Q, M, Sub, V, F, S>( ) -> Vec where S: ScalarValue + 'a, - Q: GraphQLTypeMeta, - M: GraphQLTypeMeta, - Sub: GraphQLTypeMeta, + Q: GraphQLType, + M: GraphQLType, + Sub: GraphQLType, V: Visitor<'a, S> + 'a, F: Fn() -> V, { @@ -885,9 +723,9 @@ pub fn expect_passes_rule_with_schema<'a, Q, M, Sub, V, F, S>( q: &'a str, ) where S: ScalarValue + 'a, - Q: GraphQLTypeMeta, - M: GraphQLTypeMeta, - Sub: GraphQLTypeMeta, + Q: GraphQLType, + M: GraphQLType, + Sub: GraphQLType, V: Visitor<'a, S> + 'a, F: Fn() -> V, { @@ -916,8 +754,8 @@ pub fn expect_fails_rule_with_schema<'a, Q, M, V, F, S>( expected_errors: &[RuleError], ) where S: ScalarValue + 'a, - Q: GraphQLTypeMeta, - M: GraphQLTypeMeta, + Q: GraphQLType, + M: GraphQLType, V: Visitor<'a, S> + 'a, F: Fn() -> V, { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 7490b7f26..c951b1e5c 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -144,15 +144,6 @@ fn impl_scalar_struct( type Context = (); type TypeInfo = (); - fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - } - - impl #crate_name::GraphQLTypeMeta for #ident - where - S: #crate_name::ScalarValue, - { fn name(_: &Self::TypeInfo) -> Option<&str> { Some(#name) } @@ -168,6 +159,15 @@ fn impl_scalar_struct( #description .into_meta() } + + fn resolve( + &self, + info: &(), + selection: Option<&[#crate_name::Selection]>, + executor: &#crate_name::Executor, + ) -> #crate_name::ExecutionResult { + #crate_name::GraphQLType::resolve(&self.0, info, selection, executor) + } } impl #crate_name::ToInputValue for #ident diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 08e93cc49..af06d112a 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -433,7 +433,7 @@ impl ToTokens for UnionDefinition { let var_check = &var.resolver_check; quote! { if #var_check { - return <#var_ty as #crate_path::GraphQLTypeMeta<#scalar>>::name(&()) + return <#var_ty as #crate_path::GraphQLType<#scalar>>::name(&()) .unwrap().to_string(); } } @@ -443,9 +443,7 @@ impl ToTokens for UnionDefinition { let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| { let var_ty = &var.ty; - let get_name = quote! { - (<#var_ty as #crate_path::GraphQLTypeMeta<#scalar>>::name(&())) - }; + let get_name = quote! { (<#var_ty as #crate_path::GraphQLType<#scalar>>::name(&())) }; quote! { if type_name == #get_name.unwrap() { return #crate_path::IntoResolvable::into( @@ -467,7 +465,7 @@ impl ToTokens for UnionDefinition { let var_ty = &var.ty; let get_name = quote! { - (<#var_ty as #crate_path::GraphQLTypeMeta<#scalar>>::name(&())) + (<#var_ty as #crate_path::GraphQLType<#scalar>>::name(&())) }; quote! { if type_name == #get_name.unwrap() { @@ -529,10 +527,24 @@ impl ToTokens for UnionDefinition { type Context = #context; type TypeInfo = (); - fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> { + fn name(_ : &Self::TypeInfo) -> Option<&str> { Some(#name) } + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #crate_path::Registry<'r, #scalar> + ) -> #crate_path::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + let types = &[ + #( registry.get_type::<&#var_types>(&(())), )* + ]; + registry.build_union_type::<#ty_full>(info, types) + #description + .into_meta() + } + fn concrete_type_name( &self, context: &Self::Context, @@ -563,31 +575,6 @@ impl ToTokens for UnionDefinition { } }; - let meta_type_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics #crate_path::GraphQLTypeMeta<#scalar> for #ty_full - #where_clause - { - fn name(_ : &Self::TypeInfo) -> Option<&str> { - Some(#name) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut #crate_path::Registry<'r, #scalar> - ) -> #crate_path::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - let types = &[ - #( registry.get_type::<&#var_types>(&(())), )* - ]; - registry.build_union_type::<#ty_full>(info, types) - #description - .into_meta() - } - } - }; - let async_type_impl = quote! { #[automatically_derived] impl#ext_impl_generics #crate_path::GraphQLTypeAsync<#scalar> for #ty_full @@ -634,7 +621,7 @@ impl ToTokens for UnionDefinition { } }; - into.append_all(&[union_impl, output_type_impl, type_impl, async_type_impl, meta_type_impl]); + into.append_all(&[union_impl, output_type_impl, type_impl, async_type_impl]); } } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 2aa7ff9dc..fc449aa8a 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -286,23 +286,6 @@ pub fn build_scalar( type Context = (); type TypeInfo = (); - fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - - fn resolve( - &self, - info: &(), - selection: Option<&[#crate_name::Selection<#generic_type>]>, - executor: &#crate_name::Executor, - ) -> #crate_name::ExecutionResult<#generic_type> { - Ok(#resolve_body) - } - } - - impl#generic_type_decl #crate_name::GraphQLTypeMeta<#generic_type> for #impl_for_type - #generic_type_bound - { fn name(_: &Self::TypeInfo) -> Option<&str> { Some(#name) } @@ -318,6 +301,15 @@ pub fn build_scalar( #description .into_meta() } + + fn resolve( + &self, + info: &(), + selection: Option<&[#crate_name::Selection<#generic_type>]>, + executor: &#crate_name::Executor, + ) -> #crate_name::ExecutionResult<#generic_type> { + Ok(#resolve_body) + } } impl#generic_type_decl #crate_name::ToInputValue<#generic_type> for #impl_for_type diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 658676c2f..03395b262 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -793,7 +793,7 @@ impl GraphQLTypeDefiniton { #name => { panic!("Tried to resolve async field {} on type {:?} with a sync resolver", #name, - >::name(_info) + >::name(_info) ); }, ) @@ -968,7 +968,7 @@ impl GraphQLTypeDefiniton { _ => { panic!("Field {} not found on type {:?}", field, - >::name(info) + >::name(info) ); } } @@ -1009,10 +1009,25 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); - fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { + fn name(_: &Self::TypeInfo) -> Option<&str> { Some(#name) } + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut #juniper_crate_name::Registry<'r, #scalar> + ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> + where #scalar : 'r, + { + let fields = vec![ + #( #field_definitions ),* + ]; + let meta = registry.build_object_type::<#ty>( info, &fields ) + #description + #interfaces; + meta.into_meta() + } + #[allow(unused_variables)] #[allow(unused_mut)] fn resolve_field( @@ -1027,7 +1042,7 @@ impl GraphQLTypeDefiniton { _ => { panic!("Field {} not found on type {:?}", field, - >::name(_info) + >::name(_info) ); } } @@ -1040,29 +1055,6 @@ impl GraphQLTypeDefiniton { } - impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty #type_generics_tokens - #where_clause - { - fn name(_: &Self::TypeInfo) -> Option<&str> { - Some(#name) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut #juniper_crate_name::Registry<'r, #scalar> - ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> - where #scalar : 'r, - { - let fields = vec![ - #( #field_definitions ),* - ]; - let meta = registry.build_object_type::<#ty>( info, &fields ) - #description - #interfaces; - meta.into_meta() - } - } - #resolve_field_async ); output @@ -1252,32 +1244,7 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); - fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - - fn resolve_field( - &self, - _: &(), - _: &str, - _: &#juniper_crate_name::Arguments<#scalar>, - _: &#juniper_crate_name::Executor, - ) -> #juniper_crate_name::ExecutionResult<#scalar> { - panic!("Called `resolve_field` on subscription object"); - } - - - fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String { - #name.to_string() - } - } - ); - - let graphql_meta_implementation = quote!( - impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty #type_generics_tokens - #where_clause - { - fn name(_: &Self::TypeInfo) -> Option<&'static str> { + fn name(_: &Self::TypeInfo) -> Option<&str> { Some(#name) } @@ -1295,6 +1262,21 @@ impl GraphQLTypeDefiniton { #interfaces; meta.into_meta() } + + fn resolve_field( + &self, + _: &(), + _: &str, + _: &#juniper_crate_name::Arguments<#scalar>, + _: &#juniper_crate_name::Executor, + ) -> #juniper_crate_name::ExecutionResult<#scalar> { + panic!("Called `resolve_field` on subscription object"); + } + + + fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String { + #name.to_string() + } } ); @@ -1343,7 +1325,6 @@ impl GraphQLTypeDefiniton { quote!( #graphql_implementation - #graphql_meta_implementation #subscription_implementation ) } @@ -1487,26 +1468,6 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some(#name) - } - - fn resolve( - &self, - _: &(), - _: Option<&[#juniper_crate_name::Selection<#scalar>]>, - _: &#juniper_crate_name::Executor - ) -> #juniper_crate_name::ExecutionResult<#scalar> { - let v = match self { - #( #resolves )* - }; - Ok(v) - } - } - - impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty - #where_clause - { fn name(_: &()) -> Option<&'static str> { Some(#name) } @@ -1523,6 +1484,18 @@ impl GraphQLTypeDefiniton { #description .into_meta() } + + fn resolve( + &self, + _: &(), + _: Option<&[#juniper_crate_name::Selection<#scalar>]>, + _: &#juniper_crate_name::Executor + ) -> #juniper_crate_name::ExecutionResult<#scalar> { + let v = match self { + #( #resolves )* + }; + Ok(v) + } } impl#impl_generics #juniper_crate_name::FromInputValue<#scalar> for #ty @@ -1738,14 +1711,6 @@ impl GraphQLTypeDefiniton { type Context = #context; type TypeInfo = (); - fn type_name(&self, _: &()) -> Option<&'static str> { - Some(#name) - } - } - - impl#impl_generics #juniper_crate_name::GraphQLTypeMeta<#scalar> for #ty #type_generics_tokens - #where_clause - { fn name(_: &()) -> Option<&'static str> { Some(#name) } From 1698f11fbfcd778f5d2c6ff38fda40cd1f8f3229 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 1 Jul 2020 11:57:38 +0300 Subject: [PATCH 05/79] Correct PoC to compile --- .../src/codegen/interface_attr.rs | 116 ++++++++---------- juniper/src/lib.rs | 6 +- juniper/src/types/base.rs | 10 ++ 3 files changed, 63 insertions(+), 69 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8e14b102e..d18b8a058 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,10 +1,10 @@ //! Tests for `#[graphql_interface]` macro. -use juniper::{execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, GraphQLTypeMeta}; +use juniper::{execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; /* SUGARED #[derive(GraphQLObject)] -#[graphql(implements Character)] +#[graphql(implements(Character))] struct Human { id: String, home_planet: String, @@ -16,11 +16,12 @@ struct Human { home_planet: String, } #[automatically_derived] -impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLType<__S> for Human { - type Context = >::Context; - type TypeInfo = >::TypeInfo; +impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { + type Context = >::Context; + type TypeInfo = >::TypeInfo; - fn as_dyn_graphql_type(&self) -> &(dyn GraphQLType<__S, Context = Self::Context, TypeInfo = Self::TypeInfo> + 'static + Send + Sync) { + #[inline] + fn as_dyn_graphql_type(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { self } } @@ -43,7 +44,7 @@ impl Character f /* SUGARED #[derive(GraphQLObject)] -#[graphql(implements Character)] +#[graphql(implements(Character))] struct Droid { id: String, primary_function: String, @@ -55,11 +56,12 @@ struct Droid { primary_function: String, } #[automatically_derived] -impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLType<__S> for Droid { - type Context = >::Context; - type TypeInfo = >::TypeInfo; +impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { + type Context = >::Context; + type TypeInfo = >::TypeInfo; - fn as_dyn_graphql_type(&self) -> &(dyn GraphQLType<__S, Context = Self::Context, TypeInfo = Self::TypeInfo> + 'static + Send + Sync) { + #[inline] + fn as_dyn_graphql_type(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { self } } @@ -89,7 +91,7 @@ impl Character f // ------------------------------------------ /* SUGARED -#[graphql_interface] +#[graphql_interface(for(Human, Droid))] trait Character { fn id(&self) -> &str; @@ -97,20 +99,19 @@ trait Character { fn as_droid(&self) -> Option<&Droid> { None } } DESUGARS INTO: */ -trait Character: ::juniper::AsDynGraphQLType { +trait Character: ::juniper::AsDynGraphQLValue { fn id(&self) -> &str; fn as_droid(&self) -> Option<&Droid> { None } } #[automatically_derived] -impl<'__obj> ::juniper::marker::GraphQLInterface for dyn Character + '__obj + Send + Sync +impl<'__obj, __S> ::juniper::marker::GraphQLInterface<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, { fn mark() { - if let Some(objects) = ::juniper::GRAPHQL_IFACE_TYPES.get("Character") { - for obj in objects { - (obj.mark_fn)(); - } - } + >::mark(); + >::mark(); } } #[automatically_derived] @@ -119,22 +120,21 @@ where __S: ::juniper::ScalarValue, { fn mark() { - if let Some(objects) = ::juniper::GRAPHQL_IFACE_TYPES.get("Character") { - for obj in objects { - (obj.mark_fn)(); - } - } + ::juniper::sa::assert_type_ne_all!(Human, Droid); + + >::mark(); + >::mark(); } } #[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +impl<'__obj, __S> ::juniper::GraphQLValue<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync where __S: ::juniper::ScalarValue, { type Context = (); type TypeInfo = (); fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) + >::name(info) } fn resolve_field( &self, @@ -161,10 +161,9 @@ where } fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - // First, check custom downcaster to be used. if ({ Character::as_droid(self) } as ::std::option::Option<&Droid>).is_some() { - return >::name(info) + return >::name(info) .unwrap() .to_string(); } @@ -174,7 +173,7 @@ where } fn resolve_into_type( &self, - ti: &Self::TypeInfo, + info: &Self::TypeInfo, type_name: &str, _: Option<&[::juniper::Selection<__S>]>, executor: &::juniper::Executor, @@ -182,15 +181,15 @@ where let context = executor.context(); // First, check custom downcaster to be used. - if type_name == (>::name(ti)).unwrap() { + if type_name == (>::name(info)).unwrap() { return ::juniper::IntoResolvable::into( Character::as_droid(self), executor.context(), ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - None => Ok(::juniper::Value::null()), - }); + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }); } // Otherwise, resolve inner type as dyn object. @@ -198,18 +197,18 @@ where self.as_dyn_graphql_type(), executor.context(), ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - None => Ok(::juniper::Value::null()), - }); + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }); } } #[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLTypeMeta<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +impl<'__obj, __S> ::juniper::GraphQLType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync where __S: ::juniper::ScalarValue, { - fn name(_: &Self::TypeInfo) -> Option<&str> { + fn name(_: &Self::TypeInfo) -> Option<&'static str> { Some("Character") } fn meta<'r>( @@ -219,15 +218,8 @@ where where __S: 'r, { - //panic!("🔬 {:#?}", registry.types); - // Ensure custom downcaster type is registered - //let _ = registry.get_type::<&Droid>(info); - - // Ensure all child types are registered - // TODO: how? - // TODO: get_type_by_name and iter - //let _ = registry.get_type::<&Human>(info); - + let _ = registry.get_type::<&Human>(info); + let _ = registry.get_type::<&Droid>(info); let fields = vec![ // TODO: try array @@ -240,10 +232,10 @@ where } } #[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLTypeAsync<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +impl<'__obj, __S> ::juniper::GraphQLValueAsync<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync where __S: ::juniper::ScalarValue, - Self: Send + Sync, + Self: Sync, __S: Send + Sync, { fn resolve_field_async<'b>( @@ -253,20 +245,20 @@ where arguments: &'b ::juniper::Arguments<__S>, executor: &'b ::juniper::Executor, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - // TODO: similar to what happens in GraphQLType impl - let res = self.resolve_field(info, field_name, arguments, executor); + // TODO: similar to what happens in GraphQLValue impl + let res = ::juniper::GraphQLValue::resolve_field(self, info, field_name, arguments, executor); ::juniper::futures::future::FutureExt::boxed(async move { res }) } fn resolve_into_type_async<'b>( &'b self, - ti: &'b Self::TypeInfo, + info: &'b Self::TypeInfo, type_name: &str, se: Option<&'b [::juniper::Selection<'b, __S>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - // TODO: similar to what happens in GraphQLType impl - let res = self.resolve_into_type(ti, type_name, se, executor); + // TODO: similar to what happens in GraphQLValue impl + let res = ::juniper::GraphQLValue::resolve_into_type(self, info, type_name, se, executor); ::juniper::futures::future::FutureExt::boxed(async move { res }) } } @@ -275,7 +267,7 @@ where fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> where - Q: GraphQLTypeMeta + 'q, + Q: GraphQLType + 'q, S: ScalarValue + 'q, { RootNode::new( @@ -395,13 +387,3 @@ mod poc { ); } } - -/* -struct Woop; - -impl Woop { - fn get(&self) -> for<'r> fn(info: &TI, registry: &mut Registry<'r, S>) -> MetaType<'r, S> { - unimplemented!() - } -} -*/ \ No newline at end of file diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 4ae623cae..a3653ff77 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -182,8 +182,10 @@ pub use crate::{ }, types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, - base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, - marker::{self, GraphQLUnion, GraphQLInterface}, + base::{ + Arguments, AsDynGraphQLValue, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind, + }, + marker::{self, GraphQLInterface, GraphQLUnion}, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ GraphQLSubscriptionType, GraphQLSubscriptionValue, SubscriptionConnection, diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index f646f517e..a8f6a485c 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -290,6 +290,16 @@ where crate::sa::assert_obj_safe!(GraphQLValue); +pub type DynGraphQLValue = + dyn GraphQLValue + Send + Sync + 'static; + +pub trait AsDynGraphQLValue { + type Context; + type TypeInfo; + + fn as_dyn_graphql_type(&self) -> &DynGraphQLValue; +} + /// Primary trait used to expose Rust types in a GraphQL schema. /// /// All of the convenience macros ultimately expand into an implementation of From 1e7a36986df81ae100004ca80d073401836431b9 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 1 Jul 2020 12:53:00 +0300 Subject: [PATCH 06/79] Bootstrap #[graphql_interface] expansion --- juniper_codegen/src/graphql_interface/attr.rs | 41 +++++++++++++++++-- juniper_codegen/src/graphql_interface/mod.rs | 2 +- juniper_codegen/src/graphql_union/attr.rs | 23 +++-------- juniper_codegen/src/lib.rs | 2 +- juniper_codegen/src/result.rs | 10 +++-- juniper_codegen/src/util/mod.rs | 32 +++++++++++++++ 6 files changed, 84 insertions(+), 26 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index e6d525352..fc3dfae3f 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -1,8 +1,43 @@ //! Code generation for `#[graphql_interface]` macro. -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; + +use crate::{ + result::GraphQLScope, + util::{strip_attr, unite_attrs}, +}; + +/// [`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 { - unimplemented!() -} \ No newline at end of file + if let Ok(mut ast) = syn::parse2::(body.clone()) { + let trait_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); + ast.attrs = strip_attr("graphql_interface", ast.attrs); + expand_on_trait(trait_attrs, ast) + } else if let Ok(mut ast) = syn::parse2::(body) { + let impl_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); + ast.attrs = strip_attr("graphql_interface", ast.attrs); + expand_on_impl(impl_attrs, ast) + } else { + Err(syn::Error::new( + Span::call_site(), + "#[graphql_interface] attribute is applicable to trait definitions and trait \ + implementations only", + )) + } +} + +/// Expands `#[graphql_interface]` macro placed on trait definition. +pub fn expand_on_trait( + attrs: Vec, + ast: syn::ItemTrait, +) -> syn::Result { + todo!() +} + +/// Expands `#[graphql_interface]` macro placed on trait implementation block. +pub fn expand_on_impl(attrs: Vec, ast: syn::ItemImpl) -> syn::Result { + todo!() +} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index c98f0efbf..de97b32b9 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -2,4 +2,4 @@ //! //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces -pub mod attr; \ No newline at end of file +pub mod attr; diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index 0c9ebc32a..2fbc0b00d 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -8,7 +8,9 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ result::GraphQLScope, - util::{path_eq_single, span_container::SpanContainer, unparenthesize}, + util::{ + path_eq_single, span_container::SpanContainer, strip_attr, unite_attrs, unparenthesize, + }, }; use super::{ @@ -27,23 +29,8 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result &str { match self { + Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", + Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveObject | Self::ImplObject => "#sec-Objects", Self::DeriveInputObject => "#sec-Input-Objects", - Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveEnum => "#sec-Enums", Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars", } @@ -35,9 +38,10 @@ impl GraphQLScope { impl fmt::Display for GraphQLScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { + Self::InterfaceAttr | Self::InterfaceDerive => "interface", + Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveObject | Self::ImplObject => "object", Self::DeriveInputObject => "input object", - Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveEnum => "enum", Self::DeriveScalar | Self::ImplScalar => "scalar", }; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index f35e4395d..6a84f29ef 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -44,6 +44,38 @@ pub fn name_of_type(ty: &syn::Type) -> Option { .map(|segment| segment.ident.clone()) } +/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given +/// `attr_path` and `attr_args`. +/// +/// This function is generally used for uniting `proc_macro_attribute` with its body attributes. +pub fn unite_attrs( + (attr_path, attr_args): (&str, &TokenStream), + attrs: &Vec, +) -> Vec { + let mut full_attrs = Vec::with_capacity(attrs.len() + 1); + let attr_path = syn::Ident::new(attr_path, Span::call_site()); + full_attrs.push(parse_quote! { #[#attr_path(#attr_args)] }); + full_attrs.extend_from_slice(attrs); + full_attrs +} + +/// Strips all `attr_path` attributes from the given `attrs` collection. +/// +/// This function is generally used for removing duplicate attributes during `proc_macro_attribute` +/// expansion, so avoid unnecessary expansion duplication. +pub fn strip_attr(attr_path: &str, attrs: Vec) -> Vec { + attrs + .into_iter() + .filter_map(|attr| { + if path_eq_single(&attr.path, attr_path) { + None + } else { + Some(attr) + } + }) + .collect() +} + /// Compares a path to a one-segment string value, /// return true if equal. pub fn path_eq_single(path: &syn::Path, value: &str) -> bool { From 8758f21e9ec53707791f63c5f1f2ab9392dcb0a2 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 1 Jul 2020 13:33:41 +0300 Subject: [PATCH 07/79] Bootstrap #[graphql_interface] meta parsing --- juniper_codegen/src/graphql_interface/mod.rs | 162 +++++++++++++++++++ juniper_codegen/src/graphql_union/mod.rs | 60 +------ juniper_codegen/src/lib.rs | 63 ++++++++ juniper_codegen/src/util/mod.rs | 5 + 4 files changed, 236 insertions(+), 54 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index de97b32b9..7864a80da 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,3 +3,165 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; + +use std::collections::HashMap; + +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, +}; + +use crate::util::{ + dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, +}; + +/* +/// Helper alias for the type of [`InterfaceMeta::external_downcasters`] field. +type InterfaceMetaDowncasters = HashMap>;*/ + +/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_interface]`) attribute when +/// generating code for [GraphQL interface][1] type. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug, Default)] +struct InterfaceMeta { + /// Explicitly specified name of [GraphQL interface][1] type. + /// + /// If absent, then Rust type name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub name: Option>, + + /// Explicitly specified [description][2] of [GraphQL interface][1] type. + /// + /// If absent, then Rust doc comment is used as [description][2], if any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + pub description: Option>, + + /// Explicitly specified type of `juniper::Context` to use for resolving this + /// [GraphQL interface][1] type with. + /// + /// If absent, then unit type `()` is assumed as type of `juniper::Context`. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub context: Option>, + + /// Explicitly specified type of `juniper::ScalarValue` to use for resolving this + /// [GraphQL interface][1] type with. + /// + /// If absent, then generated code will be generic over any `juniper::ScalarValue` type, which, + /// in turn, requires all [interface][1] implementors to be generic over any + /// `juniper::ScalarValue` type too. That's why this type should be specified only if one of the + /// implementors implements `juniper::GraphQLType` in a non-generic way over + /// `juniper::ScalarValue` type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub scalar: Option>, + + /* + /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementors. + /// + /// If absent, then macro will try to auto-infer all the possible variants from the type + /// declaration, if possible. That's why specifying an external resolver function has sense, + /// when some custom [union][1] variant resolving logic is involved, or variants cannot be + /// inferred. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub external_downcasters: InterfaceMetaDowncasters,*/ + /// Indicator whether the generated code is intended to be used only inside the `juniper` + /// library. + pub is_internal: bool, +} + +impl Parse for InterfaceMeta { + fn parse(input: ParseStream) -> syn::Result { + let mut output = Self::default(); + + while !input.is_empty() { + let ident: syn::Ident = input.parse()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + output + .name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| dup_attr_err(ident.span()))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + output + .description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| dup_attr_err(ident.span()))? + } + "ctx" | "context" | "Context" => { + input.parse::()?; + let ctx = input.parse::()?; + output + .context + .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) + .none_or_else(|_| dup_attr_err(ident.span()))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + output + .scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| dup_attr_err(ident.span()))? + } + "internal" => { + output.is_internal = true; + } + _ => { + return Err(syn::Error::new(ident.span(), "unknown attribute")); + } + } + if input.lookahead1().peek(syn::Token![,]) { + input.parse::()?; + } + } + + Ok(output) + } +} + +impl InterfaceMeta { + /// Tries to merge two [`InterfaceMeta`]s into single one, reporting about duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + context: try_merge_opt!(context: self, another), + scalar: try_merge_opt!(scalar: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`InterfaceMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a + /// trait definition. + pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut meta = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if meta.description.is_none() { + meta.description = get_doc_comment(attrs); + } + + Ok(meta) + } +} diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 59f066d69..ed7820725 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -15,58 +15,9 @@ use syn::{ spanned::Spanned as _, }; -use crate::util::{filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _}; - -/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of -/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related -/// to the `$another` struct (a later one). -/// -/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. -/// By default, [`SpanContainer::span_ident`] is used. -macro_rules! try_merge_opt { - ($field:ident: $self:ident, $another:ident => $span:ident) => {{ - if let Some(v) = $self.$field { - $another - .$field - .replace(v) - .none_or_else(|dup| dup_attr_err(dup.$span()))?; - } - $another.$field - }}; - - ($field:ident: $self:ident, $another:ident) => { - try_merge_opt!($field: $self, $another => span_ident) - }; -} - -/// Attempts to merge a [`HashMap`]ed `$field` of a `$self` struct with the same `$field` of -/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error -/// with a [`Span`] related to the `$another` struct (a later one). -/// -/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. -/// By default, [`SpanContainer::span_ident`] is used. -macro_rules! try_merge_hashmap { - ($field:ident: $self:ident, $another:ident => $span:ident) => {{ - if !$self.$field.is_empty() { - for (ty, rslvr) in $self.$field { - $another - .$field - .insert(ty, rslvr) - .none_or_else(|dup| dup_attr_err(dup.$span()))?; - } - } - $another.$field - }}; - - ($field:ident: $self:ident, $another:ident) => { - try_merge_hashmap!($field: $self, $another => span_ident) - }; -} - -/// Creates and returns duplication error pointing to the given `span`. -fn dup_attr_err(span: Span) -> syn::Error { - syn::Error::new(span, "duplicated attribute") -} +use crate::util::{ + dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, +}; /// Helper alias for the type of [`UnionMeta::external_resolvers`] field. type UnionMetaResolvers = HashMap>; @@ -213,7 +164,8 @@ impl UnionMeta { }) } - /// Parses [`UnionMeta`] from the given multiple `name`d attributes placed on type definition. + /// Parses [`UnionMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a type + /// definition. pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut meta = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) @@ -291,7 +243,7 @@ impl UnionVariantMeta { }) } - /// Parses [`UnionVariantMeta`] from the given multiple `name`d attributes placed on + /// Parses [`UnionVariantMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a /// variant/field/method definition. pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { filter_attrs(name, attrs) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 09742f35f..1f7ff082c 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -12,6 +12,69 @@ extern crate proc_macro; mod result; mod util; +// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust +// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, +// and so we cannot move the definition into a sub-module and use the `#[macro_export]` +// attribute. +/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of +/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related +/// to the `$another` struct (a later one). +/// +/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. +/// By default, [`SpanContainer::span_ident`] is used. +/// +/// [`Span`]: proc_macro2::Span +/// [`SpanContainer`]: crate::util::span_container::SpanContainer +/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident +macro_rules! try_merge_opt { + ($field:ident: $self:ident, $another:ident => $span:ident) => {{ + if let Some(v) = $self.$field { + $another + .$field + .replace(v) + .none_or_else(|dup| dup_attr_err(dup.$span()))?; + } + $another.$field + }}; + + ($field:ident: $self:ident, $another:ident) => { + try_merge_opt!($field: $self, $another => span_ident) + }; +} + +// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust +// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, +// and so we cannot move the definition into a sub-module and use the `#[macro_export]` +// attribute. +/// Attempts to merge a [`HashMap`]ed `$field` of a `$self` struct with the same `$field` of +/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error +/// with a [`Span`] related to the `$another` struct (a later one). +/// +/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. +/// By default, [`SpanContainer::span_ident`] is used. +/// +/// [`HashMap`]: std::collections::HashMap +/// [`Span`]: proc_macro2::Span +/// [`SpanContainer`]: crate::util::span_container::SpanContainer +/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident +macro_rules! try_merge_hashmap { + ($field:ident: $self:ident, $another:ident => $span:ident) => {{ + if !$self.$field.is_empty() { + for (ty, rslvr) in $self.$field { + $another + .$field + .insert(ty, rslvr) + .none_or_else(|dup| dup_attr_err(dup.$span()))?; + } + } + $another.$field + }}; + + ($field:ident: $self:ident, $another:ident) => { + try_merge_hashmap!($field: $self, $another => span_ident) + }; +} + mod derive_enum; mod derive_input_object; mod derive_object; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 6a84f29ef..f41cc9fa2 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -19,6 +19,11 @@ use syn::{ pub use self::option_ext::OptionExt; +/// Creates and returns duplication [`syn::Error`] pointing to the given [`Span`]. +pub fn dup_attr_err(span: Span) -> syn::Error { + syn::Error::new(span, "duplicated attribute") +} + /// Returns the name of a type. /// If the type does not end in a simple ident, `None` is returned. pub fn name_of_type(ty: &syn::Type) -> Option { From 335f6936ae9ebf05908eecca188b729512b49518 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 6 Jul 2020 18:39:38 +0300 Subject: [PATCH 08/79] Bootstrap #[graphql_interface] very basic code generation [skip ci] --- docs/book/content/types/interfaces.md | 2 +- .../src/codegen/interface_attr.rs | 177 +------- .../juniper_tests/src/codegen/mod.rs | 1 + .../src/codegen/poc_interface_attr.rs | 389 ++++++++++++++++ juniper/src/lib.rs | 4 +- juniper/src/macros/mod.rs | 4 +- juniper/src/types/async_await.rs | 2 +- juniper/src/types/base.rs | 2 +- juniper/src/types/subscriptions.rs | 2 +- juniper_codegen/src/derive_scalar_value.rs | 12 +- juniper_codegen/src/graphql_interface/attr.rs | 41 +- juniper_codegen/src/graphql_interface/mod.rs | 428 +++++++++++++++++- juniper_codegen/src/graphql_union/attr.rs | 5 +- juniper_codegen/src/graphql_union/derive.rs | 2 - juniper_codegen/src/graphql_union/mod.rs | 36 +- juniper_codegen/src/lib.rs | 35 +- juniper_codegen/src/util/mod.rs | 68 +-- juniper_codegen/src/util/parse_buffer_ext.rs | 21 + 18 files changed, 967 insertions(+), 264 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs create mode 100644 juniper_codegen/src/util/parse_buffer_ext.rs diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 0cc79a890..3b43a7396 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -63,7 +63,7 @@ juniper::graphql_interface!(<'a> &'a dyn Character: () as "Character" where Scal # fn main() {} ``` -The `instance_resolvers` declaration lists all the implementors of the given +The `instance_resolvers` declaration lists all the implementers of the given interface and how to resolve them. As you can see, you lose a bit of the point with using traits: you need to list diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d18b8a058..0594936dc 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,6 +1,6 @@ //! Tests for `#[graphql_interface]` macro. -use juniper::{execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; +use juniper::{execute, graphql_object, graphql_interface, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; /* SUGARED #[derive(GraphQLObject)] @@ -21,7 +21,7 @@ impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { type TypeInfo = >::TypeInfo; #[inline] - fn as_dyn_graphql_type(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { + fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { self } } @@ -61,7 +61,7 @@ impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { type TypeInfo = >::TypeInfo; #[inline] - fn as_dyn_graphql_type(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { + fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { self } } @@ -90,177 +90,12 @@ impl Character f // ------------------------------------------ -/* SUGARED -#[graphql_interface(for(Human, Droid))] +#[graphql_interface(implementers(Human, Droid))] trait Character { fn id(&self) -> &str; - #[graphql_interface(downcast)] - fn as_droid(&self) -> Option<&Droid> { None } -} - DESUGARS INTO: */ -trait Character: ::juniper::AsDynGraphQLValue { - fn id(&self) -> &str; - - fn as_droid(&self) -> Option<&Droid> { None } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::marker::GraphQLInterface<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - fn mark() { - >::mark(); - >::mark(); - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::marker::IsOutputType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - fn mark() { - ::juniper::sa::assert_type_ne_all!(Human, Droid); - - >::mark(); - >::mark(); - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLValue<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - type Context = (); - type TypeInfo = (); - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - fn resolve_field( - &self, - _: &Self::TypeInfo, - field: &str, - _: &juniper::Arguments<__S>, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult<__S> { - match field { - "id" => { - let res = self.id(); - ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - None => Ok(juniper::Value::null()), - }) - } - _ => { - panic!( - "Field {} not found on GraphQL interface {}", - field, "Character", - ); - } - } - } - - fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - // First, check custom downcaster to be used. - if ({ Character::as_droid(self) } as ::std::option::Option<&Droid>).is_some() { - return >::name(info) - .unwrap() - .to_string(); - } - - // Otherwise, get concrete type name as dyn object. - self.as_dyn_graphql_type().concrete_type_name(context, info) - } - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<__S>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<__S> { - let context = executor.context(); - - // First, check custom downcaster to be used. - if type_name == (>::name(info)).unwrap() { - return ::juniper::IntoResolvable::into( - Character::as_droid(self), - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); - } - - // Otherwise, resolve inner type as dyn object. - return ::juniper::IntoResolvable::into( - self.as_dyn_graphql_type(), - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - fn name(_: &Self::TypeInfo) -> Option<&'static str> { - Some("Character") - } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, __S>, - ) -> ::juniper::meta::MetaType<'r, __S> - where - __S: 'r, - { - let _ = registry.get_type::<&Human>(info); - let _ = registry.get_type::<&Droid>(info); - - let fields = vec![ - // TODO: try array - registry.field_convert::<&str, _, Self::Context>("id", info), - ]; - - registry - .build_interface_type:: + '__obj + Send + Sync>(info, &fields) - .into_meta() - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLValueAsync<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, - Self: Sync, - __S: Send + Sync, -{ - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field_name: &'b str, - arguments: &'b ::juniper::Arguments<__S>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - // TODO: similar to what happens in GraphQLValue impl - let res = ::juniper::GraphQLValue::resolve_field(self, info, field_name, arguments, executor); - ::juniper::futures::future::FutureExt::boxed(async move { res }) - } - - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - se: Option<&'b [::juniper::Selection<'b, __S>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - // TODO: similar to what happens in GraphQLValue impl - let res = ::juniper::GraphQLValue::resolve_into_type(self, info, type_name, se, executor); - ::juniper::futures::future::FutureExt::boxed(async move { res }) - } + //#[graphql_interface(downcast)] + //fn as_droid(&self) -> Option<&Droid> { None } } // ------------------------------------------ diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 37e12c31f..072a485aa 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -4,6 +4,7 @@ mod derive_object; mod derive_object_with_raw_idents; mod impl_object; mod impl_scalar; +mod poc_interface_attr; mod interface_attr; mod scalar_value_transparent; mod union_attr; diff --git a/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs b/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs new file mode 100644 index 000000000..3ece2c680 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs @@ -0,0 +1,389 @@ +//! Tests for `#[graphql_interface]` macro. + +use juniper::{execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; + +/* SUGARED +#[derive(GraphQLObject)] +#[graphql(implements(Character))] +struct Human { + id: String, + home_planet: String, +} + DESUGARS INTO: */ +#[derive(GraphQLObject)] +struct Human { + id: String, + home_planet: String, +} +#[automatically_derived] +impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { + type Context = >::Context; + type TypeInfo = >::TypeInfo; + + #[inline] + fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { + self + } +} + +/* SUGARED +#[graphql_interface] +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} + DESUGARS INTO: */ +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} + +// ------------------------------------------ + +/* SUGARED +#[derive(GraphQLObject)] +#[graphql(implements(Character))] +struct Droid { + id: String, + primary_function: String, +} + DESUGARS INTO: */ +#[derive(GraphQLObject)] +struct Droid { + id: String, + primary_function: String, +} +#[automatically_derived] +impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { + type Context = >::Context; + type TypeInfo = >::TypeInfo; + + #[inline] + fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { + self + } +} + +/* SUGARED +#[graphql_interface] +impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + + fn as_droid(&self) -> Option<&Droid> { + Some(self) + } +} + DESUGARS INTO: */ +impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + + fn as_droid(&self) -> Option<&Droid> { + Some(self) + } +} + +// ------------------------------------------ + +/* SUGARED +#[graphql_interface(for(Human, Droid))] +trait Character { + fn id(&self) -> &str; + + #[graphql_interface(downcast)] + fn as_droid(&self) -> Option<&Droid> { None } +} + DESUGARS INTO: */ +trait Character: ::juniper::AsDynGraphQLValue { + fn id(&self) -> &str; + + fn as_droid(&self) -> Option<&Droid> { None } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::marker::GraphQLInterface<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + fn mark() { + >::mark(); + >::mark(); + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::marker::IsOutputType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + fn mark() { + ::juniper::sa::assert_type_ne_all!(Human, Droid); + + >::mark(); + >::mark(); + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::GraphQLValue<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + type Context = (); + type TypeInfo = (); + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + fn resolve_field( + &self, + _: &Self::TypeInfo, + field: &str, + _: &juniper::Arguments<__S>, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult<__S> { + match field { + "id" => { + let res = self.id(); + ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(juniper::Value::null()), + }) + } + _ => { + panic!( + "Field {} not found on GraphQL interface {}", + field, "Character", + ); + } + } + } + + fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { + // First, check custom downcaster to be used. + if ({ Character::as_droid(self) } as ::std::option::Option<&Droid>).is_some() { + return >::name(info) + .unwrap() + .to_string(); + } + + // Otherwise, get concrete type name as dyn object. + self.as_dyn_graphql_value().concrete_type_name(context, info) + } + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<__S>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<__S> { + let context = executor.context(); + + // First, check custom downcaster to be used. + if type_name == (>::name(info)).unwrap() { + return ::juniper::IntoResolvable::into( + Character::as_droid(self), + executor.context(), + ) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }); + } + + // Otherwise, resolve inner type as dyn object. + return ::juniper::IntoResolvable::into( + self.as_dyn_graphql_value(), + executor.context(), + ) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }); + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::GraphQLType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, +{ + fn name(_: &Self::TypeInfo) -> Option<&'static str> { + Some("Character") + } + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, __S>, + ) -> ::juniper::meta::MetaType<'r, __S> + where + __S: 'r, + { + let _ = registry.get_type::<&Human>(info); + let _ = registry.get_type::<&Droid>(info); + + let fields = vec![ + // TODO: try array + registry.field_convert::<&str, _, Self::Context>("id", info), + ]; + + registry + .build_interface_type:: + '__obj + Send + Sync>(info, &fields) + .into_meta() + } +} +#[automatically_derived] +impl<'__obj, __S> ::juniper::GraphQLValueAsync<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync +where + __S: ::juniper::ScalarValue, + Self: Sync, + __S: Send + Sync, +{ + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field_name: &'b str, + arguments: &'b ::juniper::Arguments<__S>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + // TODO: similar to what happens in GraphQLValue impl + let res = ::juniper::GraphQLValue::resolve_field(self, info, field_name, arguments, executor); + ::juniper::futures::future::FutureExt::boxed(async move { res }) + } + + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + se: Option<&'b [::juniper::Selection<'b, __S>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + // TODO: similar to what happens in GraphQLValue impl + let res = ::juniper::GraphQLValue::resolve_into_type(self, info, type_name, se, executor); + ::juniper::futures::future::FutureExt::boxed(async move { res }) + } +} + +// ------------------------------------------ + +fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> + where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, +{ + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +mod poc { + use super::*; + + type DynCharacter<'a, S = DefaultScalarValue> = dyn Character + 'a + Send + Sync; + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_id_for_human() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"id": "human-32"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_for_droid() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"id": "droid-99"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + panic!("🔬 {:#?}", schema.schema); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + humanId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index a3653ff77..acfa79d20 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -128,8 +128,8 @@ pub use futures::future::BoxFuture; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum, - GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, + graphql_object, graphql_scalar, graphql_subscription, graphql_union, graphql_interface, + GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, }; #[macro_use] diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index 1d4c57006..ff4f38caf 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -3,8 +3,8 @@ #[macro_use] mod common; -#[macro_use] -mod interface; +//#[macro_use] +//mod interface; #[cfg(test)] mod tests; diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index c43e30d66..1620219e3 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -115,7 +115,7 @@ crate::sa::assert_obj_safe!(GraphQLValueAsync); /// Extension of [`GraphQLType`] trait with asynchronous queries/mutations resolvers. /// -/// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementors, so +/// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementers, so /// doesn't require manual or code-generated implementation. pub trait GraphQLTypeAsync: GraphQLValueAsync + GraphQLType where diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index a8f6a485c..a983d411f 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -297,7 +297,7 @@ pub trait AsDynGraphQLValue { type Context; type TypeInfo; - fn as_dyn_graphql_type(&self) -> &DynGraphQLValue; + fn as_dyn_graphql_value(&self) -> &DynGraphQLValue; } /// Primary trait used to expose Rust types in a GraphQL schema. diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs index 0efd1b385..d48138dfb 100644 --- a/juniper/src/types/subscriptions.rs +++ b/juniper/src/types/subscriptions.rs @@ -177,7 +177,7 @@ crate::sa::assert_obj_safe!(GraphQLSubscriptionValue: diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 254fb93e9..deca44021 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -1,10 +1,10 @@ use crate::{ result::GraphQLScope, - util::{self, span_container::SpanContainer}, + util::{self, span_container::SpanContainer, ParseBufferExt as _}, }; use proc_macro2::TokenStream; use quote::quote; -use syn::{self, spanned::Spanned, Data, Fields, Ident, Variant}; +use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant}; #[derive(Debug, Default)] struct TransparentAttributes { @@ -25,12 +25,12 @@ impl syn::parse::Parse for TransparentAttributes { let ident: syn::Ident = input.parse()?; match ident.to_string().as_str() { "name" => { - input.parse::()?; + input.parse::()?; let val = input.parse::()?; output.name = Some(val.value()); } "description" => { - input.parse::()?; + input.parse::()?; let val = input.parse::()?; output.description = Some(val.value()); } @@ -39,9 +39,7 @@ impl syn::parse::Parse for TransparentAttributes { } _ => return Err(syn::Error::new(ident.span(), "unknown attribute")), } - if input.lookahead1().peek(syn::Token![,]) { - input.parse::()?; - } + input.try_parse::()?; } Ok(output) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index fc3dfae3f..370b9174e 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -1,12 +1,16 @@ //! Code generation for `#[graphql_interface]` macro. use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ result::GraphQLScope, - util::{strip_attr, unite_attrs}, + util::{span_container::SpanContainer, strip_attrs, unite_attrs}, }; +use super::{InterfaceDefinition, InterfaceMeta}; + /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -14,11 +18,11 @@ const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body.clone()) { let trait_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = strip_attr("graphql_interface", ast.attrs); + ast.attrs = strip_attrs("graphql_interface", ast.attrs); expand_on_trait(trait_attrs, ast) } else if let Ok(mut ast) = syn::parse2::(body) { let impl_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = strip_attr("graphql_interface", ast.attrs); + ast.attrs = strip_attrs("graphql_interface", ast.attrs); expand_on_impl(impl_attrs, ast) } else { Err(syn::Error::new( @@ -34,7 +38,36 @@ pub fn expand_on_trait( attrs: Vec, ast: syn::ItemTrait, ) -> syn::Result { - todo!() + let meta = InterfaceMeta::from_attrs("graphql_interface", &attrs)?; + + let trait_span = ast.span(); + let trait_ident = &ast.ident; + + let name = meta + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| trait_ident.unraw().to_string()); + + let context = meta.context.map(SpanContainer::into_inner); + //.or_else(|| variants.iter().find_map(|v| v.context_ty.as_ref()).cloned()); + + let generated_code = InterfaceDefinition { + name, + ty: parse_quote! { #trait_ident }, + is_trait_object: true, + description: meta.description.map(SpanContainer::into_inner), + context, + scalar: meta.scalar.map(SpanContainer::into_inner), + generics: ast.generics.clone(), + implementers: vec![], // TODO + }; + + Ok(quote! { + #ast + + #generated_code + }) } /// Expands `#[graphql_interface]` macro placed on trait implementation block. diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 7864a80da..61943596b 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -4,24 +4,28 @@ pub mod attr; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens, TokenStreamExt as _}; use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, + token, }; use crate::util::{ dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, + ParseBufferExt as _, }; /* /// Helper alias for the type of [`InterfaceMeta::external_downcasters`] field. type InterfaceMetaDowncasters = HashMap>;*/ -/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_interface]`) attribute when -/// generating code for [GraphQL interface][1] type. +/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_interface]`) attribute placed +/// on a trait definition, when generating code for [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] @@ -53,16 +57,23 @@ struct InterfaceMeta { /// [GraphQL interface][1] type with. /// /// If absent, then generated code will be generic over any `juniper::ScalarValue` type, which, - /// in turn, requires all [interface][1] implementors to be generic over any + /// in turn, requires all [interface][1] implementers to be generic over any /// `juniper::ScalarValue` type too. That's why this type should be specified only if one of the - /// implementors implements `juniper::GraphQLType` in a non-generic way over + /// implementers implements `juniper::GraphQLType` in a non-generic way over /// `juniper::ScalarValue` type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub scalar: Option>, + /// Explicitly specified Rust types of [GraphQL objects][2] implementing this + /// [GraphQL interface][1] type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Objects + pub implementers: HashSet>, + /* - /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementors. + /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers. /// /// If absent, then macro will try to auto-infer all the possible variants from the type /// declaration, if possible. That's why specifying an external resolver function has sense, @@ -84,7 +95,7 @@ impl Parse for InterfaceMeta { let ident: syn::Ident = input.parse()?; match ident.to_string().as_str() { "name" => { - input.parse::()?; + input.parse::()?; let name = input.parse::()?; output .name @@ -96,7 +107,7 @@ impl Parse for InterfaceMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "desc" | "description" => { - input.parse::()?; + input.parse::()?; let desc = input.parse::()?; output .description @@ -108,7 +119,7 @@ impl Parse for InterfaceMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "ctx" | "context" | "Context" => { - input.parse::()?; + input.parse::()?; let ctx = input.parse::()?; output .context @@ -116,13 +127,26 @@ impl Parse for InterfaceMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; + input.parse::()?; let scl = input.parse::()?; output .scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| dup_attr_err(ident.span()))? } + "for" | "implementers" => { + let inner; + syn::parenthesized!(inner in input); + while !inner.is_empty() { + let impler = inner.parse::()?; + let impler_span = impler.span(); + output + .implementers + .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) + .none_or_else(|_| dup_attr_err(impler_span))?; + inner.try_parse::()?; + } + } "internal" => { output.is_internal = true; } @@ -130,9 +154,7 @@ impl Parse for InterfaceMeta { return Err(syn::Error::new(ident.span(), "unknown attribute")); } } - if input.lookahead1().peek(syn::Token![,]) { - input.parse::()?; - } + input.try_parse::()?; } Ok(output) @@ -140,13 +162,14 @@ impl Parse for InterfaceMeta { } impl InterfaceMeta { - /// Tries to merge two [`InterfaceMeta`]s into single one, reporting about duplicates, if any. + /// Tries to merge two [`InterfaceMeta`]s into a single one, reporting about duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + implementers: try_merge_hashset!(implementers: self, another => span_joined), is_internal: self.is_internal || another.is_internal, }) } @@ -165,3 +188,380 @@ impl InterfaceMeta { Ok(meta) } } + +/// Definition of [GraphQL interface][1] implementer for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +struct InterfaceImplementerDefinition { + /// Rust type that this [GraphQL interface][1] implementer resolves into. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub ty: syn::Type, + + /// Rust code for downcasting into this [GraphQL interface][1] implementer. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub downcast_code: Option, + + /// Rust code for checking whether [GraphQL interface][1] should be downcast into this + /// implementer. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub downcast_check: Option, + + /// Rust type of `juniper::Context` that this [GraphQL interface][1] implementer requires for + /// downcasting. + /// + /// It's available only when code generation happens for Rust traits and a trait method contains + /// context argument. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub context_ty: Option, + + /// [`Span`] that points to the Rust source code which defines this [GraphQL interface][1] + /// implementer. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + pub span: Span, +} + +/// Definition of [GraphQL interface][1] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +struct InterfaceDefinition { + /// Name of this [GraphQL interface][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub name: String, + + /// Rust type that this [GraphQL interface][1] is represented with. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub ty: syn::Type, + + /// Generics of the Rust type that this [GraphQL interface][1] is implemented for. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub generics: syn::Generics, + + /// Indicator whether code should be generated for a trait object, rather than for a regular + /// Rust type. + pub is_trait_object: bool, + + /// Description of this [GraphQL interface][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub description: Option, + + /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` implementation with + /// for this [GraphQL interface][1]. + /// + /// If [`None`] then generated code will use unit type `()` as `juniper::Context`. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub context: Option, + + /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` implementation with + /// for this [GraphQL interface][1]. + /// + /// If [`None`] then generated code will be generic over any `juniper::ScalarValue` type, which, + /// in turn, requires all [interface][1] implementers to be generic over any + /// `juniper::ScalarValue` type too. That's why this type should be specified only if one of the + /// implementers implements `juniper::GraphQLType` in a non-generic way over + /// `juniper::ScalarValue` type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub scalar: Option, + + /// Implementers definitions of this [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub implementers: Vec, +} + +impl ToTokens for InterfaceDefinition { + fn to_tokens(&self, into: &mut TokenStream) { + let name = &self.name; + let ty = &self.ty; + + let context = self + .context + .as_ref() + .map(|ctx| quote! { #ctx }) + .unwrap_or_else(|| quote! { () }); + + let scalar = self + .scalar + .as_ref() + .map(|scl| quote! { #scl }) + .unwrap_or_else(|| quote! { __S }); + + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let impler_types: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + + let all_implers_unique = if impler_types.len() > 1 { + Some(quote! { ::juniper::sa::assert_type_ne_all!(#(#impler_types),*); }) + } else { + None + }; + + let custom_downcast_checks = self.implementers.iter().filter_map(|impler| { + let impler_check = impler.downcast_check.as_ref()?; + let impler_ty = &impler.ty; + + Some(quote! { + if #impler_check { + return <#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info) + .unwrap().to_string(); + } + }) + }); + let regular_downcast_check = if self.is_trait_object { + quote! { + self.as_dyn_graphql_value().concrete_type_name(context, info) + } + } else { + quote! { + panic!( + "GraphQL interface {} cannot be downcast into any of its implementers in its \ + current state", + #name, + ); + } + }; + + let custom_downcasts = self.implementers.iter().filter_map(|impler| { + let downcast_code = impler.downcast_code.as_ref()?; + let impler_ty = &impler.ty; + + let get_name = quote! { + (<#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info)) + }; + Some(quote! { + if type_name == #get_name.unwrap() { + return ::juniper::IntoResolvable::into( + { #downcast_code }, + executor.context() + ) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }); + } + }) + }); + let custom_async_downcasts = self.implementers.iter().filter_map(|impler| { + let downcast_code = impler.downcast_code.as_ref()?; + let impler_ty = &impler.ty; + + let get_name = quote! { + (<#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info)) + }; + Some(quote! { + if type_name == #get_name.unwrap() { + let res = ::juniper::IntoResolvable::into( + { #downcast_code }, + executor.context() + ); + return ::juniper::futures::future::FutureExt::boxed(async move { + match res? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + }, + None => Ok(::juniper::Value::null()), + } + }); + } + }) + }); + let (regular_downcast, regular_async_downcast) = if self.is_trait_object { + let sync = quote! { + return ::juniper::IntoResolvable::into( + self.as_dyn_graphql_value(), + executor.context(), + ) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) + }; + let r#async = quote! { + let res = ::juniper::IntoResolvable::into( + self.as_dyn_graphql_value(), + executor.context() + ); + return ::juniper::futures::future::FutureExt::boxed(async move { + match res? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + }, + None => Ok(::juniper::Value::null()), + } + }); + }; + (sync, r#async) + } else { + let panic = quote! { + panic!( + "Concrete type {} cannot be downcast from on GraphQL interface {}", + type_name, #name, + ); + }; + (panic.clone(), panic) + }; + + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let mut ext_generics = self.generics.clone(); + if self.is_trait_object { + ext_generics.params.push(parse_quote! { '__obj }); + } + if self.scalar.is_none() { + ext_generics.params.push(parse_quote! { #scalar }); + ext_generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + let (ext_impl_generics, _, where_clause) = ext_generics.split_for_impl(); + + let mut where_async = where_clause + .cloned() + .unwrap_or_else(|| parse_quote! { where }); + where_async.predicates.push(parse_quote! { Self: Sync }); + if self.scalar.is_none() { + where_async + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + + let mut ty_full = quote! { #ty#ty_generics }; + if self.is_trait_object { + let mut ty_params = None; + if !self.generics.params.is_empty() { + let params = &self.generics.params; + ty_params = Some(quote! { #params, }); + }; + ty_full = quote! { + dyn #ty<#ty_params #scalar, Context = #context, TypeInfo = ()> + + '__obj + Send + Sync + }; + } + + let type_impl = quote! { + #[automatically_derived] + impl#ext_impl_generics ::juniper::GraphQLType<#scalar> for #ty_full + #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar> + ) -> ::juniper::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + // TODO: enumerate implementors + // TODO: fields + registry.build_interface_type::<#ty_full>(info, &[]) + #description + .into_meta() + } + } + }; + + let value_impl = quote! { + #[automatically_derived] + impl#ext_impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + + fn concrete_type_name( + &self, + context: &Self::Context, + info: &Self::TypeInfo, + ) -> String { + #( #custom_downcast_checks )* + #regular_downcast_check + } + + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<#scalar>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let context = executor.context(); + #( #custom_downcasts )* + #regular_downcast + } + } + }; + + let value_async_impl = quote! { + #[automatically_derived] + impl#ext_impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full + #where_async + { + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + _: Option<&'b [::juniper::Selection<'b, #scalar>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let context = executor.context(); + #( #custom_async_downcasts )* + #regular_async_downcast + } + } + }; + + let output_type_impl = quote! { + #[automatically_derived] + impl#ext_impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full + #where_clause + { + fn mark() { + #( <#impler_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + } + } + }; + + let interface_impl = quote! { + #[automatically_derived] + impl#ext_impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty_full + #where_clause + { + fn mark() { + #all_implers_unique + + #( <#impler_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + } + } + }; + + into.append_all(&[ + interface_impl, + output_type_impl, + type_impl, + value_impl, + value_async_impl, + ]); + } +} diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index 2fbc0b00d..bf654a096 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -9,7 +9,7 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ result::GraphQLScope, util::{ - path_eq_single, span_container::SpanContainer, strip_attr, unite_attrs, unparenthesize, + path_eq_single, span_container::SpanContainer, strip_attrs, unite_attrs, unparenthesize, }, }; @@ -30,7 +30,7 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result syn::Result syn::Result { scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics, variants, - span: enum_span, }) } @@ -214,6 +213,5 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics, variants, - span: struct_span, }) } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index ed7820725..9338b8d0f 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -13,10 +13,12 @@ use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, + token, }; use crate::util::{ dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, + ParseBufferExt as _, }; /// Helper alias for the type of [`UnionMeta::external_resolvers`] field. @@ -85,7 +87,7 @@ impl Parse for UnionMeta { let ident: syn::Ident = input.parse()?; match ident.to_string().as_str() { "name" => { - input.parse::()?; + input.parse::()?; let name = input.parse::()?; output .name @@ -97,7 +99,7 @@ impl Parse for UnionMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "desc" | "description" => { - input.parse::()?; + input.parse::()?; let desc = input.parse::()?; output .description @@ -109,7 +111,7 @@ impl Parse for UnionMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "ctx" | "context" | "Context" => { - input.parse::()?; + input.parse::()?; let ctx = input.parse::()?; output .context @@ -117,7 +119,7 @@ impl Parse for UnionMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; + input.parse::()?; let scl = input.parse::()?; output .scalar @@ -126,7 +128,7 @@ impl Parse for UnionMeta { } "on" => { let ty = input.parse::()?; - input.parse::()?; + input.parse::()?; let rslvr = input.parse::()?; let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr); let rslvr_span = rslvr_spanned.span_joined(); @@ -142,9 +144,7 @@ impl Parse for UnionMeta { return Err(syn::Error::new(ident.span(), "unknown attribute")); } } - if input.lookahead1().peek(syn::Token![,]) { - input.parse::()?; - } + input.try_parse::()?; } Ok(output) @@ -152,7 +152,7 @@ impl Parse for UnionMeta { } impl UnionMeta { - /// Tries to merge two [`UnionMeta`]s into single one, reporting about duplicates, if any. + /// Tries to merge two [`UnionMeta`]s into a single one, reporting about duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), @@ -213,7 +213,7 @@ impl Parse for UnionVariantMeta { .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| dup_attr_err(ident.span()))?, "with" => { - input.parse::()?; + input.parse::()?; let rslvr = input.parse::()?; output .external_resolver @@ -224,9 +224,7 @@ impl Parse for UnionVariantMeta { return Err(syn::Error::new(ident.span(), "unknown attribute")); } } - if input.lookahead1().peek(syn::Token![,]) { - input.parse::()?; - } + input.try_parse::()?; } Ok(output) @@ -234,7 +232,7 @@ impl Parse for UnionVariantMeta { } impl UnionVariantMeta { - /// Tries to merge two [`UnionVariantMeta`]s into single one, reporting about duplicates, if + /// Tries to merge two [`UnionVariantMeta`]s into a single one, reporting about duplicates, if /// any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { @@ -308,6 +306,8 @@ struct UnionDefinition { pub ty: syn::Type, /// Generics of the Rust type that this [GraphQL union][1] is implemented for. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Unions pub generics: syn::Generics, /// Indicator whether code should be generated for a trait object, rather than for a regular @@ -342,11 +342,6 @@ struct UnionDefinition { /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions pub variants: Vec, - - /// [`Span`] that points to the Rust source code which defines this [GraphQL union][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub span: Span, } impl ToTokens for UnionDefinition { @@ -446,8 +441,7 @@ impl ToTokens for UnionDefinition { if self.scalar.is_none() { ext_generics.params.push(parse_quote! { #scalar }); ext_generics - .where_clause - .get_or_insert_with(|| parse_quote! { where }) + .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1f7ff082c..777c52d9a 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -46,7 +46,7 @@ macro_rules! try_merge_opt { // doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, // and so we cannot move the definition into a sub-module and use the `#[macro_export]` // attribute. -/// Attempts to merge a [`HashMap`]ed `$field` of a `$self` struct with the same `$field` of +/// Attempts to merge a [`HashMap`] `$field` of a `$self` struct with the same `$field` of /// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error /// with a [`Span`] related to the `$another` struct (a later one). /// @@ -75,6 +75,39 @@ macro_rules! try_merge_hashmap { }; } +// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust +// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently, +// and so we cannot move the definition into a sub-module and use the `#[macro_export]` +// attribute. +/// Attempts to merge a [`HashSet`] `$field` of a `$self` struct with the same `$field` of +/// `$another` struct. If some [`HashSet`] entries are duplicated, then throws a duplication error +/// with a [`Span`] related to the `$another` struct (a later one). +/// +/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods. +/// By default, [`SpanContainer::span_ident`] is used. +/// +/// [`HashSet`]: std::collections::HashSet +/// [`Span`]: proc_macro2::Span +/// [`SpanContainer`]: crate::util::span_container::SpanContainer +/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident +macro_rules! try_merge_hashset { + ($field:ident: $self:ident, $another:ident => $span:ident) => {{ + if !$self.$field.is_empty() { + for ty in $self.$field { + $another + .$field + .replace(ty) + .none_or_else(|dup| dup_attr_err(dup.$span()))?; + } + } + $another.$field + }}; + + ($field:ident: $self:ident, $another:ident) => { + try_merge_hashset!($field: $self, $another => span_ident) + }; +} + mod derive_enum; mod derive_input_object; mod derive_object; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index f41cc9fa2..65c499c3f 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -2,6 +2,7 @@ pub mod duplicate; pub mod option_ext; +pub mod parse_buffer_ext; pub mod parse_impl; pub mod span_container; @@ -13,11 +14,14 @@ use quote::quote; use span_container::SpanContainer; use std::collections::HashMap; use syn::{ - parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Lit, Meta, MetaList, - MetaNameValue, NestedMeta, Token, + parse::{Parse, ParseStream}, + parse_quote, + punctuated::Punctuated, + spanned::Spanned, + token, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta, }; -pub use self::option_ext::OptionExt; +pub use self::{option_ext::OptionExt, parse_buffer_ext::ParseBufferExt}; /// Creates and returns duplication [`syn::Error`] pointing to the given [`Span`]. pub fn dup_attr_err(span: Span) -> syn::Error { @@ -68,7 +72,7 @@ pub fn unite_attrs( /// /// This function is generally used for removing duplicate attributes during `proc_macro_attribute` /// expansion, so avoid unnecessary expansion duplication. -pub fn strip_attr(attr_path: &str, attrs: Vec) -> Vec { +pub fn strip_attrs(attr_path: &str, attrs: Vec) -> Vec { attrs .into_iter() .filter_map(|attr| { @@ -344,15 +348,15 @@ pub struct ObjectAttributes { pub is_internal: bool, } -impl syn::parse::Parse for ObjectAttributes { - fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { +impl Parse for ObjectAttributes { + fn parse(input: ParseStream) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { let ident: syn::Ident = input.parse()?; match ident.to_string().as_str() { "name" => { - input.parse::()?; + input.parse::()?; let val = input.parse::()?; output.name = Some(SpanContainer::new( ident.span(), @@ -361,7 +365,7 @@ impl syn::parse::Parse for ObjectAttributes { )); } "description" => { - input.parse::()?; + input.parse::()?; let val = input.parse::()?; output.description = Some(SpanContainer::new( ident.span(), @@ -370,7 +374,7 @@ impl syn::parse::Parse for ObjectAttributes { )); } "context" | "Context" => { - input.parse::()?; + input.parse::()?; // TODO: remove legacy support for string based Context. let ctx = if let Ok(val) = input.parse::() { eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated"); @@ -382,16 +386,16 @@ impl syn::parse::Parse for ObjectAttributes { output.context = Some(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)); } "scalar" | "Scalar" => { - input.parse::()?; + input.parse::()?; let val = input.parse::()?; output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val)); } "interfaces" => { - input.parse::()?; + input.parse::()?; let content; syn::bracketed!(content in input); output.interfaces = - syn::punctuated::Punctuated::::parse_terminated( + syn::punctuated::Punctuated::::parse_terminated( &content, )? .into_iter() @@ -411,9 +415,7 @@ impl syn::parse::Parse for ObjectAttributes { return Err(syn::Error::new(ident.span(), "unknown attribute")); } } - if input.lookahead1().peek(syn::Token![,]) { - input.parse::()?; - } + input.try_parse::()?; } Ok(output) @@ -421,7 +423,7 @@ impl syn::parse::Parse for ObjectAttributes { } impl ObjectAttributes { - pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result { + pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::Result { let attr_opt = find_graphql_attr(attrs); if let Some(attr) = attr_opt { // Need to unwrap outer (), which are not present for proc macro attributes, @@ -448,8 +450,8 @@ pub struct FieldAttributeArgument { pub description: Option, } -impl parse::Parse for FieldAttributeArgument { - fn parse(input: parse::ParseStream) -> parse::Result { +impl Parse for FieldAttributeArgument { + fn parse(input: ParseStream) -> syn::Result { let name = input.parse()?; let mut arg = Self { @@ -463,7 +465,7 @@ impl parse::Parse for FieldAttributeArgument { syn::parenthesized!(content in input); while !content.is_empty() { let name = content.parse::()?; - content.parse::()?; + content.parse::()?; match name.to_string().as_str() { "name" => { @@ -480,7 +482,7 @@ impl parse::Parse for FieldAttributeArgument { } // Discard trailing comma. - content.parse::().ok(); + content.parse::().ok(); } Ok(arg) @@ -502,13 +504,13 @@ enum FieldAttribute { Default(SpanContainer>), } -impl parse::Parse for FieldAttribute { - fn parse(input: parse::ParseStream) -> parse::Result { +impl Parse for FieldAttribute { + fn parse(input: ParseStream) -> syn::Result { let ident = input.parse::()?; match ident.to_string().as_str() { "name" => { - input.parse::()?; + input.parse::()?; let lit = input.parse::()?; let raw = lit.value(); if !is_valid_name(&raw) { @@ -522,7 +524,7 @@ impl parse::Parse for FieldAttribute { } } "description" => { - input.parse::()?; + input.parse::()?; let lit = input.parse::()?; Ok(FieldAttribute::Description(SpanContainer::new( ident.span(), @@ -531,8 +533,8 @@ impl parse::Parse for FieldAttribute { ))) } "deprecated" | "deprecation" => { - let reason = if input.peek(Token![=]) { - input.parse::()?; + let reason = if input.peek(token::Eq) { + input.parse::()?; Some(input.parse::()?) } else { None @@ -553,7 +555,7 @@ impl parse::Parse for FieldAttribute { "arguments" => { let arg_content; syn::parenthesized!(arg_content in input); - let args = Punctuated::::parse_terminated( + let args = Punctuated::::parse_terminated( &arg_content, )?; let map = args @@ -563,8 +565,8 @@ impl parse::Parse for FieldAttribute { Ok(FieldAttribute::Arguments(map)) } "default" => { - let default_expr = if input.peek(Token![=]) { - input.parse::()?; + let default_expr = if input.peek(token::Eq) { + input.parse::()?; let lit = input.parse::()?; let default_expr = lit.parse::()?; SpanContainer::new(ident.span(), Some(lit.span()), Some(default_expr)) @@ -592,9 +594,9 @@ pub struct FieldAttributes { pub default: Option>>, } -impl parse::Parse for FieldAttributes { - fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { - let items = Punctuated::::parse_terminated(&input)?; +impl Parse for FieldAttributes { + fn parse(input: ParseStream) -> syn::Result { + let items = Punctuated::::parse_terminated(&input)?; let mut output = Self::default(); @@ -633,7 +635,7 @@ impl FieldAttributes { pub fn from_attrs( attrs: &[syn::Attribute], _mode: FieldAttributeParseMode, - ) -> syn::parse::Result { + ) -> syn::Result { let doc_comment = get_doc_comment(&attrs); let deprecation = get_deprecated(&attrs); diff --git a/juniper_codegen/src/util/parse_buffer_ext.rs b/juniper_codegen/src/util/parse_buffer_ext.rs new file mode 100644 index 000000000..aaf719cab --- /dev/null +++ b/juniper_codegen/src/util/parse_buffer_ext.rs @@ -0,0 +1,21 @@ +use syn::{ + parse::{Parse, ParseBuffer}, + token::Token, +}; + +pub trait ParseBufferExt { + /// Tries to parse `T` as the next token. + /// + /// Doesn't move [`ParseStream`]'s cursor if there is no `T`. + fn try_parse(&self) -> syn::Result>; +} + +impl<'a> ParseBufferExt for ParseBuffer<'a> { + fn try_parse(&self) -> syn::Result> { + Ok(if self.lookahead1().peek(|_| T::default()) { + Some(self.parse()?) + } else { + None + }) + } +} From 60d19ff07d11b29b514ee75d3e69f5b748c3e455 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 7 Jul 2020 13:02:54 +0300 Subject: [PATCH 09/79] Upd trait code generation and fix keywords usage [skip ci] --- .../src/codegen/interface_attr.rs | 4 ++-- juniper_codegen/src/graphql_interface/attr.rs | 22 ++++++++++++++++--- juniper_codegen/src/graphql_interface/mod.rs | 7 ++++-- juniper_codegen/src/graphql_union/mod.rs | 18 +++++++-------- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 0594936dc..7cf4edf86 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -90,12 +90,12 @@ impl Character f // ------------------------------------------ -#[graphql_interface(implementers(Human, Droid))] +#[graphql_interface(for(Human, Droid))] trait Character { fn id(&self) -> &str; //#[graphql_interface(downcast)] - //fn as_droid(&self) -> Option<&Droid> { None } + fn as_droid(&self) -> Option<&Droid> { None } } // ------------------------------------------ diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 370b9174e..51a6d1dcb 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -9,7 +9,7 @@ use crate::{ util::{span_container::SpanContainer, strip_attrs, unite_attrs}, }; -use super::{InterfaceDefinition, InterfaceMeta}; +use super::{InterfaceDefinition, InterfaceMeta, InterfaceImplementerDefinition}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -36,7 +36,7 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, - ast: syn::ItemTrait, + mut ast: syn::ItemTrait, ) -> syn::Result { let meta = InterfaceMeta::from_attrs("graphql_interface", &attrs)?; @@ -60,9 +60,25 @@ pub fn expand_on_trait( context, scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics.clone(), - implementers: vec![], // TODO + implementers: meta.implementers.iter().map(|ty| { + let span = ty.span_ident(); + InterfaceImplementerDefinition { + ty: ty.as_ref().clone(), + downcast_code: None, + downcast_check: None, + context_ty: None, + span, + } + }).collect() }; + ast.generics.params.push(parse_quote! { + GraphQLScalarValue: ::juniper::ScalarValue = ::juniper::DefaultScalarValue + }); + ast.supertraits.push(parse_quote! { + ::juniper::AsDynGraphQLValue + }); + Ok(quote! { #ast diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 61943596b..8be854cdb 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -13,6 +13,7 @@ use syn::{ parse_quote, spanned::Spanned as _, token, + ext::IdentExt as _, }; use crate::util::{ @@ -92,7 +93,7 @@ impl Parse for InterfaceMeta { let mut output = Self::default(); while !input.is_empty() { - let ident: syn::Ident = input.parse()?; + let ident = input.call(syn::Ident::parse_any)?; // required to parse keywords match ident.to_string().as_str() { "name" => { input.parse::()?; @@ -469,7 +470,9 @@ impl ToTokens for InterfaceDefinition { ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { - // TODO: enumerate implementors + // Ensure all implementer types are registered. + #( let _ = registry.get_type::<#impler_types>(info); )* + // TODO: fields registry.build_interface_type::<#ty_full>(info, &[]) #description diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 9338b8d0f..ce77ad1f4 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -379,7 +379,7 @@ impl ToTokens for UnionDefinition { let var_check = &var.resolver_check; quote! { if #var_check { - return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(&()) + return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(info) .unwrap().to_string(); } } @@ -389,7 +389,7 @@ impl ToTokens for UnionDefinition { let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| { let var_ty = &var.ty; - let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(&())) }; + let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)) }; quote! { if type_name == #get_name.unwrap() { return ::juniper::IntoResolvable::into( @@ -397,7 +397,7 @@ impl ToTokens for UnionDefinition { executor.context() ) .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), None => Ok(::juniper::Value::null()), }); } @@ -411,7 +411,7 @@ impl ToTokens for UnionDefinition { let var_ty = &var.ty; let get_name = quote! { - (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(&())) + (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)) }; quote! { if type_name == #get_name.unwrap() { @@ -423,7 +423,7 @@ impl ToTokens for UnionDefinition { match res? { Some((ctx, r)) => { let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(&(), &r).await + subexec.resolve_with_ctx_async(info, &r).await }, None => Ok(::juniper::Value::null()), } @@ -478,7 +478,7 @@ impl ToTokens for UnionDefinition { where #scalar: 'r, { let types = &[ - #( registry.get_type::<&#var_types>(&(())), )* + #( registry.get_type::<#var_types>(info), )* ]; registry.build_union_type::<#ty_full>(info, types) #description @@ -502,7 +502,7 @@ impl ToTokens for UnionDefinition { fn concrete_type_name( &self, context: &Self::Context, - _: &Self::TypeInfo, + info: &Self::TypeInfo, ) -> String { #( #match_names )* panic!( @@ -514,7 +514,7 @@ impl ToTokens for UnionDefinition { fn resolve_into_type( &self, - _: &Self::TypeInfo, + info: &Self::TypeInfo, type_name: &str, _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, @@ -536,7 +536,7 @@ impl ToTokens for UnionDefinition { { fn resolve_into_type_async<'b>( &'b self, - _: &'b Self::TypeInfo, + info: &'b Self::TypeInfo, type_name: &str, _: Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> From e3fed203efb0b0cad8d984337bf74ebabe063f7f Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 7 Jul 2020 14:18:58 +0300 Subject: [PATCH 10/79] Expand trait impls [skip ci] --- .../src/codegen/interface_attr.rs | 18 ---- juniper_codegen/src/graphql_interface/attr.rs | 83 ++++++++++++++----- juniper_codegen/src/graphql_interface/mod.rs | 2 +- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 7cf4edf86..8386b3406 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -26,19 +26,12 @@ impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { } } -/* SUGARED #[graphql_interface] impl Character for Human { fn id(&self) -> &str { &self.id } } - DESUGARS INTO: */ -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} // ------------------------------------------ @@ -66,7 +59,6 @@ impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { } } -/* SUGARED #[graphql_interface] impl Character for Droid { fn id(&self) -> &str { @@ -77,16 +69,6 @@ impl Character for Droid { Some(self) } } - DESUGARS INTO: */ -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - - fn as_droid(&self) -> Option<&Droid> { - Some(self) - } -} // ------------------------------------------ diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 51a6d1dcb..fd0e9e6ee 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -9,7 +9,7 @@ use crate::{ util::{span_container::SpanContainer, strip_attrs, unite_attrs}, }; -use super::{InterfaceDefinition, InterfaceMeta, InterfaceImplementerDefinition}; +use super::{InterfaceDefinition, InterfaceImplementerDefinition, InterfaceMeta}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -19,18 +19,20 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result(body.clone()) { let trait_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = strip_attrs("graphql_interface", ast.attrs); - expand_on_trait(trait_attrs, ast) + return expand_on_trait(trait_attrs, ast); } else if let Ok(mut ast) = syn::parse2::(body) { - let impl_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = strip_attrs("graphql_interface", ast.attrs); - expand_on_impl(impl_attrs, ast) - } else { - Err(syn::Error::new( - Span::call_site(), - "#[graphql_interface] attribute is applicable to trait definitions and trait \ - implementations only", - )) + if ast.trait_.is_some() { + let impl_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); + ast.attrs = strip_attrs("graphql_interface", ast.attrs); + return expand_on_impl(impl_attrs, ast); + } } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_interface] attribute is applicable to trait definitions and trait \ + implementations only", + )) } /// Expands `#[graphql_interface]` macro placed on trait definition. @@ -60,16 +62,20 @@ pub fn expand_on_trait( context, scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics.clone(), - implementers: meta.implementers.iter().map(|ty| { - let span = ty.span_ident(); - InterfaceImplementerDefinition { - ty: ty.as_ref().clone(), - downcast_code: None, - downcast_check: None, - context_ty: None, - span, - } - }).collect() + implementers: meta + .implementers + .iter() + .map(|ty| { + let span = ty.span_ident(); + InterfaceImplementerDefinition { + ty: ty.as_ref().clone(), + downcast_code: None, + downcast_check: None, + context_ty: None, + span, + } + }) + .collect(), }; ast.generics.params.push(parse_quote! { @@ -87,6 +93,37 @@ pub fn expand_on_trait( } /// Expands `#[graphql_interface]` macro placed on trait implementation block. -pub fn expand_on_impl(attrs: Vec, ast: syn::ItemImpl) -> syn::Result { - todo!() +pub fn expand_on_impl( + attrs: Vec, + mut ast: syn::ItemImpl, +) -> syn::Result { + for attr in attrs { + if !attr.tokens.is_empty() && attr.tokens.to_string().as_str() != "()" { + return Err(syn::Error::new( + attr.tokens.span(), + "#[graphql_interface] attribute cannot have any arguments when placed on a trait \ + implementation", + )); + } + } + + ast.generics.params.push(parse_quote! { + GraphQLScalarValue: ::juniper::ScalarValue + }); + + let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); + let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; + if let syn::PathArguments::None = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { + + }); + } else if let syn::PathArguments::AngleBracketed(a) = trait_params { + a.args.push(parse_quote! { + GraphQLScalarValue + }); + } + + Ok(quote! { + #ast + }) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 8be854cdb..cb05e9ccf 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -9,11 +9,11 @@ use std::collections::{HashMap, HashSet}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt as _}; use syn::{ + ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, token, - ext::IdentExt as _, }; use crate::util::{ From 1a32282757fe9081e0e3a3d930aea01ea98294bd Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 7 Jul 2020 18:24:33 +0300 Subject: [PATCH 11/79] Tune up objects [skip ci] --- .../src/codegen/interface_attr.rs | 40 +-------- juniper_codegen/src/derive_enum.rs | 2 +- juniper_codegen/src/derive_input_object.rs | 2 +- juniper_codegen/src/derive_object.rs | 12 ++- juniper_codegen/src/graphql_interface/attr.rs | 40 ++++++--- juniper_codegen/src/graphql_interface/mod.rs | 14 ++- juniper_codegen/src/graphql_union/mod.rs | 4 +- juniper_codegen/src/impl_object.rs | 18 ++-- juniper_codegen/src/util/mod.rs | 87 ++++++++++++++----- juniper_codegen/src/util/parse_buffer_ext.rs | 66 +++++++++++++- 10 files changed, 181 insertions(+), 104 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8386b3406..241efd1fe 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2,29 +2,12 @@ use juniper::{execute, graphql_object, graphql_interface, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; -/* SUGARED #[derive(GraphQLObject)] -#[graphql(implements(Character))] +#[graphql(impl = dyn Character)] struct Human { id: String, home_planet: String, } - DESUGARS INTO: */ -#[derive(GraphQLObject)] -struct Human { - id: String, - home_planet: String, -} -#[automatically_derived] -impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - #[inline] - fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { - self - } -} #[graphql_interface] impl Character for Human { @@ -35,29 +18,12 @@ impl Character for Human { // ------------------------------------------ -/* SUGARED #[derive(GraphQLObject)] -#[graphql(implements(Character))] +#[graphql(impl = dyn Character)] struct Droid { id: String, primary_function: String, } - DESUGARS INTO: */ -#[derive(GraphQLObject)] -struct Droid { - id: String, - primary_function: String, -} -#[automatically_derived] -impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - #[inline] - fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { - self - } -} #[graphql_interface] impl Character for Droid { @@ -72,7 +38,7 @@ impl Character for Droid { // ------------------------------------------ -#[graphql_interface(for(Human, Droid))] +#[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index 089f744a8..99f72033b 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -138,7 +138,7 @@ pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result no generics possible generics: syn::Generics::default(), - interfaces: None, + interfaces: vec![], include_type_generics: true, generic_scalar: true, no_async: attrs.no_async.is_some(), diff --git a/juniper_codegen/src/derive_input_object.rs b/juniper_codegen/src/derive_input_object.rs index e1c15f5a8..20a0228fe 100644 --- a/juniper_codegen/src/derive_input_object.rs +++ b/juniper_codegen/src/derive_input_object.rs @@ -137,7 +137,7 @@ pub fn impl_input_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Res description: attrs.description.map(SpanContainer::into_inner), fields, generics: ast.generics, - interfaces: None, + interfaces: vec![], include_type_generics: true, generic_scalar: true, no_async: attrs.no_async.is_some(), diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs index bbcc08703..de2ac2dc2 100644 --- a/juniper_codegen/src/derive_object.rs +++ b/juniper_codegen/src/derive_object.rs @@ -89,12 +89,6 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R // Early abort after checking all fields proc_macro_error::abort_if_dirty(); - if !attrs.interfaces.is_empty() { - attrs.interfaces.iter().for_each(|elm| { - error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface) - }); - } - if let Some(duplicates) = crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str()) { @@ -124,7 +118,11 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R description: attrs.description.map(SpanContainer::into_inner), fields, generics: ast.generics, - interfaces: None, + interfaces: attrs + .interfaces + .into_iter() + .map(SpanContainer::into_inner) + .collect(), include_type_generics: true, generic_scalar: true, no_async: attrs.no_async.is_some(), diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index fd0e9e6ee..4ff37501c 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -50,10 +50,35 @@ pub fn expand_on_trait( .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| trait_ident.unraw().to_string()); + if !meta.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + meta.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| trait_ident.span()), + ); + } let context = meta.context.map(SpanContainer::into_inner); //.or_else(|| variants.iter().find_map(|v| v.context_ty.as_ref()).cloned()); + let implementers = meta + .implementers + .iter() + .map(|ty| { + let span = ty.span_ident(); + InterfaceImplementerDefinition { + ty: ty.as_ref().clone(), + downcast_code: None, + downcast_check: None, + context_ty: None, + span, + } + }) + .collect(); + + proc_macro_error::abort_if_dirty(); + let generated_code = InterfaceDefinition { name, ty: parse_quote! { #trait_ident }, @@ -62,20 +87,7 @@ pub fn expand_on_trait( context, scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics.clone(), - implementers: meta - .implementers - .iter() - .map(|ty| { - let span = ty.span_ident(); - InterfaceImplementerDefinition { - ty: ty.as_ref().clone(), - downcast_code: None, - downcast_check: None, - context_ty: None, - span, - } - }) - .collect(), + implementers, }; ast.generics.params.push(parse_quote! { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index cb05e9ccf..343acf5d3 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -9,7 +9,6 @@ use std::collections::{HashMap, HashSet}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt as _}; use syn::{ - ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, @@ -18,7 +17,7 @@ use syn::{ use crate::util::{ dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, - ParseBufferExt as _, + ParseBufferExt as _, ParseBufferExt, }; /* @@ -93,7 +92,7 @@ impl Parse for InterfaceMeta { let mut output = Self::default(); while !input.is_empty() { - let ident = input.call(syn::Ident::parse_any)?; // required to parse keywords + let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; @@ -136,16 +135,15 @@ impl Parse for InterfaceMeta { .none_or_else(|_| dup_attr_err(ident.span()))? } "for" | "implementers" => { - let inner; - syn::parenthesized!(inner in input); - while !inner.is_empty() { - let impler = inner.parse::()?; + input.parse::()?; + for impler in input.parse_maybe_wrapped_and_punctuated::< + syn::Type, token::Bracket, token::Comma, + >()? { let impler_span = impler.span(); output .implementers .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) .none_or_else(|_| dup_attr_err(impler_span))?; - inner.try_parse::()?; } } "internal" => { diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index ce77ad1f4..011257023 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -477,10 +477,10 @@ impl ToTokens for UnionDefinition { ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar: 'r, { - let types = &[ + let types = [ #( registry.get_type::<#var_types>(info), )* ]; - registry.build_union_type::<#ty_full>(info, types) + registry.build_union_type::<#ty_full>(info, &types) #description .into_meta() } diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index b8e0d3f97..ad5ab94f3 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -200,18 +200,12 @@ fn create( description: _impl.description, fields, generics: _impl.generics.clone(), - interfaces: if !_impl.attrs.interfaces.is_empty() { - Some( - _impl - .attrs - .interfaces - .into_iter() - .map(SpanContainer::into_inner) - .collect(), - ) - } else { - None - }, + interfaces: _impl + .attrs + .interfaces + .into_iter() + .map(SpanContainer::into_inner) + .collect(), include_type_generics: false, generic_scalar: false, no_async: _impl.attrs.no_async.is_some(), diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 65c499c3f..cc2494949 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -6,13 +6,12 @@ pub mod parse_buffer_ext; pub mod parse_impl; pub mod span_container; -use std::ops::Deref as _; +use std::{collections::HashMap, ops::Deref as _}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; use quote::quote; use span_container::SpanContainer; -use std::collections::HashMap; use syn::{ parse::{Parse, ParseStream}, parse_quote, @@ -119,7 +118,7 @@ pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool { /// [`syn::TypeParen`]s asap). pub fn unparenthesize(ty: &syn::Type) -> &syn::Type { match ty { - syn::Type::Paren(ty) => unparenthesize(ty.elem.deref()), + syn::Type::Paren(ty) => unparenthesize(&*ty.elem), _ => ty, } } @@ -353,7 +352,7 @@ impl Parse for ObjectAttributes { let mut output = Self::default(); while !input.is_empty() { - let ident: syn::Ident = input.parse()?; + let ident = input.parse_any_ident()?; match ident.to_string().as_str() { "name" => { input.parse::()?; @@ -390,15 +389,11 @@ impl Parse for ObjectAttributes { let val = input.parse::()?; output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val)); } - "interfaces" => { + "impl" | "implements" | "interfaces" => { input.parse::()?; - let content; - syn::bracketed!(content in input); - output.interfaces = - syn::punctuated::Punctuated::::parse_terminated( - &content, - )? - .into_iter() + output.interfaces = input.parse_maybe_wrapped_and_punctuated::< + syn::Type, token::Bracket, token::Comma, + >()?.into_iter() .map(|interface| { SpanContainer::new(ident.span(), Some(interface.span()), interface) }) @@ -708,7 +703,7 @@ pub struct GraphQLTypeDefiniton { pub description: Option, pub fields: Vec, pub generics: syn::Generics, - pub interfaces: Option>, + pub interfaces: Vec, // Due to syn parsing differences, // when parsing an impl the type generics are included in the type // directly, but in syn::DeriveInput, the type generics are @@ -852,13 +847,42 @@ impl GraphQLTypeDefiniton { .as_ref() .map(|description| quote!( .description(#description) )); - let interfaces = self.interfaces.as_ref().map(|items| { - quote!( + let interfaces = if !self.interfaces.is_empty() { + let interfaces_ty = self.interfaces.iter().map(|ty| { + let mut ty: syn::Type = unparenthesize(ty).clone(); + + if let syn::Type::TraitObject(dyn_ty) = &mut ty { + let mut dyn_ty = dyn_ty.clone(); + if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = + dyn_ty.bounds.first_mut().unwrap() + { + let trait_params = &mut path.segments.last_mut().unwrap().arguments; + if let syn::PathArguments::None = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { + <#scalar, Context = Self::Context, TypeInfo = Self::TypeInfo> + }); + } else if let syn::PathArguments::AngleBracketed(a) = trait_params { + a.args.push(parse_quote! { #scalar }); + a.args.push(parse_quote! { Context = Self::Context }); + a.args.push(parse_quote! { TypeInfo = Self::TypeInfo }); + } + } + dyn_ty.bounds.push(parse_quote! { Send }); + dyn_ty.bounds.push(parse_quote! { Sync }); + ty = dyn_ty.into(); + } + + ty + }); + + Some(quote!( .interfaces(&[ - #( registry.get_type::< #items >(&()) ,)* + #( registry.get_type::<#interfaces_ty>(&()) ,)* ]) - ) - }); + )) + } else { + None + }; // Preserve the original type_generics before modification, // since alteration makes them invalid if self.generic_scalar @@ -887,6 +911,25 @@ impl GraphQLTypeDefiniton { }; let (impl_generics, _, where_clause) = generics.split_for_impl(); + let as_dyn_value = if !self.interfaces.is_empty() { + Some(quote! { + #[automatically_derived] + impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens + #where_clause + { + type Context = >::Context; + type TypeInfo = >::TypeInfo; + + #[inline] + fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> { + self + } + } + }) + } else { + None + }; + let resolve_field_async = { let resolve_matches_async = self.fields.iter().map(|field| { let name = &field.name; @@ -1041,7 +1084,7 @@ impl GraphQLTypeDefiniton { ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar : 'r, { - let fields = vec![ + let fields = [ #( #field_definitions ),* ]; let meta = registry.build_object_type::<#ty>( info, &fields ) @@ -1089,6 +1132,8 @@ impl GraphQLTypeDefiniton { } #resolve_field_async + + #as_dyn_value ); output } @@ -1184,13 +1229,15 @@ impl GraphQLTypeDefiniton { .as_ref() .map(|description| quote!( .description(#description) )); + let interfaces = quote!(); + /* let interfaces = self.interfaces.as_ref().map(|items| { quote!( .interfaces(&[ #( registry.get_type::< #items >(&()) ,)* ]) ) - }); + });*/ // Preserve the original type_generics before modification, // since alteration makes them invalid if self.generic_scalar diff --git a/juniper_codegen/src/util/parse_buffer_ext.rs b/juniper_codegen/src/util/parse_buffer_ext.rs index aaf719cab..9cceab3c8 100644 --- a/juniper_codegen/src/util/parse_buffer_ext.rs +++ b/juniper_codegen/src/util/parse_buffer_ext.rs @@ -1,6 +1,13 @@ +use std::{ + any::TypeId, + iter::{self, FromIterator as _}, +}; + use syn::{ + ext::IdentExt as _, parse::{Parse, ParseBuffer}, - token::Token, + punctuated::Punctuated, + token::{self, Token}, }; pub trait ParseBufferExt { @@ -8,14 +15,69 @@ pub trait ParseBufferExt { /// /// Doesn't move [`ParseStream`]'s cursor if there is no `T`. fn try_parse(&self) -> syn::Result>; + + /// Checks whether next token is `T`. + /// + /// Doesn't move [`ParseStream`]'s cursor. + fn is_next(&self) -> bool; + + /// Parses next token as [`syn::Ident`] _allowing_ Rust keywords, while default [`Parse`] + /// implementation for [`syn::Ident`] disallows keywords. + /// + /// Always moves [`ParseStream`]'s cursor. + fn parse_any_ident(&self) -> syn::Result; + + /// Checks whether next token is a wrapper `W` and if yes, then parses the wrapped tokens as `T` + /// [`Punctuated`] with `P`. Otherwise, parses just `T`. + /// + /// Always moves [`ParseStream`]'s cursor. + fn parse_maybe_wrapped_and_punctuated(&self) -> syn::Result> + where + T: Parse, + W: Default + Token + 'static, + P: Default + Parse + Token; } impl<'a> ParseBufferExt for ParseBuffer<'a> { fn try_parse(&self) -> syn::Result> { - Ok(if self.lookahead1().peek(|_| T::default()) { + Ok(if self.is_next::() { Some(self.parse()?) } else { None }) } + + fn is_next(&self) -> bool { + self.lookahead1().peek(|_| T::default()) + } + + fn parse_any_ident(&self) -> syn::Result { + self.call(syn::Ident::parse_any) + } + + fn parse_maybe_wrapped_and_punctuated(&self) -> syn::Result> + where + T: Parse, + W: Default + Token + 'static, + P: Default + Parse + Token, + { + Ok(if self.is_next::() { + let inner; + if TypeId::of::() == TypeId::of::() { + let _ = syn::bracketed!(inner in self); + } else if TypeId::of::() == TypeId::of::() { + let _ = syn::braced!(inner in self); + } else if TypeId::of::() == TypeId::of::() { + let _ = syn::parenthesized!(inner in self); + } else { + panic!( + "ParseBufferExt::parse_maybe_wrapped_and_punctuated supports only brackets, \ + braces and parentheses as wrappers.", + ); + } + Punctuated::parse_terminated(&inner)? + } else { + Punctuated::from_iter(iter::once(self.parse::()?)) + }) + } } From cda7f31ceded3a3e768a18d6b294bb56f00414e0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 7 Jul 2020 19:12:53 +0300 Subject: [PATCH 12/79] Finally! Complies at least... [skip ci] --- .../src/codegen/poc_interface_attr.rs | 28 +++++++++-- juniper/src/lib.rs | 2 +- juniper/src/types/async_await.rs | 3 ++ juniper/src/types/base.rs | 5 ++ juniper_codegen/src/graphql_interface/attr.rs | 5 +- juniper_codegen/src/graphql_interface/mod.rs | 4 +- juniper_codegen/src/graphql_union/mod.rs | 4 +- juniper_codegen/src/util/mod.rs | 47 ++++++++++--------- 8 files changed, 67 insertions(+), 31 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs b/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs index 3ece2c680..9951d05a9 100644 --- a/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs @@ -16,7 +16,10 @@ struct Human { home_planet: String, } #[automatically_derived] -impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { +impl<__S: ::juniper::ScalarValue + Send + Sync> ::juniper::AsDynGraphQLValue<__S> for Human +where + Self: Sync, +{ type Context = >::Context; type TypeInfo = >::TypeInfo; @@ -24,6 +27,10 @@ impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Human { fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { self } + #[inline] + fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<__S, Self::Context, Self::TypeInfo> { + self + } } /* SUGARED @@ -34,7 +41,10 @@ impl Character for Human { } } DESUGARS INTO: */ -impl Character for Human { +impl Character for Human +where + Self: Sync, +{ fn id(&self) -> &str { &self.id } @@ -56,7 +66,10 @@ struct Droid { primary_function: String, } #[automatically_derived] -impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { +impl<__S: ::juniper::ScalarValue + Send + Sync> ::juniper::AsDynGraphQLValue<__S> for Droid +where + Self: Sync, +{ type Context = >::Context; type TypeInfo = >::TypeInfo; @@ -64,6 +77,10 @@ impl<__S: ::juniper::ScalarValue> ::juniper::AsDynGraphQLValue<__S> for Droid { fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { self } + #[inline] + fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<__S, Self::Context, Self::TypeInfo> { + self + } } /* SUGARED @@ -78,7 +95,10 @@ impl Character for Droid { } } DESUGARS INTO: */ -impl Character for Droid { +impl Character for Droid +where + Self: Sync, +{ fn id(&self) -> &str { &self.id } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index acfa79d20..890e21f53 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -181,7 +181,7 @@ pub use crate::{ model::{RootNode, SchemaType}, }, types::{ - async_await::{GraphQLTypeAsync, GraphQLValueAsync}, + async_await::{GraphQLTypeAsync, GraphQLValueAsync, DynGraphQLValueAsync}, base::{ Arguments, AsDynGraphQLValue, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind, }, diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index 1620219e3..4fa758fea 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -113,6 +113,9 @@ where crate::sa::assert_obj_safe!(GraphQLValueAsync); +pub type DynGraphQLValueAsync = +dyn GraphQLValueAsync + Send + 'static; + /// Extension of [`GraphQLType`] trait with asynchronous queries/mutations resolvers. /// /// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementers, so diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index a983d411f..3944013a9 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -7,6 +7,7 @@ use crate::{ schema::meta::{Argument, MetaType}, value::{DefaultScalarValue, Object, ScalarValue, Value}, GraphQLEnum, + types::async_await::DynGraphQLValueAsync, }; /// GraphQL type kind @@ -298,8 +299,12 @@ pub trait AsDynGraphQLValue { type TypeInfo; fn as_dyn_graphql_value(&self) -> &DynGraphQLValue; + + fn as_dyn_graphql_value_async(&self) -> &DynGraphQLValueAsync; } +crate::sa::assert_obj_safe!(AsDynGraphQLValue); + /// Primary trait used to expose Rust types in a GraphQL schema. /// /// All of the convenience macros ultimately expand into an implementation of diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 4ff37501c..91b695c63 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -120,7 +120,10 @@ pub fn expand_on_impl( } ast.generics.params.push(parse_quote! { - GraphQLScalarValue: ::juniper::ScalarValue + GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync + }); + ast.generics.make_where_clause().predicates.push(parse_quote! { + Self: Sync }); let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 343acf5d3..7e562d768 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -391,8 +391,8 @@ impl ToTokens for InterfaceDefinition { }; let r#async = quote! { let res = ::juniper::IntoResolvable::into( - self.as_dyn_graphql_value(), - executor.context() + self.as_dyn_graphql_value_async(), + executor.context(), ); return ::juniper::futures::future::FutureExt::boxed(async move { match res? { diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 011257023..e5ab0148e 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -394,7 +394,7 @@ impl ToTokens for UnionDefinition { if type_name == #get_name.unwrap() { return ::juniper::IntoResolvable::into( { #expr }, - executor.context() + executor.context(), ) .and_then(|res| match res { Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), @@ -417,7 +417,7 @@ impl ToTokens for UnionDefinition { if type_name == #get_name.unwrap() { let res = ::juniper::IntoResolvable::into( { #expr }, - executor.context() + executor.context(), ); return ::juniper::futures::future::FutureExt::boxed(async move { match res? { diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index cc2494949..735469383 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -911,25 +911,6 @@ impl GraphQLTypeDefiniton { }; let (impl_generics, _, where_clause) = generics.split_for_impl(); - let as_dyn_value = if !self.interfaces.is_empty() { - Some(quote! { - #[automatically_derived] - impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens - #where_clause - { - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - #[inline] - fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> { - self - } - } - }) - } else { - None - }; - let resolve_field_async = { let resolve_matches_async = self.fields.iter().map(|field| { let name = &field.name; @@ -1016,6 +997,30 @@ impl GraphQLTypeDefiniton { // FIXME: add where clause for interfaces. + let as_dyn_value = if !self.interfaces.is_empty() { + Some(quote! { + #[automatically_derived] + impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens + #where_async + { + type Context = >::Context; + type TypeInfo = >::TypeInfo; + + #[inline] + fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> { + self + } + + #[inline] + fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<#scalar, Self::Context, Self::TypeInfo> { + self + } + } + }) + } else { + None + }; + quote!( impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #type_generics_tokens #where_async @@ -1042,6 +1047,8 @@ impl GraphQLTypeDefiniton { } } } + + #as_dyn_value ) }; @@ -1132,8 +1139,6 @@ impl GraphQLTypeDefiniton { } #resolve_field_async - - #as_dyn_value ); output } From 264138abd92ce007cd8a2e1bbec4e6c57a2a3a96 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 18 Aug 2020 18:40:38 +0300 Subject: [PATCH 13/79] Parse meta for fields and its arguments [skip ci] - also, refactor and bikeshed new macros code --- .../src/codegen/interface_attr.rs | 6 +- juniper/src/tests/fixtures/mod.rs | 4 +- juniper_codegen/src/graphql_interface/mod.rs | 194 +++++++++++++++++- juniper_codegen/src/graphql_union/mod.rs | 28 +-- juniper_codegen/src/lib.rs | 6 +- juniper_codegen/src/util/err.rs | 37 ++++ juniper_codegen/src/util/mod.rs | 8 +- 7 files changed, 247 insertions(+), 36 deletions(-) create mode 100644 juniper_codegen/src/util/err.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 241efd1fe..d6d30190c 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -11,6 +11,8 @@ struct Human { #[graphql_interface] impl Character for Human { + + #[graphql_interface] fn id(&self) -> &str { &self.id } @@ -137,7 +139,6 @@ mod poc { }"#; let schema = schema(QueryRoot::Human); - panic!("🔬 {:#?}", schema.schema); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -153,13 +154,14 @@ mod poc { const DOC: &str = r#"{ character { ... on Droid { - humanId: id + droidId: id primaryFunction } } }"#; let schema = schema(QueryRoot::Droid); + //panic!("🔬 {:#?}", schema.schema); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, diff --git a/juniper/src/tests/fixtures/mod.rs b/juniper/src/tests/fixtures/mod.rs index 71b5bec84..90bd64e68 100644 --- a/juniper/src/tests/fixtures/mod.rs +++ b/juniper/src/tests/fixtures/mod.rs @@ -1,4 +1,4 @@ //! Library fixtures -/// GraphQL schema and data from Star Wars. -pub mod starwars; +///// GraphQL schema and data from Star Wars. +//pub mod starwars; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 7e562d768..1c71f2749 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -16,8 +16,8 @@ use syn::{ }; use crate::util::{ - dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, - ParseBufferExt as _, ParseBufferExt, + err, filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, + OptionExt as _, ParseBufferExt as _, }; /* @@ -104,7 +104,7 @@ impl Parse for InterfaceMeta { Some(name.span()), name.value(), )) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; @@ -116,7 +116,7 @@ impl Parse for InterfaceMeta { Some(desc.span()), desc.value(), )) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; @@ -124,7 +124,7 @@ impl Parse for InterfaceMeta { output .context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; @@ -132,7 +132,7 @@ impl Parse for InterfaceMeta { output .scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "for" | "implementers" => { input.parse::()?; @@ -143,14 +143,14 @@ impl Parse for InterfaceMeta { output .implementers .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) - .none_or_else(|_| dup_attr_err(impler_span))?; + .none_or_else(|_| err::dup_arg(impler_span))?; } } "internal" => { output.is_internal = true; } - _ => { - return Err(syn::Error::new(ident.span(), "unknown attribute")); + name => { + return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; @@ -188,6 +188,182 @@ impl InterfaceMeta { } } +#[derive(Debug, Default)] +struct FieldMeta { + pub name: Option>, + pub description: Option>, + pub deprecated: Option>>, + pub ignore: Option>, +} + +impl Parse for FieldMeta { + fn parse(input: ParseStream) -> syn::Result { + let mut output = Self::default(); + + while !input.is_empty() { + let ident = input.parse::()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + output + .name + .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + output + .description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "deprecated" => { + let mut reason = None; + if input.is_next::() { + input.parse::()?; + reason = Some(input.parse::()?); + } + output + .deprecated + .replace(SpanContainer::new( + ident.span(), + reason.as_ref().map(|r| r.span()), + reason, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ignore" | "skip" => output + .ignore + .replace(SpanContainer::new(ident.span(), None, ident.clone())) + .none_or_else(|_| err::dup_arg(&ident))?, + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + + Ok(output) + } +} + +impl FieldMeta { + /// Tries to merge two [`FieldMeta`]s into a single one, reporting about duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + deprecated: try_merge_opt!(deprecated: self, another), + ignore: try_merge_opt!(ignore: self, another), + }) + } + + /// Parses [`FieldMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a + /// function/method definition. + pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut meta = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if meta.description.is_none() { + meta.description = get_doc_comment(attrs).map(|sc| { + let span = sc.span_ident(); + sc.map(|desc| syn::LitStr::new(&desc, span)) + }); + } + + if meta.deprecated.is_none() { + meta.deprecated = get_deprecated(attrs).map(|sc| { + let span = sc.span_ident(); + sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span))) + }); + } + + Ok(meta) + } +} + +#[derive(Debug, Default)] +struct ArgumentMeta { + pub name: Option>, + pub description: Option>, + pub default: Option>>, +} + +impl Parse for ArgumentMeta { + fn parse(input: ParseStream) -> syn::Result { + let mut output = Self::default(); + + while !input.is_empty() { + let ident = input.parse::()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + output + .name + .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + output + .description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "default" => { + let mut expr = None; + if input.is_next::() { + input.parse::()?; + expr = Some(input.parse::()?); + } else if input.is_next::() { + let inner; + let _ = syn::parenthesized!(inner in input); + expr = Some(inner.parse::()?); + } + output + .default + .replace(SpanContainer::new( + ident.span(), + expr.as_ref().map(|e| e.span()), + expr, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + + Ok(output) + } +} + +impl ArgumentMeta { + /// Tries to merge two [`ArgumentMeta`]s into a single one, reporting about duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + default: try_merge_opt!(default: self, another), + }) + } + + /// Parses [`ArgumentMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a + /// function argument. + pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) + } +} + /// Definition of [GraphQL interface][1] implementer for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index a0509a592..bc67aec28 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -17,7 +17,7 @@ use syn::{ }; use crate::util::{ - dup_attr_err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, + err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, ParseBufferExt as _, }; @@ -84,7 +84,7 @@ impl Parse for UnionMeta { let mut output = Self::default(); while !input.is_empty() { - let ident: syn::Ident = input.parse()?; + let ident = input.parse::()?; match ident.to_string().as_str() { "name" => { input.parse::()?; @@ -96,7 +96,7 @@ impl Parse for UnionMeta { Some(name.span()), name.value(), )) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "desc" | "description" => { input.parse::()?; @@ -108,7 +108,7 @@ impl Parse for UnionMeta { Some(desc.span()), desc.value(), )) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { input.parse::()?; @@ -116,7 +116,7 @@ impl Parse for UnionMeta { output .context .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; @@ -124,7 +124,7 @@ impl Parse for UnionMeta { output .scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } "on" => { let ty = input.parse::()?; @@ -135,13 +135,13 @@ impl Parse for UnionMeta { output .external_resolvers .insert(ty, rslvr_spanned) - .none_or_else(|_| dup_attr_err(rslvr_span))? + .none_or_else(|_| err::dup_arg(rslvr_span))? } "internal" => { output.is_internal = true; } - _ => { - return Err(syn::Error::new(ident.span(), "unknown attribute")); + name => { + return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; @@ -206,22 +206,22 @@ impl Parse for UnionVariantMeta { let mut output = Self::default(); while !input.is_empty() { - let ident: syn::Ident = input.parse()?; + let ident = input.parse::()?; match ident.to_string().as_str() { "ignore" | "skip" => output .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) - .none_or_else(|_| dup_attr_err(ident.span()))?, + .none_or_else(|_| err::dup_arg(&ident))?, "with" => { input.parse::()?; let rslvr = input.parse::()?; output .external_resolver .replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr)) - .none_or_else(|_| dup_attr_err(ident.span()))? + .none_or_else(|_| err::dup_arg(&ident))? } - _ => { - return Err(syn::Error::new(ident.span(), "unknown attribute")); + name => { + return Err(err::unknown_arg(&ident, name)); } } input.try_parse::()?; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 777c52d9a..dadbee8cd 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -32,7 +32,7 @@ macro_rules! try_merge_opt { $another .$field .replace(v) - .none_or_else(|dup| dup_attr_err(dup.$span()))?; + .none_or_else(|dup| crate::util::err::dup_arg(&dup.$span()))?; } $another.$field }}; @@ -64,7 +64,7 @@ macro_rules! try_merge_hashmap { $another .$field .insert(ty, rslvr) - .none_or_else(|dup| dup_attr_err(dup.$span()))?; + .none_or_else(|dup| crate::util::err::dup_arg(&dup.$span()))?; } } $another.$field @@ -97,7 +97,7 @@ macro_rules! try_merge_hashset { $another .$field .replace(ty) - .none_or_else(|dup| dup_attr_err(dup.$span()))?; + .none_or_else(|dup| crate::util::err::dup_arg(&dup.$span()))?; } } $another.$field diff --git a/juniper_codegen/src/util/err.rs b/juniper_codegen/src/util/err.rs new file mode 100644 index 000000000..d3b5b7167 --- /dev/null +++ b/juniper_codegen/src/util/err.rs @@ -0,0 +1,37 @@ +use proc_macro2::Span; +use syn::spanned::Spanned; + +/// Creates "duplicated argument" [`syn::Error`] for the given `name` pointing to the given +/// [`Span`]. +#[must_use] +pub fn dup_arg(span: S) -> syn::Error { + syn::Error::new(span.as_span(), "duplicated attribute argument found") +} + +/// Creates "unknown argument" [`syn::Error`] for the given `name` pointing to the given [`Span`]. +#[must_use] +pub fn unknown_arg(span: S, name: &str) -> syn::Error { + syn::Error::new( + span.as_span(), + format!("unknown `{}` attribute argument", name), + ) +} + +pub trait AsSpan { + #[must_use] + fn as_span(&self) -> Span; +} + +impl AsSpan for Span { + #[inline] + fn as_span(&self) -> Self { + *self + } +} + +impl AsSpan for &T { + #[inline] + fn as_span(&self) -> Span { + self.span() + } +} \ No newline at end of file diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index cb327299d..dae844918 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -3,10 +3,11 @@ pub mod duplicate; pub mod option_ext; pub mod parse_buffer_ext; +pub mod err; pub mod parse_impl; pub mod span_container; -use std::{collections::HashMap, ops::Deref as _}; +use std::{collections::HashMap}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; @@ -22,11 +23,6 @@ use syn::{ pub use self::{option_ext::OptionExt, parse_buffer_ext::ParseBufferExt}; -/// Creates and returns duplication [`syn::Error`] pointing to the given [`Span`]. -pub fn dup_attr_err(span: Span) -> syn::Error { - syn::Error::new(span, "duplicated attribute") -} - /// Returns the name of a type. /// If the type does not end in a simple ident, `None` is returned. pub fn name_of_type(ty: &syn::Type) -> Option { From b0596866cf05101be92819526119d8a1dcd033be Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 19 Aug 2020 18:56:05 +0300 Subject: [PATCH 14/79] Impl filling fields meta and bootstrap field resolution [skip ci] --- .../src/codegen/interface_attr.rs | 2 +- juniper/src/schema/meta.rs | 1 + juniper_codegen/src/graphql_interface/attr.rs | 12 +- juniper_codegen/src/graphql_interface/mod.rs | 110 +++++++++++++++++- 4 files changed, 119 insertions(+), 6 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d6d30190c..403d2da73 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -12,7 +12,7 @@ struct Human { #[graphql_interface] impl Character for Human { - #[graphql_interface] + //#[graphql_interface] fn id(&self) -> &str { &self.id } diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 9cebd29d0..938da61db 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -647,6 +647,7 @@ impl<'a, S> Field<'a, S> { /// /// If the description hasn't been set, the description is set to the provided line. /// Otherwise, the doc string is added to the current description after a newline. + // TODO: remove after full proc macros pub fn push_docstring(mut self, multiline: &[&str]) -> Field<'a, S> { if let Some(docstring) = clean_docstring(multiline) { match &mut self.description { diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 91b695c63..e52f34669 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -9,7 +9,7 @@ use crate::{ util::{span_container::SpanContainer, strip_attrs, unite_attrs}, }; -use super::{InterfaceDefinition, InterfaceImplementerDefinition, InterfaceMeta}; +use super::{InterfaceDefinition, InterfaceImplementerDefinition, InterfaceMeta, InterfaceFieldDefinition, InterfaceFieldArgumentDefinition}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -87,6 +87,16 @@ pub fn expand_on_trait( context, scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics.clone(), + fields: vec![ + InterfaceFieldDefinition { + name: "id".to_string(), + ty: parse_quote! { &str }, + description: None, + deprecated: None, + arguments: vec![], + is_async: false, + } + ], implementers, }; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 1c71f2749..427ca30bf 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -364,6 +364,22 @@ impl ArgumentMeta { } } +struct InterfaceFieldArgumentDefinition { + pub name: String, + pub ty: syn::Type, + pub description: Option, + pub default: Option>, +} + +struct InterfaceFieldDefinition { + pub name: String, + pub ty: syn::Type, + pub description: Option, + pub deprecated: Option>, + pub arguments: Vec, + pub is_async: bool, +} + /// Definition of [GraphQL interface][1] implementer for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -448,6 +464,8 @@ struct InterfaceDefinition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub scalar: Option, + pub fields: Vec, + /// Implementers definitions of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -484,6 +502,71 @@ impl ToTokens for InterfaceDefinition { None }; + let fields_meta = self.fields.iter().map(|field| { + let (name, ty) = (&field.name, &field.ty); + + let description = field + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let deprecated = field.deprecated.as_ref().map(|reason| { + let reason = reason + .as_ref() + .map(|rsn| quote! { Some(#rsn) }) + .unwrap_or_else(|| quote! { None }); + quote! { .deprecated(#reason) } + }); + + let arguments = field.arguments.iter().map(|arg| { + let (name, ty) = (&arg.name, &arg.ty); + + let description = arg + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let method = if let Some(val) = &arg.default { + let val = val + .as_ref() + .map(|v| quote! { #v }) + .unwrap_or_else(|| quote! { <#ty as Default>::default() }); + quote! { .arg_with_default::<#ty>(#name, &(#val), info) } + } else { + quote! { .arg::<#ty>(#name, info) } + }; + + quote! { .argument(registry#method#description) } + }); + + quote! { + registry.field_convert::<#ty, _, Self::Context>(#name, info) + #( #arguments )* + #description + #deprecated + } + }); + + let fields_resolvers = self.fields.iter().map(|field| { + let (name, ty) = (&field.name, &field.ty); + + let code = quote! {}; + + quote! { + #name => { + let res: #ty = (|| { #code })(); + + ::juniper::IntoResolvable::into(res, executor.context()) + .and_then(|res| match res { + Some((ctx, r)) => executor + .replaced_context(ctx) + .resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) + }, + } + }); + let custom_downcast_checks = self.implementers.iter().filter_map(|impler| { let impler_check = impler.downcast_check.as_ref()?; let impler_ty = &impler.ty; @@ -647,10 +730,12 @@ impl ToTokens for InterfaceDefinition { // Ensure all implementer types are registered. #( let _ = registry.get_type::<#impler_types>(info); )* - // TODO: fields - registry.build_interface_type::<#ty_full>(info, &[]) - #description - .into_meta() + let fields = [ + #( #fields_meta, )* + ]; + registry.build_interface_type::<#ty_full>(info, &fields) + #description + .into_meta() } } }; @@ -667,6 +752,23 @@ impl ToTokens for InterfaceDefinition { >::name(info) } + fn resolve_field( + &self, + info: &Self::TypeInfo, + field: &str, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match field { + #( #fields_resolvers )* + _ => panic!( + "Field `{}` not found on type `{}`", + field, + >::name(info) + ), + } + } + fn concrete_type_name( &self, context: &Self::Context, From 4fb13a913c139b93a3fdf2b0d0d9ca9203a9810a Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 20 Aug 2020 19:01:31 +0300 Subject: [PATCH 15/79] Poking with fields resolution [skip ci] --- .../src/codegen/interface_attr.rs | 7 +- juniper/Cargo.toml | 1 + juniper/src/executor/mod.rs | 12 ++ juniper/src/lib.rs | 2 +- juniper_codegen/src/graphql_interface/attr.rs | 74 +++++--- juniper_codegen/src/graphql_interface/mod.rs | 163 +++++++++++++++--- 6 files changed, 209 insertions(+), 50 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 403d2da73..a070ffd9f 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -11,9 +11,8 @@ struct Human { #[graphql_interface] impl Character for Human { - //#[graphql_interface] - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } } @@ -29,7 +28,7 @@ struct Droid { #[graphql_interface] impl Character for Droid { - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } @@ -42,7 +41,7 @@ impl Character for Droid { #[graphql_interface(for = [Human, Droid])] trait Character { - fn id(&self) -> &str; + async fn id(&self) -> &str; //#[graphql_interface(downcast)] fn as_droid(&self) -> Option<&Droid> { None } diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 1e44d5682..2c65283b2 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -34,6 +34,7 @@ schema-language = ["graphql-parser-integration"] [dependencies] juniper_codegen = { version = "0.14.2", path = "../juniper_codegen" } +async-trait = "0.1.38" bson = { version = "1.0", optional = true } chrono = { version = "0.4", optional = true } fnv = "1.0.3" diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index f58aef1c8..20028b722 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -80,6 +80,18 @@ where field_path: Arc>, } +pub trait FromExecutor { + /// Performs the conversion. + fn from(value: &T) -> &Self; +} + +/* +impl<'r, 'a, CtxA, CtxB, ScA, ScB> FromExecutor> for Executor<'r, 'a, CtxB, ScB> { + fn from(a: Executor<'r, 'a, CtxA, ScA>) -> Self { + todo!() + } +}*/ + /// Error type for errors that occur during query execution /// /// All execution errors contain the source position in the query of the field diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 2199e2fcd..6e80ceabf 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -119,7 +119,7 @@ extern crate bson; // These are required by the code generated via the `juniper_codegen` macros. #[doc(hidden)] -pub use {futures, static_assertions as sa}; +pub use {async_trait::async_trait, futures, static_assertions as sa}; #[doc(inline)] pub use futures::future::BoxFuture; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index e52f34669..baf41f604 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -9,7 +9,10 @@ use crate::{ util::{span_container::SpanContainer, strip_attrs, unite_attrs}, }; -use super::{InterfaceDefinition, InterfaceImplementerDefinition, InterfaceMeta, InterfaceFieldDefinition, InterfaceFieldArgumentDefinition}; +use super::{ + InterfaceDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, + InterfaceImplementerDefinition, InterfaceMeta, +}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -79,6 +82,8 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); + let is_async_trait = true; + let generated_code = InterfaceDefinition { name, ty: parse_quote! { #trait_ident }, @@ -87,16 +92,15 @@ pub fn expand_on_trait( context, scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics.clone(), - fields: vec![ - InterfaceFieldDefinition { - name: "id".to_string(), - ty: parse_quote! { &str }, - description: None, - deprecated: None, - arguments: vec![], - is_async: false, - } - ], + fields: vec![InterfaceFieldDefinition { + name: "id".to_string(), + ty: parse_quote! { &str }, + description: None, + deprecated: None, + method: parse_quote! { id }, + arguments: vec![], + is_async: true, + }], implementers, }; @@ -106,6 +110,21 @@ pub fn expand_on_trait( ast.supertraits.push(parse_quote! { ::juniper::AsDynGraphQLValue }); + if is_async_trait { + ast.attrs.push(parse_quote! { #[::juniper::async_trait] }); + for item in ast.items.iter_mut() { + if let syn::TraitItem::Method(m) = item { + if m.sig.asyncness.is_some() { + m.sig + .generics + .where_clause + .get_or_insert_with(|| parse_quote! { where }) + .predicates + .push(parse_quote! { GraphQLScalarValue: 'async_trait }) + } + } + } + } Ok(quote! { #ast @@ -129,23 +148,38 @@ pub fn expand_on_impl( } } + let is_async_trait = true; + ast.generics.params.push(parse_quote! { GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync }); - ast.generics.make_where_clause().predicates.push(parse_quote! { - Self: Sync - }); + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Sync }); let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { - - }); + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { }); } else if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { - GraphQLScalarValue - }); + a.args.push(parse_quote! { GraphQLScalarValue }); + } + + if is_async_trait { + ast.attrs.push(parse_quote! { #[::juniper::async_trait] }); + for item in ast.items.iter_mut() { + if let syn::ImplItem::Method(m) = item { + if m.sig.asyncness.is_some() { + m.sig + .generics + .where_clause + .get_or_insert_with(|| parse_quote! { where }) + .predicates + .push(parse_quote! { GraphQLScalarValue: 'async_trait }) + } + } + } } Ok(quote! { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 427ca30bf..b586c4455 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -371,12 +371,30 @@ struct InterfaceFieldArgumentDefinition { pub default: Option>, } +enum InterfaceFieldArgument { + Regular(InterfaceFieldArgumentDefinition), + Context, + Executor, +} + +impl InterfaceFieldArgument { + #[must_use] + pub fn as_regular(&self) -> Option<&InterfaceFieldArgumentDefinition> { + if let Self::Regular(arg) = self { + Some(arg) + } else { + None + } + } +} + struct InterfaceFieldDefinition { pub name: String, pub ty: syn::Type, pub description: Option, pub deprecated: Option>, - pub arguments: Vec, + pub method: syn::Ident, + pub arguments: Vec, pub is_async: bool, } @@ -518,7 +536,9 @@ impl ToTokens for InterfaceDefinition { quote! { .deprecated(#reason) } }); - let arguments = field.arguments.iter().map(|arg| { + let arguments = field.arguments.iter().filter_map(|arg| { + let arg = arg.as_regular()?; + let (name, ty) = (&arg.name, &arg.ty); let description = arg @@ -536,7 +556,7 @@ impl ToTokens for InterfaceDefinition { quote! { .arg::<#ty>(#name, info) } }; - quote! { .argument(registry#method#description) } + Some(quote! { .argument(registry#method#description) }) }); quote! { @@ -547,26 +567,6 @@ impl ToTokens for InterfaceDefinition { } }); - let fields_resolvers = self.fields.iter().map(|field| { - let (name, ty) = (&field.name, &field.ty); - - let code = quote! {}; - - quote! { - #name => { - let res: #ty = (|| { #code })(); - - ::juniper::IntoResolvable::into(res, executor.context()) - .and_then(|res| match res { - Some((ctx, r)) => executor - .replaced_context(ctx) - .resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }) - }, - } - }); - let custom_downcast_checks = self.implementers.iter().filter_map(|impler| { let impler_check = impler.downcast_check.as_ref()?; let impler_ty = &impler.ty; @@ -700,6 +700,7 @@ impl ToTokens for InterfaceDefinition { } let mut ty_full = quote! { #ty#ty_generics }; + let mut ty_interface = quote! { #ty#ty_generics }; if self.is_trait_object { let mut ty_params = None; if !self.generics.params.is_empty() { @@ -710,8 +711,102 @@ impl ToTokens for InterfaceDefinition { dyn #ty<#ty_params #scalar, Context = #context, TypeInfo = ()> + '__obj + Send + Sync }; + ty_interface = quote! { #ty<#ty_params #scalar> }; } + let fields_sync_resolvers = self.fields.iter().filter_map(|field| { + if field.is_async { + return None; + } + let (name, ty, method) = (&field.name, &field.ty, &field.method); + let arguments = field.arguments.iter().map(|arg| match arg { + InterfaceFieldArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + let err_text = format!( + "Internal error: missing argument `{}` - validation must have failed", + &name, + ); + quote! { args.get::<#ty>(#name).expect(#err_text) } + } + InterfaceFieldArgument::Context => quote! { executor.context() }, + InterfaceFieldArgument::Executor => quote! { &executor }, + }); + + Some(quote! { + #name => { + let res: #ty = ::#method(self#( , #arguments )*); + ::juniper::IntoResolvable::into(res, executor.context()) + .and_then(|res| match res { + Some((ctx, r)) => executor + .replaced_context(ctx) + .resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) + }, + }) + }); + let fields_sync_panic = { + let names = self + .fields + .iter() + .filter_map(|field| { + if field.is_async { + Some(&field.name) + } else { + None + } + }) + .collect::>(); + if names.is_empty() { + None + } else { + Some(quote! { + #( #names )|* => panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + field, + >::name(info).unwrap(), + ), + }) + } + }; + + let fields_async_resolvers = self.fields.iter().map(|field| { + let (name, ty) = (&field.name, &field.ty); + + let method = &field.method; + let arguments = field.arguments.iter().map(|arg| match arg { + InterfaceFieldArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + let err_text = format!( + "Internal error: missing argument `{}` - validation must have failed", + &name, + ); + quote! { args.get::<#ty>(#name).expect(#err_text) } + } + InterfaceFieldArgument::Context => quote! { executor.context() }, + InterfaceFieldArgument::Executor => quote! { &executor }, + }); + let awt = if field.is_async { + Some(quote! { .await }) + } else { + None + }; + + quote! { + #name => Box::pin(async move { + let res: #ty = ::#method(self#( , #arguments )*)#awt; + + match ::juniper::IntoResolvable::into(res, executor.context())? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + }, + None => Ok(::juniper::Value::null()), + } + }), + } + }); + let type_impl = quote! { #[automatically_derived] impl#ext_impl_generics ::juniper::GraphQLType<#scalar> for #ty_full @@ -760,11 +855,12 @@ impl ToTokens for InterfaceDefinition { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { match field { - #( #fields_resolvers )* + #( #fields_sync_resolvers )* + #fields_sync_panic _ => panic!( "Field `{}` not found on type `{}`", field, - >::name(info) + >::name(info).unwrap(), ), } } @@ -797,6 +893,23 @@ impl ToTokens for InterfaceDefinition { impl#ext_impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_async { + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match field { + #( #fields_async_resolvers )* + _ => panic!( + "Field `{}` not found on type `{}`", + field, + >::name(info).unwrap(), + ), + } + } + fn resolve_into_type_async<'b>( &'b self, info: &'b Self::TypeInfo, From d49b9a5bc0142a73fd0560b7371f3f92b59f0cb8 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 21 Aug 2020 17:29:04 +0300 Subject: [PATCH 16/79] Solve Rust's teen async HRTB problems [skip ci] --- .../src/codegen/interface_attr.rs | 10 +++++----- juniper_codegen/src/graphql_interface/attr.rs | 12 +++++------- juniper_codegen/src/graphql_interface/mod.rs | 18 +++++++----------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index a070ffd9f..2255e9b1a 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -12,8 +12,8 @@ struct Human { #[graphql_interface] impl Character for Human { //#[graphql_interface] - async fn id(&self) -> &str { - &self.id + async fn id(&self, _: &()) -> String { + self.id.to_string() } } @@ -28,8 +28,8 @@ struct Droid { #[graphql_interface] impl Character for Droid { - async fn id(&self) -> &str { - &self.id + async fn id(&self, _: &()) -> String { + self.id.to_string() } fn as_droid(&self) -> Option<&Droid> { @@ -41,7 +41,7 @@ impl Character for Droid { #[graphql_interface(for = [Human, Droid])] trait Character { - async fn id(&self) -> &str; + async fn id(&self, context: &()) -> String; //#[graphql_interface(downcast)] fn as_droid(&self) -> Option<&Droid> { None } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index baf41f604..2d7efc79f 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -10,7 +10,7 @@ use crate::{ }; use super::{ - InterfaceDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, + InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldDefinition, InterfaceImplementerDefinition, InterfaceMeta, }; @@ -94,11 +94,11 @@ pub fn expand_on_trait( generics: ast.generics.clone(), fields: vec![InterfaceFieldDefinition { name: "id".to_string(), - ty: parse_quote! { &str }, + ty: parse_quote! { String }, description: None, deprecated: None, method: parse_quote! { id }, - arguments: vec![], + arguments: vec![InterfaceFieldArgument::Context], is_async: true, }], implementers, @@ -117,8 +117,7 @@ pub fn expand_on_trait( if m.sig.asyncness.is_some() { m.sig .generics - .where_clause - .get_or_insert_with(|| parse_quote! { where }) + .make_where_clause() .predicates .push(parse_quote! { GraphQLScalarValue: 'async_trait }) } @@ -173,8 +172,7 @@ pub fn expand_on_impl( if m.sig.asyncness.is_some() { m.sig .generics - .where_clause - .get_or_insert_with(|| parse_quote! { where }) + .make_where_clause() .predicates .push(parse_quote! { GraphQLScalarValue: 'async_trait }) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index b586c4455..0451ab572 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -786,16 +786,14 @@ impl ToTokens for InterfaceDefinition { InterfaceFieldArgument::Context => quote! { executor.context() }, InterfaceFieldArgument::Executor => quote! { &executor }, }); - let awt = if field.is_async { - Some(quote! { .await }) - } else { - None - }; - quote! { - #name => Box::pin(async move { - let res: #ty = ::#method(self#( , #arguments )*)#awt; + let mut fut = quote! { ::#method(self#( , #arguments )*) }; + if !field.is_async { + fut = quote! { ::juniper::futures::future::ready(#fut) }; + } + quote! { + #name => Box::pin(::juniper::futures::FutureExt::then(#fut, move |res: #ty| async move { match ::juniper::IntoResolvable::into(res, executor.context())? { Some((ctx, r)) => { let subexec = executor.replaced_context(ctx); @@ -803,7 +801,7 @@ impl ToTokens for InterfaceDefinition { }, None => Ok(::juniper::Value::null()), } - }), + })), } }); @@ -881,7 +879,6 @@ impl ToTokens for InterfaceDefinition { _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - let context = executor.context(); #( #custom_downcasts )* #regular_downcast } @@ -917,7 +914,6 @@ impl ToTokens for InterfaceDefinition { _: Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - let context = executor.context(); #( #custom_async_downcasts )* #regular_async_downcast } From 118e428e7a7fe7c57621b792871b6275402fed0f Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 21 Aug 2020 19:10:26 +0300 Subject: [PATCH 17/79] Start parsing trait methods [skip ci] --- juniper_codegen/src/graphql_interface/attr.rs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 2d7efc79f..a727b5166 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -1,16 +1,20 @@ //! Code generation for `#[graphql_interface]` macro. +use std::mem; + use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ result::GraphQLScope, - util::{span_container::SpanContainer, strip_attrs, unite_attrs}, + util::{ + path_eq_single, span_container::SpanContainer, strip_attrs, to_camel_case, unite_attrs, + }, }; use super::{ - InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldDefinition, + FieldMeta, InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldDefinition, InterfaceImplementerDefinition, InterfaceMeta, }; @@ -184,3 +188,45 @@ pub fn expand_on_impl( #ast }) } + +fn parse_field_from_trait_method( + method: &mut syn::TraitItemMethod, +) -> Option { + let method_attrs = method.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + method.attrs = mem::take(&mut method.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) + .collect(); + + let meta = FieldMeta::from_attrs("graphql_interface", &method_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if meta.ignore.is_some() { + return None; + } + + let method_ident = &method.sig.ident; + + let name = meta + .name + .as_ref() + .map(syn::LitStr::value) + .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + meta.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| method_ident.span()), + ); + return None; + } + + Some(InterfaceFieldDefinition { + name, + method: method_ident.clone(), + }) +} From c39b0b242428456803ef296a55058207a8e51d6e Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 25 Aug 2020 17:24:13 +0300 Subject: [PATCH 18/79] Finish parsing fields from trait methods [skip ci] --- juniper_codegen/src/graphql_interface/attr.rs | 212 ++++++++++++++++-- juniper_codegen/src/graphql_interface/mod.rs | 24 ++ 2 files changed, 220 insertions(+), 16 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a727b5166..5df2508ad 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -4,18 +4,20 @@ use std::mem; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ result::GraphQLScope, util::{ path_eq_single, span_container::SpanContainer, strip_attrs, to_camel_case, unite_attrs, + unparenthesize, }, }; use super::{ - FieldMeta, InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldDefinition, - InterfaceImplementerDefinition, InterfaceMeta, + ArgumentMeta, FieldMeta, InterfaceDefinition, InterfaceFieldArgument, + InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceImplementerDefinition, + InterfaceMeta, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -86,6 +88,17 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); + let fields = ast + .items + .iter_mut() + .filter_map(|item| match item { + syn::TraitItem::Method(m) => parse_field_from_trait_method(m), + _ => None, + }) + .collect(); + + proc_macro_error::abort_if_dirty(); + let is_async_trait = true; let generated_code = InterfaceDefinition { @@ -96,15 +109,7 @@ pub fn expand_on_trait( context, scalar: meta.scalar.map(SpanContainer::into_inner), generics: ast.generics.clone(), - fields: vec![InterfaceFieldDefinition { - name: "id".to_string(), - ty: parse_quote! { String }, - description: None, - deprecated: None, - method: parse_quote! { id }, - arguments: vec![InterfaceFieldArgument::Context], - is_async: true, - }], + fields, implementers, }; @@ -184,9 +189,7 @@ pub fn expand_on_impl( } } - Ok(quote! { - #ast - }) + Ok(quote! { #ast }) } fn parse_field_from_trait_method( @@ -213,7 +216,7 @@ fn parse_field_from_trait_method( let name = meta .name .as_ref() - .map(syn::LitStr::value) + .map(|m| m.as_ref().value()) .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( @@ -225,8 +228,185 @@ fn parse_field_from_trait_method( return None; } + let arguments = { + if method.sig.inputs.is_empty() { + return err_no_method_receiver(&method.sig.inputs); + } + let args_len = method.sig.inputs.len(); + let mut args_iter = method.sig.inputs.iter_mut(); + match args_iter.next().unwrap() { + syn::FnArg::Receiver(rcv) => { + if !rcv.reference.is_some() || 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) => parse_field_argument_from_method_argument(arg), + }) + .collect() + }; + + let ty = match &method.sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => unparenthesize(&*ty).clone(), + }; + + let description = meta.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = meta + .deprecated + .as_ref() + .map(|d| d.as_ref().as_ref().map(syn::LitStr::value)); + Some(InterfaceFieldDefinition { name, + ty, + description, + deprecated, method: method_ident.clone(), + arguments, + is_async: method.sig.asyncness.is_some(), }) } + +fn parse_field_argument_from_method_argument( + argument: &mut syn::PatType, +) -> Option { + let argument_attrs = argument.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + argument.attrs = mem::take(&mut argument.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) + .collect(); + + let meta = ArgumentMeta::from_attrs("graphql_interface", &argument_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if meta.context.is_some() { + if let Some(span) = &meta.executor { + return err_arg_both_context_and_executor(&span); + } + ensure_no_regular_field_argument_meta(&meta)?; + return Some(InterfaceFieldArgument::Context); + } + + if meta.executor.is_some() { + if let Some(span) = &meta.context { + return err_arg_both_context_and_executor(&span); + } + ensure_no_regular_field_argument_meta(&meta)?; + return Some(InterfaceFieldArgument::Executor); + } + + if let syn::Pat::Ident(name) = &*argument.pat { + let arg = match name.ident.unraw().to_string().as_str() { + "context" | "ctx" => Some(InterfaceFieldArgument::Context), + "executor" => Some(InterfaceFieldArgument::Executor), + _ => None, + }; + if arg.is_some() { + ensure_no_regular_field_argument_meta(&meta)?; + return arg; + } + } + + let name = if let Some(name) = meta.name.as_ref() { + name.as_ref().value() + } else if let syn::Pat::Ident(name) = &*argument.pat { + to_camel_case(&name.ident.unraw().to_string()) + } else { + ERR.custom( + argument.pat.span(), + "trait method argument should be declared as a single identifier", + ) + .note(String::from( + "use `#[graphql_interface(name = ...)]` attribute to specify custom argument's name \ + without requiring it being a single identifier", + )) + .emit(); + return None; + }; + if name.starts_with("__") { + ERR.no_double_underscore( + meta.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| argument.pat.span()), + ); + return None; + } + + Some(InterfaceFieldArgument::Regular( + InterfaceFieldArgumentDefinition { + name, + ty: argument.ty.as_ref().clone(), + description: meta.description.as_ref().map(|d| d.as_ref().value()), + default: meta.default.as_ref().map(|v| v.as_ref().clone()), + }, + )) +} + +fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> { + if let Some(span) = &meta.name { + return err_invalid_arg_meta(&span, "name"); + } + if let Some(span) = &meta.description { + return err_invalid_arg_meta(&span, "description"); + } + if let Some(span) = &meta.default { + return err_invalid_arg_meta(&span, "default"); + } + Some(()) +} + +fn err_invalid_method_receiver(span: &S) -> Option { + ERR.custom( + span.span(), + "trait method receiver can only be a shared reference `&self`", + ) + .emit(); + return None; +} + +fn err_no_method_receiver(span: &S) -> Option { + ERR.custom( + span.span(), + "trait method should have a shared reference receiver `&self`", + ) + .emit(); + return None; +} + +fn err_arg_both_context_and_executor(span: &S) -> Option { + ERR.custom( + span.span(), + "trait method argument cannot be both `juniper::Context` and `juniper::Executor` at the \ + same time", + ) + .emit(); + return None; +} + +fn err_invalid_arg_meta(span: &S, attr: &str) -> Option { + ERR.custom( + span.span(), + format!( + "attribute `#[graphql_interface({} = ...)]` is not allowed here", + attr + ), + ) + .emit(); + return None; +} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0451ab572..92b87b515 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -290,6 +290,8 @@ struct ArgumentMeta { pub name: Option>, pub description: Option>, pub default: Option>>, + pub context: Option>, + pub executor: Option>, } impl Parse for ArgumentMeta { @@ -334,6 +336,26 @@ impl Parse for ArgumentMeta { )) .none_or_else(|_| err::dup_arg(&ident))? } + "ctx" | "context" | "Context" => { + output + .context + .replace(SpanContainer::new( + ident.span(), + Some(ident.span()), + ident.clone(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "exec" | "executor" => { + output + .executor + .replace(SpanContainer::new( + ident.span(), + Some(ident.span()), + ident.clone(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } name => { return Err(err::unknown_arg(&ident, name)); } @@ -352,6 +374,8 @@ impl ArgumentMeta { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), default: try_merge_opt!(default: self, another), + context: try_merge_opt!(context: self, another), + executor: try_merge_opt!(executor: self, another), }) } From 06c9c4dca8ba898988a9ba485b51430d4ccaf743 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 25 Aug 2020 18:08:01 +0300 Subject: [PATCH 19/79] Autodetect trait asyncness and allow to specify it [skip ci] --- juniper_codegen/src/graphql_interface/attr.rs | 34 ++++---- juniper_codegen/src/graphql_interface/mod.rs | 82 ++++++++++++++++--- 2 files changed, 89 insertions(+), 27 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 5df2508ad..4c5a57faa 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -17,7 +17,7 @@ use crate::{ use super::{ ArgumentMeta, FieldMeta, InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceImplementerDefinition, - InterfaceMeta, + InterfaceMeta, ImplementerMeta, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -51,7 +51,6 @@ pub fn expand_on_trait( ) -> syn::Result { let meta = InterfaceMeta::from_attrs("graphql_interface", &attrs)?; - let trait_span = ast.span(); let trait_ident = &ast.ident; let name = meta @@ -99,7 +98,15 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); - let is_async_trait = true; + let is_async_trait = meta.asyncness.is_some() + || ast + .items + .iter() + .find_map(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness, + _ => None, + }) + .is_some(); let generated_code = InterfaceDefinition { name, @@ -146,17 +153,17 @@ pub fn expand_on_impl( attrs: Vec, mut ast: syn::ItemImpl, ) -> syn::Result { - for attr in attrs { - if !attr.tokens.is_empty() && attr.tokens.to_string().as_str() != "()" { - return Err(syn::Error::new( - attr.tokens.span(), - "#[graphql_interface] attribute cannot have any arguments when placed on a trait \ - implementation", - )); - } - } + let meta = ImplementerMeta::from_attrs("graphql_interface", &attrs)?; - let is_async_trait = true; + let is_async_trait = meta.asyncness.is_some() + || ast + .items + .iter() + .find_map(|item| match item { + syn::ImplItem::Method(m) => m.sig.asyncness, + _ => None, + }) + .is_some(); ast.generics.params.push(parse_quote! { GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync @@ -232,7 +239,6 @@ fn parse_field_from_trait_method( if method.sig.inputs.is_empty() { return err_no_method_receiver(&method.sig.inputs); } - let args_len = method.sig.inputs.len(); let mut args_iter = method.sig.inputs.iter_mut(); match args_iter.next().unwrap() { syn::FnArg::Receiver(rcv) => { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 92b87b515..99eab69f4 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -4,7 +4,7 @@ pub mod attr; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt as _}; @@ -72,6 +72,8 @@ struct InterfaceMeta { /// [2]: https://spec.graphql.org/June2018/#sec-Objects pub implementers: HashSet>, + pub asyncness: Option>, + /* /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers. /// @@ -146,6 +148,13 @@ impl Parse for InterfaceMeta { .none_or_else(|_| err::dup_arg(impler_span))?; } } + "async" => { + let span = ident.span(); + output + .asyncness + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))?; + } "internal" => { output.is_internal = true; } @@ -169,6 +178,7 @@ impl InterfaceMeta { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), + asyncness: try_merge_opt!(asyncness: self, another), is_internal: self.is_internal || another.is_internal, }) } @@ -188,6 +198,58 @@ impl InterfaceMeta { } } +/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait +/// implementation block, when generating code for [GraphQL interface][1] type. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug, Default)] +struct ImplementerMeta { + pub asyncness: Option>, +} + +impl Parse for ImplementerMeta { + fn parse(input: ParseStream) -> syn::Result { + let mut output = Self::default(); + + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "async" => { + let span = ident.span(); + output + .asyncness + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))?; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + + Ok(output) + } +} + +impl ImplementerMeta { + /// Tries to merge two [`ImplementerMeta`]s into a single one, reporting about duplicates, if + /// any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + asyncness: try_merge_opt!(asyncness: self, another), + }) + } + + /// Parses [`ImplementerMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a + /// trait implementation block. + pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) + } +} + #[derive(Debug, Default)] struct FieldMeta { pub name: Option>, @@ -337,24 +399,18 @@ impl Parse for ArgumentMeta { .none_or_else(|_| err::dup_arg(&ident))? } "ctx" | "context" | "Context" => { + let span = ident.span(); output .context - .replace(SpanContainer::new( - ident.span(), - Some(ident.span()), - ident.clone(), - )) - .none_or_else(|_| err::dup_arg(&ident))? + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))? } "exec" | "executor" => { + let span = ident.span(); output .executor - .replace(SpanContainer::new( - ident.span(), - Some(ident.span()), - ident.clone(), - )) - .none_or_else(|_| err::dup_arg(&ident))? + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))? } name => { return Err(err::unknown_arg(&ident, name)); From 4c17bd173c26c29d8f04d49e6dc50980aeae9591 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 26 Aug 2020 16:47:35 +0300 Subject: [PATCH 20/79] Allow to autogenerate trait object alias via attribute --- .../src/codegen/interface_attr.rs | 586 +++++++++++++++++- .../juniper_tests/src/codegen/mod.rs | 1 - .../src/codegen/poc_interface_attr.rs | 409 ------------ juniper_codegen/src/graphql_interface/attr.rs | 4 +- juniper_codegen/src/graphql_interface/mod.rs | 84 ++- 5 files changed, 628 insertions(+), 456 deletions(-) delete mode 100644 integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 2255e9b1a..26335bc0b 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,6 +1,551 @@ //! Tests for `#[graphql_interface]` macro. -use juniper::{execute, graphql_object, graphql_interface, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; +use juniper::{ + execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, + EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, +}; + +fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> +where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, +{ + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +mod trivial { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + type DynCharacter<'a, S = DefaultScalarValue> = + dyn Character + 'a + Send + Sync; + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +mod trivial_async { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + trait Character { + async fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + async fn id(&self) -> &str { + &self.id + } + } + + type DynCharacter<'a, S = DefaultScalarValue> = + dyn Character + 'a + Send + Sync; + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +mod dyn_alias { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +// ------------------------------------------- #[derive(GraphQLObject)] #[graphql(impl = dyn Character)] @@ -17,8 +562,6 @@ impl Character for Human { } } -// ------------------------------------------ - #[derive(GraphQLObject)] #[graphql(impl = dyn Character)] struct Droid { @@ -37,34 +580,27 @@ impl Character for Droid { } } -// ------------------------------------------ +#[derive(GraphQLObject)] +struct Ewok { + id: String, + funny: bool, +} #[graphql_interface(for = [Human, Droid])] trait Character { async fn id(&self, context: &()) -> String; //#[graphql_interface(downcast)] - fn as_droid(&self) -> Option<&Droid> { None } -} - -// ------------------------------------------ - -fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> - where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) + fn as_droid(&self) -> Option<&Droid> { + None + } } mod poc { use super::*; - type DynCharacter<'a, S = DefaultScalarValue> = dyn Character + 'a + Send + Sync; + type DynCharacter<'a, S = DefaultScalarValue> = + dyn Character + 'a + Send + Sync; enum QueryRoot { Human, @@ -100,10 +636,7 @@ mod poc { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"id": "human-32"}}), - vec![], - )), + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![],)), ); } @@ -119,10 +652,7 @@ mod poc { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"id": "droid-99"}}), - vec![], - )), + Ok((graphql_value!({"character": {"id": "droid-99"}}), vec![],)), ); } diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 072a485aa..37e12c31f 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -4,7 +4,6 @@ mod derive_object; mod derive_object_with_raw_idents; mod impl_object; mod impl_scalar; -mod poc_interface_attr; mod interface_attr; mod scalar_value_transparent; mod union_attr; diff --git a/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs b/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs deleted file mode 100644 index 9951d05a9..000000000 --- a/integration_tests/juniper_tests/src/codegen/poc_interface_attr.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! Tests for `#[graphql_interface]` macro. - -use juniper::{execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables}; - -/* SUGARED -#[derive(GraphQLObject)] -#[graphql(implements(Character))] -struct Human { - id: String, - home_planet: String, -} - DESUGARS INTO: */ -#[derive(GraphQLObject)] -struct Human { - id: String, - home_planet: String, -} -#[automatically_derived] -impl<__S: ::juniper::ScalarValue + Send + Sync> ::juniper::AsDynGraphQLValue<__S> for Human -where - Self: Sync, -{ - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - #[inline] - fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { - self - } - #[inline] - fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<__S, Self::Context, Self::TypeInfo> { - self - } -} - -/* SUGARED -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - DESUGARS INTO: */ -impl Character for Human -where - Self: Sync, -{ - fn id(&self) -> &str { - &self.id - } -} - -// ------------------------------------------ - -/* SUGARED -#[derive(GraphQLObject)] -#[graphql(implements(Character))] -struct Droid { - id: String, - primary_function: String, -} - DESUGARS INTO: */ -#[derive(GraphQLObject)] -struct Droid { - id: String, - primary_function: String, -} -#[automatically_derived] -impl<__S: ::juniper::ScalarValue + Send + Sync> ::juniper::AsDynGraphQLValue<__S> for Droid -where - Self: Sync, -{ - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - #[inline] - fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<__S, Self::Context, Self::TypeInfo> { - self - } - #[inline] - fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<__S, Self::Context, Self::TypeInfo> { - self - } -} - -/* SUGARED -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - - fn as_droid(&self) -> Option<&Droid> { - Some(self) - } -} - DESUGARS INTO: */ -impl Character for Droid -where - Self: Sync, -{ - fn id(&self) -> &str { - &self.id - } - - fn as_droid(&self) -> Option<&Droid> { - Some(self) - } -} - -// ------------------------------------------ - -/* SUGARED -#[graphql_interface(for(Human, Droid))] -trait Character { - fn id(&self) -> &str; - - #[graphql_interface(downcast)] - fn as_droid(&self) -> Option<&Droid> { None } -} - DESUGARS INTO: */ -trait Character: ::juniper::AsDynGraphQLValue { - fn id(&self) -> &str; - - fn as_droid(&self) -> Option<&Droid> { None } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::marker::GraphQLInterface<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - fn mark() { - >::mark(); - >::mark(); - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::marker::IsOutputType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - fn mark() { - ::juniper::sa::assert_type_ne_all!(Human, Droid); - - >::mark(); - >::mark(); - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLValue<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - type Context = (); - type TypeInfo = (); - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - fn resolve_field( - &self, - _: &Self::TypeInfo, - field: &str, - _: &juniper::Arguments<__S>, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult<__S> { - match field { - "id" => { - let res = self.id(); - ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - None => Ok(juniper::Value::null()), - }) - } - _ => { - panic!( - "Field {} not found on GraphQL interface {}", - field, "Character", - ); - } - } - } - - fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - // First, check custom downcaster to be used. - if ({ Character::as_droid(self) } as ::std::option::Option<&Droid>).is_some() { - return >::name(info) - .unwrap() - .to_string(); - } - - // Otherwise, get concrete type name as dyn object. - self.as_dyn_graphql_value().concrete_type_name(context, info) - } - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<__S>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<__S> { - let context = executor.context(); - - // First, check custom downcaster to be used. - if type_name == (>::name(info)).unwrap() { - return ::juniper::IntoResolvable::into( - Character::as_droid(self), - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); - } - - // Otherwise, resolve inner type as dyn object. - return ::juniper::IntoResolvable::into( - self.as_dyn_graphql_value(), - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLType<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, -{ - fn name(_: &Self::TypeInfo) -> Option<&'static str> { - Some("Character") - } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, __S>, - ) -> ::juniper::meta::MetaType<'r, __S> - where - __S: 'r, - { - let _ = registry.get_type::<&Human>(info); - let _ = registry.get_type::<&Droid>(info); - - let fields = vec![ - // TODO: try array - registry.field_convert::<&str, _, Self::Context>("id", info), - ]; - - registry - .build_interface_type:: + '__obj + Send + Sync>(info, &fields) - .into_meta() - } -} -#[automatically_derived] -impl<'__obj, __S> ::juniper::GraphQLValueAsync<__S> for dyn Character<__S, Context = (), TypeInfo = ()> + '__obj + Send + Sync -where - __S: ::juniper::ScalarValue, - Self: Sync, - __S: Send + Sync, -{ - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field_name: &'b str, - arguments: &'b ::juniper::Arguments<__S>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - // TODO: similar to what happens in GraphQLValue impl - let res = ::juniper::GraphQLValue::resolve_field(self, info, field_name, arguments, executor); - ::juniper::futures::future::FutureExt::boxed(async move { res }) - } - - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - se: Option<&'b [::juniper::Selection<'b, __S>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - // TODO: similar to what happens in GraphQLValue impl - let res = ::juniper::GraphQLValue::resolve_into_type(self, info, type_name, se, executor); - ::juniper::futures::future::FutureExt::boxed(async move { res }) - } -} - -// ------------------------------------------ - -fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> - where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -mod poc { - use super::*; - - type DynCharacter<'a, S = DefaultScalarValue> = dyn Character + 'a + Send + Sync; - - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn resolves_id_for_human() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"id": "human-32"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn resolves_id_for_droid() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"id": "droid-99"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - panic!("🔬 {:#?}", schema.schema); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - humanId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } -} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 4c5a57faa..eb2fc753b 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -111,7 +111,8 @@ pub fn expand_on_trait( let generated_code = InterfaceDefinition { name, ty: parse_quote! { #trait_ident }, - is_trait_object: true, + trait_object: Some(meta.alias.map(|a| a.as_ref().clone())), + visibility: ast.vis.clone(), description: meta.description.map(SpanContainer::into_inner), context, scalar: meta.scalar.map(SpanContainer::into_inner), @@ -126,6 +127,7 @@ pub fn expand_on_trait( ast.supertraits.push(parse_quote! { ::juniper::AsDynGraphQLValue }); + ast.attrs.push(parse_quote! { #[allow(unused_qualifications)] }); if is_async_trait { ast.attrs.push(parse_quote! { #[::juniper::async_trait] }); for item in ast.items.iter_mut() { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 99eab69f4..91bf49183 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -72,6 +72,8 @@ struct InterfaceMeta { /// [2]: https://spec.graphql.org/June2018/#sec-Objects pub implementers: HashSet>, + pub alias: Option>, + pub asyncness: Option>, /* @@ -148,6 +150,14 @@ impl Parse for InterfaceMeta { .none_or_else(|_| err::dup_arg(impler_span))?; } } + "dyn" => { + input.parse::()?; + let alias = input.parse::()?; + output + .alias + .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) + .none_or_else(|_| err::dup_arg(&ident))? + } "async" => { let span = ident.span(); output @@ -178,6 +188,7 @@ impl InterfaceMeta { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), + alias: try_merge_opt!(alias: self, another), asyncness: try_merge_opt!(asyncness: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -510,7 +521,7 @@ struct InterfaceImplementerDefinition { /// [`Span`] that points to the Rust source code which defines this [GraphQL interface][1] /// implementer. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub span: Span, } @@ -533,9 +544,9 @@ struct InterfaceDefinition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub generics: syn::Generics, - /// Indicator whether code should be generated for a trait object, rather than for a regular - /// Rust type. - pub is_trait_object: bool, + pub trait_object: Option>, + + pub visibility: syn::Visibility, /// Description of this [GraphQL interface][1] to put into GraphQL schema. /// @@ -586,6 +597,7 @@ impl ToTokens for InterfaceDefinition { .as_ref() .map(|scl| quote! { #scl }) .unwrap_or_else(|| quote! { __S }); + let is_generic_scalar = self.scalar.is_none(); let description = self .description @@ -658,7 +670,7 @@ impl ToTokens for InterfaceDefinition { } }) }); - let regular_downcast_check = if self.is_trait_object { + let regular_downcast_check = if self.trait_object.is_some() { quote! { self.as_dyn_graphql_value().concrete_type_name(context, info) } @@ -717,7 +729,7 @@ impl ToTokens for InterfaceDefinition { } }) }); - let (regular_downcast, regular_async_downcast) = if self.is_trait_object { + let (regular_downcast, regular_async_downcast) = if self.trait_object.is_some() { let sync = quote! { return ::juniper::IntoResolvable::into( self.as_dyn_graphql_value(), @@ -757,10 +769,10 @@ impl ToTokens for InterfaceDefinition { let (_, ty_generics, _) = self.generics.split_for_impl(); let mut ext_generics = self.generics.clone(); - if self.is_trait_object { + if self.trait_object.is_some() { ext_generics.params.push(parse_quote! { '__obj }); } - if self.scalar.is_none() { + if is_generic_scalar { ext_generics.params.push(parse_quote! { #scalar }); ext_generics .make_where_clause() @@ -773,7 +785,7 @@ impl ToTokens for InterfaceDefinition { .cloned() .unwrap_or_else(|| parse_quote! { where }); where_async.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_none() { + if is_generic_scalar { where_async .predicates .push(parse_quote! { #scalar: Send + Sync }); @@ -781,7 +793,7 @@ impl ToTokens for InterfaceDefinition { let mut ty_full = quote! { #ty#ty_generics }; let mut ty_interface = quote! { #ty#ty_generics }; - if self.is_trait_object { + if self.trait_object.is_some() { let mut ty_params = None; if !self.generics.params.is_empty() { let params = &self.generics.params; @@ -794,6 +806,41 @@ impl ToTokens for InterfaceDefinition { ty_interface = quote! { #ty<#ty_params #scalar> }; } + let mut dyn_alias = quote! {}; + if let Some(Some(alias)) = self.trait_object.as_ref().as_ref() { + let doc = format!( + "Helper alias for the `{}` [trait object][2] implementing [GraphQL interface][1].\ + \n\n\ + [1]: https://spec.graphql.org/June2018/#sec-Interfaces\n\ + [2]: https://doc.rust-lang.org/reference/types/trait-object.html", + quote! { #ty }, + ); + + let mut ty_params = None; + if !self.generics.params.is_empty() { + let params = &self.generics.params; + ty_params = Some(quote! { #params }); + }; + let (scalar_left, scalar_right) = if is_generic_scalar { + let left = Some(quote! { , S = ::juniper::DefaultScalarValue }); + (left, quote! { S }) + } else { + (None, scalar.clone()) + }; + let ty_params_left = ty_params.as_ref().map(|ps| quote! { , #ps }); + let ty_params_right = ty_params.map(|ps| quote! { #ps, }); + + let vis = &self.visibility; + + dyn_alias = quote! { + #[allow(unused_qualifications)] + #[doc = #doc] + #vis type #alias<'a #ty_params_left #scalar_left> = + dyn #ty<#ty_params_right #scalar_right, Context = #context, TypeInfo = ()> + + 'a + Send + Sync; + } + } + let fields_sync_resolvers = self.fields.iter().filter_map(|field| { if field.is_async { return None; @@ -873,13 +920,15 @@ impl ToTokens for InterfaceDefinition { } quote! { - #name => Box::pin(::juniper::futures::FutureExt::then(#fut, move |res: #ty| async move { - match ::juniper::IntoResolvable::into(res, executor.context())? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - }, - None => Ok(::juniper::Value::null()), + #name => Box::pin(::juniper::futures::FutureExt::then(#fut, move |res: #ty| { + async move { + match ::juniper::IntoResolvable::into(res, executor.context())? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + }, + None => Ok(::juniper::Value::null()), + } } })), } @@ -1025,6 +1074,7 @@ impl ToTokens for InterfaceDefinition { }; into.append_all(&[ + dyn_alias, interface_impl, output_type_impl, type_impl, From e758667fe4155d824a9d125d481c371a6dec56a1 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 28 Aug 2020 15:54:31 +0300 Subject: [PATCH 21/79] Support generics in trait definition and asyncify them correctly --- .../src/codegen/interface_attr.rs | 495 +++++++++++++++++- juniper_codegen/src/graphql_interface/attr.rs | 98 ++-- juniper_codegen/src/util/mod.rs | 8 +- 3 files changed, 551 insertions(+), 50 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 26335bc0b..fc204e853 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -194,12 +194,12 @@ mod trivial { } } -mod trivial_async { +mod dyn_alias { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] trait Character { - async fn id(&self) -> &str; + fn id(&self) -> &str; } #[derive(GraphQLObject)] @@ -211,7 +211,7 @@ mod trivial_async { #[graphql_interface] impl Character for Human { - async fn id(&self) -> &str { + fn id(&self) -> &str { &self.id } } @@ -225,14 +225,11 @@ mod trivial_async { #[graphql_interface] impl Character for Droid { - async fn id(&self) -> &str { + fn id(&self) -> &str { &self.id } } - type DynCharacter<'a, S = DefaultScalarValue> = - dyn Character + 'a + Send + Sync; - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -371,12 +368,12 @@ mod trivial_async { } } -mod dyn_alias { +mod trivial_async { use super::*; #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] trait Character { - fn id(&self) -> &str; + async fn id(&self) -> &str; } #[derive(GraphQLObject)] @@ -388,7 +385,7 @@ mod dyn_alias { #[graphql_interface] impl Character for Human { - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } } @@ -402,7 +399,7 @@ mod dyn_alias { #[graphql_interface] impl Character for Droid { - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } } @@ -545,6 +542,480 @@ mod dyn_alias { } } +mod generic { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } +} + +mod generic_async { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + async fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } +} + +mod generic_lifetime_async { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character<'me, A, B> { + async fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character<'_, u8, ()>)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl<'me, A, B> Character<'me, A, B> for Human { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character<'_, u8, ()>)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl<'me, A, B> Character<'me, A, B> for Droid { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } +} + // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index eb2fc753b..a3d7afd03 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -15,9 +15,9 @@ use crate::{ }; use super::{ - ArgumentMeta, FieldMeta, InterfaceDefinition, InterfaceFieldArgument, + ArgumentMeta, FieldMeta, ImplementerMeta, InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceImplementerDefinition, - InterfaceMeta, ImplementerMeta, + InterfaceMeta, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -127,20 +127,20 @@ pub fn expand_on_trait( ast.supertraits.push(parse_quote! { ::juniper::AsDynGraphQLValue }); - ast.attrs.push(parse_quote! { #[allow(unused_qualifications)] }); + ast.attrs + .push(parse_quote! { #[allow(unused_qualifications)] }); if is_async_trait { - ast.attrs.push(parse_quote! { #[::juniper::async_trait] }); - for item in ast.items.iter_mut() { - if let syn::TraitItem::Method(m) = item { - if m.sig.asyncness.is_some() { - m.sig - .generics - .make_where_clause() - .predicates - .push(parse_quote! { GraphQLScalarValue: 'async_trait }) + inject_async_trait( + &mut ast.attrs, + ast.items.iter_mut().filter_map(|i| { + if let syn::TraitItem::Method(m) = i { + Some(&mut m.sig) + } else { + None } - } - } + }), + &ast.generics, + ); } Ok(quote! { @@ -159,13 +159,13 @@ pub fn expand_on_impl( let is_async_trait = meta.asyncness.is_some() || ast - .items - .iter() - .find_map(|item| match item { - syn::ImplItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); + .items + .iter() + .find_map(|item| match item { + syn::ImplItem::Method(m) => m.sig.asyncness, + _ => None, + }) + .is_some(); ast.generics.params.push(parse_quote! { GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync @@ -174,33 +174,63 @@ pub fn expand_on_impl( .make_where_clause() .predicates .push(parse_quote! { Self: Sync }); + ast.attrs + .push(parse_quote! { #[allow(unused_qualifications)] }); let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { }); - } else if let syn::PathArguments::AngleBracketed(a) = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + } + if let syn::PathArguments::AngleBracketed(a) = trait_params { a.args.push(parse_quote! { GraphQLScalarValue }); } if is_async_trait { - ast.attrs.push(parse_quote! { #[::juniper::async_trait] }); - for item in ast.items.iter_mut() { - if let syn::ImplItem::Method(m) = item { - if m.sig.asyncness.is_some() { - m.sig - .generics - .make_where_clause() - .predicates - .push(parse_quote! { GraphQLScalarValue: 'async_trait }) + inject_async_trait( + &mut ast.attrs, + ast.items.iter_mut().filter_map(|i| { + if let syn::ImplItem::Method(m) = i { + Some(&mut m.sig) + } else { + None } - } - } + }), + &ast.generics, + ); } Ok(quote! { #ast }) } +fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) +where + M: IntoIterator, +{ + attrs.push(parse_quote! { #[allow(clippy::type_repetition_in_bounds)] }); + attrs.push(parse_quote! { #[::juniper::async_trait] }); + + for method in methods.into_iter() { + if method.asyncness.is_some() { + let where_clause = &mut method.generics.make_where_clause().predicates; + for p in &generics.params { + let ty_param = match p { + syn::GenericParam::Type(t) => { + let ty_param = &t.ident; + quote! { #ty_param } + } + syn::GenericParam::Lifetime(l) => { + let ty_param = &l.lifetime; + quote! { #ty_param } + } + syn::GenericParam::Const(_) => continue, + }; + where_clause.push(parse_quote! { #ty_param: 'async_trait }); + } + } + } +} + fn parse_field_from_trait_method( method: &mut syn::TraitItemMethod, ) -> Option { diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index dae844918..bb9963e95 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -854,10 +854,9 @@ impl GraphQLTypeDefiniton { { let trait_params = &mut path.segments.last_mut().unwrap().arguments; if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { - <#scalar, Context = Self::Context, TypeInfo = Self::TypeInfo> - }); - } else if let syn::PathArguments::AngleBracketed(a) = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + } + if let syn::PathArguments::AngleBracketed(a) = trait_params { a.args.push(parse_quote! { #scalar }); a.args.push(parse_quote! { Context = Self::Context }); a.args.push(parse_quote! { TypeInfo = Self::TypeInfo }); @@ -865,6 +864,7 @@ impl GraphQLTypeDefiniton { } dyn_ty.bounds.push(parse_quote! { Send }); dyn_ty.bounds.push(parse_quote! { Sync }); + ty = dyn_ty.into(); } From c16dc00a5e889b5eb2222b00341209d543ca2d53 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 28 Aug 2020 17:35:59 +0300 Subject: [PATCH 22/79] Temporary disable explicit async --- .../src/codegen/interface_attr.rs | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index fc204e853..b2173c2ec 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -542,6 +542,164 @@ mod trivial_async { } } +/* +mod explicit_async { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + fn id(&self) -> &str; + + async fn info(&self) -> String { + format!("None available") + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + + async fn info(&self) -> String { + format!("Home planet is {}", &self.home_planet) + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface(async)] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_info_field() { + const DOC: &str = r#"{ + character { + info + } + }"#; + + for (root, expected) in &[ + (QueryRoot::Human, "Home planet is earth"), + (QueryRoot::Droid, "None available"), + ] { + let schema = schema(*root); + + let expected: &str = *expected; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"info": expected}}), vec![])), + ); + } + } +} + */ + mod generic { use super::*; From 33d98776d01b8da031dd3430e6cad81757c92850 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 28 Aug 2020 19:00:57 +0300 Subject: [PATCH 23/79] Cover arguments and custom names/descriptions in tests --- .../src/codegen/interface_attr.rs | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index b2173c2ec..f1125ada3 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1174,6 +1174,308 @@ mod generic_lifetime_async { } } +mod argument { + use super::*; + + #[graphql_interface(for = Human, dyn = DynCharacter)] + trait Character { + fn id(&self, is_planet: bool) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self, is_planet: bool) -> &str { + if is_planet { + &self.home_planet + } else { + &self.id + } + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }) + } + } + + #[tokio::test] + async fn resolves_id_field() { + let schema = schema(QueryRoot); + + for (input, expected) in &[ + ("{ character { id(isPlanet: true) } }", "earth"), + ("{ character { id(isPlanet: false) } }", "human-32"), + ] { + let expected: &str = *expected; + + assert_eq!( + execute(*input, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected}}), vec![])), + ); + } + } + + #[tokio::test] + async fn camelcases_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { "fields": [{"args": [{"name": "isPlanet"}]}]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { "fields": [{"args": [{"description": None}]}]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_no_defaults() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + args { + defaultValue + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { "fields": [{"args": [{"defaultValue": None}]}]}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comments { + use super::*; + + /// Rust docs. + #[graphql_interface(for = Human, dyn = DynCharacter)] + trait Character { + /// Rust `id` docs. + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }) + } + } + + #[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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", "fields": [{"description": "Rust `id` docs."}], + }}), + vec![], + )), + ); + } +} + +mod explicit_name_and_description { + use super::*; + + /// Rust docs. + #[graphql_interface(name = "MyChar", desc = "My character.", for = Human, dyn = DynCharacter)] + trait Character { + /// Rust `id` docs. + #[graphql_interface(name = "myid", desc = "My character ID.")] + fn id( + &self, + #[graphql_interface(name = "myname", desc = "My argument.", default)] n: Option, + ) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self, _: Option) -> &str { + &self.id + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }) + } + } + + #[tokio::test] + async fn resolves_myid_field() { + const DOC: &str = r#"{ + character { + myid + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"myid": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + name + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyChar", + "fields": [{"name": "myid", "args": [{"name": "myname"}]}], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + description + fields { + description + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My character.", + "fields": [{ + "description": "My character ID.", + "args": [{"description": "My argument."}], + }], + }}), + vec![], + )), + ); + } +} + // ------------------------------------------- #[derive(GraphQLObject)] From 1185f463d3818086092a6dfc332ab97cd8afcb65 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 31 Aug 2020 17:18:27 +0300 Subject: [PATCH 24/79] Re-enable tests with explicit async and fix the codegen to satisfy it --- .../src/codegen/interface_attr.rs | 7 ++++--- juniper/Cargo.toml | 8 ++++---- juniper_codegen/src/graphql_interface/attr.rs | 20 +++++++++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index f1125ada3..b80ecd261 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -542,7 +542,6 @@ mod trivial_async { } } -/* mod explicit_async { use super::*; @@ -551,7 +550,7 @@ mod explicit_async { fn id(&self) -> &str; async fn info(&self) -> String { - format!("None available") + "None available".to_owned() } } @@ -698,7 +697,6 @@ mod explicit_async { } } } - */ mod generic { use super::*; @@ -1476,6 +1474,9 @@ mod explicit_name_and_description { } } + +// TODO: check field name camelCases +// TODO: check argument defaults // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index f2e594963..07e601285 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -19,9 +19,6 @@ edition = "2018" travis-ci = { repository = "graphql-rust/juniper" } [features] -expose-test-schema = ["anyhow", "serde_json"] -schema-language = ["graphql-parser-integration"] -graphql-parser-integration = ["graphql-parser"] default = [ "bson", "chrono", @@ -29,12 +26,15 @@ default = [ "url", "uuid", ] +expose-test-schema = ["anyhow", "serde_json"] +schema-language = ["graphql-parser-integration"] +graphql-parser-integration = ["graphql-parser"] [dependencies] juniper_codegen = { version = "0.14.2", path = "../juniper_codegen" } anyhow = { default-features = false, version = "1.0.32", optional = true } -async-trait = "0.1.38" +async-trait = "0.1.39" bson = { version = "1.0", optional = true } chrono = { default-features = false, version = "0.4", optional = true } fnv = "1.0.3" diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a3d7afd03..e3bcc48b5 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -107,6 +107,14 @@ pub fn expand_on_trait( _ => None, }) .is_some(); + let has_default_async_methods = ast + .items + .iter() + .find(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), + _ => false, + }) + .is_some(); let generated_code = InterfaceDefinition { name, @@ -127,8 +135,12 @@ pub fn expand_on_trait( ast.supertraits.push(parse_quote! { ::juniper::AsDynGraphQLValue }); + if is_async_trait && has_default_async_methods { + // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits + ast.supertraits.push(parse_quote! { Sync }); + } ast.attrs - .push(parse_quote! { #[allow(unused_qualifications)] }); + .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); if is_async_trait { inject_async_trait( &mut ast.attrs, @@ -170,12 +182,8 @@ pub fn expand_on_impl( ast.generics.params.push(parse_quote! { GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync }); - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: Sync }); ast.attrs - .push(parse_quote! { #[allow(unused_qualifications)] }); + .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; From 3f385f7578288499bbf83a346485223b39442a2f Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 31 Aug 2020 18:04:08 +0300 Subject: [PATCH 25/79] Check implementers are registered in schema and vice versa --- .../src/codegen/interface_attr.rs | 217 +++++++++++++++++- juniper_codegen/src/graphql_interface/mod.rs | 6 +- 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index b80ecd261..54e4f5e2a 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -161,6 +161,60 @@ mod trivial { ); } + #[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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + #[tokio::test] async fn uses_trait_name() { const DOC: &str = r#"{ @@ -509,6 +563,60 @@ mod trivial_async { ); } + #[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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + #[tokio::test] async fn uses_trait_name() { const DOC: &str = r#"{ @@ -839,6 +947,60 @@ mod generic { ); } + #[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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + #[tokio::test] async fn uses_trait_name_without_type_params() { const DOC: &str = r#"{ @@ -997,6 +1159,60 @@ mod generic_async { ); } + #[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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + #[tokio::test] async fn uses_trait_name_without_type_params() { const DOC: &str = r#"{ @@ -1474,7 +1690,6 @@ mod explicit_name_and_description { } } - // TODO: check field name camelCases // TODO: check argument defaults // ------------------------------------------- diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 91bf49183..0005c1cc8 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -604,7 +604,11 @@ impl ToTokens for InterfaceDefinition { .as_ref() .map(|desc| quote! { .description(#desc) }); - let impler_types: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let mut impler_types: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + impler_types.sort_unstable_by(|a, b| { + let (a, b) = (quote! { #a }.to_string(), quote! { #b }.to_string()); + a.cmp(&b) + }); let all_implers_unique = if impler_types.len() > 1 { Some(quote! { ::juniper::sa::assert_type_ne_all!(#(#impler_types),*); }) From 23b5204a6b6307d38ef15495c3a55ad7077e479a Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 31 Aug 2020 18:10:49 +0300 Subject: [PATCH 26/79] Check argument camelCases --- .../src/codegen/interface_attr.rs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 54e4f5e2a..d7f42ef98 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1393,7 +1393,7 @@ mod argument { #[graphql_interface(for = Human, dyn = DynCharacter)] trait Character { - fn id(&self, is_planet: bool) -> &str; + fn id_wide(&self, is_planet: bool) -> &str; } #[derive(GraphQLObject)] @@ -1405,7 +1405,7 @@ mod argument { #[graphql_interface] impl Character for Human { - fn id(&self, is_planet: bool) -> &str { + fn id_wide(&self, is_planet: bool) -> &str { if is_planet { &self.home_planet } else { @@ -1431,14 +1431,14 @@ mod argument { let schema = schema(QueryRoot); for (input, expected) in &[ - ("{ character { id(isPlanet: true) } }", "earth"), - ("{ character { id(isPlanet: false) } }", "human-32"), + ("{ character { idWide(isPlanet: true) } }", "earth"), + ("{ character { idWide(isPlanet: false) } }", "human-32"), ] { let expected: &str = *expected; assert_eq!( execute(*input, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"id": expected}}), vec![])), + Ok((graphql_value!({"character": {"idWide": expected}}), vec![])), ); } } @@ -1448,6 +1448,7 @@ mod argument { const DOC: &str = r#"{ __type(name: "Character") { fields { + name args { name } @@ -1460,7 +1461,9 @@ mod argument { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": { "fields": [{"args": [{"name": "isPlanet"}]}]}}), + graphql_value!({"__type": {"fields": [ + {"name": "idWide", "args": [{"name": "isPlanet"}]}, + ]}}), vec![], )), ); @@ -1581,10 +1584,10 @@ mod explicit_name_and_description { #[graphql_interface(name = "MyChar", desc = "My character.", for = Human, dyn = DynCharacter)] trait Character { /// Rust `id` docs. - #[graphql_interface(name = "myid", desc = "My character ID.")] + #[graphql_interface(name = "myId", desc = "My character ID.")] fn id( &self, - #[graphql_interface(name = "myname", desc = "My argument.", default)] n: Option, + #[graphql_interface(name = "myName", desc = "My argument.", default)] n: Option, ) -> &str; } @@ -1618,7 +1621,7 @@ mod explicit_name_and_description { async fn resolves_myid_field() { const DOC: &str = r#"{ character { - myid + myId } }"#; @@ -1626,7 +1629,7 @@ mod explicit_name_and_description { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"myid": "human-32"}}), vec![])), + Ok((graphql_value!({"character": {"myId": "human-32"}}), vec![])), ); } @@ -1651,7 +1654,7 @@ mod explicit_name_and_description { Ok(( graphql_value!({"__type": { "name": "MyChar", - "fields": [{"name": "myid", "args": [{"name": "myname"}]}], + "fields": [{"name": "myId", "args": [{"name": "myName"}]}], }}), vec![], )), @@ -1690,7 +1693,6 @@ mod explicit_name_and_description { } } -// TODO: check field name camelCases // TODO: check argument defaults // ------------------------------------------- From 92870ad57b4a7d88a3a0d106dc1e446554d7306a Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 31 Aug 2020 18:39:02 +0300 Subject: [PATCH 27/79] Test argument defaults, and allow Into coercions for them --- .../src/codegen/interface_attr.rs | 93 ++++++++++++++++++- juniper_codegen/src/graphql_interface/mod.rs | 4 +- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d7f42ef98..8cd3b13b8 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1516,6 +1516,98 @@ mod argument { } } +mod default_argument { + use super::*; + + #[graphql_interface(for = Human, dyn = DynCharacter)] + trait Character { + async fn id( + &self, + #[graphql_interface(default)] first: String, + #[graphql_interface(default = "second".to_string())] second: String, + #[graphql_interface(default = "t")] third: String, + ) -> String; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + } + + #[graphql_interface] + impl Character for Human { + async fn id(&self, first: String, second: String, third: String) -> String { + format!("{}|{}&{}", first, second, third) + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + Box::new(Human { + id: "human-32".to_string(), + }) + } + } + + #[tokio::test] + async fn resolves_id_field() { + let schema = schema(QueryRoot); + + for (input, expected) in &[ + ("{ character { id } }", "|second&t"), + (r#"{ character { id(first: "first") } }"#, "first|second&t"), + (r#"{ character { id(second: "") } }"#, "|&t"), + ( + r#"{ character { id(first: "first", second: "") } }"#, + "first|&t", + ), + ( + r#"{ character { id(first: "first", second: "", third: "") } }"#, + "first|&", + ), + ] { + let expected: &str = *expected; + + assert_eq!( + execute(*input, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected}}), vec![])), + ); + } + } + + #[tokio::test] + async fn has_defaults() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + args { + name + defaultValue + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"args": [ + {"name": "first", "defaultValue": r#""""#}, + {"name": "second", "defaultValue": r#""second""#}, + {"name": "third", "defaultValue": r#""t""#}, + ]}]}}), + vec![], + )), + ); + } +} + mod description_from_doc_comments { use super::*; @@ -1693,7 +1785,6 @@ mod explicit_name_and_description { } } -// TODO: check argument defaults // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0005c1cc8..ad2494eda 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -645,9 +645,9 @@ impl ToTokens for InterfaceDefinition { let method = if let Some(val) = &arg.default { let val = val .as_ref() - .map(|v| quote! { #v }) + .map(|v| quote! { (#v).into() }) .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote! { .arg_with_default::<#ty>(#name, &(#val), info) } + quote! { .arg_with_default::<#ty>(#name, &#val, info) } } else { quote! { .arg::<#ty>(#name, info) } }; From fefcd0c418f861e3ac70d2bddcbae48f6c4d6ba6 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 1 Sep 2020 17:51:07 +0300 Subject: [PATCH 28/79] Re-enable markers --- .../src/codegen/derive_input_object.rs | 6 +- .../juniper_tests/src/codegen/impl_object.rs | 15 ++- juniper/src/types/marker.rs | 25 ++-- juniper_codegen/src/derive_scalar_value.rs | 3 + juniper_codegen/src/util/mod.rs | 114 ++++++++++++------ 5 files changed, 114 insertions(+), 49 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index 85604a847..d018ad8b8 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -1,8 +1,8 @@ use fnv::FnvHashMap; use juniper::{ - DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLValue, InputValue, - ToInputValue, + marker, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLValue, + InputValue, ToInputValue, }; #[derive(GraphQLInputObject, Debug, PartialEq)] @@ -50,6 +50,8 @@ struct OverrideDocComment { #[derive(Debug, PartialEq)] struct Fake; +impl<'a> marker::IsInputType for &'a Fake {} + impl<'a> FromInputValue for &'a Fake { fn from_input_value(_v: &InputValue) -> Option<&'a Fake> { None diff --git a/integration_tests/juniper_tests/src/codegen/impl_object.rs b/integration_tests/juniper_tests/src/codegen/impl_object.rs index 5f6760863..913221711 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_object.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_object.rs @@ -3,7 +3,7 @@ use juniper::DefaultScalarValue; use juniper::Object; #[cfg(test)] -use juniper::{self, execute, EmptyMutation, EmptySubscription, RootNode, Value, Variables}; +use juniper::{self, execute, EmptyMutation, EmptySubscription, RootNode, Value, Variables, FieldError}; pub struct MyObject; @@ -84,3 +84,16 @@ where f((type_info, fields)); } + +mod fallible { + use super::*; + + struct Obj; + + #[juniper::graphql_object] + impl Obj { + fn test(&self, arg: String) -> Result { + Ok(arg) + } + } +} \ No newline at end of file diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 574ce0087..15931b591 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -77,7 +77,7 @@ pub trait GraphQLUnion: GraphQLType { /// 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. -pub trait IsOutputType: GraphQLType { +pub trait IsOutputType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types @@ -113,6 +113,13 @@ where { } +impl IsOutputType for Result +where + T: IsOutputType, + S: ScalarValue, +{ +} + impl IsOutputType for Vec where T: IsOutputType, @@ -120,7 +127,7 @@ where { } -impl<'a, S, T> IsOutputType for &'a [T] +impl IsOutputType for [T] where T: IsOutputType, S: ScalarValue, @@ -134,7 +141,7 @@ where { } -impl<'a, S, T> IsInputType for &'a [T] +impl IsInputType for [T] where T: IsInputType, S: ScalarValue, @@ -143,29 +150,29 @@ where impl<'a, S, T> IsInputType for &T where - T: IsInputType, + T: IsInputType + ?Sized, S: ScalarValue, { } impl<'a, S, T> IsOutputType for &T where - T: IsOutputType, + T: IsOutputType + ?Sized, S: ScalarValue, { } impl IsInputType for Box where - T: IsInputType, + T: IsInputType + ?Sized, S: ScalarValue, { } impl IsOutputType for Box where - T: IsOutputType, + T: IsOutputType + ?Sized, S: ScalarValue, { } -impl<'a, S> IsInputType for &str where S: ScalarValue {} -impl<'a, S> IsOutputType for &str where S: ScalarValue {} +impl<'a, S> IsInputType for str where S: ScalarValue {} +impl<'a, S> IsOutputType for str where S: ScalarValue {} diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index deca44021..15c40fd53 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -193,6 +193,9 @@ fn impl_scalar_struct( <#inner_ty as ::juniper::ParseScalarValue>::from_str(value) } } + + impl ::juniper::marker::IsOutputType for #ident { } + impl ::juniper::marker::IsInputType for #ident { } ); Ok(content) diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index bb9963e95..2f02a6e34 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1,13 +1,13 @@ #![allow(clippy::single_match)] pub mod duplicate; +pub mod err; pub mod option_ext; pub mod parse_buffer_ext; -pub mod err; pub mod parse_impl; pub mod span_container; -use std::{collections::HashMap}; +use std::collections::HashMap; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; @@ -890,12 +890,9 @@ impl GraphQLTypeDefiniton { if self.scalar.is_none() && self.generic_scalar { // No custom scalar specified, but always generic specified. // Therefore we inject the generic scalar. - generics.params.push(parse_quote!(__S)); - - let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); - // Insert ScalarValue constraint. - where_clause + generics + .make_where_clause() .predicates .push(parse_quote!(__S: ::juniper::ScalarValue)); } @@ -990,8 +987,6 @@ impl GraphQLTypeDefiniton { .push(parse_quote!( #scalar: Send + Sync )); where_async.predicates.push(parse_quote!(Self: Sync)); - // FIXME: add where clause for interfaces. - let as_dyn_value = if !self.interfaces.is_empty() { Some(quote! { #[automatically_derived] @@ -1047,26 +1042,24 @@ impl GraphQLTypeDefiniton { ) }; - // FIXME: enable this if interfaces are supported - // let marks = self.fields.iter().map(|field| { - // let field_ty = &field._type; + let marks = self.fields.iter().map(|field| { + let field_ty = &field._type; - // let field_marks = field.args.iter().map(|arg| { - // let arg_ty = &arg._type; - // quote!(<#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark();) - // }); + let field_marks = field.args.iter().map(|arg| { + let arg_ty = &arg._type; + quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + }); - // quote!( - // #( #field_marks)* - // <#field_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - // ) - // }); + quote! { + #( #field_marks )* + <#field_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); let output = quote!( impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause { fn mark() { - // FIXME: enable this if interfaces are supported - // #( #marks )* + #( #marks )* } } @@ -1229,15 +1222,42 @@ impl GraphQLTypeDefiniton { .as_ref() .map(|description| quote!( .description(#description) )); - let interfaces = quote!(); - /* - let interfaces = self.interfaces.as_ref().map(|items| { - quote!( + let interfaces = if !self.interfaces.is_empty() { + let interfaces_ty = self.interfaces.iter().map(|ty| { + let mut ty: syn::Type = unparenthesize(ty).clone(); + + if let syn::Type::TraitObject(dyn_ty) = &mut ty { + let mut dyn_ty = dyn_ty.clone(); + if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = + dyn_ty.bounds.first_mut().unwrap() + { + let trait_params = &mut path.segments.last_mut().unwrap().arguments; + if let syn::PathArguments::None = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + } + if let syn::PathArguments::AngleBracketed(a) = trait_params { + a.args.push(parse_quote! { #scalar }); + a.args.push(parse_quote! { Context = Self::Context }); + a.args.push(parse_quote! { TypeInfo = Self::TypeInfo }); + } + } + dyn_ty.bounds.push(parse_quote! { Send }); + dyn_ty.bounds.push(parse_quote! { Sync }); + + ty = dyn_ty.into(); + } + + ty + }); + + Some(quote!( .interfaces(&[ - #( registry.get_type::< #items >(&()) ,)* + #( registry.get_type::<#interfaces_ty>(&()) ,)* ]) - ) - });*/ + )) + } else { + None + }; // Preserve the original type_generics before modification, // since alteration makes them invalid if self.generic_scalar @@ -1313,7 +1333,29 @@ impl GraphQLTypeDefiniton { }, ); + let marks = self.fields.iter().map(|field| { + let field_ty = &field._type; + + let field_marks = field.args.iter().map(|arg| { + let arg_ty = &arg._type; + quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + }); + + quote! { + #( #field_marks )* + <<#field_ty as ::juniper::futures::Stream>::Item as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); + let graphql_implementation = quote!( + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens + #where_clause + { + fn mark() { + #( #marks )* + } + } + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens #where_clause { @@ -1777,18 +1819,16 @@ impl GraphQLTypeDefiniton { {} ); - // FIXME: enable this if interfaces are supported - // let marks = self.fields.iter().map(|field| { - // let _ty = &field._type; - // quote!(<#_ty as ::juniper::marker::IsInputType<#scalar>>::mark();) - // }); + let marks = self.fields.iter().map(|field| { + let ty = &field._type; + quote! { <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + }); let mut body = quote!( impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty #type_generics_tokens #where_clause { fn mark() { - // FIXME: enable this if interfaces are supported - // #( #marks )* + #( #marks )* } } From 337693e708f5e1aecffabc08f5dbeaac104551d3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 1 Sep 2020 18:14:42 +0300 Subject: [PATCH 29/79] Re-enable markers and relax Sized requirement on IsInputType/IsOutputType marker traits --- .../derive_incompatible_object.stderr | 9 ++++ .../src/codegen/derive_input_object.rs | 6 ++- .../juniper_tests/src/codegen/impl_object.rs | 15 +++++- juniper/src/types/marker.rs | 25 ++++++---- juniper_actix/src/lib.rs | 24 +++++---- juniper_codegen/src/derive_scalar_value.rs | 3 ++ juniper_codegen/src/graphql_union/mod.rs | 2 +- juniper_codegen/src/util/mod.rs | 49 +++++++++++++------ 8 files changed, 95 insertions(+), 38 deletions(-) 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 69932a987..92eeb5189 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,3 +1,12 @@ +error[E0277]: the trait bound `ObjectA: juniper::marker::IsInputType<__S>` is not satisfied + --> $DIR/derive_incompatible_object.rs:6:10 + | +6 | #[derive(juniper::GraphQLInputObject)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::marker::IsInputType<__S>` is not implemented for `ObjectA` + | + = note: required by `juniper::marker::IsInputType::mark` + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `ObjectA: juniper::FromInputValue<__S>` is not satisfied --> $DIR/derive_incompatible_object.rs:6:10 | diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index 85604a847..d018ad8b8 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -1,8 +1,8 @@ use fnv::FnvHashMap; use juniper::{ - DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLValue, InputValue, - ToInputValue, + marker, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLValue, + InputValue, ToInputValue, }; #[derive(GraphQLInputObject, Debug, PartialEq)] @@ -50,6 +50,8 @@ struct OverrideDocComment { #[derive(Debug, PartialEq)] struct Fake; +impl<'a> marker::IsInputType for &'a Fake {} + impl<'a> FromInputValue for &'a Fake { fn from_input_value(_v: &InputValue) -> Option<&'a Fake> { None diff --git a/integration_tests/juniper_tests/src/codegen/impl_object.rs b/integration_tests/juniper_tests/src/codegen/impl_object.rs index 5f6760863..463be9b75 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_object.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_object.rs @@ -3,7 +3,7 @@ use juniper::DefaultScalarValue; use juniper::Object; #[cfg(test)] -use juniper::{self, execute, EmptyMutation, EmptySubscription, RootNode, Value, Variables}; +use juniper::{execute, EmptyMutation, EmptySubscription, FieldError, RootNode, Value, Variables}; pub struct MyObject; @@ -84,3 +84,16 @@ where f((type_info, fields)); } + +mod fallible { + use super::*; + + struct Obj; + + #[juniper::graphql_object] + impl Obj { + fn test(&self, arg: String) -> Result { + Ok(arg) + } + } +} diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 24d4b58f0..86824aba0 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -53,7 +53,7 @@ pub trait GraphQLUnion: GraphQLType { /// 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. -pub trait IsOutputType: GraphQLType { +pub trait IsOutputType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types @@ -89,6 +89,13 @@ where { } +impl IsOutputType for Result +where + T: IsOutputType, + S: ScalarValue, +{ +} + impl IsOutputType for Vec where T: IsOutputType, @@ -96,7 +103,7 @@ where { } -impl<'a, S, T> IsOutputType for &'a [T] +impl IsOutputType for [T] where T: IsOutputType, S: ScalarValue, @@ -110,7 +117,7 @@ where { } -impl<'a, S, T> IsInputType for &'a [T] +impl IsInputType for [T] where T: IsInputType, S: ScalarValue, @@ -119,29 +126,29 @@ where impl<'a, S, T> IsInputType for &T where - T: IsInputType, + T: IsInputType + ?Sized, S: ScalarValue, { } impl<'a, S, T> IsOutputType for &T where - T: IsOutputType, + T: IsOutputType + ?Sized, S: ScalarValue, { } impl IsInputType for Box where - T: IsInputType, + T: IsInputType + ?Sized, S: ScalarValue, { } impl IsOutputType for Box where - T: IsOutputType, + T: IsOutputType + ?Sized, S: ScalarValue, { } -impl<'a, S> IsInputType for &str where S: ScalarValue {} -impl<'a, S> IsOutputType for &str where S: ScalarValue {} +impl<'a, S> IsInputType for str where S: ScalarValue {} +impl<'a, S> IsOutputType for str where S: ScalarValue {} diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs index 4cda347c8..de1998922 100644 --- a/juniper_actix/src/lib.rs +++ b/juniper_actix/src/lib.rs @@ -227,19 +227,22 @@ pub async fn playground_handler( pub mod subscriptions { use std::{fmt, sync::Arc}; - use actix::prelude::*; - use actix::{Actor, StreamHandler}; - use actix_web::http::header::{HeaderName, HeaderValue}; - use actix_web::{web, HttpRequest, HttpResponse}; + use actix::{prelude::*, Actor, StreamHandler}; + use actix_web::{ + http::header::{HeaderName, HeaderValue}, + web, HttpRequest, HttpResponse, + }; use actix_web_actors::ws; use tokio::sync::Mutex; - use juniper::futures::{ - stream::{SplitSink, SplitStream, StreamExt}, - SinkExt, + use juniper::{ + futures::{ + stream::{SplitSink, SplitStream, StreamExt}, + SinkExt, + }, + GraphQLSubscriptionType, GraphQLTypeAsync, RootNode, ScalarValue, }; - use juniper::{GraphQLSubscriptionType, GraphQLTypeAsync, RootNode, ScalarValue}; use juniper_graphql_ws::{ArcSchema, ClientMessage, Connection, Init, ServerMessage}; /// Serves the graphql-ws protocol over a WebSocket connection. @@ -782,7 +785,10 @@ mod subscription_tests { use juniper::{ futures::{SinkExt, StreamExt}, http::tests::{run_ws_test_suite, WsIntegration, WsIntegrationMessage}, - tests::fixtures::starwars::{model::Database, schema::Query, schema::Subscription}, + tests::fixtures::starwars::{ + model::Database, + schema::{Query, Subscription}, + }, EmptyMutation, LocalBoxFuture, }; use juniper_graphql_ws::ConnectionConfig; diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 254fb93e9..98129a625 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -195,6 +195,9 @@ fn impl_scalar_struct( <#inner_ty as ::juniper::ParseScalarValue>::from_str(value) } } + + impl ::juniper::marker::IsOutputType for #ident { } + impl ::juniper::marker::IsInputType for #ident { } ); Ok(content) diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 129044526..6ed0a3b55 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -611,7 +611,7 @@ impl ToTokens for UnionDefinition { #where_clause { fn mark() { - #( <#var_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + #( <#var_types as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* } } }; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index f91696c91..394016ca6 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -831,12 +831,9 @@ impl GraphQLTypeDefiniton { if self.scalar.is_none() && self.generic_scalar { // No custom scalar specified, but always generic specified. // Therefore we inject the generic scalar. - generics.params.push(parse_quote!(__S)); - - let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); - // Insert ScalarValue constraint. - where_clause + generics + .make_where_clause() .predicates .push(parse_quote!(__S: ::juniper::ScalarValue)); } @@ -1001,10 +998,10 @@ impl GraphQLTypeDefiniton { ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar : 'r, { - let fields = vec![ + let fields = [ #( #field_definitions ),* ]; - let meta = registry.build_object_type::<#ty>( info, &fields ) + let meta = registry.build_object_type::<#ty>(info, &fields) #description #interfaces; meta.into_meta() @@ -1226,7 +1223,29 @@ impl GraphQLTypeDefiniton { }, ); + let marks = self.fields.iter().map(|field| { + let field_ty = &field._type; + + let field_marks = field.args.iter().map(|arg| { + let arg_ty = &arg._type; + quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + }); + + quote! { + #( #field_marks )* + <<#field_ty as ::juniper::futures::Stream>::Item as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); + let graphql_implementation = quote!( + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens + #where_clause + { + fn mark() { + #( #marks )* + } + } + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens #where_clause { @@ -1240,10 +1259,10 @@ impl GraphQLTypeDefiniton { ) -> ::juniper::meta::MetaType<'r, #scalar> where #scalar : 'r, { - let fields = vec![ + let fields = [ #( #field_definitions ),* ]; - let meta = registry.build_object_type::<#ty>( info, &fields ) + let meta = registry.build_object_type::<#ty>(info, &fields) #description #interfaces; meta.into_meta() @@ -1690,18 +1709,16 @@ impl GraphQLTypeDefiniton { {} ); - // FIXME: enable this if interfaces are supported - // let marks = self.fields.iter().map(|field| { - // let _ty = &field._type; - // quote!(<#_ty as ::juniper::marker::IsInputType<#scalar>>::mark();) - // }); + let marks = self.fields.iter().map(|field| { + let field_ty = &field._type; + quote! { <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + }); let mut body = quote!( impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty #type_generics_tokens #where_clause { fn mark() { - // FIXME: enable this if interfaces are supported - // #( #marks )* + #( #marks )* } } From 190618fa32561d472fc000687c1a82106f950752 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 1 Sep 2020 18:32:52 +0300 Subject: [PATCH 30/79] Revert 'juniper_actix' fmt --- juniper_actix/src/lib.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs index de1998922..4cda347c8 100644 --- a/juniper_actix/src/lib.rs +++ b/juniper_actix/src/lib.rs @@ -227,22 +227,19 @@ pub async fn playground_handler( pub mod subscriptions { use std::{fmt, sync::Arc}; - use actix::{prelude::*, Actor, StreamHandler}; - use actix_web::{ - http::header::{HeaderName, HeaderValue}, - web, HttpRequest, HttpResponse, - }; + use actix::prelude::*; + use actix::{Actor, StreamHandler}; + use actix_web::http::header::{HeaderName, HeaderValue}; + use actix_web::{web, HttpRequest, HttpResponse}; use actix_web_actors::ws; use tokio::sync::Mutex; - use juniper::{ - futures::{ - stream::{SplitSink, SplitStream, StreamExt}, - SinkExt, - }, - GraphQLSubscriptionType, GraphQLTypeAsync, RootNode, ScalarValue, + use juniper::futures::{ + stream::{SplitSink, SplitStream, StreamExt}, + SinkExt, }; + use juniper::{GraphQLSubscriptionType, GraphQLTypeAsync, RootNode, ScalarValue}; use juniper_graphql_ws::{ArcSchema, ClientMessage, Connection, Init, ServerMessage}; /// Serves the graphql-ws protocol over a WebSocket connection. @@ -785,10 +782,7 @@ mod subscription_tests { use juniper::{ futures::{SinkExt, StreamExt}, http::tests::{run_ws_test_suite, WsIntegration, WsIntegrationMessage}, - tests::fixtures::starwars::{ - model::Database, - schema::{Query, Subscription}, - }, + tests::fixtures::starwars::{model::Database, schema::Query, schema::Subscription}, EmptyMutation, LocalBoxFuture, }; use juniper_graphql_ws::ConnectionConfig; From 63faed579ecdb7bad9253c4eeb19e832ea8449ed Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 1 Sep 2020 18:54:07 +0300 Subject: [PATCH 31/79] Fix missing marks for object --- .../object/impl_argument_no_object.stderr | 9 +++++++ juniper_codegen/src/util/mod.rs | 26 +++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr index 19ead7c46..946ea4fde 100644 --- a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr +++ b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr @@ -1,3 +1,12 @@ +error[E0277]: the trait bound `Obj: juniper::marker::IsInputType` is not satisfied + --> $DIR/impl_argument_no_object.rs:8:1 + | +8 | #[juniper::graphql_object] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::marker::IsInputType` is not implemented for `Obj` + | + = note: required by `juniper::marker::IsInputType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Obj: juniper::FromInputValue` is not satisfied --> $DIR/impl_argument_no_object.rs:8:1 | diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 394016ca6..6095a70dc 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -959,26 +959,24 @@ impl GraphQLTypeDefiniton { ) }; - // FIXME: enable this if interfaces are supported - // let marks = self.fields.iter().map(|field| { - // let field_ty = &field._type; + let marks = self.fields.iter().map(|field| { + let field_ty = &field._type; - // let field_marks = field.args.iter().map(|arg| { - // let arg_ty = &arg._type; - // quote!(<#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark();) - // }); + let field_marks = field.args.iter().map(|arg| { + let arg_ty = &arg._type; + quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + }); - // quote!( - // #( #field_marks)* - // <#field_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - // ) - // }); + quote! { + #( #field_marks )* + <#field_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); let output = quote!( impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause { fn mark() { - // FIXME: enable this if interfaces are supported - // #( #marks )* + #( #marks )* } } From fe5a2786c540844a5906910e496c13ce59e8fec5 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 14:07:15 +0300 Subject: [PATCH 32/79] Fix subscriptions marks --- juniper/src/lib.rs | 2 +- juniper/src/macros/interface.rs | 4 ++++ juniper/src/macros/subscription_helpers.rs | 21 +++++++++++++------ juniper/src/tests/fixtures/starwars/schema.rs | 15 ++++++++----- juniper_codegen/src/util/mod.rs | 3 ++- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index cf8bac69e..154ab67c7 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -183,7 +183,7 @@ pub use crate::{ types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, - marker::{self, GraphQLUnion}, + marker::{self, GraphQLUnion, IsOutputType}, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue, diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs index d23c63cd2..bca44882b 100644 --- a/juniper/src/macros/interface.rs +++ b/juniper/src/macros/interface.rs @@ -178,6 +178,10 @@ macro_rules! graphql_interface { } ); + $crate::__juniper_impl_trait!( + impl<$($scalar)* $(, $lifetimes)* > IsOutputType for $name { } + ); + $crate::__juniper_impl_trait!( impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name { type Context = $ctx; diff --git a/juniper/src/macros/subscription_helpers.rs b/juniper/src/macros/subscription_helpers.rs index 406cc3ece..9d533185a 100644 --- a/juniper/src/macros/subscription_helpers.rs +++ b/juniper/src/macros/subscription_helpers.rs @@ -7,27 +7,36 @@ use futures::Stream; use crate::{FieldError, GraphQLValue, ScalarValue}; -/// Trait for converting `T` to `Ok(T)` if T is not Result. -/// This is useful in subscription macros when user can provide type alias for -/// Stream or Result and then a function on Stream should be called. +/// Trait for wrapping [`Stream`] into [`Ok`] if it's not [`Result`]. +/// +/// Used in subscription macros when user can provide type alias for [`Stream`] or +/// `Result` and then a function on [`Stream`] should be called. pub trait IntoFieldResult { - /// Turn current type into a generic result + /// Type of items yielded by this [`Stream`]. + type Item; + + /// Turns current [`Stream`] type into a generic [`Result`]. fn into_result(self) -> Result>; } impl IntoFieldResult for Result where + T: IntoFieldResult, E: Into>, { + type Item = T::Item; + fn into_result(self) -> Result> { self.map_err(|e| e.into()) } } -impl IntoFieldResult for T +impl IntoFieldResult for T where - T: Stream, + T: Stream, { + type Item = T::Item; + fn into_result(self) -> Result> { Ok(self) } diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index e0818e57b..9692da550 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -10,6 +10,7 @@ use std::pin::Pin; impl Context for Database {} +/* graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| { description: "A character in the Star Wars Trilogy" @@ -35,11 +36,12 @@ graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| { &dyn Droid => context.get_droid(&self.id()), } }); + */ #[crate::graphql_object( Context = Database, Scalar = crate::DefaultScalarValue, - interfaces = [&dyn Character], + // interfaces = [&dyn Character], // FIXME: make async work noasync )] @@ -55,10 +57,11 @@ impl<'a> &'a dyn Human { Some(self.name()) } + /* /// The friends of the human fn friends(&self, ctx: &Database) -> Vec<&dyn Character> { ctx.get_friends(self.as_character()) - } + }*/ /// Which movies they appear in fn appears_in(&self) -> &[Episode] { @@ -74,7 +77,7 @@ impl<'a> &'a dyn Human { #[crate::graphql_object( Context = Database, Scalar = crate::DefaultScalarValue, - interfaces = [&dyn Character], + // interfaces = [&dyn Character], // FIXME: make async work noasync )] @@ -90,10 +93,11 @@ impl<'a> &'a dyn Droid { Some(self.name()) } + /* /// The friends of the droid fn friends(&self, ctx: &Database) -> Vec<&dyn Character> { ctx.get_friends(self.as_character()) - } + }*/ /// Which movies they appear in fn appears_in(&self) -> &[Episode] { @@ -126,12 +130,13 @@ impl Query { database.get_droid(&id) } + /* #[graphql(arguments(episode( description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" )))] fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { Some(database.get_hero(episode).as_character()) - } + }*/ } #[derive(GraphQLObject)] diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 6095a70dc..ef74df016 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1231,7 +1231,8 @@ impl GraphQLTypeDefiniton { quote! { #( #field_marks )* - <<#field_ty as ::juniper::futures::Stream>::Item as ::juniper::marker::IsOutputType<#scalar>>::mark(); + <<#field_ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item + as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); From 0ac2701c0fac48eb0909b02c693f59263f57bfbe Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 14:40:39 +0300 Subject: [PATCH 33/79] Deduce result type correctly via traits --- juniper/src/executor/mod.rs | 14 ++++++++++ juniper/src/tests/fixtures/starwars/schema.rs | 2 +- juniper_codegen/src/util/mod.rs | 26 ++++++++++++++----- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index f58aef1c8..07be7ac9e 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -253,6 +253,8 @@ where T: GraphQLValue, S: ScalarValue, { + type Type; + #[doc(hidden)] fn into(self, ctx: &'a C) -> FieldResult, S>; } @@ -263,6 +265,8 @@ where S: ScalarValue, T::Context: FromContext, { + type Type = T; + fn into(self, ctx: &'a C) -> FieldResult, S> { Ok(Some((FromContext::from(ctx), self))) } @@ -274,6 +278,8 @@ where T: GraphQLValue, T::Context: FromContext, { + type Type = T; + fn into(self, ctx: &'a C) -> FieldResult, S> { self.map(|v: T| Some((>::from(ctx), v))) .map_err(IntoFieldError::into_field_error) @@ -285,6 +291,8 @@ where S: ScalarValue, T: GraphQLValue, { + type Type = T; + fn into(self, _: &'a C) -> FieldResult, S> { Ok(Some(self)) } @@ -295,6 +303,8 @@ where S: ScalarValue, T: GraphQLValue, { + type Type = T; + fn into(self, _: &'a C) -> FieldResult)>, S> { Ok(self.map(|(ctx, v)| (ctx, Some(v)))) } @@ -305,6 +315,8 @@ where S: ScalarValue, T: GraphQLValue, { + type Type = T; + fn into(self, _: &'a C) -> FieldResult, S> { self.map(Some) } @@ -316,6 +328,8 @@ where S: ScalarValue, T: GraphQLValue, { + type Type = T; + fn into(self, _: &'a C) -> FieldResult)>, S> { self.map(|o| o.map(|(ctx, v)| (ctx, Some(v)))) } diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 9692da550..dd8f44bd9 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -3,7 +3,7 @@ use crate::{ executor::Context, graphql_subscription, - tests::fixtures::starwars::model::{Character, Database, Droid, Episode, Human}, + tests::fixtures::starwars::model::{/*Character, */Database, Droid, Episode, Human}, GraphQLObject, }; use std::pin::Pin; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index ef74df016..ac6e5a5ef 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -960,16 +960,21 @@ impl GraphQLTypeDefiniton { }; let marks = self.fields.iter().map(|field| { - let field_ty = &field._type; - let field_marks = field.args.iter().map(|arg| { let arg_ty = &arg._type; quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } }); + let field_ty = &field._type; + let resolved_ty = quote! { + <#field_ty as ::juniper::IntoResolvable< + '_, #scalar, _, >::Context, + >>::Type + }; + quote! { #( #field_marks )* - <#field_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); @@ -1222,17 +1227,24 @@ impl GraphQLTypeDefiniton { ); let marks = self.fields.iter().map(|field| { - let field_ty = &field._type; - let field_marks = field.args.iter().map(|arg| { let arg_ty = &arg._type; quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } }); + let field_ty = &field._type; + let stream_item_ty = quote! { + <#field_ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item + }; + let resolved_ty = quote! { + <#stream_item_ty as ::juniper::IntoResolvable< + '_, #scalar, _, >::Context, + >>::Type + }; + quote! { #( #field_marks )* - <<#field_ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item - as ::juniper::marker::IsOutputType<#scalar>>::mark(); + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); From 8f10cce806818a70d7f6203dfd8bad1731720d31 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 15:05:23 +0300 Subject: [PATCH 34/79] Final fixes --- juniper/src/tests/fixtures/starwars/schema.rs | 17 +++++--------- juniper/src/types/marker.rs | 22 +++++++------------ 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index dd8f44bd9..e0818e57b 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -3,14 +3,13 @@ use crate::{ executor::Context, graphql_subscription, - tests::fixtures::starwars::model::{/*Character, */Database, Droid, Episode, Human}, + tests::fixtures::starwars::model::{Character, Database, Droid, Episode, Human}, GraphQLObject, }; use std::pin::Pin; impl Context for Database {} -/* graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| { description: "A character in the Star Wars Trilogy" @@ -36,12 +35,11 @@ graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| { &dyn Droid => context.get_droid(&self.id()), } }); - */ #[crate::graphql_object( Context = Database, Scalar = crate::DefaultScalarValue, - // interfaces = [&dyn Character], + interfaces = [&dyn Character], // FIXME: make async work noasync )] @@ -57,11 +55,10 @@ impl<'a> &'a dyn Human { Some(self.name()) } - /* /// The friends of the human fn friends(&self, ctx: &Database) -> Vec<&dyn Character> { ctx.get_friends(self.as_character()) - }*/ + } /// Which movies they appear in fn appears_in(&self) -> &[Episode] { @@ -77,7 +74,7 @@ impl<'a> &'a dyn Human { #[crate::graphql_object( Context = Database, Scalar = crate::DefaultScalarValue, - // interfaces = [&dyn Character], + interfaces = [&dyn Character], // FIXME: make async work noasync )] @@ -93,11 +90,10 @@ impl<'a> &'a dyn Droid { Some(self.name()) } - /* /// The friends of the droid fn friends(&self, ctx: &Database) -> Vec<&dyn Character> { ctx.get_friends(self.as_character()) - }*/ + } /// Which movies they appear in fn appears_in(&self) -> &[Episode] { @@ -130,13 +126,12 @@ impl Query { database.get_droid(&id) } - /* #[graphql(arguments(episode( description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" )))] fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { Some(database.get_hero(episode).as_character()) - }*/ + } } #[derive(GraphQLObject)] diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 86824aba0..930bd811d 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -53,7 +53,8 @@ pub trait GraphQLUnion: GraphQLType { /// 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. -pub trait IsOutputType { +// TODO: Re-enable GraphQLType requirement in #682 +pub trait IsOutputType/*: GraphQLType*/ { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types @@ -89,13 +90,6 @@ where { } -impl IsOutputType for Result -where - T: IsOutputType, - S: ScalarValue, -{ -} - impl IsOutputType for Vec where T: IsOutputType, @@ -103,7 +97,7 @@ where { } -impl IsOutputType for [T] +impl<'a, S, T> IsOutputType for &'a [T] where T: IsOutputType, S: ScalarValue, @@ -117,7 +111,7 @@ where { } -impl IsInputType for [T] +impl<'a, S, T> IsInputType for &'a [T] where T: IsInputType, S: ScalarValue, @@ -126,13 +120,13 @@ where impl<'a, S, T> IsInputType for &T where - T: IsInputType + ?Sized, + T: IsInputType, S: ScalarValue, { } impl<'a, S, T> IsOutputType for &T where - T: IsOutputType + ?Sized, + T: IsOutputType, S: ScalarValue, { } @@ -150,5 +144,5 @@ where { } -impl<'a, S> IsInputType for str where S: ScalarValue {} -impl<'a, S> IsOutputType for str where S: ScalarValue {} +impl<'a, S> IsInputType for &str where S: ScalarValue {} +impl<'a, S> IsOutputType for &str where S: ScalarValue {} From 6a0240af4612ca2f0bad959b94657ad9950530d3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 15:06:29 +0300 Subject: [PATCH 35/79] Fmt --- juniper/src/types/marker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 930bd811d..792377417 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -54,7 +54,7 @@ pub trait GraphQLUnion: GraphQLType { /// 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 From 871f91ec4600d4910f5d5a74133e8b36a95a8584 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 16:07:32 +0300 Subject: [PATCH 36/79] Restore marks checking --- juniper_codegen/src/graphql_interface/mod.rs | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index ad2494eda..6e66e97bb 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -663,6 +663,25 @@ impl ToTokens for InterfaceDefinition { } }); + let fields_marks = self.fields.iter().map(|field| { + let arguments_marks = field.arguments.iter().filter_map(|arg| { + let arg_ty = &arg.as_regular()?.ty; + Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) + }); + + let field_ty = &field.ty; + let resolved_ty = quote! { + <#field_ty as ::juniper::IntoResolvable< + '_, #scalar, _, >::Context, + >>::Type + }; + + quote! { + #( #arguments_marks )* + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); + let custom_downcast_checks = self.implementers.iter().filter_map(|impler| { let impler_check = impler.downcast_check.as_ref()?; let impler_ty = &impler.ty; @@ -1059,7 +1078,8 @@ impl ToTokens for InterfaceDefinition { #where_clause { fn mark() { - #( <#impler_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + #( #fields_marks )* + #( <#impler_types as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* } } }; From 4f884109edb7cbdc817171c82cfc16149f852419 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 16:47:23 +0300 Subject: [PATCH 37/79] Support custom ScalarValue --- .../src/codegen/interface_attr.rs | 254 ++++++++++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 17 +- juniper_codegen/src/graphql_interface/mod.rs | 10 + 3 files changed, 277 insertions(+), 4 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8cd3b13b8..6b02dd3de 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1785,6 +1785,260 @@ mod explicit_name_and_description { } } +mod explicit_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(scalar = DefaultScalarValue)] + trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, scalar = DefaultScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface(scalar = DefaultScalarValue)] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, scalar = DefaultScalarValue)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface(scalar = DefaultScalarValue)] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema::<_, DefaultScalarValue, _>(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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::<_, DefaultScalarValue, _>(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(scalar = MyScalarValue)] + trait Character { + async fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, scalar = MyScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface(scalar = MyScalarValue)] + impl Character for Human { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, scalar = MyScalarValue)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface(scalar = MyScalarValue)] + impl Character for Droid { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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::<_, MyScalarValue, _>(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index e3bcc48b5..c977e2693 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -179,19 +179,28 @@ pub fn expand_on_impl( }) .is_some(); - ast.generics.params.push(parse_quote! { - GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync - }); + let is_generic_scalar = meta.scalar.is_none(); + ast.attrs .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); + if is_generic_scalar { + ast.generics.params.push(parse_quote! { + GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync + }); + } + let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; if let syn::PathArguments::None = trait_params { *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); } if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { GraphQLScalarValue }); + a.args.push(if is_generic_scalar { + parse_quote! { GraphQLScalarValue } + } else { + syn::GenericArgument::Type(meta.scalar.clone().unwrap().into_inner()) + }); } if is_async_trait { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 6e66e97bb..798523397 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -215,6 +215,7 @@ impl InterfaceMeta { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] struct ImplementerMeta { + pub scalar: Option>, pub asyncness: Option>, } @@ -225,6 +226,14 @@ impl Parse for ImplementerMeta { while !input.is_empty() { let ident = input.parse_any_ident()?; match ident.to_string().as_str() { + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + output + .scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } "async" => { let span = ident.span(); output @@ -248,6 +257,7 @@ impl ImplementerMeta { /// any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { + scalar: try_merge_opt!(scalar: self, another), asyncness: try_merge_opt!(asyncness: self, another), }) } From f2110be2e515c9c99108f4fd4203c79b1d2be085 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 2 Sep 2020 19:12:59 +0300 Subject: [PATCH 38/79] Cover deprecations with tests --- .../src/codegen/interface_attr.rs | 319 +++++++++++++++++- .../juniper_tests/src/codegen/union_attr.rs | 2 +- 2 files changed, 312 insertions(+), 9 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 6b02dd3de..62c992676 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1608,7 +1608,7 @@ mod default_argument { } } -mod description_from_doc_comments { +mod description_from_doc_comment { use super::*; /// Rust docs. @@ -1669,18 +1669,163 @@ mod description_from_doc_comments { } } -mod explicit_name_and_description { +mod deprecation_from_attr { + #![allow(deprecated)] + + use super::*; + + #[graphql_interface(for = Human, dyn = DynCharacter)] + trait Character { + fn id(&self) -> &str; + + #[deprecated] + fn a(&self) -> &str { + "a" + } + + #[deprecated(note = "Use `id`.")] + fn b(&self) -> &str { + "b" + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": None}, + {"name": "a", "deprecationReason": None}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + #![allow(deprecated)] + use super::*; /// Rust docs. #[graphql_interface(name = "MyChar", desc = "My character.", for = Human, dyn = DynCharacter)] trait Character { /// Rust `id` docs. - #[graphql_interface(name = "myId", desc = "My character ID.")] + #[graphql_interface(name = "myId", desc = "My character ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] fn id( &self, #[graphql_interface(name = "myName", desc = "My argument.", default)] n: Option, ) -> &str; + + #[graphql_interface(deprecated)] + #[deprecated(note = "Should be omitted.")] + fn a(&self) -> &str { + "a" + } + + fn b(&self) -> &str { + "b" + } } #[derive(GraphQLObject)] @@ -1710,10 +1855,12 @@ mod explicit_name_and_description { } #[tokio::test] - async fn resolves_myid_field() { + async fn resolves_fields() { const DOC: &str = r#"{ character { myId + a + b } }"#; @@ -1721,7 +1868,10 @@ mod explicit_name_and_description { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"myId": "human-32"}}), vec![])), + Ok(( + graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + vec![], + )), ); } @@ -1730,7 +1880,7 @@ mod explicit_name_and_description { const DOC: &str = r#"{ __type(name: "MyChar") { name - fields { + fields(includeDeprecated: true) { name args { name @@ -1746,7 +1896,11 @@ mod explicit_name_and_description { Ok(( graphql_value!({"__type": { "name": "MyChar", - "fields": [{"name": "myId", "args": [{"name": "myName"}]}], + "fields": [ + {"name": "myId", "args": [{"name": "myName"}]}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], }}), vec![], )), @@ -1758,7 +1912,8 @@ mod explicit_name_and_description { const DOC: &str = r#"{ __type(name: "MyChar") { description - fields { + fields(includeDeprecated: true) { + name description args { description @@ -1775,8 +1930,54 @@ mod explicit_name_and_description { graphql_value!({"__type": { "description": "My character.", "fields": [{ + "name": "myId", "description": "My character ID.", "args": [{"description": "My argument."}], + }, { + "name": "a", + "description": None, + "args": [], + }, { + "name": "b", + "description": None, + "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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": None, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": None, }], }}), vec![], @@ -2039,6 +2240,108 @@ mod custom_scalar { } } +mod ignored_methods { + use super::*; + + #[graphql_interface(for = Human, dyn = DynCharacter)] + trait Character { + fn id(&self) -> &str; + + #[graphql_interface(ignore)] + fn ignored(&self) -> Option<&Human> { + None + } + + #[graphql_interface(skip)] + fn skipped(&self) {} + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }) + } + } + + #[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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn ignored_methods_are_not_fields() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index 2581512cd..98549f23c 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -305,7 +305,7 @@ mod generic { } } -mod description_from_doc_comments { +mod description_from_doc_comment { use super::*; /// Rust docs. From b2041a11d1c07a708c03035f1b8255641910ab58 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 4 Sep 2020 17:16:29 +0300 Subject: [PATCH 39/79] Impl dowcasting via methods --- .../src/codegen/interface_attr.rs | 160 ++++++- .../juniper_tests/src/codegen/union_attr.rs | 2 +- juniper_codegen/src/common/mod.rs | 10 + juniper_codegen/src/common/parse/attr.rs | 64 +++ .../src/common/parse/downcaster.rs | 88 ++++ .../parse/mod.rs} | 3 + juniper_codegen/src/derive_scalar_value.rs | 3 +- juniper_codegen/src/graphql_interface/attr.rs | 417 ++++++++++-------- juniper_codegen/src/graphql_interface/mod.rs | 175 ++++++-- juniper_codegen/src/graphql_union/attr.rs | 95 +--- juniper_codegen/src/graphql_union/derive.rs | 3 +- juniper_codegen/src/graphql_union/mod.rs | 9 +- juniper_codegen/src/lib.rs | 7 +- juniper_codegen/src/util/err.rs | 37 -- juniper_codegen/src/util/mod.rs | 14 +- juniper_codegen/src/util/option_ext.rs | 24 - 16 files changed, 728 insertions(+), 383 deletions(-) create mode 100644 juniper_codegen/src/common/mod.rs create mode 100644 juniper_codegen/src/common/parse/attr.rs create mode 100644 juniper_codegen/src/common/parse/downcaster.rs rename juniper_codegen/src/{util/parse_buffer_ext.rs => common/parse/mod.rs} (98%) delete mode 100644 juniper_codegen/src/util/err.rs delete mode 100644 juniper_codegen/src/util/option_ext.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 62c992676..a58834608 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2240,7 +2240,7 @@ mod custom_scalar { } } -mod ignored_methods { +mod ignored_method { use super::*; #[graphql_interface(for = Human, dyn = DynCharacter)] @@ -2321,7 +2321,7 @@ mod ignored_methods { } #[tokio::test] - async fn ignored_methods_are_not_fields() { + async fn is_not_field() { const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -2342,6 +2342,162 @@ mod ignored_methods { } } +mod downcast_method { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + fn id(&self) -> &str; + + #[graphql_interface(downcast)] + fn as_droid(&self) -> Option<&Droid> { + None + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + + fn as_droid(&self) -> Option<&Droid> { + Some(self) + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index 98549f23c..6ca69a8b6 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -821,7 +821,7 @@ mod explicit_custom_context { } } -mod ignored_methods { +mod ignored_method { use super::*; #[graphql_union] diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs new file mode 100644 index 000000000..3af3a42e9 --- /dev/null +++ b/juniper_codegen/src/common/mod.rs @@ -0,0 +1,10 @@ +pub(crate) mod parse; + +/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested +/// [`syn::TypeParen`]s asap). +pub(crate) fn unparenthesize(ty: &syn::Type) -> &syn::Type { + match ty { + syn::Type::Paren(ty) => unparenthesize(&*ty.elem), + _ => ty, + } +} \ No newline at end of file diff --git a/juniper_codegen/src/common/parse/attr.rs b/juniper_codegen/src/common/parse/attr.rs new file mode 100644 index 000000000..8d39a885f --- /dev/null +++ b/juniper_codegen/src/common/parse/attr.rs @@ -0,0 +1,64 @@ +pub(crate) mod err { + use proc_macro2::Span; + use syn::spanned::Spanned; + + /// Creates "duplicated argument" [`syn::Error`] for the given `name` pointing to the given + /// `span`. + #[must_use] + pub(crate) fn dup_arg(span: S) -> syn::Error { + syn::Error::new(span.as_span(), "duplicated attribute argument found") + } + + /// Creates "unknown argument" [`syn::Error`] for the given `name` pointing to the given `span`. + #[must_use] + pub(crate) fn unknown_arg(span: S, name: &str) -> syn::Error { + syn::Error::new( + span.as_span(), + format!("unknown `{}` attribute argument", name), + ) + } + + pub(crate) trait AsSpan { + #[must_use] + fn as_span(&self) -> Span; + } + + impl AsSpan for Span { + #[inline] + fn as_span(&self) -> Self { + *self + } + } + + impl AsSpan for &T { + #[inline] + fn as_span(&self) -> Span { + self.span() + } + } +} + +/// Handy extension of [`Option`] methods used in this crate. +pub(crate) trait OptionExt { + type Inner; + + /// Transforms the `Option` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)` + /// to `Err(err(v))`. + fn none_or_else(self, err: F) -> Result<(), E> + where + F: FnOnce(Self::Inner) -> E; +} + +impl OptionExt for Option { + type Inner = T; + + fn none_or_else(self, err: F) -> Result<(), E> + where + F: FnOnce(T) -> E, + { + match self { + Some(v) => Err(err(v)), + None => Ok(()), + } + } +} diff --git a/juniper_codegen/src/common/parse/downcaster.rs b/juniper_codegen/src/common/parse/downcaster.rs new file mode 100644 index 000000000..5953e3dd1 --- /dev/null +++ b/juniper_codegen/src/common/parse/downcaster.rs @@ -0,0 +1,88 @@ +use proc_macro2::Span; +use syn::{ext::IdentExt as _, spanned::Spanned as _}; + +use crate::common::unparenthesize; + +/// Parses downcasting output type from the downcaster method return type. +/// +/// # Errors +/// +/// If return type is invalid (not `Option<&OutputType>`), then returns the [`Span`] to display the +/// corresponding error at. +pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result { + let ret_ty = match &ret_ty { + syn::ReturnType::Type(_, ty) => &*ty, + _ => return Err(ret_ty.span()), + }; + + let path = match unparenthesize(ret_ty) { + syn::Type::Path(syn::TypePath { qself: None, path }) => path, + _ => return Err(ret_ty.span()), + }; + + let (ident, args) = match path.segments.last() { + Some(syn::PathSegment { + ident, + arguments: syn::PathArguments::AngleBracketed(generic), + }) => (ident, &generic.args), + _ => return Err(ret_ty.span()), + }; + + if ident.unraw() != "Option" { + return Err(ret_ty.span()); + } + if args.len() != 1 { + return Err(ret_ty.span()); + } + + let out_ty = match args.first() { + Some(syn::GenericArgument::Type(inner_ty)) => match unparenthesize(inner_ty) { + syn::Type::Reference(inner_ty) => { + if inner_ty.mutability.is_some() { + return Err(inner_ty.span()); + } + unparenthesize(&*inner_ty.elem).clone() + } + _ => return Err(ret_ty.span()), + }, + _ => return Err(ret_ty.span()), + }; + Ok(out_ty) +} + +/// Parses context type used for downcasting from the downcaster method signature. +/// +/// Returns [`None`] if downcaster method doesn't accept context. +/// +/// # Errors +/// +/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at. +pub(crate) fn context_ty(sig: &syn::Signature) -> Result, Span> { + match sig.receiver() { + Some(syn::FnArg::Receiver(rcv)) => { + if rcv.reference.is_none() || rcv.mutability.is_some() { + return Err(rcv.span()); + } + } + _ => return Err(sig.span()), + } + + if sig.inputs.len() > 2 { + return Err(sig.inputs.span()); + } + + let second_arg_ty = match sig.inputs.iter().nth(1) { + Some(syn::FnArg::Typed(arg)) => &*arg.ty, + None => return Ok(None), + _ => return Err(sig.inputs.span()), + }; + match unparenthesize(second_arg_ty) { + syn::Type::Reference(ref_ty) => { + if ref_ty.mutability.is_some() { + return Err(ref_ty.span()); + } + Ok(Some(unparenthesize(&*ref_ty.elem).clone())) + } + ty => Err(ty.span()), + } +} \ No newline at end of file diff --git a/juniper_codegen/src/util/parse_buffer_ext.rs b/juniper_codegen/src/common/parse/mod.rs similarity index 98% rename from juniper_codegen/src/util/parse_buffer_ext.rs rename to juniper_codegen/src/common/parse/mod.rs index 9cceab3c8..c6c0d11d0 100644 --- a/juniper_codegen/src/util/parse_buffer_ext.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -1,3 +1,6 @@ +pub(crate) mod attr; +pub(crate) mod downcaster; + use std::{ any::TypeId, iter::{self, FromIterator as _}, diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 15c40fd53..1c4f2f326 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -1,6 +1,7 @@ use crate::{ result::GraphQLScope, - util::{self, span_container::SpanContainer, ParseBufferExt as _}, + common::parse::ParseBufferExt as _, + util::{self, span_container::SpanContainer}, }; use proc_macro2::TokenStream; use quote::quote; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c977e2693..c4d4c5f69 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -7,17 +7,17 @@ use quote::quote; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ + common::{parse, unparenthesize}, result::GraphQLScope, util::{ path_eq_single, span_container::SpanContainer, strip_attrs, to_camel_case, unite_attrs, - unparenthesize, }, }; use super::{ - ArgumentMeta, FieldMeta, ImplementerMeta, InterfaceDefinition, InterfaceFieldArgument, - InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceImplementerDefinition, - InterfaceMeta, + ArgumentMeta, ImplementerDefinition, ImplementerDowncastDefinition, ImplementerMeta, + InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldArgumentDefinition, + InterfaceFieldDefinition, InterfaceMeta, TraitMethodMeta, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -70,15 +70,14 @@ pub fn expand_on_trait( let context = meta.context.map(SpanContainer::into_inner); //.or_else(|| variants.iter().find_map(|v| v.context_ty.as_ref()).cloned()); - let implementers = meta + let mut implementers: Vec<_> = meta .implementers .iter() .map(|ty| { let span = ty.span_ident(); - InterfaceImplementerDefinition { + ImplementerDefinition { ty: ty.as_ref().clone(), - downcast_code: None, - downcast_check: None, + downcast: None, context_ty: None, span, } @@ -87,14 +86,27 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); - let fields = ast - .items - .iter_mut() - .filter_map(|item| match item { - syn::TraitItem::Method(m) => parse_field_from_trait_method(m), - _ => None, - }) - .collect(); + let mut fields = vec![]; + for item in &mut ast.items { + if let syn::TraitItem::Method(m) = item { + match TraitMethod::parse(m) { + Some(TraitMethod::Field(f)) => fields.push(f), + Some(TraitMethod::Downcast(d)) => { + match implementers.iter_mut().find(|i| i.ty == d.ty) { + Some(impler) => { + impler.downcast = d.downcast; + impler.context_ty = d.context_ty; + } + None => ERR.emit_custom( + d.span, + "downcasting is possible only to interface implementers", + ), + } + } + _ => {} + } + } + } proc_macro_error::abort_if_dirty(); @@ -248,219 +260,278 @@ where } } -fn parse_field_from_trait_method( - method: &mut syn::TraitItemMethod, -) -> Option { - let method_attrs = method.attrs.clone(); +enum TraitMethod { + Field(InterfaceFieldDefinition), + Downcast(ImplementerDefinition), +} - // Remove repeated attributes from the method, to omit incorrect expansion. - method.attrs = mem::take(&mut method.attrs) - .into_iter() - .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) - .collect(); +impl TraitMethod { + fn parse(method: &mut syn::TraitItemMethod) -> Option { + let method_attrs = method.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + method.attrs = mem::take(&mut method.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) + .collect(); - let meta = FieldMeta::from_attrs("graphql_interface", &method_attrs) - .map_err(|e| proc_macro_error::emit_error!(e)) - .ok()?; + let meta = TraitMethodMeta::from_attrs("graphql_interface", &method_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; - if meta.ignore.is_some() { - return None; + if meta.ignore.is_some() { + return None; + } + + if meta.downcast.is_some() { + return Some(Self::Downcast(Self::parse_downcast(method)?)); + } + + Some(Self::Field(Self::parse_field(method, meta)?)) } - let method_ident = &method.sig.ident; + fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { + let method_span = method.sig.span(); + let method_ident = &method.sig.ident; - let name = meta - .name - .as_ref() - .map(|m| m.as_ref().value()) - .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string())); - if name.starts_with("__") { - ERR.no_double_underscore( - meta.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| method_ident.span()), - ); - return None; + let ty = parse::downcaster::output_type(&method.sig.output) + .map_err(|span| { + ERR.emit_custom( + span, + "expects trait method return type to be `Option<&ImplementerType>` only", + ) + }) + .ok()?; + let context_ty = parse::downcaster::context_ty(&method.sig) + .map_err(|span| { + ERR.emit_custom( + span, + "expects trait method to accept `&self` only and, optionally, `&Context`", + ) + }) + .ok()?; + if let Some(is_async) = &method.sig.asyncness { + ERR.emit_custom( + is_async.span(), + "async downcast to interface implementer is not supported", + ); + return None; + } + + /* + let downcast = { + /* TODO + if let Some(other) = trait_meta.external_resolvers.get(&ty) { + ERR.custom( + method_span, + format!( + "trait method `{}` conflicts with the external resolver function `{}` declared \ + on the trait to resolve the variant type `{}`", + method_ident, + other.to_token_stream(), + ty.to_token_stream(), + + ), + ) + .note(String::from( + "use `#[graphql_union(ignore)]` attribute to ignore this trait method for union \ + variants resolution", + )) + .emit(); + }*/ + + };*/ + + let downcast = ImplementerDowncastDefinition::Method { + name: method_ident.clone(), + with_context: context_ty.is_some(), + }; + + Some(ImplementerDefinition { + ty, + downcast: Some(downcast), + context_ty, + span: method_span, + }) } - let arguments = { - if method.sig.inputs.is_empty() { - return err_no_method_receiver(&method.sig.inputs); + fn parse_field( + method: &mut syn::TraitItemMethod, + meta: TraitMethodMeta, + ) -> Option { + let method_ident = &method.sig.ident; + + let name = meta + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + meta.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| method_ident.span()), + ); + return None; } - let mut args_iter = method.sig.inputs.iter_mut(); - match args_iter.next().unwrap() { - syn::FnArg::Receiver(rcv) => { - if !rcv.reference.is_some() || rcv.mutability.is_some() { - return err_invalid_method_receiver(rcv); - } + + let arguments = { + if method.sig.inputs.is_empty() { + return err_no_method_receiver(&method.sig.inputs); } - 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); + let mut args_iter = method.sig.inputs.iter_mut(); + match args_iter.next().unwrap() { + syn::FnArg::Receiver(rcv) => { + if !rcv.reference.is_some() || rcv.mutability.is_some() { + return err_invalid_method_receiver(rcv); } } - return err_no_method_receiver(arg); - } + syn::FnArg::Typed(arg) => { + if let syn::Pat::Ident(a) = &*arg.pat { + if a.ident.to_string().as_str() != "self" { + return err_invalid_method_receiver(arg); + } + } + return err_no_method_receiver(arg); + } + }; + args_iter + .filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(arg) => Self::parse_field_argument(arg), + }) + .collect() }; - args_iter - .filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => parse_field_argument_from_method_argument(arg), - }) - .collect() - }; - let ty = match &method.sig.output { - syn::ReturnType::Default => parse_quote! { () }, - syn::ReturnType::Type(_, ty) => unparenthesize(&*ty).clone(), - }; + let ty = match &method.sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => unparenthesize(&*ty).clone(), + }; - let description = meta.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = meta - .deprecated - .as_ref() - .map(|d| d.as_ref().as_ref().map(syn::LitStr::value)); + let description = meta.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = meta + .deprecated + .as_ref() + .map(|d| d.as_ref().as_ref().map(syn::LitStr::value)); - Some(InterfaceFieldDefinition { - name, - ty, - description, - deprecated, - method: method_ident.clone(), - arguments, - is_async: method.sig.asyncness.is_some(), - }) -} + Some(InterfaceFieldDefinition { + name, + ty, + description, + deprecated, + method: method_ident.clone(), + arguments, + is_async: method.sig.asyncness.is_some(), + }) + } -fn parse_field_argument_from_method_argument( - argument: &mut syn::PatType, -) -> Option { - let argument_attrs = argument.attrs.clone(); + fn parse_field_argument(argument: &mut syn::PatType) -> Option { + let argument_attrs = argument.attrs.clone(); - // Remove repeated attributes from the method, to omit incorrect expansion. - argument.attrs = mem::take(&mut argument.attrs) - .into_iter() - .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) - .collect(); + // Remove repeated attributes from the method, to omit incorrect expansion. + argument.attrs = mem::take(&mut argument.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) + .collect(); - let meta = ArgumentMeta::from_attrs("graphql_interface", &argument_attrs) - .map_err(|e| proc_macro_error::emit_error!(e)) - .ok()?; + let meta = ArgumentMeta::from_attrs("graphql_interface", &argument_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; - if meta.context.is_some() { - if let Some(span) = &meta.executor { - return err_arg_both_context_and_executor(&span); + if meta.context.is_some() { + return Some(InterfaceFieldArgument::Context); } - ensure_no_regular_field_argument_meta(&meta)?; - return Some(InterfaceFieldArgument::Context); - } - - if meta.executor.is_some() { - if let Some(span) = &meta.context { - return err_arg_both_context_and_executor(&span); + if meta.executor.is_some() { + return Some(InterfaceFieldArgument::Executor); + } + if let syn::Pat::Ident(name) = &*argument.pat { + let arg = match name.ident.unraw().to_string().as_str() { + "context" | "ctx" => Some(InterfaceFieldArgument::Context), + "executor" => Some(InterfaceFieldArgument::Executor), + _ => None, + }; + if arg.is_some() { + ensure_no_regular_field_argument_meta(&meta)?; + return arg; + } } - ensure_no_regular_field_argument_meta(&meta)?; - return Some(InterfaceFieldArgument::Executor); - } - if let syn::Pat::Ident(name) = &*argument.pat { - let arg = match name.ident.unraw().to_string().as_str() { - "context" | "ctx" => Some(InterfaceFieldArgument::Context), - "executor" => Some(InterfaceFieldArgument::Executor), - _ => None, + let name = if let Some(name) = meta.name.as_ref() { + name.as_ref().value() + } else if let syn::Pat::Ident(name) = &*argument.pat { + to_camel_case(&name.ident.unraw().to_string()) + } else { + ERR.custom( + argument.pat.span(), + "trait method argument should be declared as a single identifier", + ) + .note(String::from( + "use `#[graphql_interface(name = ...)]` attribute to specify custom argument's \ + name without requiring it being a single identifier", + )) + .emit(); + return None; }; - if arg.is_some() { - ensure_no_regular_field_argument_meta(&meta)?; - return arg; + if name.starts_with("__") { + ERR.no_double_underscore( + meta.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| argument.pat.span()), + ); + return None; } - } - let name = if let Some(name) = meta.name.as_ref() { - name.as_ref().value() - } else if let syn::Pat::Ident(name) = &*argument.pat { - to_camel_case(&name.ident.unraw().to_string()) - } else { - ERR.custom( - argument.pat.span(), - "trait method argument should be declared as a single identifier", - ) - .note(String::from( - "use `#[graphql_interface(name = ...)]` attribute to specify custom argument's name \ - without requiring it being a single identifier", + Some(InterfaceFieldArgument::Regular( + InterfaceFieldArgumentDefinition { + name, + ty: argument.ty.as_ref().clone(), + description: meta.description.as_ref().map(|d| d.as_ref().value()), + default: meta.default.as_ref().map(|v| v.as_ref().clone()), + }, )) - .emit(); - return None; - }; - if name.starts_with("__") { - ERR.no_double_underscore( - meta.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| argument.pat.span()), - ); - return None; } - - Some(InterfaceFieldArgument::Regular( - InterfaceFieldArgumentDefinition { - name, - ty: argument.ty.as_ref().clone(), - description: meta.description.as_ref().map(|d| d.as_ref().value()), - default: meta.default.as_ref().map(|v| v.as_ref().clone()), - }, - )) } fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> { if let Some(span) = &meta.name { - return err_invalid_arg_meta(&span, "name"); + return err_disallowed_attr(&span, "name"); } if let Some(span) = &meta.description { - return err_invalid_arg_meta(&span, "description"); + return err_disallowed_attr(&span, "description"); } if let Some(span) = &meta.default { - return err_invalid_arg_meta(&span, "default"); + return err_disallowed_attr(&span, "default"); } Some(()) } -fn err_invalid_method_receiver(span: &S) -> Option { +fn err_disallowed_attr(span: &S, attr: &str) -> Option { ERR.custom( span.span(), - "trait method receiver can only be a shared reference `&self`", - ) - .emit(); - return None; -} - -fn err_no_method_receiver(span: &S) -> Option { - ERR.custom( - span.span(), - "trait method should have a shared reference receiver `&self`", + format!( + "attribute `#[graphql_interface({} = ...)]` is not allowed here", + attr, + ), ) .emit(); return None; } -fn err_arg_both_context_and_executor(span: &S) -> Option { +fn err_invalid_method_receiver(span: &S) -> Option { ERR.custom( span.span(), - "trait method argument cannot be both `juniper::Context` and `juniper::Executor` at the \ - same time", + "trait method receiver can only be a shared reference `&self`", ) .emit(); return None; } -fn err_invalid_arg_meta(span: &S, attr: &str) -> Option { +fn err_no_method_receiver(span: &S) -> Option { ERR.custom( span.span(), - format!( - "attribute `#[graphql_interface({} = ...)]` is not allowed here", - attr - ), + "trait method should have a shared reference receiver `&self`", ) .emit(); return None; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 798523397..b45f3e636 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -15,9 +15,12 @@ use syn::{ token, }; -use crate::util::{ - err, filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, - OptionExt as _, ParseBufferExt as _, +use crate::{ + common::parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, }; /* @@ -85,7 +88,7 @@ struct InterfaceMeta { /// inferred. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub external_downcasters: InterfaceMetaDowncasters,*/ + pub downcasters: InterfaceMetaDowncasters,*/ /// Indicator whether the generated code is intended to be used only inside the `juniper` /// library. pub is_internal: bool, @@ -272,14 +275,15 @@ impl ImplementerMeta { } #[derive(Debug, Default)] -struct FieldMeta { +struct TraitMethodMeta { pub name: Option>, pub description: Option>, pub deprecated: Option>>, pub ignore: Option>, + pub downcast: Option>, } -impl Parse for FieldMeta { +impl Parse for TraitMethodMeta { fn parse(input: ParseStream) -> syn::Result { let mut output = Self::default(); @@ -321,6 +325,10 @@ impl Parse for FieldMeta { .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, + "downcast" => output + .downcast + .replace(SpanContainer::new(ident.span(), None, ident.clone())) + .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } @@ -332,7 +340,7 @@ impl Parse for FieldMeta { } } -impl FieldMeta { +impl TraitMethodMeta { /// Tries to merge two [`FieldMeta`]s into a single one, reporting about duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { @@ -340,6 +348,7 @@ impl FieldMeta { description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), + downcast: try_merge_opt!(downcast: self, another), }) } @@ -350,6 +359,32 @@ impl FieldMeta { .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + if let Some(ignore) = &meta.ignore { + if meta.name.is_some() + || meta.description.is_some() + || meta.deprecated.is_some() + || meta.downcast.is_some() + { + return Err(syn::Error::new( + ignore.span(), + "`ignore` attribute argument is not composable with any other arguments", + )); + } + } + + if let Some(downcast) = &meta.downcast { + if meta.name.is_some() + || meta.description.is_some() + || meta.deprecated.is_some() + || meta.ignore.is_some() + { + return Err(syn::Error::new( + downcast.span(), + "`downcast` attribute argument is not composable with any other arguments", + )); + } + } + if meta.description.is_none() { meta.description = get_doc_comment(attrs).map(|sc| { let span = sc.span_ident(); @@ -459,9 +494,37 @@ impl ArgumentMeta { /// Parses [`ArgumentMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a /// function argument. pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - filter_attrs(name, attrs) + let meta = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if let Some(context) = &meta.context { + if meta.name.is_some() + || meta.description.is_some() + || meta.default.is_some() + || meta.executor.is_some() + { + return Err(syn::Error::new( + context.span(), + "`context` attribute argument is not composable with any other arguments", + )); + } + } + + if let Some(executor) = &meta.executor { + if meta.name.is_some() + || meta.description.is_some() + || meta.default.is_some() + || meta.context.is_some() + { + return Err(syn::Error::new( + executor.span(), + "`executor` attribute argument is not composable with any other arguments", + )); + } + } + + Ok(meta) } } @@ -499,25 +562,26 @@ struct InterfaceFieldDefinition { pub is_async: bool, } +enum ImplementerDowncastDefinition { + Method { + name: syn::Ident, + with_context: bool, + }, + External { + path: syn::Path, + }, +} + /// Definition of [GraphQL interface][1] implementer for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct InterfaceImplementerDefinition { +struct ImplementerDefinition { /// Rust type that this [GraphQL interface][1] implementer resolves into. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub ty: syn::Type, - /// Rust code for downcasting into this [GraphQL interface][1] implementer. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub downcast_code: Option, - - /// Rust code for checking whether [GraphQL interface][1] should be downcast into this - /// implementer. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub downcast_check: Option, + pub downcast: Option, /// Rust type of `juniper::Context` that this [GraphQL interface][1] implementer requires for /// downcasting. @@ -588,7 +652,7 @@ struct InterfaceDefinition { /// Implementers definitions of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub implementers: Vec, + pub implementers: Vec, } impl ToTokens for InterfaceDefinition { @@ -693,13 +757,30 @@ impl ToTokens for InterfaceDefinition { }); let custom_downcast_checks = self.implementers.iter().filter_map(|impler| { - let impler_check = impler.downcast_check.as_ref()?; let impler_ty = &impler.ty; + let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); + let fn_path = match impler.downcast.as_ref()? { + ImplementerDowncastDefinition::Method { name, with_context } => { + if !with_context { + ctx_arg = None; + } + quote! { #ty::#name } + } + ImplementerDowncastDefinition::External { path } => { + quote! { #path } + } + }; + + // Doing this may be quite an expensive, because resolving may contain some heavy + // computation, so we're preforming it twice. Unfortunately, we have no other options + // here, until the `juniper::GraphQLType` itself will allow to do it in some cleverer + // way. Some(quote! { - if #impler_check { + if ({ #fn_path(self #ctx_arg) } as ::std::option::Option<&#impler_ty>).is_some() { return <#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info) - .unwrap().to_string(); + .unwrap() + .to_string(); } }) }); @@ -718,17 +799,28 @@ impl ToTokens for InterfaceDefinition { }; let custom_downcasts = self.implementers.iter().filter_map(|impler| { - let downcast_code = impler.downcast_code.as_ref()?; let impler_ty = &impler.ty; - let get_name = quote! { - (<#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info)) + let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); + let fn_path = match impler.downcast.as_ref()? { + ImplementerDowncastDefinition::Method { name, with_context } => { + if !with_context { + ctx_arg = None; + } + quote! { #ty::#name } + } + ImplementerDowncastDefinition::External { path } => { + quote! { #path } + } }; + Some(quote! { - if type_name == #get_name.unwrap() { + if type_name == < + #impler_ty as ::juniper::GraphQLType<#scalar> + >::name(info).unwrap() { return ::juniper::IntoResolvable::into( - { #downcast_code }, - executor.context() + { #fn_path(self #ctx_arg) }, + executor.context(), ) .and_then(|res| match res { Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), @@ -738,17 +830,28 @@ impl ToTokens for InterfaceDefinition { }) }); let custom_async_downcasts = self.implementers.iter().filter_map(|impler| { - let downcast_code = impler.downcast_code.as_ref()?; let impler_ty = &impler.ty; - let get_name = quote! { - (<#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info)) + let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); + let fn_path = match impler.downcast.as_ref()? { + ImplementerDowncastDefinition::Method { name, with_context } => { + if !with_context { + ctx_arg = None; + } + quote! { #ty::#name } + } + ImplementerDowncastDefinition::External { path } => { + quote! { #path } + } }; + Some(quote! { - if type_name == #get_name.unwrap() { + if type_name == < + #impler_ty as ::juniper::GraphQLType<#scalar> + >::name(info).unwrap() { let res = ::juniper::IntoResolvable::into( - { #downcast_code }, - executor.context() + { #fn_path(self #ctx_arg) }, + executor.context(), ); return ::juniper::futures::future::FutureExt::boxed(async move { match res? { diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index a1a4cd3aa..02afeaadd 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -1,16 +1,15 @@ //! Code generation for `#[graphql_union]` macro. -use std::{mem, ops::Deref as _}; +use std::mem; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ + common::parse, result::GraphQLScope, - util::{ - path_eq_single, span_container::SpanContainer, strip_attrs, unite_attrs, unparenthesize, - }, + util::{path_eq_single, span_container::SpanContainer, strip_attrs, unite_attrs}, }; use super::{ @@ -141,7 +140,7 @@ fn parse_variant_from_trait_method( let method_span = method.sig.span(); let method_ident = &method.sig.ident; - let ty = parse_trait_method_output_type(&method.sig) + let ty = parse::downcaster::output_type(&method.sig.output) .map_err(|span| { ERR.emit_custom( span, @@ -149,7 +148,7 @@ fn parse_variant_from_trait_method( ) }) .ok()?; - let method_context_ty = parse_trait_method_input_args(&method.sig) + let method_context_ty = parse::downcaster::context_ty(&method.sig) .map_err(|span| { ERR.emit_custom( span, @@ -160,7 +159,7 @@ fn parse_variant_from_trait_method( if let Some(is_async) = &method.sig.asyncness { ERR.emit_custom( is_async.span(), - "doesn't support async union variants resolvers yet", + "async downcast to union variants is not supported", ); return None; } @@ -212,85 +211,3 @@ fn parse_variant_from_trait_method( span: method_span, }) } - -/// Parses type of [GraphQL union][1] variant from the return type of trait method. -/// -/// If return type is invalid, then returns the [`Span`] to display the corresponding error at. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions -fn parse_trait_method_output_type(sig: &syn::Signature) -> Result { - let ret_ty = match &sig.output { - syn::ReturnType::Type(_, ty) => ty.deref(), - _ => return Err(sig.span()), - }; - - let path = match unparenthesize(ret_ty) { - syn::Type::Path(syn::TypePath { qself: None, path }) => path, - _ => return Err(ret_ty.span()), - }; - - let (ident, args) = match path.segments.last() { - Some(syn::PathSegment { - ident, - arguments: syn::PathArguments::AngleBracketed(generic), - }) => (ident, &generic.args), - _ => return Err(ret_ty.span()), - }; - - if ident.unraw() != "Option" { - return Err(ret_ty.span()); - } - - if args.len() != 1 { - return Err(ret_ty.span()); - } - let var_ty = match args.first() { - Some(syn::GenericArgument::Type(inner_ty)) => match unparenthesize(inner_ty) { - syn::Type::Reference(inner_ty) => { - if inner_ty.mutability.is_some() { - return Err(inner_ty.span()); - } - unparenthesize(inner_ty.elem.deref()).clone() - } - _ => return Err(ret_ty.span()), - }, - _ => return Err(ret_ty.span()), - }; - Ok(var_ty) -} - -/// Parses trait method input arguments and validates them to be acceptable for resolving into -/// [GraphQL union][1] variant type. Returns type of the context used in input arguments, if any. -/// -/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions -fn parse_trait_method_input_args(sig: &syn::Signature) -> Result, Span> { - match sig.receiver() { - Some(syn::FnArg::Receiver(rcv)) => { - if rcv.reference.is_none() || rcv.mutability.is_some() { - return Err(rcv.span()); - } - } - _ => return Err(sig.span()), - } - - if sig.inputs.len() > 2 { - return Err(sig.inputs.span()); - } - - let second_arg_ty = match sig.inputs.iter().nth(1) { - Some(syn::FnArg::Typed(arg)) => arg.ty.deref(), - None => return Ok(None), - _ => return Err(sig.inputs.span()), - }; - match unparenthesize(second_arg_ty) { - syn::Type::Reference(ref_ty) => { - if ref_ty.mutability.is_some() { - return Err(ref_ty.span()); - } - Ok(Some(ref_ty.elem.deref().clone())) - } - ty => Err(ty.span()), - } -} diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index 332b015f4..5ebc2f9cc 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -6,8 +6,9 @@ use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; use crate::{ + common::unparenthesize, result::GraphQLScope, - util::{span_container::SpanContainer, unparenthesize}, + util::{span_container::SpanContainer}, }; use super::{ diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index e42db6812..1413204af 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -16,9 +16,12 @@ use syn::{ token, }; -use crate::util::{ - err, filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _, - ParseBufferExt as _, +use crate::{ + common::parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, }; /// Helper alias for the type of [`UnionMeta::external_resolvers`] field. diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index dadbee8cd..24567d9a5 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -32,7 +32,7 @@ macro_rules! try_merge_opt { $another .$field .replace(v) - .none_or_else(|dup| crate::util::err::dup_arg(&dup.$span()))?; + .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?; } $another.$field }}; @@ -64,7 +64,7 @@ macro_rules! try_merge_hashmap { $another .$field .insert(ty, rslvr) - .none_or_else(|dup| crate::util::err::dup_arg(&dup.$span()))?; + .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?; } } $another.$field @@ -97,7 +97,7 @@ macro_rules! try_merge_hashset { $another .$field .replace(ty) - .none_or_else(|dup| crate::util::err::dup_arg(&dup.$span()))?; + .none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?; } } $another.$field @@ -117,6 +117,7 @@ mod impl_scalar; mod graphql_interface; mod graphql_union; +mod common; use proc_macro::TokenStream; use proc_macro_error::{proc_macro_error, ResultExt as _}; diff --git a/juniper_codegen/src/util/err.rs b/juniper_codegen/src/util/err.rs deleted file mode 100644 index d3b5b7167..000000000 --- a/juniper_codegen/src/util/err.rs +++ /dev/null @@ -1,37 +0,0 @@ -use proc_macro2::Span; -use syn::spanned::Spanned; - -/// Creates "duplicated argument" [`syn::Error`] for the given `name` pointing to the given -/// [`Span`]. -#[must_use] -pub fn dup_arg(span: S) -> syn::Error { - syn::Error::new(span.as_span(), "duplicated attribute argument found") -} - -/// Creates "unknown argument" [`syn::Error`] for the given `name` pointing to the given [`Span`]. -#[must_use] -pub fn unknown_arg(span: S, name: &str) -> syn::Error { - syn::Error::new( - span.as_span(), - format!("unknown `{}` attribute argument", name), - ) -} - -pub trait AsSpan { - #[must_use] - fn as_span(&self) -> Span; -} - -impl AsSpan for Span { - #[inline] - fn as_span(&self) -> Self { - *self - } -} - -impl AsSpan for &T { - #[inline] - fn as_span(&self) -> Span { - self.span() - } -} \ No newline at end of file diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index f28a5c23c..849e950c4 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1,9 +1,6 @@ #![allow(clippy::single_match)] pub mod duplicate; -pub mod err; -pub mod option_ext; -pub mod parse_buffer_ext; pub mod parse_impl; pub mod span_container; @@ -21,7 +18,7 @@ use syn::{ token, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta, }; -pub use self::{option_ext::OptionExt, parse_buffer_ext::ParseBufferExt}; +use crate::common::{parse::ParseBufferExt as _, unparenthesize}; /// Returns the name of a type. /// If the type does not end in a simple ident, `None` is returned. @@ -110,15 +107,6 @@ pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool { } } -/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested -/// [`syn::TypeParen`]s asap). -pub fn unparenthesize(ty: &syn::Type) -> &syn::Type { - match ty { - syn::Type::Paren(ty) => unparenthesize(&*ty.elem), - _ => ty, - } -} - #[derive(Debug)] pub struct DeprecationAttr { pub reason: Option, diff --git a/juniper_codegen/src/util/option_ext.rs b/juniper_codegen/src/util/option_ext.rs deleted file mode 100644 index 3007abd65..000000000 --- a/juniper_codegen/src/util/option_ext.rs +++ /dev/null @@ -1,24 +0,0 @@ -/// Handy extension of [`Option`] methods used in this crate. -pub trait OptionExt { - type Inner; - - /// Transforms the `Option` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)` - /// to `Err(err(v))`. - fn none_or_else(self, err: F) -> Result<(), E> - where - F: FnOnce(Self::Inner) -> E; -} - -impl OptionExt for Option { - type Inner = T; - - fn none_or_else(self, err: F) -> Result<(), E> - where - F: FnOnce(T) -> E, - { - match self { - Some(v) => Err(err(v)), - None => Ok(()), - } - } -} From ea793932b8cd93ad77e5531b3a6f84f80fc6a37a Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 4 Sep 2020 19:01:49 +0300 Subject: [PATCH 40/79] Impl dowcasting via external functions --- .../src/codegen/interface_attr.rs | 150 ++++++++++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 84 ++++++---- juniper_codegen/src/graphql_interface/mod.rs | 65 ++++---- juniper_codegen/src/graphql_union/mod.rs | 4 +- 4 files changed, 242 insertions(+), 61 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index a58834608..6ee742306 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2498,6 +2498,156 @@ mod downcast_method { } } +mod external_downcast { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(context = Database)] + #[graphql_interface(on Droid = DynCharacter::as_droid)] + trait Character { + fn id(&self) -> &str; + } + + impl<'a, S: ScalarValue> DynCharacter<'a, S> { + fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { + db.droid.as_ref() + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = Database)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = Database)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + struct Database { + droid: Option, + } + impl juniper::Context for Database {} + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(context = Database)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "?????".to_string(), + primary_function: "???".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + let db = Database { droid: None }; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).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); + let db = Database { + droid: Some(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let db = Database { + droid: Some(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + + for (root, expected_id) in &[(QueryRoot::Human, "human-32"), (QueryRoot::Droid, "?????")] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + // ------------------------------------------- #[derive(GraphQLObject)] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c4d4c5f69..63b838c7d 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -3,7 +3,7 @@ use std::mem; use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -83,6 +83,16 @@ pub fn expand_on_trait( } }) .collect(); + for (ty, downcast) in &meta.external_downcasts { + match implementers.iter_mut().find(|i| &i.ty == ty) { + Some(impler) => { + impler.downcast = Some(ImplementerDowncastDefinition::External { + path: downcast.inner().clone(), + }); + } + None => err_only_implementer_downcast(&downcast.span_joined()), + } + } proc_macro_error::abort_if_dirty(); @@ -94,13 +104,14 @@ pub fn expand_on_trait( Some(TraitMethod::Downcast(d)) => { match implementers.iter_mut().find(|i| i.ty == d.ty) { Some(impler) => { - impler.downcast = d.downcast; - impler.context_ty = d.context_ty; + if let Some(external) = &impler.downcast { + err_duplicate_downcast(m, external, &impler.ty); + } else { + impler.downcast = d.downcast; + impler.context_ty = d.context_ty; + } } - None => ERR.emit_custom( - d.span, - "downcasting is possible only to interface implementers", - ), + None => err_only_implementer_downcast(&d.span), } } _ => {} @@ -318,30 +329,6 @@ impl TraitMethod { return None; } - /* - let downcast = { - /* TODO - if let Some(other) = trait_meta.external_resolvers.get(&ty) { - ERR.custom( - method_span, - format!( - "trait method `{}` conflicts with the external resolver function `{}` declared \ - on the trait to resolve the variant type `{}`", - method_ident, - other.to_token_stream(), - ty.to_token_stream(), - - ), - ) - .note(String::from( - "use `#[graphql_union(ignore)]` attribute to ignore this trait method for union \ - variants resolution", - )) - .emit(); - }*/ - - };*/ - let downcast = ImplementerDowncastDefinition::Method { name: method_ident.clone(), with_context: context_ty.is_some(), @@ -536,3 +523,38 @@ fn err_no_method_receiver(span: &S) -> Option { .emit(); return None; } + +fn err_only_implementer_downcast(span: &S) { + ERR.custom( + span.span(), + "downcasting is possible only to interface implementers", + ) + .emit(); +} + +fn err_duplicate_downcast( + method: &syn::TraitItemMethod, + external: &ImplementerDowncastDefinition, + impler_ty: &syn::Type, +) { + let external = match external { + ImplementerDowncastDefinition::External { path } => path, + _ => unreachable!(), + }; + + ERR.custom( + method.span(), + format!( + "trait method `{}` conflicts with the external downcast function `{}` declared \ + on the trait to downcast into the implementer type `{}`", + method.sig.ident, + external.to_token_stream(), + impler_ty.to_token_stream(), + ), + ) + .note(String::from( + "use `#[graphql_interface(ignore)]` attribute to ignore this trait method for interface \ + implementers downcasting", + )) + .emit() +} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index b45f3e636..1fa6423f0 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -4,7 +4,7 @@ pub mod attr; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt as _}; @@ -23,9 +23,8 @@ use crate::{ util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, }; -/* -/// Helper alias for the type of [`InterfaceMeta::external_downcasters`] field. -type InterfaceMetaDowncasters = HashMap>;*/ +/// Helper alias for the type of [`InterfaceMeta::external_downcasts`] field. +type InterfaceMetaDowncasts = HashMap>; /// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_interface]`) attribute placed /// on a trait definition, when generating code for [GraphQL interface][1] type. @@ -79,7 +78,6 @@ struct InterfaceMeta { pub asyncness: Option>, - /* /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers. /// /// If absent, then macro will try to auto-infer all the possible variants from the type @@ -88,7 +86,8 @@ struct InterfaceMeta { /// inferred. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub downcasters: InterfaceMetaDowncasters,*/ + pub external_downcasts: InterfaceMetaDowncasts, + /// Indicator whether the generated code is intended to be used only inside the `juniper` /// library. pub is_internal: bool, @@ -168,6 +167,17 @@ impl Parse for InterfaceMeta { .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } + "on" => { + let ty = input.parse::()?; + input.parse::()?; + let dwncst = input.parse::()?; + let dwncst_spanned = SpanContainer::new(ident.span(), Some(ty.span()), dwncst); + let dwncst_span = dwncst_spanned.span_joined(); + output + .external_downcasts + .insert(ty, dwncst_spanned) + .none_or_else(|_| err::dup_arg(dwncst_span))? + } "internal" => { output.is_internal = true; } @@ -193,6 +203,9 @@ impl InterfaceMeta { implementers: try_merge_hashset!(implementers: self, another => span_joined), alias: try_merge_opt!(alias: self, another), asyncness: try_merge_opt!(asyncness: self, another), + external_downcasts: try_merge_hashmap!( + external_downcasts: self, another => span_joined + ), is_internal: self.is_internal || another.is_internal, }) } @@ -568,7 +581,7 @@ enum ImplementerDowncastDefinition { with_context: bool, }, External { - path: syn::Path, + path: syn::ExprPath, }, } @@ -818,14 +831,13 @@ impl ToTokens for InterfaceDefinition { if type_name == < #impler_ty as ::juniper::GraphQLType<#scalar> >::name(info).unwrap() { - return ::juniper::IntoResolvable::into( - { #fn_path(self #ctx_arg) }, - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); + return ::juniper::IntoResolvable::into({ #fn_path(self #ctx_arg) }, context) + .and_then(|res| match res { + Some((ctx, r)) => executor + .replaced_context(ctx) + .resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }); } }) }); @@ -849,10 +861,7 @@ impl ToTokens for InterfaceDefinition { if type_name == < #impler_ty as ::juniper::GraphQLType<#scalar> >::name(info).unwrap() { - let res = ::juniper::IntoResolvable::into( - { #fn_path(self #ctx_arg) }, - executor.context(), - ); + let res = ::juniper::IntoResolvable::into({ #fn_path(self #ctx_arg) }, context); return ::juniper::futures::future::FutureExt::boxed(async move { match res? { Some((ctx, r)) => { @@ -867,19 +876,15 @@ impl ToTokens for InterfaceDefinition { }); let (regular_downcast, regular_async_downcast) = if self.trait_object.is_some() { let sync = quote! { - return ::juniper::IntoResolvable::into( - self.as_dyn_graphql_value(), - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }) + return ::juniper::IntoResolvable::into(self.as_dyn_graphql_value(), context) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) }; let r#async = quote! { let res = ::juniper::IntoResolvable::into( - self.as_dyn_graphql_value_async(), - executor.context(), + self.as_dyn_graphql_value_async(), context, ); return ::juniper::futures::future::FutureExt::boxed(async move { match res? { @@ -1144,6 +1149,7 @@ impl ToTokens for InterfaceDefinition { _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { + let context = executor.context(); #( #custom_downcasts )* #regular_downcast } @@ -1179,6 +1185,7 @@ impl ToTokens for InterfaceDefinition { _: Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let context = executor.context(); #( #custom_async_downcasts )* #regular_async_downcast } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 1413204af..28586e37e 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -162,7 +162,9 @@ impl UnionMeta { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), - external_resolvers: try_merge_hashmap!(external_resolvers: self, another => span_joined), + external_resolvers: try_merge_hashmap!( + external_resolvers: self, another => span_joined + ), is_internal: self.is_internal || another.is_internal, }) } From 236ede47d7376106cc48d3247572262c77b0aaa8 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 7 Sep 2020 19:03:14 +0300 Subject: [PATCH 41/79] Support custom context, vol. 1 --- .../src/codegen/interface_attr.rs | 302 +++++++++--------- juniper_codegen/src/common/mod.rs | 73 ++++- juniper_codegen/src/graphql_interface/attr.rs | 5 +- juniper_codegen/src/graphql_interface/mod.rs | 8 +- 4 files changed, 226 insertions(+), 162 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 6ee742306..d7d47cc23 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2173,6 +2173,7 @@ mod custom_scalar { ch } } + #[tokio::test] async fn resolves_human() { const DOC: &str = r#"{ @@ -2240,6 +2241,150 @@ mod custom_scalar { } } +mod explicit_custom_context { + use super::*; + + pub struct CustomContext; + impl juniper::Context for CustomContext {} + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, context = CustomContext)] + trait Character { + async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; + + async fn info<'b>(&'b self, context: &()) -> &'b str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = CustomContext)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + &self.id + } + + async fn info<'b>(&'b self, _: &()) -> &'b str { + &self.home_planet + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = CustomContext)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + &self.id + } + + async fn info<'b>(&'b self, _: &()) -> &'b str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &CustomContext).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, &Variables::new(), &CustomContext).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + info + } + }"#; + + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "human-32", "earth"), + (QueryRoot::Droid, "droid-99", "run"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &CustomContext).await, + Ok(( + graphql_value!({"character": {"id": expected_id, "info": expected_info}}), + vec![], + )), + ); + } + } +} + mod ignored_method { use super::*; @@ -2647,160 +2792,3 @@ mod external_downcast { } } } - -// ------------------------------------------- - -#[derive(GraphQLObject)] -#[graphql(impl = dyn Character)] -struct Human { - id: String, - home_planet: String, -} - -#[graphql_interface] -impl Character for Human { - //#[graphql_interface] - async fn id(&self, _: &()) -> String { - self.id.to_string() - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = dyn Character)] -struct Droid { - id: String, - primary_function: String, -} - -#[graphql_interface] -impl Character for Droid { - async fn id(&self, _: &()) -> String { - self.id.to_string() - } - - fn as_droid(&self) -> Option<&Droid> { - Some(self) - } -} - -#[derive(GraphQLObject)] -struct Ewok { - id: String, - funny: bool, -} - -#[graphql_interface(for = [Human, Droid])] -trait Character { - async fn id(&self, context: &()) -> String; - - //#[graphql_interface(downcast)] - fn as_droid(&self) -> Option<&Droid> { - None - } -} - -mod poc { - use super::*; - - type DynCharacter<'a, S = DefaultScalarValue> = - dyn Character + 'a + Send + Sync; - - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn resolves_id_for_human() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"id": "human-32"}}), vec![],)), - ); - } - - #[tokio::test] - async fn resolves_id_for_droid() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"id": "droid-99"}}), vec![],)), - ); - } - - #[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, &Variables::new(), &()).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); - //panic!("🔬 {:#?}", schema.schema); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } -} diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 3af3a42e9..5329d942d 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,5 +1,7 @@ pub(crate) mod parse; +use proc_macro2::Span; + /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested /// [`syn::TypeParen`]s asap). pub(crate) fn unparenthesize(ty: &syn::Type) -> &syn::Type { @@ -7,4 +9,73 @@ pub(crate) fn unparenthesize(ty: &syn::Type) -> &syn::Type { syn::Type::Paren(ty) => unparenthesize(&*ty.elem), _ => ty, } -} \ No newline at end of file +} + +pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { + use syn::{GenericArgument as GA, Type as T}; + + match ty { + T::Array(syn::TypeArray { elem, .. }) + | T::Group(syn::TypeGroup { elem, .. }) + | T::Paren(syn::TypeParen { elem, .. }) + | T::Ptr(syn::TypePtr { elem, .. }) + | T::Slice(syn::TypeSlice { elem, .. }) => anonymize_lifetimes(&mut *elem), + + T::Tuple(syn::TypeTuple { elems, .. }) => { + for ty in elems.iter_mut() { + anonymize_lifetimes(ty); + } + } + + T::ImplTrait(syn::TypeImplTrait { bounds, .. }) + | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { + for bound in bounds.iter_mut() { + match bound { + syn::TypeParamBound::Lifetime(lt) => { + lt.ident = syn::Ident::new("_", Span::call_site()) + } + syn::TypeParamBound::Trait(_) => todo!(), + } + } + } + + T::Reference(ref_ty) => { + if let Some(lt) = ref_ty.lifetime.as_mut() { + lt.ident = syn::Ident::new("_", Span::call_site()); + } + anonymize_lifetimes(&mut *ref_ty.elem); + } + + T::BareFn(_) => todo!(), + + T::Path(ty) => { + for seg in ty.path.segments.iter_mut() { + match &mut seg.arguments { + syn::PathArguments::AngleBracketed(angle) => { + for arg in angle.args.iter_mut() { + match arg { + GA::Lifetime(lt) => { + lt.ident = syn::Ident::new("_", Span::call_site()); + } + GA::Type(ty) => anonymize_lifetimes(ty), + GA::Binding(b) => anonymize_lifetimes(&mut b.ty), + GA::Constraint(_) | GA::Const(_) => {} + } + } + } + syn::PathArguments::Parenthesized(args) => { + for ty in args.inputs.iter_mut() { + anonymize_lifetimes(ty); + } + if let syn::ReturnType::Type(_, ty) = &mut args.output { + anonymize_lifetimes(&mut *ty); + } + } + syn::PathArguments::None => {} + } + } + } + + T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) | T::__Nonexhaustive => {} + } +} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 63b838c7d..0c2135a5f 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -7,7 +7,7 @@ use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ - common::{parse, unparenthesize}, + common::{parse, unparenthesize, anonymize_lifetimes}, result::GraphQLScope, util::{ path_eq_single, span_container::SpanContainer, strip_attrs, to_camel_case, unite_attrs, @@ -391,10 +391,11 @@ impl TraitMethod { .collect() }; - let ty = match &method.sig.output { + let mut ty = match &method.sig.output { syn::ReturnType::Default => parse_quote! { () }, syn::ReturnType::Type(_, ty) => unparenthesize(&*ty).clone(), }; + anonymize_lifetimes(&mut ty); let description = meta.description.as_ref().map(|d| d.as_ref().value()); let deprecated = meta diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 1fa6423f0..a4ab7e49d 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -996,7 +996,9 @@ impl ToTokens for InterfaceDefinition { ); quote! { args.get::<#ty>(#name).expect(#err_text) } } - InterfaceFieldArgument::Context => quote! { executor.context() }, + InterfaceFieldArgument::Context => quote! { + ::juniper::FromContext::from(executor.context()) + }, InterfaceFieldArgument::Executor => quote! { &executor }, }); @@ -1051,7 +1053,9 @@ impl ToTokens for InterfaceDefinition { ); quote! { args.get::<#ty>(#name).expect(#err_text) } } - InterfaceFieldArgument::Context => quote! { executor.context() }, + InterfaceFieldArgument::Context => quote! { + ::juniper::FromContext::from(executor.context()) + }, InterfaceFieldArgument::Executor => quote! { &executor }, }); From ed5f0e52bbc1cd72c537af0cf74dcdd34b6431c4 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 8 Sep 2020 17:18:26 +0300 Subject: [PATCH 42/79] Support custom context, vol. 2 --- .../src/codegen/interface_attr.rs | 331 +++++++++++++++++- .../juniper_tests/src/codegen/union_attr.rs | 24 +- juniper_codegen/src/common/mod.rs | 23 +- .../src/common/parse/downcaster.rs | 12 +- juniper_codegen/src/common/parse/mod.rs | 30 +- juniper_codegen/src/graphql_interface/attr.rs | 60 ++-- juniper_codegen/src/graphql_interface/mod.rs | 29 +- juniper_codegen/src/graphql_union/derive.rs | 4 +- juniper_codegen/src/util/mod.rs | 6 +- 9 files changed, 442 insertions(+), 77 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d7d47cc23..fb3f02749 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2251,7 +2251,9 @@ mod explicit_custom_context { trait Character { async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - async fn info<'b>(&'b self, context: &()) -> &'b str; + async fn info<'b>(&'b self, ctx: &()) -> &'b str; + + fn more<'c>(&'c self, #[graphql_interface(context)] custom: &CustomContext) -> &'c str; } #[derive(GraphQLObject)] @@ -2270,6 +2272,10 @@ mod explicit_custom_context { async fn info<'b>(&'b self, _: &()) -> &'b str { &self.home_planet } + + fn more(&self, _: &CustomContext) -> &'static str { + "human" + } } #[derive(GraphQLObject)] @@ -2288,6 +2294,10 @@ mod explicit_custom_context { async fn info<'b>(&'b self, _: &()) -> &'b str { &self.primary_function } + + fn more(&self, _: &CustomContext) -> &'static str { + "droid" + } } #[derive(Clone, Copy)] @@ -2363,19 +2373,172 @@ mod explicit_custom_context { character { id info + more } }"#; - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-32", "earth"), - (QueryRoot::Droid, "droid-99", "run"), + for (root, expected_id, expected_info, expexted_more) in &[ + (QueryRoot::Human, "human-32", "earth", "human"), + (QueryRoot::Droid, "droid-99", "run", "droid"), ] { let schema = schema(*root); let expected_id: &str = *expected_id; let expected_info: &str = *expected_info; + let expexted_more: &str = *expexted_more; assert_eq!( execute(DOC, None, &schema, &Variables::new(), &CustomContext).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + "more": expexted_more, + }}), + vec![], + )), + ); + } + } +} + +mod inferred_custom_context_from_field { + use super::*; + + pub struct CustomContext(String); + impl juniper::Context for CustomContext {} + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + async fn id<'a>(&self, context: &'a CustomContext) -> &'a str; + + async fn info<'b>(&'b self, context: &()) -> &'b str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = CustomContext)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { + &ctx.0 + } + + async fn info<'b>(&'b self, _: &()) -> &'b str { + &self.home_planet + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = CustomContext)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { + &ctx.0 + } + + async fn info<'b>(&'b self, _: &()) -> &'b str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + let ctx = CustomContext("in-ctx".into()); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).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); + let ctx = CustomContext("in-droid".into()); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + info + } + }"#; + + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "human-ctx", "earth"), + (QueryRoot::Droid, "droid-ctx", "run"), + ] { + let schema = schema(*root); + let ctx = CustomContext(expected_id.to_string()); + + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, Ok(( graphql_value!({"character": {"id": expected_id, "info": expected_info}}), vec![], @@ -2385,6 +2548,156 @@ mod explicit_custom_context { } } +mod inferred_custom_context_from_downcast { + use super::*; + + struct Database { + droid: Option, + } + impl juniper::Context for Database {} + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + #[graphql_interface(downcast)] + fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid>; + + async fn info(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = Database)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn as_droid<'db>(&self, _: &'db Database) -> Option<&'db Droid> { + None + } + + async fn info(&self) -> &str { + &self.home_planet + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character, context = Database)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { + db.droid.as_ref() + } + + async fn info(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(context = Database)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + let db = Database { droid: None }; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).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); + let db = Database { + droid: Some(Droid { + id: "droid-88".to_string(), + primary_function: "sit".to_string(), + }), + }; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-88", "primaryFunction": "sit"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + let db = Database { droid: None }; + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"character": {"info": expected_info}}), + vec![], + )), + ); + } + } +} + mod ignored_method { use super::*; @@ -2646,6 +2959,11 @@ mod downcast_method { mod external_downcast { use super::*; + struct Database { + droid: Option, + } + impl juniper::Context for Database {} + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] #[graphql_interface(context = Database)] #[graphql_interface(on Droid = DynCharacter::as_droid)] @@ -2687,11 +3005,6 @@ mod external_downcast { } } - struct Database { - droid: Option, - } - impl juniper::Context for Database {} - #[derive(Clone, Copy)] enum QueryRoot { Human, diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index 6ca69a8b6..ebad82217 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -647,27 +647,27 @@ mod custom_scalar { } } -mod inferred_custom_context { +mod explicit_custom_context { use super::*; - #[graphql_union] + #[graphql_union(context = CustomContext)] trait Character { - fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> { + fn as_human(&self) -> Option<&HumanCustomContext> { None } - fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> { + fn as_droid(&self) -> Option<&DroidCustomContext> { None } } impl Character for HumanCustomContext { - fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> { + fn as_human(&self) -> Option<&HumanCustomContext> { Some(&self) } } impl Character for DroidCustomContext { - fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> { + fn as_droid(&self) -> Option<&DroidCustomContext> { Some(&self) } } @@ -734,27 +734,27 @@ mod inferred_custom_context { } } -mod explicit_custom_context { +mod inferred_custom_context { use super::*; - #[graphql_union(context = CustomContext)] + #[graphql_union] trait Character { - fn as_human(&self) -> Option<&HumanCustomContext> { + fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> { None } - fn as_droid(&self) -> Option<&DroidCustomContext> { + fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> { None } } impl Character for HumanCustomContext { - fn as_human(&self) -> Option<&HumanCustomContext> { + fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> { Some(&self) } } impl Character for DroidCustomContext { - fn as_droid(&self) -> Option<&DroidCustomContext> { + fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> { Some(&self) } } diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 5329d942d..ea10b7c64 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -2,15 +2,6 @@ pub(crate) mod parse; use proc_macro2::Span; -/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested -/// [`syn::TypeParen`]s asap). -pub(crate) fn unparenthesize(ty: &syn::Type) -> &syn::Type { - match ty { - syn::Type::Paren(ty) => unparenthesize(&*ty.elem), - _ => ty, - } -} - pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { use syn::{GenericArgument as GA, Type as T}; @@ -34,7 +25,9 @@ pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { syn::TypeParamBound::Lifetime(lt) => { lt.ident = syn::Ident::new("_", Span::call_site()) } - syn::TypeParamBound::Trait(_) => todo!(), + syn::TypeParamBound::Trait(_) => { + todo!("Anonymizing lifetimes in trait is not yet supported") + } } } } @@ -46,8 +39,6 @@ pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { anonymize_lifetimes(&mut *ref_ty.elem); } - T::BareFn(_) => todo!(), - T::Path(ty) => { for seg in ty.path.segments.iter_mut() { match &mut seg.arguments { @@ -76,6 +67,12 @@ pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { } } - T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) | T::__Nonexhaustive => {} + // These types unlikely will be used as GraphQL types. + T::BareFn(_) + | T::Infer(_) + | T::Macro(_) + | T::Never(_) + | T::Verbatim(_) + | T::__Nonexhaustive => {} } } diff --git a/juniper_codegen/src/common/parse/downcaster.rs b/juniper_codegen/src/common/parse/downcaster.rs index 5953e3dd1..9882a7f9a 100644 --- a/juniper_codegen/src/common/parse/downcaster.rs +++ b/juniper_codegen/src/common/parse/downcaster.rs @@ -1,7 +1,7 @@ use proc_macro2::Span; use syn::{ext::IdentExt as _, spanned::Spanned as _}; -use crate::common::unparenthesize; +use crate::common::parse::TypeExt as _; /// Parses downcasting output type from the downcaster method return type. /// @@ -15,7 +15,7 @@ pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result { _ => return Err(ret_ty.span()), }; - let path = match unparenthesize(ret_ty) { + let path = match ret_ty.unparenthesized() { syn::Type::Path(syn::TypePath { qself: None, path }) => path, _ => return Err(ret_ty.span()), }; @@ -36,12 +36,12 @@ pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result { } let out_ty = match args.first() { - Some(syn::GenericArgument::Type(inner_ty)) => match unparenthesize(inner_ty) { + Some(syn::GenericArgument::Type(inner_ty)) => match inner_ty.unparenthesized() { syn::Type::Reference(inner_ty) => { if inner_ty.mutability.is_some() { return Err(inner_ty.span()); } - unparenthesize(&*inner_ty.elem).clone() + inner_ty.elem.unparenthesized().clone() } _ => return Err(ret_ty.span()), }, @@ -76,12 +76,12 @@ pub(crate) fn context_ty(sig: &syn::Signature) -> Result, Span None => return Ok(None), _ => return Err(sig.inputs.span()), }; - match unparenthesize(second_arg_ty) { + match second_arg_ty.unparenthesized() { syn::Type::Reference(ref_ty) => { if ref_ty.mutability.is_some() { return Err(ref_ty.span()); } - Ok(Some(unparenthesize(&*ref_ty.elem).clone())) + Ok(Some(ref_ty.elem.unparenthesized().clone())) } ty => Err(ty.span()), } diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index c6c0d11d0..878b9d606 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -13,7 +13,7 @@ use syn::{ token::{self, Token}, }; -pub trait ParseBufferExt { +pub(crate) trait ParseBufferExt { /// Tries to parse `T` as the next token. /// /// Doesn't move [`ParseStream`]'s cursor if there is no `T`. @@ -84,3 +84,31 @@ impl<'a> ParseBufferExt for ParseBuffer<'a> { }) } } + +pub(crate) trait TypeExt { + /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested + /// [`syn::TypeParen`]s asap). + fn unparenthesized(&self) -> &Self; + + /// Retrieves the inner [`syn::Type`] from the given reference type, or just returns "as is" if + /// the type is not a reference. + /// + /// Also, unparenthesizes the type, if required. + fn unreferenced(&self) -> &Self; +} + +impl TypeExt for syn::Type { + fn unparenthesized(&self) -> &Self { + match self { + Self::Paren(ty) => ty.elem.unparenthesized(), + ty => ty, + } + } + + fn unreferenced(&self) -> &Self { + match self.unparenthesized() { + Self::Reference(ref_ty) => &*ref_ty.elem, + ty => ty, + } + } +} \ No newline at end of file diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 0c2135a5f..c0ee31d98 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -7,7 +7,10 @@ use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ - common::{parse, unparenthesize, anonymize_lifetimes}, + common::{ + anonymize_lifetimes, + parse::{self, TypeExt as _}, + }, result::GraphQLScope, util::{ path_eq_single, span_container::SpanContainer, strip_attrs, to_camel_case, unite_attrs, @@ -16,8 +19,8 @@ use crate::{ use super::{ ArgumentMeta, ImplementerDefinition, ImplementerDowncastDefinition, ImplementerMeta, - InterfaceDefinition, InterfaceFieldArgument, InterfaceFieldArgumentDefinition, - InterfaceFieldDefinition, InterfaceMeta, TraitMethodMeta, + InterfaceDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceMeta, + MethodArgument, TraitMethodMeta, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -67,9 +70,6 @@ pub fn expand_on_trait( ); } - let context = meta.context.map(SpanContainer::into_inner); - //.or_else(|| variants.iter().find_map(|v| v.context_ty.as_ref()).cloned()); - let mut implementers: Vec<_> = meta .implementers .iter() @@ -121,6 +121,24 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); + let context = meta + .context + .map(SpanContainer::into_inner) + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments + .iter() + .find_map(MethodArgument::context_ty) + .cloned() + }) + }) + .or_else(|| { + implementers + .iter() + .find_map(|impler| impler.context_ty.as_ref()) + .cloned() + }); + let is_async_trait = meta.asyncness.is_some() || ast .items @@ -393,7 +411,7 @@ impl TraitMethod { let mut ty = match &method.sig.output { syn::ReturnType::Default => parse_quote! { () }, - syn::ReturnType::Type(_, ty) => unparenthesize(&*ty).clone(), + syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), }; anonymize_lifetimes(&mut ty); @@ -414,7 +432,7 @@ impl TraitMethod { }) } - fn parse_field_argument(argument: &mut syn::PatType) -> Option { + fn parse_field_argument(argument: &mut syn::PatType) -> Option { let argument_attrs = argument.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. @@ -428,15 +446,17 @@ impl TraitMethod { .ok()?; if meta.context.is_some() { - return Some(InterfaceFieldArgument::Context); + return Some(MethodArgument::Context(argument.ty.unreferenced().clone())); } if meta.executor.is_some() { - return Some(InterfaceFieldArgument::Executor); + return Some(MethodArgument::Executor); } if let syn::Pat::Ident(name) = &*argument.pat { let arg = match name.ident.unraw().to_string().as_str() { - "context" | "ctx" => Some(InterfaceFieldArgument::Context), - "executor" => Some(InterfaceFieldArgument::Executor), + "context" | "ctx" => { + Some(MethodArgument::Context(argument.ty.unreferenced().clone())) + } + "executor" => Some(MethodArgument::Executor), _ => None, }; if arg.is_some() { @@ -456,7 +476,7 @@ impl TraitMethod { ) .note(String::from( "use `#[graphql_interface(name = ...)]` attribute to specify custom argument's \ - name without requiring it being a single identifier", + name without requiring it being a single identifier", )) .emit(); return None; @@ -471,14 +491,12 @@ impl TraitMethod { return None; } - Some(InterfaceFieldArgument::Regular( - InterfaceFieldArgumentDefinition { - name, - ty: argument.ty.as_ref().clone(), - description: meta.description.as_ref().map(|d| d.as_ref().value()), - default: meta.default.as_ref().map(|v| v.as_ref().clone()), - }, - )) + Some(MethodArgument::Regular(InterfaceFieldArgumentDefinition { + name, + ty: argument.ty.as_ref().clone(), + description: meta.description.as_ref().map(|d| d.as_ref().value()), + default: meta.default.as_ref().map(|v| v.as_ref().clone()), + })) } } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index a4ab7e49d..eccfe3f49 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -548,13 +548,13 @@ struct InterfaceFieldArgumentDefinition { pub default: Option>, } -enum InterfaceFieldArgument { +enum MethodArgument { Regular(InterfaceFieldArgumentDefinition), - Context, + Context(syn::Type), Executor, } -impl InterfaceFieldArgument { +impl MethodArgument { #[must_use] pub fn as_regular(&self) -> Option<&InterfaceFieldArgumentDefinition> { if let Self::Regular(arg) = self { @@ -563,6 +563,15 @@ impl InterfaceFieldArgument { None } } + + #[must_use] + fn context_ty(&self) -> Option<&syn::Type> { + if let Self::Context(ty) = self { + Some(ty) + } else { + None + } + } } struct InterfaceFieldDefinition { @@ -571,7 +580,7 @@ struct InterfaceFieldDefinition { pub description: Option, pub deprecated: Option>, pub method: syn::Ident, - pub arguments: Vec, + pub arguments: Vec, pub is_async: bool, } @@ -988,7 +997,7 @@ impl ToTokens for InterfaceDefinition { } let (name, ty, method) = (&field.name, &field.ty, &field.method); let arguments = field.arguments.iter().map(|arg| match arg { - InterfaceFieldArgument::Regular(arg) => { + MethodArgument::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); let err_text = format!( "Internal error: missing argument `{}` - validation must have failed", @@ -996,10 +1005,10 @@ impl ToTokens for InterfaceDefinition { ); quote! { args.get::<#ty>(#name).expect(#err_text) } } - InterfaceFieldArgument::Context => quote! { + MethodArgument::Context(_) => quote! { ::juniper::FromContext::from(executor.context()) }, - InterfaceFieldArgument::Executor => quote! { &executor }, + MethodArgument::Executor => quote! { &executor }, }); Some(quote! { @@ -1045,7 +1054,7 @@ impl ToTokens for InterfaceDefinition { let method = &field.method; let arguments = field.arguments.iter().map(|arg| match arg { - InterfaceFieldArgument::Regular(arg) => { + MethodArgument::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); let err_text = format!( "Internal error: missing argument `{}` - validation must have failed", @@ -1053,10 +1062,10 @@ impl ToTokens for InterfaceDefinition { ); quote! { args.get::<#ty>(#name).expect(#err_text) } } - InterfaceFieldArgument::Context => quote! { + MethodArgument::Context(_) => quote! { ::juniper::FromContext::from(executor.context()) }, - InterfaceFieldArgument::Executor => quote! { &executor }, + MethodArgument::Executor => quote! { &executor }, }); let mut fut = quote! { ::#method(self#( , #arguments )*) }; diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index 5ebc2f9cc..f55ab2214 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -6,7 +6,7 @@ use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; use crate::{ - common::unparenthesize, + common::parse::TypeExt as _, result::GraphQLScope, util::{span_container::SpanContainer}, }; @@ -115,7 +115,7 @@ fn parse_variant_from_enum_variant( let mut iter = fields.unnamed.iter(); let first = iter.next().unwrap(); if iter.next().is_none() { - Ok(unparenthesize(&first.ty).clone()) + Ok(first.ty.unparenthesized().clone()) } else { Err(fields.span()) } diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 849e950c4..c07682126 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -18,7 +18,7 @@ use syn::{ token, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta, }; -use crate::common::{parse::ParseBufferExt as _, unparenthesize}; +use crate::common::parse::{ParseBufferExt as _, TypeExt as _}; /// Returns the name of a type. /// If the type does not end in a simple ident, `None` is returned. @@ -833,7 +833,7 @@ impl GraphQLTypeDefiniton { let interfaces = if !self.interfaces.is_empty() { let interfaces_ty = self.interfaces.iter().map(|ty| { - let mut ty: syn::Type = unparenthesize(ty).clone(); + let mut ty: syn::Type = ty.unparenthesized().clone(); if let syn::Type::TraitObject(dyn_ty) = &mut ty { let mut dyn_ty = dyn_ty.clone(); @@ -1217,7 +1217,7 @@ impl GraphQLTypeDefiniton { let interfaces = if !self.interfaces.is_empty() { let interfaces_ty = self.interfaces.iter().map(|ty| { - let mut ty: syn::Type = unparenthesize(ty).clone(); + let mut ty: syn::Type = ty.unparenthesized().clone(); if let syn::Type::TraitObject(dyn_ty) = &mut ty { let mut dyn_ty = dyn_ty.clone(); From 4a59c6708e726e485551af73a846c184be73a621 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 8 Sep 2020 18:12:42 +0300 Subject: [PATCH 43/79] Cover fallible field with test --- .../src/codegen/interface_attr.rs | 177 +++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index fb3f02749..3f8c16118 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2,7 +2,8 @@ use juniper::{ execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, - EmptySubscription, GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, + EmptySubscription, FieldError, FieldResult, GraphQLObject, GraphQLType, IntoFieldError, + RootNode, ScalarValue, Variables, }; fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> @@ -806,6 +807,176 @@ mod explicit_async { } } +mod fallible_field { + use super::*; + + struct CustomError; + + impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + } + } + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + fn id(&self) -> Result<&str, CustomError>; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + fn id(&self) -> Result<&str, CustomError> { + Ok(&self.id) + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + fn id(&self) -> Result<&str, CustomError> { + Ok(&self.id) + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Character", + "kind": "INTERFACE", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }] + }}), + vec![], + )), + ); + } +} + mod generic { use super::*; @@ -2245,6 +2416,7 @@ mod explicit_custom_context { use super::*; pub struct CustomContext; + impl juniper::Context for CustomContext {} #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, context = CustomContext)] @@ -2405,6 +2577,7 @@ mod inferred_custom_context_from_field { use super::*; pub struct CustomContext(String); + impl juniper::Context for CustomContext {} #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] @@ -2554,6 +2727,7 @@ mod inferred_custom_context_from_downcast { struct Database { droid: Option, } + impl juniper::Context for Database {} #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] @@ -2962,6 +3136,7 @@ mod external_downcast { struct Database { droid: Option, } + impl juniper::Context for Database {} #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] From d8ea7c93003797a11024da96b22ce9b056898fe4 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 8 Sep 2020 19:24:00 +0300 Subject: [PATCH 44/79] Impl explicit generic ScalarValue, vol.1 --- .../src/codegen/interface_attr.rs | 127 ++++++++++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 105 ++++++++++++--- 2 files changed, 212 insertions(+), 20 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 3f8c16118..220c2746e 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2216,6 +2216,7 @@ mod explicit_scalar { ch } } + #[tokio::test] async fn resolves_human() { const DOC: &str = r#"{ @@ -2412,6 +2413,132 @@ mod custom_scalar { } } +mod explicit_generic_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, scalar = S)] + trait Character { + fn id(&self) -> FieldResult<&str, S>; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface(scalar = S)] + impl Character for Human { + fn id(&self) -> FieldResult<&str, S> { + Ok(&self.id) + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface(scalar = S)] + impl Character for Droid { + fn id(&self) -> FieldResult<&str, S> { + Ok(&self.id) + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + mod explicit_custom_context { use super::*; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c0ee31d98..bc2bdd16e 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -139,6 +139,32 @@ pub fn expand_on_trait( .cloned() }); + let is_implicit_generic_scalar = meta.scalar.is_none(); + let is_explicit_generic_scalar = meta + .scalar + .as_ref() + .map(|sc| { + ast.generics + .params + .iter() + .find(|p| { + if let syn::GenericParam::Type(tp) = p { + let ident = &tp.ident; + let ty: syn::Type = parse_quote! { #ident }; + &ty == sc.as_ref() + } else { + false + } + }) + .is_some() + }) + .unwrap_or_default(); + let scalar = meta + .scalar + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| parse_quote! { GraphQLScalarValue }); + let is_async_trait = meta.asyncness.is_some() || ast .items @@ -170,18 +196,28 @@ pub fn expand_on_trait( implementers, }; - ast.generics.params.push(parse_quote! { - GraphQLScalarValue: ::juniper::ScalarValue = ::juniper::DefaultScalarValue - }); + ast.attrs + .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); + + if is_implicit_generic_scalar { + ast.generics.params.push(parse_quote! { + GraphQLScalarValue: ::juniper::ScalarValue = ::juniper::DefaultScalarValue + }); + } + if is_implicit_generic_scalar { + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } ast.supertraits.push(parse_quote! { - ::juniper::AsDynGraphQLValue + ::juniper::AsDynGraphQLValue<#scalar> }); if is_async_trait && has_default_async_methods { // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits ast.supertraits.push(parse_quote! { Sync }); } - ast.attrs - .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); + if is_async_trait { inject_async_trait( &mut ast.attrs, @@ -220,28 +256,58 @@ pub fn expand_on_impl( }) .is_some(); - let is_generic_scalar = meta.scalar.is_none(); + let is_implicit_generic_scalar = meta.scalar.is_none(); + let is_explicit_generic_scalar = meta + .scalar + .as_ref() + .map(|sc| { + ast.generics + .params + .iter() + .find(|p| { + if let syn::GenericParam::Type(tp) = p { + let ident = &tp.ident; + let ty: syn::Type = parse_quote! { #ident }; + &ty == sc.as_ref() + } else { + false + } + }) + .is_some() + }) + .unwrap_or_default(); ast.attrs .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); - if is_generic_scalar { + if is_implicit_generic_scalar { ast.generics.params.push(parse_quote! { GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync }); } - - let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); - let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + if is_explicit_generic_scalar { + let scalar = &meta.scalar; + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { + #scalar: ::juniper::ScalarValue + Send + Sync + }); } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(if is_generic_scalar { - parse_quote! { GraphQLScalarValue } - } else { - syn::GenericArgument::Type(meta.scalar.clone().unwrap().into_inner()) - }); + + if !is_explicit_generic_scalar { + let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); + let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; + if let syn::PathArguments::None = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + } + if let syn::PathArguments::AngleBracketed(a) = trait_params { + a.args.push(if is_implicit_generic_scalar { + parse_quote! { GraphQLScalarValue } + } else { + syn::GenericArgument::Type(meta.scalar.clone().unwrap().into_inner()) + }); + } } if is_async_trait { @@ -265,7 +331,6 @@ fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generi where M: IntoIterator, { - attrs.push(parse_quote! { #[allow(clippy::type_repetition_in_bounds)] }); attrs.push(parse_quote! { #[::juniper::async_trait] }); for method in methods.into_iter() { From 8bcd8998223f2e7222e696529da7bb2fbe28ea4e Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 9 Sep 2020 17:53:58 +0300 Subject: [PATCH 45/79] Impl explicit generic ScalarValue, vol.2 --- .../src/codegen/interface_attr.rs | 2 +- juniper_codegen/src/common/mod.rs | 47 +++++++++- juniper_codegen/src/common/parse/mod.rs | 59 +++++++++++- juniper_codegen/src/graphql_interface/attr.rs | 93 +++++++++---------- juniper_codegen/src/graphql_interface/mod.rs | 83 ++++++++++------- 5 files changed, 199 insertions(+), 85 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 220c2746e..2c651af52 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2417,7 +2417,7 @@ mod explicit_generic_scalar { use super::*; #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, scalar = S)] - trait Character { + trait Character { fn id(&self) -> FieldResult<&str, S>; } diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index ea10b7c64..fb1d54cfc 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,6 +1,8 @@ pub(crate) mod parse; -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::parse_quote; pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { use syn::{GenericArgument as GA, Type as T}; @@ -76,3 +78,46 @@ pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { | T::__Nonexhaustive => {} } } + +#[derive(Clone, Debug)] +pub(crate) enum ScalarValueType { + Concrete(syn::Type), + ExplicitGeneric(syn::Ident), + ImplicitGeneric, +} + +impl ScalarValueType { + #[must_use] + pub(crate) fn is_generic(&self) -> bool { + matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric) + } + + #[must_use] + pub(crate) fn is_explicit_generic(&self) -> bool { + matches!(self, Self::ExplicitGeneric(_)) + } + + #[must_use] + pub(crate) fn is_implicit_generic(&self) -> bool { + matches!(self, Self::ImplicitGeneric) + } + + #[must_use] + pub(crate) fn as_tokens(&self) -> Option { + match self { + Self::Concrete(ty) => Some(quote! { #ty }), + Self::ExplicitGeneric(ty_param) => Some(quote! { #ty_param }), + Self::ImplicitGeneric => None, + } + } + + #[must_use] + pub(crate) fn default_scalar(&self) -> syn::Type { + match self { + Self::Concrete(ty) => ty.clone(), + Self::ExplicitGeneric(_) | Self::ImplicitGeneric => { + parse_quote! { ::juniper::DefaultScalarValue } + } + } + } +} diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 878b9d606..1fc916a83 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod attr; pub(crate) mod downcaster; use std::{ + mem, any::TypeId, iter::{self, FromIterator as _}, }; @@ -11,6 +12,7 @@ use syn::{ parse::{Parse, ParseBuffer}, punctuated::Punctuated, token::{self, Token}, + parse_quote, }; pub(crate) trait ParseBufferExt { @@ -111,4 +113,59 @@ impl TypeExt for syn::Type { ty => ty, } } -} \ No newline at end of file +} + +pub(crate) trait GenericsExt { + fn remove_defaults(&mut self); + + fn move_bounds_to_where_clause(&mut self); +} + +impl GenericsExt for syn::Generics { + fn remove_defaults(&mut self) { + use syn::GenericParam as P; + + for p in &mut self.params { + match p { + P::Type(p) => { + p.eq_token = None; + p.default = None; + } + P::Lifetime(_) => {} + P::Const(p) => { + p.eq_token = None; + p.default = None; + } + } + } + } + + fn move_bounds_to_where_clause(&mut self) { + use syn::GenericParam as P; + + let _ = self.make_where_clause(); + let where_clause = self.where_clause.as_mut().unwrap(); + + for p in &mut self.params { + match p { + P::Type(p) => { + if p.colon_token.is_some() { + p.colon_token = None; + let bounds = mem::take(&mut p.bounds); + let ty = &p.ident; + where_clause.predicates.push(parse_quote! { #ty: #bounds }); + } + } + P::Lifetime(p) => { + if p.colon_token.is_some() { + p.colon_token = None; + let bounds = mem::take(&mut p.bounds); + let lt = &p.lifetime; + where_clause.predicates.push(parse_quote! { #lt: #bounds }); + } + } + P::Const(_) => {} + } + } + } +} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index bc2bdd16e..d38838dab 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -10,6 +10,7 @@ use crate::{ common::{ anonymize_lifetimes, parse::{self, TypeExt as _}, + ScalarValueType, }, result::GraphQLScope, util::{ @@ -139,31 +140,27 @@ pub fn expand_on_trait( .cloned() }); - let is_implicit_generic_scalar = meta.scalar.is_none(); - let is_explicit_generic_scalar = meta + let scalar_ty = meta .scalar .as_ref() .map(|sc| { ast.generics .params .iter() - .find(|p| { + .find_map(|p| { if let syn::GenericParam::Type(tp) = p { let ident = &tp.ident; let ty: syn::Type = parse_quote! { #ident }; - &ty == sc.as_ref() - } else { - false + if &ty == sc.as_ref() { + return Some(&tp.ident); + } } + None }) - .is_some() + .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) + .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) }) - .unwrap_or_default(); - let scalar = meta - .scalar - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| parse_quote! { GraphQLScalarValue }); + .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); let is_async_trait = meta.asyncness.is_some() || ast @@ -190,7 +187,7 @@ pub fn expand_on_trait( visibility: ast.vis.clone(), description: meta.description.map(SpanContainer::into_inner), context, - scalar: meta.scalar.map(SpanContainer::into_inner), + scalar: scalar_ty.clone(), generics: ast.generics.clone(), fields, implementers, @@ -199,20 +196,23 @@ pub fn expand_on_trait( ast.attrs .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); - if is_implicit_generic_scalar { - ast.generics.params.push(parse_quote! { - GraphQLScalarValue: ::juniper::ScalarValue = ::juniper::DefaultScalarValue - }); - } - if is_implicit_generic_scalar { + let scalar = if scalar_ty.is_explicit_generic() { + scalar_ty.as_tokens().unwrap() + } else { + quote! { GraphQLScalarValue } + }; + let default_scalar = scalar_ty.default_scalar(); + if !scalar_ty.is_explicit_generic() { ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + .params + .push(parse_quote! { #scalar = #default_scalar }); } - ast.supertraits.push(parse_quote! { - ::juniper::AsDynGraphQLValue<#scalar> - }); + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + ast.supertraits + .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar> }); if is_async_trait && has_default_async_methods { // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits ast.supertraits.push(parse_quote! { Sync }); @@ -256,57 +256,52 @@ pub fn expand_on_impl( }) .is_some(); - let is_implicit_generic_scalar = meta.scalar.is_none(); - let is_explicit_generic_scalar = meta + let scalar_ty = meta .scalar .as_ref() .map(|sc| { ast.generics .params .iter() - .find(|p| { + .find_map(|p| { if let syn::GenericParam::Type(tp) = p { let ident = &tp.ident; let ty: syn::Type = parse_quote! { #ident }; - &ty == sc.as_ref() - } else { - false + if &ty == sc.as_ref() { + return Some(&tp.ident); + } } + None }) - .is_some() + .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) + .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) }) - .unwrap_or_default(); + .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); ast.attrs .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); - if is_implicit_generic_scalar { - ast.generics.params.push(parse_quote! { - GraphQLScalarValue: ::juniper::ScalarValue + Send + Sync - }); + let scalar = scalar_ty + .as_tokens() + .unwrap_or_else(|| quote! { GraphQLScalarValue }); + if scalar_ty.is_implicit_generic() { + ast.generics.params.push(parse_quote! { #scalar }); } - if is_explicit_generic_scalar { - let scalar = &meta.scalar; + if scalar_ty.is_generic() { ast.generics .make_where_clause() .predicates - .push(parse_quote! { - #scalar: ::juniper::ScalarValue + Send + Sync - }); + .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); } - if !is_explicit_generic_scalar { + if !scalar_ty.is_explicit_generic() { let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; if let syn::PathArguments::None = trait_params { *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); } if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(if is_implicit_generic_scalar { - parse_quote! { GraphQLScalarValue } - } else { - syn::GenericArgument::Type(meta.scalar.clone().unwrap().into_inner()) - }); + a.args.push(parse_quote! { #scalar }); } } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index eccfe3f49..0403de17e 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -16,9 +16,12 @@ use syn::{ }; use crate::{ - common::parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, + common::{ + parse::{ + attr::{err, OptionExt as _}, + GenericsExt as _, ParseBufferExt as _, + }, + ScalarValueType, }, util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, }; @@ -667,7 +670,7 @@ struct InterfaceDefinition { /// `juniper::ScalarValue` type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub scalar: Option, + pub scalar: ScalarValueType, pub fields: Vec, @@ -688,12 +691,7 @@ impl ToTokens for InterfaceDefinition { .map(|ctx| quote! { #ctx }) .unwrap_or_else(|| quote! { () }); - let scalar = self - .scalar - .as_ref() - .map(|scl| quote! { #scl }) - .unwrap_or_else(|| quote! { __S }); - let is_generic_scalar = self.scalar.is_none(); + let scalar = self.scalar.as_tokens().unwrap_or_else(|| quote! { __S }); let description = self .description @@ -916,14 +914,21 @@ impl ToTokens for InterfaceDefinition { (panic.clone(), panic) }; - let (_, ty_generics, _) = self.generics.split_for_impl(); + let mut generics = self.generics.clone(); + if self.trait_object.is_some() { + generics.remove_defaults(); + generics.move_bounds_to_where_clause(); + } + let (_, ty_generics, _) = generics.split_for_impl(); - let mut ext_generics = self.generics.clone(); + let mut ext_generics = generics.clone(); if self.trait_object.is_some() { ext_generics.params.push(parse_quote! { '__obj }); } - if is_generic_scalar { + if self.scalar.is_implicit_generic() { ext_generics.params.push(parse_quote! { #scalar }); + } + if self.scalar.is_generic() { ext_generics .make_where_clause() .predicates @@ -935,25 +940,32 @@ impl ToTokens for InterfaceDefinition { .cloned() .unwrap_or_else(|| parse_quote! { where }); where_async.predicates.push(parse_quote! { Self: Sync }); - if is_generic_scalar { + if self.scalar.is_generic() { where_async .predicates .push(parse_quote! { #scalar: Send + Sync }); } let mut ty_full = quote! { #ty#ty_generics }; - let mut ty_interface = quote! { #ty#ty_generics }; + let mut ty_interface = ty_full.clone(); if self.trait_object.is_some() { let mut ty_params = None; - if !self.generics.params.is_empty() { - let params = &self.generics.params; + if !generics.params.is_empty() { + let params = &generics.params; ty_params = Some(quote! { #params, }); }; - ty_full = quote! { - dyn #ty<#ty_params #scalar, Context = #context, TypeInfo = ()> + - '__obj + Send + Sync + + let scalar = if self.scalar.is_explicit_generic() { + None + } else { + Some(&scalar) }; ty_interface = quote! { #ty<#ty_params #scalar> }; + + let scalar = scalar.map(|sc| quote! { #sc, }); + ty_full = quote! { + dyn #ty<#ty_params #scalar Context = #context, TypeInfo = ()> + '__obj + Send + Sync + }; } let mut dyn_alias = quote! {}; @@ -966,19 +978,24 @@ impl ToTokens for InterfaceDefinition { quote! { #ty }, ); - let mut ty_params = None; - if !self.generics.params.is_empty() { - let params = &self.generics.params; - ty_params = Some(quote! { #params }); - }; - let (scalar_left, scalar_right) = if is_generic_scalar { - let left = Some(quote! { , S = ::juniper::DefaultScalarValue }); - (left, quote! { S }) - } else { - (None, scalar.clone()) + let (mut ty_params_left, mut ty_params_right) = (None, None); + if !generics.params.is_empty() { + let params = &generics.params; + ty_params_right = Some(quote! { #params, }); + + // We should preserve defaults for left side. + let mut generics = self.generics.clone(); + generics.move_bounds_to_where_clause(); + let params = &generics.params; + ty_params_left = Some(quote! { , #params }); }; - let ty_params_left = ty_params.as_ref().map(|ps| quote! { , #ps }); - let ty_params_right = ty_params.map(|ps| quote! { #ps, }); + + let (mut scalar_left, mut scalar_right) = (None, None); + if !self.scalar.is_explicit_generic() { + let default_scalar = self.scalar.default_scalar(); + scalar_left = Some(quote! { , S = #default_scalar }); + scalar_right = Some(quote! { S, }); + } let vis = &self.visibility; @@ -986,7 +1003,7 @@ impl ToTokens for InterfaceDefinition { #[allow(unused_qualifications)] #[doc = #doc] #vis type #alias<'a #ty_params_left #scalar_left> = - dyn #ty<#ty_params_right #scalar_right, Context = #context, TypeInfo = ()> + + dyn #ty<#ty_params_right #scalar_right Context = #context, TypeInfo = ()> + 'a + Send + Sync; } } From a237715ac0f81586ef6faedfd9cca471d94f3b38 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 10 Sep 2020 16:49:22 +0300 Subject: [PATCH 46/79] Allow passing executor into methods --- .../src/codegen/interface_attr.rs | 157 +++++++++++++++++- juniper/src/executor/look_ahead.rs | 23 +-- 2 files changed, 165 insertions(+), 15 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 2c651af52..f48285fad 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2,8 +2,8 @@ use juniper::{ execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, - EmptySubscription, FieldError, FieldResult, GraphQLObject, GraphQLType, IntoFieldError, - RootNode, ScalarValue, Variables, + EmptySubscription, Executor, FieldError, FieldResult, GraphQLObject, GraphQLType, + IntoFieldError, RootNode, ScalarValue, Variables, }; fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> @@ -2667,7 +2667,7 @@ mod explicit_custom_context { } #[tokio::test] - async fn resolves_id_field() { + async fn resolves_fields() { const DOC: &str = r#"{ character { id @@ -2820,7 +2820,7 @@ mod inferred_custom_context_from_field { } #[tokio::test] - async fn resolves_id_field() { + async fn resolves_fields() { const DOC: &str = r#"{ character { id @@ -2976,7 +2976,7 @@ mod inferred_custom_context_from_downcast { } #[tokio::test] - async fn resolves_id_field() { + async fn resolves_info_field() { const DOC: &str = r#"{ character { info @@ -2999,6 +2999,153 @@ mod inferred_custom_context_from_downcast { } } +mod executor { + use juniper::LookAheadMethods as _; + + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, scalar = S)] + trait Character { + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync, + { + executor.look_ahead().field_name() + } + + async fn info<'b>( + &'b self, + #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface(scalar = S)] + impl Character for Human { + async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + where + S: Send + Sync, + { + &self.home_planet + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface(scalar = S)] + impl Character for Droid { + async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + where + S: Send + Sync, + { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"id": "id", "info": expected_info}}), + vec![], + )), + ); + } + } +} + mod ignored_method { use super::*; diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index c5e05b6a4..ea15f6b1e 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -330,10 +330,13 @@ pub struct ConcreteLookAheadSelection<'a, S: 'a> { children: Vec>, } -/// A set of common methods for `ConcreteLookAheadSelection` and `LookAheadSelection` -pub trait LookAheadMethods { +/// Set of common methods for `ConcreteLookAheadSelection` and `LookAheadSelection`. +/// +/// `'sel` lifetime is intended to point to the data that this `LookAheadSelection` (or +/// `ConcreteLookAheadSelection`) points to. +pub trait LookAheadMethods<'sel, S> { /// Get the (potentially aliased) name of the field represented by the current selection - fn field_name(&self) -> &str; + fn field_name(&self) -> &'sel str; /// Get the the child selection for a given field /// If a child has an alias, it will only match if the alias matches `name` @@ -364,14 +367,14 @@ pub trait LookAheadMethods { /// Get the (possibly aliased) names of the top level children for the current selection #[deprecated(note = "please use `children` to access the child selections instead")] - fn child_names(&self) -> Vec<&str>; + fn child_names(&self) -> Vec<&'sel str>; /// Get an iterator over the children for the current selection fn children(&self) -> Vec<&Self>; } -impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { - fn field_name(&self) -> &str { +impl<'a, S> LookAheadMethods<'a, S> for ConcreteLookAheadSelection<'a, S> { + fn field_name(&self) -> &'a str { self.alias.unwrap_or(self.name) } @@ -383,7 +386,7 @@ impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { &self.arguments } - fn child_names(&self) -> Vec<&str> { + fn child_names(&self) -> Vec<&'a str> { self.children.iter().map(|c| c.field_name()).collect() } @@ -400,8 +403,8 @@ impl<'a, S> LookAheadMethods for ConcreteLookAheadSelection<'a, S> { } } -impl<'a, S> LookAheadMethods for LookAheadSelection<'a, S> { - fn field_name(&self) -> &str { +impl<'a, S> LookAheadMethods<'a, S> for LookAheadSelection<'a, S> { + fn field_name(&self) -> &'a str { self.alias.unwrap_or(self.name) } @@ -416,7 +419,7 @@ impl<'a, S> LookAheadMethods for LookAheadSelection<'a, S> { &self.arguments } - fn child_names(&self) -> Vec<&str> { + fn child_names(&self) -> Vec<&'a str> { self.children.iter().map(|c| c.inner.field_name()).collect() } From a615c211046132c1b9741a53f7b2de07bcc1122f Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 14 Sep 2020 17:20:13 +0300 Subject: [PATCH 47/79] Generating enum, vol.1 --- .../src/codegen/interface_attr.rs | 4 +- juniper_codegen/src/graphql_interface/attr.rs | 117 ++++-- juniper_codegen/src/graphql_interface/mod.rs | 332 +++++++++++++++--- 3 files changed, 361 insertions(+), 92 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index f48285fad..5123f5948 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -21,8 +21,8 @@ where mod trivial { use super::*; - #[graphql_interface(for = [Human, Droid])] - trait Character { + #[graphql_interface(enum = Actor, for = [Human, Droid])] + trait ActorInterface { fn id(&self) -> &str; } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index d38838dab..a7962a779 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -3,7 +3,7 @@ use std::mem; use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens as _}; +use quote::{format_ident, quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -19,9 +19,10 @@ use crate::{ }; use super::{ - ArgumentMeta, ImplementerDefinition, ImplementerDowncastDefinition, ImplementerMeta, - InterfaceDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceMeta, - MethodArgument, TraitMethodMeta, + inject_async_trait, ArgumentMeta, DynValue, EnumValue, ImplementerDefinition, + ImplementerDowncastDefinition, ImplementerMeta, InterfaceDefinition, + InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceMeta, MethodArgument, + TraitMethodMeta, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -180,17 +181,26 @@ pub fn expand_on_trait( }) .is_some(); + let is_trait_object = meta.as_dyn.is_some(); + let ty = if is_trait_object { + trait_ident.clone() + } else if let Some(enum_ident) = &meta.as_enum { + enum_ident.as_ref().clone() + } else { + format_ident!("{}Value", trait_ident) + }; + let generated_code = InterfaceDefinition { name, - ty: parse_quote! { #trait_ident }, - trait_object: Some(meta.alias.map(|a| a.as_ref().clone())), + ty, + trait_object: meta.as_dyn.as_ref().map(|a| a.as_ref().clone()), visibility: ast.vis.clone(), description: meta.description.map(SpanContainer::into_inner), - context, + context: context.clone(), scalar: scalar_ty.clone(), generics: ast.generics.clone(), fields, - implementers, + implementers: implementers.clone(), }; ast.attrs @@ -232,9 +242,73 @@ pub fn expand_on_trait( ); } + let value_type = if is_trait_object { + let dyn_alias = DynValue { + ident: meta.as_dyn.as_ref().unwrap().as_ref().clone(), + visibility: ast.vis.clone(), + trait_ident: trait_ident.clone(), + trait_generics: ast.generics.clone(), + scalar: scalar_ty.clone(), + context, + }; + quote! { #dyn_alias } + } else { + let enum_type = EnumValue { + ident: meta + .as_enum + .as_ref() + .map(SpanContainer::as_ref) + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident)), + visibility: ast.vis.clone(), + variants: implementers + .iter() + .map(|impler| impler.ty.clone()) + .collect(), + trait_ident: trait_ident.clone(), + trait_generics: ast.generics.clone(), + trait_types: ast + .items + .iter() + .filter_map(|i| { + if let syn::TraitItem::Type(ty) = i { + Some((ty.ident.clone(), ty.generics.clone())) + } else { + None + } + }) + .collect(), + trait_consts: ast + .items + .iter() + .filter_map(|i| { + if let syn::TraitItem::Const(cnst) = i { + Some((cnst.ident.clone(), cnst.ty.clone())) + } else { + None + } + }) + .collect(), + trait_methods: ast + .items + .iter() + .filter_map(|i| { + if let syn::TraitItem::Method(m) = i { + Some(m.sig.clone()) + } else { + None + } + }) + .collect(), + }; + quote! { #enum_type } + }; + Ok(quote! { #ast + #value_type + #generated_code }) } @@ -322,33 +396,6 @@ pub fn expand_on_impl( Ok(quote! { #ast }) } -fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) -where - M: IntoIterator, -{ - attrs.push(parse_quote! { #[::juniper::async_trait] }); - - for method in methods.into_iter() { - if method.asyncness.is_some() { - let where_clause = &mut method.generics.make_where_clause().predicates; - for p in &generics.params { - let ty_param = match p { - syn::GenericParam::Type(t) => { - let ty_param = &t.ident; - quote! { #ty_param } - } - syn::GenericParam::Lifetime(l) => { - let ty_param = &l.lifetime; - quote! { #ty_param } - } - syn::GenericParam::Const(_) => continue, - }; - where_clause.push(parse_quote! { #ty_param: 'async_trait }); - } - } - } -} - enum TraitMethod { Field(InterfaceFieldDefinition), Downcast(ImplementerDefinition), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0403de17e..ef0f04c1b 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -7,7 +7,7 @@ pub mod attr; use std::collections::{HashMap, HashSet}; use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens, TokenStreamExt as _}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; use syn::{ parse::{Parse, ParseStream}, parse_quote, @@ -50,6 +50,17 @@ struct InterfaceMeta { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions pub description: Option>, + pub as_enum: Option>, + + pub as_dyn: Option>, + + /// Explicitly specified Rust types of [GraphQL objects][2] implementing this + /// [GraphQL interface][1] type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Objects + pub implementers: HashSet>, + /// Explicitly specified type of `juniper::Context` to use for resolving this /// [GraphQL interface][1] type with. /// @@ -70,15 +81,6 @@ struct InterfaceMeta { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub scalar: Option>, - /// Explicitly specified Rust types of [GraphQL objects][2] implementing this - /// [GraphQL interface][1] type. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - pub implementers: HashSet>, - - pub alias: Option>, - pub asyncness: Option>, /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers. @@ -159,7 +161,15 @@ impl Parse for InterfaceMeta { input.parse::()?; let alias = input.parse::()?; output - .alias + .as_dyn + .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "enum" => { + input.parse::()?; + let alias = input.parse::()?; + output + .as_enum .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -204,7 +214,8 @@ impl InterfaceMeta { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), - alias: try_merge_opt!(alias: self, another), + as_dyn: try_merge_opt!(as_dyn: self, another), + as_enum: try_merge_opt!(as_enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), external_downcasts: try_merge_hashmap!( external_downcasts: self, another => span_joined @@ -220,6 +231,15 @@ impl InterfaceMeta { .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + if let Some(as_dyn) = &meta.as_dyn { + if meta.as_enum.is_some() { + return Err(syn::Error::new( + as_dyn.span(), + "`dyn` attribute argument is not composable with `enum` attribute argument", + )); + } + } + if meta.description.is_none() { meta.description = get_doc_comment(attrs); } @@ -587,6 +607,7 @@ struct InterfaceFieldDefinition { pub is_async: bool, } +#[derive(Clone)] enum ImplementerDowncastDefinition { Method { name: syn::Ident, @@ -600,6 +621,7 @@ enum ImplementerDowncastDefinition { /// Definition of [GraphQL interface][1] implementer for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Clone)] struct ImplementerDefinition { /// Rust type that this [GraphQL interface][1] implementer resolves into. /// @@ -636,14 +658,14 @@ struct InterfaceDefinition { /// Rust type that this [GraphQL interface][1] is represented with. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub ty: syn::Type, + pub ty: syn::Ident, /// Generics of the Rust type that this [GraphQL interface][1] is implemented for. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub generics: syn::Generics, - pub trait_object: Option>, + pub trait_object: Option, pub visibility: syn::Visibility, @@ -968,46 +990,6 @@ impl ToTokens for InterfaceDefinition { }; } - let mut dyn_alias = quote! {}; - if let Some(Some(alias)) = self.trait_object.as_ref().as_ref() { - let doc = format!( - "Helper alias for the `{}` [trait object][2] implementing [GraphQL interface][1].\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces\n\ - [2]: https://doc.rust-lang.org/reference/types/trait-object.html", - quote! { #ty }, - ); - - let (mut ty_params_left, mut ty_params_right) = (None, None); - if !generics.params.is_empty() { - let params = &generics.params; - ty_params_right = Some(quote! { #params, }); - - // We should preserve defaults for left side. - let mut generics = self.generics.clone(); - generics.move_bounds_to_where_clause(); - let params = &generics.params; - ty_params_left = Some(quote! { , #params }); - }; - - let (mut scalar_left, mut scalar_right) = (None, None); - if !self.scalar.is_explicit_generic() { - let default_scalar = self.scalar.default_scalar(); - scalar_left = Some(quote! { , S = #default_scalar }); - scalar_right = Some(quote! { S, }); - } - - let vis = &self.visibility; - - dyn_alias = quote! { - #[allow(unused_qualifications)] - #[doc = #doc] - #vis type #alias<'a #ty_params_left #scalar_left> = - dyn #ty<#ty_params_right #scalar_right Context = #context, TypeInfo = ()> + - 'a + Send + Sync; - } - } - let fields_sync_resolvers = self.fields.iter().filter_map(|field| { if field.is_async { return None; @@ -1248,7 +1230,6 @@ impl ToTokens for InterfaceDefinition { }; into.append_all(&[ - dyn_alias, interface_impl, output_type_impl, type_impl, @@ -1257,3 +1238,244 @@ impl ToTokens for InterfaceDefinition { ]); } } + +struct EnumValue { + pub ident: syn::Ident, + pub visibility: syn::Visibility, + pub variants: Vec, + pub trait_ident: syn::Ident, + pub trait_generics: syn::Generics, + pub trait_types: Vec<(syn::Ident, syn::Generics)>, + pub trait_consts: Vec<(syn::Ident, syn::Type)>, + pub trait_methods: Vec, +} + +impl EnumValue { + fn variant_ident(num: usize) -> syn::Ident { + format_ident!("Impl{}", num) + } + + fn to_type_definition_tokens(&self) -> TokenStream { + let enum_ty = &self.ident; + let vis = &self.visibility; + + let doc = format!( + "Type implementing [GraphQL interface][1] represented by `{}` trait.\ + \n\n\ + [1]: https://spec.graphql.org/June2018/#sec-Interfaces", + self.trait_ident, + ); + + let variants = self.variants.iter().enumerate().map(|(n, ty)| { + let variant = Self::variant_ident(n); + + quote! { #variant(#ty), } + }); + + quote! { + #[automatically_derived] + #[doc = #doc] + #vis enum #enum_ty { + #( #variants )* + } + } + } + + fn to_from_impls_tokens(&self) -> impl Iterator + '_ { + let enum_ty = &self.ident; + + self.variants.iter().enumerate().map(move |(n, ty)| { + let variant = Self::variant_ident(n); + + quote! { + #[automatically_derived] + impl From<#ty> for #enum_ty { + fn from(v: #ty) -> Self { + Self::#variant(v) + } + } + } + }) + } + + fn to_trait_impl_tokens(&self) -> TokenStream { + let enum_ty = &self.ident; + + let trait_ident = &self.trait_ident; + let (trait_params, trait_generics, where_clause) = self.trait_generics.split_for_impl(); + + let var_ty = self.variants.first().unwrap(); + + let assoc_types = self.trait_types.iter().map(|(ty, ty_gen)| { + quote! { + type #ty#ty_gen = <#var_ty as #trait_ident#trait_generics>::#ty#ty_gen; + } + }); + + let assoc_consts = self.trait_consts.iter().map(|(ident, ty)| { + quote! { + const #ident: #ty = <#var_ty as #trait_ident#trait_generics>::#ident; + } + }); + + let methods = self.trait_methods.iter().map(|sig| { + let method = &sig.ident; + + let args = sig.inputs.iter().filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(a) => Some(&a.pat), + }); + + let and_await = if sig.asyncness.is_some() { + Some(quote! { .await }) + } else { + None + }; + + let match_arms = self.variants.iter().enumerate().map(|(n, ty)| { + let variant = Self::variant_ident(n); + let args = args.clone(); + + quote! { + Self::#variant(v) => + <#ty as #trait_ident#trait_generics>::#method(v #( , #args )* )#and_await, + } + }); + + quote! { + #sig { + match self { + #( #match_arms )* + } + } + } + }); + + let mut impl_tokens = quote! { + #[automatically_derived] + impl#trait_params #trait_ident#trait_generics for #enum_ty #where_clause { + #( #assoc_types )* + + #( #assoc_consts )* + + #( #methods )* + } + }; + + if self + .trait_methods + .iter() + .find(|sig| sig.asyncness.is_some()) + .is_some() + { + let mut ast: syn::ItemImpl = parse_quote! { #impl_tokens }; + inject_async_trait( + &mut ast.attrs, + ast.items.iter_mut().filter_map(|i| { + if let syn::ImplItem::Method(m) = i { + Some(&mut m.sig) + } else { + None + } + }), + &ast.generics, + ); + impl_tokens = quote! { #ast }; + } + + impl_tokens + } +} + +impl ToTokens for EnumValue { + fn to_tokens(&self, into: &mut TokenStream) { + into.append_all(&[self.to_type_definition_tokens()]); + into.append_all(self.to_from_impls_tokens()); + into.append_all(&[self.to_trait_impl_tokens()]); + } +} + +struct DynValue { + pub ident: syn::Ident, + pub visibility: syn::Visibility, + pub trait_ident: syn::Ident, + pub trait_generics: syn::Generics, + pub scalar: ScalarValueType, + pub context: Option, +} + +impl ToTokens for DynValue { + fn to_tokens(&self, into: &mut TokenStream) { + let dyn_ty = &self.ident; + let vis = &self.visibility; + + let doc = format!( + "Helper alias for the `{}` [trait object][2] implementing [GraphQL interface][1].\ + \n\n\ + [1]: https://spec.graphql.org/June2018/#sec-Interfaces\n\ + [2]: https://doc.rust-lang.org/reference/types/trait-object.html", + self.trait_ident, + ); + + let trait_ident = &self.trait_ident; + + let (mut ty_params_left, mut ty_params_right) = (None, None); + if !self.trait_generics.params.is_empty() { + // We should preserve defaults for left side. + let mut generics = self.trait_generics.clone(); + generics.move_bounds_to_where_clause(); + let params = &generics.params; + ty_params_left = Some(quote! { , #params }); + + generics.remove_defaults(); + let params = &generics.params; + ty_params_right = Some(quote! { #params, }); + }; + + let (mut scalar_left, mut scalar_right) = (None, None); + if !self.scalar.is_explicit_generic() { + let default_scalar = self.scalar.default_scalar(); + scalar_left = Some(quote! { , S = #default_scalar }); + scalar_right = Some(quote! { S, }); + } + + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + + let dyn_alias = quote! { + #[automatically_derived] + #[doc = #doc] + #vis type #dyn_ty<'a #ty_params_left #scalar_left> = + dyn #trait_ident<#ty_params_right #scalar_right Context = #context, TypeInfo = ()> + + 'a + Send + Sync; + }; + + into.append_all(&[dyn_alias]); + } +} + +fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) +where + M: IntoIterator, +{ + attrs.push(parse_quote! { #[::juniper::async_trait] }); + + for method in methods.into_iter() { + if method.asyncness.is_some() { + let where_clause = &mut method.generics.make_where_clause().predicates; + for p in &generics.params { + let ty_param = match p { + syn::GenericParam::Type(t) => { + let ty_param = &t.ident; + quote! { #ty_param } + } + syn::GenericParam::Lifetime(l) => { + let ty_param = &l.lifetime; + quote! { #ty_param } + } + syn::GenericParam::Const(_) => continue, + }; + where_clause.push(parse_quote! { #ty_param: 'async_trait }); + } + } + } +} From a1ce976ef9c67ee82cd89447da128789027beebc Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 16 Sep 2020 17:04:39 +0300 Subject: [PATCH 48/79] Generating enum, vol.2 --- juniper_codegen/src/common/gen.rs | 28 ++ juniper_codegen/src/common/mod.rs | 10 +- juniper_codegen/src/graphql_interface/attr.rs | 194 +++---- juniper_codegen/src/graphql_interface/mod.rs | 474 +++++++++++++++++- 4 files changed, 559 insertions(+), 147 deletions(-) create mode 100644 juniper_codegen/src/common/gen.rs diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs new file mode 100644 index 000000000..c2002c21c --- /dev/null +++ b/juniper_codegen/src/common/gen.rs @@ -0,0 +1,28 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +pub(crate) fn sync_resolving_code() -> TokenStream { + quote! { + ::juniper::IntoResolvable::into(res, executor.context()) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) + } +} + +pub(crate) fn async_resolving_code(ty: Option<&syn::Type>) -> TokenStream { + let ty = ty.map(|t| quote! { : #t }); + + quote! { + Box::pin(::juniper::futures::FutureExt::then(fut, move |res #ty| async move { + match ::juniper::IntoResolvable::into(res, executor.context())? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + }, + None => Ok(::juniper::Value::null()), + } + })) + } +} diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index fb1d54cfc..88cd07d50 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod parse; +pub(crate) mod gen; use proc_macro2::{Span, TokenStream}; use quote::quote; @@ -103,7 +104,7 @@ impl ScalarValueType { } #[must_use] - pub(crate) fn as_tokens(&self) -> Option { + pub(crate) fn ty_tokens(&self) -> Option { match self { Self::Concrete(ty) => Some(quote! { #ty }), Self::ExplicitGeneric(ty_param) => Some(quote! { #ty_param }), @@ -112,7 +113,12 @@ impl ScalarValueType { } #[must_use] - pub(crate) fn default_scalar(&self) -> syn::Type { + pub(crate) fn ty_tokens_default(&self) -> TokenStream { + self.ty_tokens().unwrap_or_else(|| quote! { __S }) + } + + #[must_use] + pub(crate) fn default_ty(&self) -> syn::Type { match self { Self::Concrete(ty) => ty.clone(), Self::ExplicitGeneric(_) | Self::ImplicitGeneric => { diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a7962a779..f6fb90aac 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -19,7 +19,7 @@ use crate::{ }; use super::{ - inject_async_trait, ArgumentMeta, DynValue, EnumValue, ImplementerDefinition, + inject_async_trait, ArgumentMeta, TraitObjectType, EnumType, ImplementerDefinition, ImplementerDowncastDefinition, ImplementerMeta, InterfaceDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceMeta, MethodArgument, TraitMethodMeta, @@ -203,32 +203,35 @@ pub fn expand_on_trait( implementers: implementers.clone(), }; - ast.attrs - .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); + if is_trait_object { + ast.attrs.push(parse_quote! { + #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] + }); - let scalar = if scalar_ty.is_explicit_generic() { - scalar_ty.as_tokens().unwrap() - } else { - quote! { GraphQLScalarValue } - }; - let default_scalar = scalar_ty.default_scalar(); - if !scalar_ty.is_explicit_generic() { + let scalar = if scalar_ty.is_explicit_generic() { + scalar_ty.ty_tokens().unwrap() + } else { + quote! { GraphQLScalarValue } + }; + let default_scalar = scalar_ty.default_ty(); + if !scalar_ty.is_explicit_generic() { + ast.generics + .params + .push(parse_quote! { #scalar = #default_scalar }); + } ast.generics - .params - .push(parse_quote! { #scalar = #default_scalar }); - } - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - ast.supertraits - .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar> }); - if is_async_trait && has_default_async_methods { - // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits - ast.supertraits.push(parse_quote! { Sync }); + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + ast.supertraits + .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar> }); } if is_async_trait { + if has_default_async_methods { + // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits + ast.supertraits.push(parse_quote! { Sync }); + } inject_async_trait( &mut ast.attrs, ast.items.iter_mut().filter_map(|i| { @@ -243,64 +246,10 @@ pub fn expand_on_trait( } let value_type = if is_trait_object { - let dyn_alias = DynValue { - ident: meta.as_dyn.as_ref().unwrap().as_ref().clone(), - visibility: ast.vis.clone(), - trait_ident: trait_ident.clone(), - trait_generics: ast.generics.clone(), - scalar: scalar_ty.clone(), - context, - }; + let dyn_alias = TraitObjectType::new(&ast, &meta, context); quote! { #dyn_alias } } else { - let enum_type = EnumValue { - ident: meta - .as_enum - .as_ref() - .map(SpanContainer::as_ref) - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", trait_ident)), - visibility: ast.vis.clone(), - variants: implementers - .iter() - .map(|impler| impler.ty.clone()) - .collect(), - trait_ident: trait_ident.clone(), - trait_generics: ast.generics.clone(), - trait_types: ast - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Type(ty) = i { - Some((ty.ident.clone(), ty.generics.clone())) - } else { - None - } - }) - .collect(), - trait_consts: ast - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Const(cnst) = i { - Some((cnst.ident.clone(), cnst.ty.clone())) - } else { - None - } - }) - .collect(), - trait_methods: ast - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(m.sig.clone()) - } else { - None - } - }) - .collect(), - }; + let enum_type = EnumType::new(&ast, &meta, &implementers); quote! { #enum_type } }; @@ -330,52 +279,57 @@ pub fn expand_on_impl( }) .is_some(); - let scalar_ty = meta - .scalar - .as_ref() - .map(|sc| { - ast.generics - .params - .iter() - .find_map(|p| { - if let syn::GenericParam::Type(tp) = p { - let ident = &tp.ident; - let ty: syn::Type = parse_quote! { #ident }; - if &ty == sc.as_ref() { - return Some(&tp.ident); - } - } - None - }) - .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) - .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) - }) - .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); + let is_trait_object = meta.as_dyn.is_some(); - ast.attrs - .push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); + if is_trait_object { + let scalar_ty = meta + .scalar + .as_ref() + .map(|sc| { + ast.generics + .params + .iter() + .find_map(|p| { + if let syn::GenericParam::Type(tp) = p { + let ident = &tp.ident; + let ty: syn::Type = parse_quote! { #ident }; + if &ty == sc.as_ref() { + return Some(&tp.ident); + } + } + None + }) + .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) + .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) + }) + .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); - let scalar = scalar_ty - .as_tokens() - .unwrap_or_else(|| quote! { GraphQLScalarValue }); - if scalar_ty.is_implicit_generic() { - ast.generics.params.push(parse_quote! { #scalar }); - } - if scalar_ty.is_generic() { - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); - } + ast.attrs.push(parse_quote! { + #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] + }); - if !scalar_ty.is_explicit_generic() { - let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); - let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + let scalar = scalar_ty + .ty_tokens() + .unwrap_or_else(|| quote! { GraphQLScalarValue }); + if scalar_ty.is_implicit_generic() { + ast.generics.params.push(parse_quote! { #scalar }); + } + if scalar_ty.is_generic() { + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { #scalar }); + + if !scalar_ty.is_explicit_generic() { + let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); + let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; + if let syn::PathArguments::None = trait_params { + *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); + } + if let syn::PathArguments::AngleBracketed(a) = trait_params { + a.args.push(parse_quote! { #scalar }); + } } } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index ef0f04c1b..4600f28a2 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -17,6 +17,7 @@ use syn::{ use crate::{ common::{ + gen, parse::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, @@ -256,6 +257,7 @@ impl InterfaceMeta { struct ImplementerMeta { pub scalar: Option>, pub asyncness: Option>, + pub as_dyn: Option>, } impl Parse for ImplementerMeta { @@ -273,6 +275,13 @@ impl Parse for ImplementerMeta { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "dyn" => { + let span = ident.span(); + output + .as_dyn + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))?; + } "async" => { let span = ident.span(); output @@ -297,6 +306,7 @@ impl ImplementerMeta { fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { scalar: try_merge_opt!(scalar: self, another), + as_dyn: try_merge_opt!(as_dyn: self, another), asyncness: try_merge_opt!(asyncness: self, another), }) } @@ -595,6 +605,29 @@ impl MethodArgument { None } } + + fn meta_method_tokens(&self) -> Option { + let arg = self.as_regular()?; + + let (name, ty) = (&arg.name, &arg.ty); + + let description = arg + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let method = if let Some(val) = &arg.default { + let val = val + .as_ref() + .map(|v| quote! { (#v).into() }) + .unwrap_or_else(|| quote! { <#ty as Default>::default() }); + quote! { .arg_with_default::<#ty>(#name, &#val, info) } + } else { + quote! { .arg::<#ty>(#name, info) } + }; + + Some(quote! { .argument(registry#method#description) }) + } } struct InterfaceFieldDefinition { @@ -607,6 +640,34 @@ struct InterfaceFieldDefinition { pub is_async: bool, } +impl InterfaceFieldDefinition { + fn meta_method_tokens(&self) -> TokenStream { + let (name, ty) = (&self.name, &self.ty); + + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let deprecated = self.deprecated.as_ref().map(|reason| { + let reason = reason + .as_ref() + .map(|rsn| quote! { Some(#rsn) }) + .unwrap_or_else(|| quote! { None }); + quote! { .deprecated(#reason) } + }); + + let arguments = self.arguments.iter().filter_map(MethodArgument::meta_method_tokens); + + quote! { + registry.field_convert::<#ty, _, Self::Context>(#name, info) + #( #arguments )* + #description + #deprecated + } + } +} + #[derive(Clone)] enum ImplementerDowncastDefinition { Method { @@ -713,7 +774,7 @@ impl ToTokens for InterfaceDefinition { .map(|ctx| quote! { #ctx }) .unwrap_or_else(|| quote! { () }); - let scalar = self.scalar.as_tokens().unwrap_or_else(|| quote! { __S }); + let scalar = self.scalar.ty_tokens().unwrap_or_else(|| quote! { __S }); let description = self .description @@ -1239,22 +1300,227 @@ impl ToTokens for InterfaceDefinition { } } -struct EnumValue { - pub ident: syn::Ident, - pub visibility: syn::Visibility, - pub variants: Vec, - pub trait_ident: syn::Ident, - pub trait_generics: syn::Generics, - pub trait_types: Vec<(syn::Ident, syn::Generics)>, - pub trait_consts: Vec<(syn::Ident, syn::Type)>, - pub trait_methods: Vec, +struct Definition { + ty: Type, + trait_generics: syn::Generics, + name: String, + description: Option, + context: Option, + scalar: ScalarValueType, + fields: Vec, + implementers: Vec, +} + +impl Definition { + fn type_impl_tokens(&self) -> TokenStream { + let scalar_ty = self.scalar.ty_tokens_default(); + + let generics = self.ty.impl_generics(&self.scalar); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let ty = self.ty.ty_tokens(); + + let name = &self.name; + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + // Sorting is required to preserve/guarantee the order of implementers registered in schema. + let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + impler_tys.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); + a.cmp(&b) + }); + + let fields_meta = self.fields.iter().map(InterfaceFieldDefinition::meta_method_tokens); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar_ty> for #ty #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar_ty> + ) -> ::juniper::meta::MetaType<'r, #scalar_ty> + where #scalar_ty: 'r, + { + // Ensure all implementer types are registered. + #( let _ = registry.get_type::<#impler_tys>(info); )* + + let fields = [ + #( #fields_meta, )* + ]; + registry.build_interface_type::<#ty>(info, &fields) + #description + .into_meta() + } + } + } + } + + fn interface_impl_tokens(&self) -> TokenStream { + let scalar_ty = self.scalar.ty_tokens_default(); + + let generics = self.ty.impl_generics(&self.scalar); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let ty = self.ty.ty_tokens(); + + let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + + let all_implers_unique = if impler_types.len() > 1 { + Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) + } else { + None + }; + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar_ty> for #ty #where_clause + { + fn mark() { + #all_implers_unique + + #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#scalar_ty>>::mark(); )* + } + } + } + } + + fn output_type_impl_tokens(&self) -> TokenStream { + let scalar_ty = self.scalar.ty_tokens_default(); + + let generics = self.ty.impl_generics(&self.scalar); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let ty = self.ty.ty_tokens(); + + let fields_marks = self.fields.iter().map(|field| { + let arguments_marks = field.arguments.iter().filter_map(|arg| { + let arg_ty = &arg.as_regular()?.ty; + Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar_ty>>::mark(); }) + }); + + let field_ty = &field.ty; + let resolved_ty = quote! { + <#field_ty as ::juniper::IntoResolvable< + '_, #scalar_ty, _, >::Context, + >>::Type + }; + + quote! { + #( #arguments_marks )* + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar_ty>>::mark(); + } + }); + + let impler_tys = self.implementers.iter().map(|impler| &impler.ty); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar_ty> for #ty #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar_ty>>::mark(); )* + } + } + } + } } -impl EnumValue { +struct EnumType { + ident: syn::Ident, + visibility: syn::Visibility, + variants: Vec, + trait_ident: syn::Ident, + trait_generics: syn::Generics, + trait_types: Vec<(syn::Ident, syn::Generics)>, + trait_consts: Vec<(syn::Ident, syn::Type)>, + trait_methods: Vec, +} + +impl EnumType { + fn new( + r#trait: &syn::ItemTrait, + meta: &InterfaceMeta, + implers: &Vec, + ) -> Self { + Self { + ident: meta + .as_enum + .as_ref() + .map(SpanContainer::as_ref) + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", r#trait.ident)), + visibility: r#trait.vis.clone(), + variants: implers.iter().map(|impler| impler.ty.clone()).collect(), + trait_ident: r#trait.ident.clone(), + trait_generics: r#trait.generics.clone(), + trait_types: r#trait + .items + .iter() + .filter_map(|i| { + if let syn::TraitItem::Type(ty) = i { + Some((ty.ident.clone(), ty.generics.clone())) + } else { + None + } + }) + .collect(), + trait_consts: r#trait + .items + .iter() + .filter_map(|i| { + if let syn::TraitItem::Const(cnst) = i { + Some((cnst.ident.clone(), cnst.ty.clone())) + } else { + None + } + }) + .collect(), + trait_methods: r#trait + .items + .iter() + .filter_map(|i| { + if let syn::TraitItem::Method(m) = i { + Some(m.sig.clone()) + } else { + None + } + }) + .collect(), + } + } + fn variant_ident(num: usize) -> syn::Ident { format_ident!("Impl{}", num) } + fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { + let mut generics = syn::Generics::default(); + if scalar.is_generic() { + let scalar_ty = scalar.ty_tokens_default(); + generics.params.push(parse_quote! { #scalar_ty }); + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); + } + generics + } + + fn ty_tokens(&self) -> TokenStream { + let ty = &self.ident; + + quote! { #ty } + } + fn to_type_definition_tokens(&self) -> TokenStream { let enum_ty = &self.ident; let vis = &self.visibility; @@ -1385,9 +1651,65 @@ impl EnumValue { impl_tokens } + + fn to_concrete_type_name_tokens(&self) -> TokenStream { + let match_arms = self.variants.iter().enumerate().map(|(n, ty)| { + let variant = Self::variant_ident(n); + + quote! { + Self::#variant(v) => + <#ty as ::juniper::GraphQLValue<_>>::concrete_type_name(v, context, info), + } + }); + + quote! { + match self { + #( #match_arms )* + } + } + } + + fn to_resolve_into_type_tokens(&self) -> TokenStream { + let resolving_code = gen::sync_resolving_code(); + + let match_arms = self.variants.iter().enumerate().map(|(n, _)| { + let variant = Self::variant_ident(n); + + quote! { + Self::#variant(res) => #resolving_code, + } + }); + + quote! { + match self { + #( #match_arms )* + } + } + } + + fn to_resolve_into_type_async_tokens(&self) -> TokenStream { + let resolving_code = gen::async_resolving_code(None); + + let match_arms = self.variants.iter().enumerate().map(|(n, _)| { + let variant = Self::variant_ident(n); + + quote! { + Self::#variant(v) => { + let fut = ::juniper::futures::future::ready(v); + #resolving_code + } + } + }); + + quote! { + match self { + #( #match_arms )* + } + } + } } -impl ToTokens for EnumValue { +impl ToTokens for EnumType { fn to_tokens(&self, into: &mut TokenStream) { into.append_all(&[self.to_type_definition_tokens()]); into.append_all(self.to_from_impls_tokens()); @@ -1395,16 +1717,79 @@ impl ToTokens for EnumValue { } } -struct DynValue { +struct TraitObjectType { pub ident: syn::Ident, pub visibility: syn::Visibility, pub trait_ident: syn::Ident, pub trait_generics: syn::Generics, - pub scalar: ScalarValueType, pub context: Option, } -impl ToTokens for DynValue { +impl TraitObjectType { + fn new(r#trait: &syn::ItemTrait, meta: &InterfaceMeta, context: Option) -> Self { + Self { + ident: meta.as_dyn.as_ref().unwrap().as_ref().clone(), + visibility: r#trait.vis.clone(), + trait_ident: r#trait.ident.clone(), + trait_generics: r#trait.generics.clone(), + context, + } + } + + fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { + let mut generics = self.trait_generics.clone(); + generics.params.push(parse_quote! { '__obj }); + if scalar.is_generic() { + let scalar_ty = scalar.ty_tokens_default(); + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); + } + generics + } + + fn ty_tokens(&self) -> TokenStream { + let ty = &self.trait_ident; + + let mut generics = self.trait_generics.clone(); + generics.remove_defaults(); + generics.move_bounds_to_where_clause(); + let ty_params = &generics.params; + + let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + + quote! { + dyn #ty<#ty_params, Context = #context_ty, TypeInfo = ()> + '__obj + Send + Sync + } + } + + fn to_concrete_type_name_tokens(&self) -> TokenStream { + quote! { + self.as_dyn_graphql_value().concrete_type_name(context, info) + } + } + + fn to_resolve_into_type_tokens(&self) -> TokenStream { + let resolving_code = gen::sync_resolving_code(); + + quote! { + let res = self.as_dyn_graphql_value(); + #resolving_code + } + } + + fn to_resolve_into_type_async_tokens(&self) -> TokenStream { + let resolving_code = gen::async_resolving_code(None); + + quote! { + let fut = ::juniper::futures::future::ready(self.as_dyn_graphql_value_async()); + #resolving_code + } + } +} + +impl ToTokens for TraitObjectType { fn to_tokens(&self, into: &mut TokenStream) { let dyn_ty = &self.ident; let vis = &self.visibility; @@ -1432,20 +1817,13 @@ impl ToTokens for DynValue { ty_params_right = Some(quote! { #params, }); }; - let (mut scalar_left, mut scalar_right) = (None, None); - if !self.scalar.is_explicit_generic() { - let default_scalar = self.scalar.default_scalar(); - scalar_left = Some(quote! { , S = #default_scalar }); - scalar_right = Some(quote! { S, }); - } - - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); let dyn_alias = quote! { #[automatically_derived] #[doc = #doc] - #vis type #dyn_ty<'a #ty_params_left #scalar_left> = - dyn #trait_ident<#ty_params_right #scalar_right Context = #context, TypeInfo = ()> + + #vis type #dyn_ty<'a #ty_params_left> = + dyn #trait_ident<#ty_params_right Context = #context_ty, TypeInfo = ()> + 'a + Send + Sync; }; @@ -1453,6 +1831,52 @@ impl ToTokens for DynValue { } } +enum Type { + Enum(EnumType), + TraitObject(TraitObjectType), +} + +impl Type { + fn is_trait_object(&self) -> bool { + matches!(self, Self::TraitObject(_)) + } + + fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { + match self { + Self::Enum(e) => e.impl_generics(scalar), + Self::TraitObject(o) => o.impl_generics(scalar), + } + } + + fn ty_tokens(&self) -> TokenStream { + match self { + Self::Enum(e) => e.ty_tokens(), + Self::TraitObject(o) => o.ty_tokens(), + } + } + + fn to_concrete_type_name_tokens(&self) -> TokenStream { + match self { + Self::Enum(e) => e.to_concrete_type_name_tokens(), + Self::TraitObject(o) => o.to_concrete_type_name_tokens(), + } + } + + fn to_resolve_into_type_tokens(&self) -> TokenStream { + match self { + Self::Enum(e) => e.to_resolve_into_type_tokens(), + Self::TraitObject(o) => o.to_resolve_into_type_tokens(), + } + } + + fn to_resolve_into_type_async_tokens(&self) -> TokenStream { + match self { + Self::Enum(e) => e.to_resolve_into_type_async_tokens(), + Self::TraitObject(o) => o.to_resolve_into_type_async_tokens(), + } + } +} + fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) where M: IntoIterator, From f494ea1fc3c46736a321066d3db6fdf423cbf79c Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 17 Sep 2020 14:16:55 +0300 Subject: [PATCH 49/79] Generating enum, vol.3 --- juniper_codegen/src/graphql_interface/mod.rs | 897 ++++++++----------- 1 file changed, 350 insertions(+), 547 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 4600f28a2..870ee08c0 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -628,11 +628,34 @@ impl MethodArgument { Some(quote! { .argument(registry#method#description) }) } + + fn resolve_field_method_tokens(&self) -> TokenStream { + match self { + Self::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + let err_text = format!( + "Internal error: missing argument `{}` - validation must have failed", + &name, + ); + + quote! { + args.get::<#ty>(#name).expect(#err_text) + } + } + + Self::Context(_) => quote! { + ::juniper::FromContext::from(executor.context()) + }, + + Self::Executor => quote! { &executor }, + } + } } struct InterfaceFieldDefinition { pub name: String, pub ty: syn::Type, + pub trait_ty: syn::Type, pub description: Option, pub deprecated: Option>, pub method: syn::Ident, @@ -657,7 +680,10 @@ impl InterfaceFieldDefinition { quote! { .deprecated(#reason) } }); - let arguments = self.arguments.iter().filter_map(MethodArgument::meta_method_tokens); + let arguments = self + .arguments + .iter() + .filter_map(MethodArgument::meta_method_tokens); quote! { registry.field_convert::<#ty, _, Self::Context>(#name, info) @@ -666,6 +692,53 @@ impl InterfaceFieldDefinition { #deprecated } } + + fn resolve_field_method_tokens(&self) -> Option { + if self.is_async { + return None; + } + + let (name, ty, method) = (&self.name, &self.ty, &self.method); + let interface_ty = &self.interface_ty; + + let arguments = self + .arguments + .iter() + .map(MethodArgument::resolve_field_method_tokens); + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #name => { + let res: #ty = ::#method(self #( , #arguments )*); + #resolving_code + } + }) + } + + fn resolve_field_async_method_tokens(&self) -> TokenStream { + let (name, ty, method) = (&self.name, &self.ty, &self.method); + let interface_ty = &self.interface_ty; + + let arguments = self + .arguments + .iter() + .map(MethodArgument::resolve_field_method_tokens); + + let mut fut = quote! { ::#method(self #( , #arguments )*) }; + if !self.is_async { + fut = quote! { ::juniper::futures::future::ready(#fut) }; + } + + let resolving_code = gen::async_resolving_code(Some(ty)); + + quote! { + #name => { + let fut = #fut; + #resolving_code + } + } + } } #[derive(Clone)] @@ -700,40 +773,114 @@ struct ImplementerDefinition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub context_ty: Option, - /// [`Span`] that points to the Rust source code which defines this [GraphQL interface][1] - /// implementer. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub span: Span, + pub scalar: ScalarValueType, + + pub interface_ty: syn::Type, } -/// Definition of [GraphQL interface][1] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct InterfaceDefinition { - /// Name of this [GraphQL interface][1] in GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub name: String, +impl ImplementerDefinition { + fn downcast_call_tokens(&self) -> Option { + let interface_ty = &self.interface_ty; + + let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); + + let fn_path = match self.downcast.as_ref()? { + ImplementerDowncastDefinition::Method { name, with_context } => { + if !with_context { + ctx_arg = None; + } + quote! { #interface_ty::#name } + } + ImplementerDowncastDefinition::External { path } => { + quote! { #path } + } + }; + Some(quote! { + #fn_path(self #ctx_arg) + }) + } + + fn concrete_type_name_method_tokens(&self) -> Option { + if self.downcast.is_none() { + return None; + } + + let ty = &self.ty; + let scalar_ty = self.scalar.ty_tokens_default(); + + let downcast = self.downcast_call_tokens(); + + // Doing this may be quite an expensive, because resolving may contain some heavy + // computation, so we're preforming it twice. Unfortunately, we have no other options here, + // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. + Some(quote! { + if (#downcast as ::std::option::Option<&#ty>).is_some() { + return <#ty as ::juniper::GraphQLType<#scalar_ty>>::name(info).unwrap().to_string(); + } + }) + } + + fn resolve_into_type_method_tokens(&self) -> Option { + if self.downcast.is_none() { + return None; + } + + let ty = &self.ty; + let scalar_ty = self.scalar.ty_tokens_default(); + + let downcast = self.downcast_call_tokens(); + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + if type_name == <#ty as ::juniper::GraphQLType<#scalar_ty>>::name(info).unwrap() { + let res = #downcast; + return #resolving_code; + } + }) + } + + fn resolve_into_type_async_method_tokens(&self) -> Option { + if self.downcast.is_none() { + return None; + } + + let ty = &self.ty; + let scalar_ty = self.scalar.ty_tokens_default(); + + let downcast = self.downcast_call_tokens(); + + let resolving_code = gen::async_resolving_code(None); + + Some(quote! { + if type_name == <#ty as ::juniper::GraphQLType<#scalar_ty>>::name(info).unwrap() { + let fut = ::juniper::futures::future::ready(#downcast); + return #resolving_code; + } + }) + } +} + +struct Definition { /// Rust type that this [GraphQL interface][1] is represented with. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub ty: syn::Ident, + ty: Type, - /// Generics of the Rust type that this [GraphQL interface][1] is implemented for. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub generics: syn::Generics, + trait_ident: syn::Ident, - pub trait_object: Option, + trait_generics: syn::Generics, - pub visibility: syn::Visibility, + /// Name of this [GraphQL interface][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + name: String, /// Description of this [GraphQL interface][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub description: Option, + description: Option, /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` implementation with /// for this [GraphQL interface][1]. @@ -741,7 +888,7 @@ struct InterfaceDefinition { /// If [`None`] then generated code will use unit type `()` as `juniper::Context`. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub context: Option, + context: Option, /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` implementation with /// for this [GraphQL interface][1]. @@ -753,338 +900,104 @@ struct InterfaceDefinition { /// `juniper::ScalarValue` type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub scalar: ScalarValueType, + scalar: ScalarValueType, - pub fields: Vec, + fields: Vec, /// Implementers definitions of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub implementers: Vec, + implementers: Vec, } -impl ToTokens for InterfaceDefinition { - fn to_tokens(&self, into: &mut TokenStream) { - let name = &self.name; - let ty = &self.ty; +impl Definition { + fn trait_ty(&self) -> syn::Type { + let ty = &self.trait_ident; + let (_, generics, _) = self.trait_generics.split_for_impl(); - let context = self - .context - .as_ref() - .map(|ctx| quote! { #ctx }) - .unwrap_or_else(|| quote! { () }); + parse_quote! { #ty#generics } + } - let scalar = self.scalar.ty_tokens().unwrap_or_else(|| quote! { __S }); + fn no_field_panic_tokens(&self) -> TokenStream { + let scalar_ty = self.scalar.ty_tokens_default(); + + quote! { + panic!( + "Field `{}` not found on type `{}`", + field, + >::name(info).unwrap(), + ) + } + } + + fn impl_graphql_type_tokens(&self) -> TokenStream { + let scalar_ty = self.scalar.ty_tokens_default(); + let generics = self.ty.impl_generics(&self.scalar); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let ty = self.ty.ty_tokens(); + + let name = &self.name; let description = self .description .as_ref() .map(|desc| quote! { .description(#desc) }); - let mut impler_types: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); - impler_types.sort_unstable_by(|a, b| { - let (a, b) = (quote! { #a }.to_string(), quote! { #b }.to_string()); + // Sorting is required to preserve/guarantee the order of implementers registered in schema. + let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + impler_tys.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); - let all_implers_unique = if impler_types.len() > 1 { - Some(quote! { ::juniper::sa::assert_type_ne_all!(#(#impler_types),*); }) - } else { - None - }; - - let fields_meta = self.fields.iter().map(|field| { - let (name, ty) = (&field.name, &field.ty); - - let description = field - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - let deprecated = field.deprecated.as_ref().map(|reason| { - let reason = reason - .as_ref() - .map(|rsn| quote! { Some(#rsn) }) - .unwrap_or_else(|| quote! { None }); - quote! { .deprecated(#reason) } - }); - - let arguments = field.arguments.iter().filter_map(|arg| { - let arg = arg.as_regular()?; - - let (name, ty) = (&arg.name, &arg.ty); - - let description = arg - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - let method = if let Some(val) = &arg.default { - let val = val - .as_ref() - .map(|v| quote! { (#v).into() }) - .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote! { .arg_with_default::<#ty>(#name, &#val, info) } - } else { - quote! { .arg::<#ty>(#name, info) } - }; - - Some(quote! { .argument(registry#method#description) }) - }); - - quote! { - registry.field_convert::<#ty, _, Self::Context>(#name, info) - #( #arguments )* - #description - #deprecated - } - }); - - let fields_marks = self.fields.iter().map(|field| { - let arguments_marks = field.arguments.iter().filter_map(|arg| { - let arg_ty = &arg.as_regular()?.ty; - Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) - }); - - let field_ty = &field.ty; - let resolved_ty = quote! { - <#field_ty as ::juniper::IntoResolvable< - '_, #scalar, _, >::Context, - >>::Type - }; - - quote! { - #( #arguments_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - } - }); - - let custom_downcast_checks = self.implementers.iter().filter_map(|impler| { - let impler_ty = &impler.ty; - - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); - let fn_path = match impler.downcast.as_ref()? { - ImplementerDowncastDefinition::Method { name, with_context } => { - if !with_context { - ctx_arg = None; - } - quote! { #ty::#name } - } - ImplementerDowncastDefinition::External { path } => { - quote! { #path } - } - }; - - // Doing this may be quite an expensive, because resolving may contain some heavy - // computation, so we're preforming it twice. Unfortunately, we have no other options - // here, until the `juniper::GraphQLType` itself will allow to do it in some cleverer - // way. - Some(quote! { - if ({ #fn_path(self #ctx_arg) } as ::std::option::Option<&#impler_ty>).is_some() { - return <#impler_ty as ::juniper::GraphQLType<#scalar>>::name(info) - .unwrap() - .to_string(); - } - }) - }); - let regular_downcast_check = if self.trait_object.is_some() { - quote! { - self.as_dyn_graphql_value().concrete_type_name(context, info) - } - } else { - quote! { - panic!( - "GraphQL interface {} cannot be downcast into any of its implementers in its \ - current state", - #name, - ); - } - }; - - let custom_downcasts = self.implementers.iter().filter_map(|impler| { - let impler_ty = &impler.ty; + let fields_meta = self + .fields + .iter() + .map(InterfaceFieldDefinition::meta_method_tokens); - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); - let fn_path = match impler.downcast.as_ref()? { - ImplementerDowncastDefinition::Method { name, with_context } => { - if !with_context { - ctx_arg = None; - } - quote! { #ty::#name } - } - ImplementerDowncastDefinition::External { path } => { - quote! { #path } + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar_ty> for #ty #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) } - }; - Some(quote! { - if type_name == < - #impler_ty as ::juniper::GraphQLType<#scalar> - >::name(info).unwrap() { - return ::juniper::IntoResolvable::into({ #fn_path(self #ctx_arg) }, context) - .and_then(|res| match res { - Some((ctx, r)) => executor - .replaced_context(ctx) - .resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); - } - }) - }); - let custom_async_downcasts = self.implementers.iter().filter_map(|impler| { - let impler_ty = &impler.ty; - - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); - let fn_path = match impler.downcast.as_ref()? { - ImplementerDowncastDefinition::Method { name, with_context } => { - if !with_context { - ctx_arg = None; - } - quote! { #ty::#name } - } - ImplementerDowncastDefinition::External { path } => { - quote! { #path } - } - }; + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar_ty> + ) -> ::juniper::meta::MetaType<'r, #scalar_ty> + where #scalar_ty: 'r, + { + // Ensure all implementer types are registered. + #( let _ = registry.get_type::<#impler_tys>(info); )* - Some(quote! { - if type_name == < - #impler_ty as ::juniper::GraphQLType<#scalar> - >::name(info).unwrap() { - let res = ::juniper::IntoResolvable::into({ #fn_path(self #ctx_arg) }, context); - return ::juniper::futures::future::FutureExt::boxed(async move { - match res? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - }, - None => Ok(::juniper::Value::null()), - } - }); + let fields = [ + #( #fields_meta, )* + ]; + registry.build_interface_type::<#ty>(info, &fields) + #description + .into_meta() } - }) - }); - let (regular_downcast, regular_async_downcast) = if self.trait_object.is_some() { - let sync = quote! { - return ::juniper::IntoResolvable::into(self.as_dyn_graphql_value(), context) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }) - }; - let r#async = quote! { - let res = ::juniper::IntoResolvable::into( - self.as_dyn_graphql_value_async(), context, - ); - return ::juniper::futures::future::FutureExt::boxed(async move { - match res? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - }, - None => Ok(::juniper::Value::null()), - } - }); - }; - (sync, r#async) - } else { - let panic = quote! { - panic!( - "Concrete type {} cannot be downcast from on GraphQL interface {}", - type_name, #name, - ); - }; - (panic.clone(), panic) - }; - - let mut generics = self.generics.clone(); - if self.trait_object.is_some() { - generics.remove_defaults(); - generics.move_bounds_to_where_clause(); - } - let (_, ty_generics, _) = generics.split_for_impl(); - - let mut ext_generics = generics.clone(); - if self.trait_object.is_some() { - ext_generics.params.push(parse_quote! { '__obj }); - } - if self.scalar.is_implicit_generic() { - ext_generics.params.push(parse_quote! { #scalar }); - } - if self.scalar.is_generic() { - ext_generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - let (ext_impl_generics, _, where_clause) = ext_generics.split_for_impl(); - - let mut where_async = where_clause - .cloned() - .unwrap_or_else(|| parse_quote! { where }); - where_async.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_generic() { - where_async - .predicates - .push(parse_quote! { #scalar: Send + Sync }); + } } + } - let mut ty_full = quote! { #ty#ty_generics }; - let mut ty_interface = ty_full.clone(); - if self.trait_object.is_some() { - let mut ty_params = None; - if !generics.params.is_empty() { - let params = &generics.params; - ty_params = Some(quote! { #params, }); - }; - - let scalar = if self.scalar.is_explicit_generic() { - None - } else { - Some(&scalar) - }; - ty_interface = quote! { #ty<#ty_params #scalar> }; + fn impl_graphql_value_tokens(&self) -> TokenStream { + let scalar_ty = self.scalar.ty_tokens_default(); - let scalar = scalar.map(|sc| quote! { #sc, }); - ty_full = quote! { - dyn #ty<#ty_params #scalar Context = #context, TypeInfo = ()> + '__obj + Send + Sync - }; - } + let generics = self.ty.impl_generics(&self.scalar); + let (impl_generics, _, where_clause) = generics.split_for_impl(); - let fields_sync_resolvers = self.fields.iter().filter_map(|field| { - if field.is_async { - return None; - } - let (name, ty, method) = (&field.name, &field.ty, &field.method); - let arguments = field.arguments.iter().map(|arg| match arg { - MethodArgument::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - let err_text = format!( - "Internal error: missing argument `{}` - validation must have failed", - &name, - ); - quote! { args.get::<#ty>(#name).expect(#err_text) } - } - MethodArgument::Context(_) => quote! { - ::juniper::FromContext::from(executor.context()) - }, - MethodArgument::Executor => quote! { &executor }, - }); + let ty = self.ty.ty_tokens(); + let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); - Some(quote! { - #name => { - let res: #ty = ::#method(self#( , #arguments )*); - ::juniper::IntoResolvable::into(res, executor.context()) - .and_then(|res| match res { - Some((ctx, r)) => executor - .replaced_context(ctx) - .resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }) - }, - }) - }); - let fields_sync_panic = { + let fields_resolvers = self + .fields + .iter() + .filter_map(InterfaceFieldDefinition::resolve_field_method_tokens); + let async_fields_panic = { let names = self .fields .iter() @@ -1103,106 +1016,47 @@ impl ToTokens for InterfaceDefinition { #( #names )|* => panic!( "Tried to resolve async field `{}` on type `{}` with a sync resolver", field, - >::name(info).unwrap(), + >::name(info).unwrap(), ), }) } }; + let no_field_panic = self.no_field_panic_tokens(); - let fields_async_resolvers = self.fields.iter().map(|field| { - let (name, ty) = (&field.name, &field.ty); - - let method = &field.method; - let arguments = field.arguments.iter().map(|arg| match arg { - MethodArgument::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - let err_text = format!( - "Internal error: missing argument `{}` - validation must have failed", - &name, - ); - quote! { args.get::<#ty>(#name).expect(#err_text) } - } - MethodArgument::Context(_) => quote! { - ::juniper::FromContext::from(executor.context()) - }, - MethodArgument::Executor => quote! { &executor }, - }); - - let mut fut = quote! { ::#method(self#( , #arguments )*) }; - if !field.is_async { - fut = quote! { ::juniper::futures::future::ready(#fut) }; - } - - quote! { - #name => Box::pin(::juniper::futures::FutureExt::then(#fut, move |res: #ty| { - async move { - match ::juniper::IntoResolvable::into(res, executor.context())? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - }, - None => Ok(::juniper::Value::null()), - } - } - })), - } - }); - - let type_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics ::juniper::GraphQLType<#scalar> for #ty_full - #where_clause - { - fn name(_ : &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_types>(info); )* + let custom_downcast_checks = self + .implementers + .iter() + .filter_map(ImplementerDefinition::concrete_type_name_method_tokens); + let regular_downcast_check = self.ty.concrete_type_name_method_tokens(); - let fields = [ - #( #fields_meta, )* - ]; - registry.build_interface_type::<#ty_full>(info, &fields) - #description - .into_meta() - } - } - }; + let custom_downcasts = self + .implementers + .iter() + .filter_map(ImplementerDefinition::resolve_into_type_method_tokens); + let regular_downcast = self.ty.resolve_into_type_method_tokens(); - let value_impl = quote! { + quote! { #[automatically_derived] - impl#ext_impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full - #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar_ty> for #ty #where_clause { - type Context = #context; + type Context = #context_ty; type TypeInfo = (); fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) + >::name(info) } fn resolve_field( &self, info: &Self::TypeInfo, field: &str, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { + args: &::juniper::Arguments<#scalar_ty>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar_ty> { match field { - #( #fields_sync_resolvers )* - #fields_sync_panic - _ => panic!( - "Field `{}` not found on type `{}`", - field, - >::name(info).unwrap(), - ), + #( #fields_resolvers )* + #async_fields_panic + _ => #no_field_panic, } } @@ -1222,148 +1076,75 @@ impl ToTokens for InterfaceDefinition { _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - let context = executor.context(); #( #custom_downcasts )* #regular_downcast } } - }; - - let value_async_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full - #where_async - { - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field: &'b str, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - match field { - #( #fields_async_resolvers )* - _ => panic!( - "Field `{}` not found on type `{}`", - field, - >::name(info).unwrap(), - ), - } - } - - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - _: Option<&'b [::juniper::Selection<'b, #scalar>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - let context = executor.context(); - #( #custom_async_downcasts )* - #regular_async_downcast - } - } - }; - - let output_type_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full - #where_clause - { - fn mark() { - #( #fields_marks )* - #( <#impler_types as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* - } - } - }; - - let interface_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty_full - #where_clause - { - fn mark() { - #all_implers_unique - - #( <#impler_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* - } - } - }; - - into.append_all(&[ - interface_impl, - output_type_impl, - type_impl, - value_impl, - value_async_impl, - ]); + } } -} -struct Definition { - ty: Type, - trait_generics: syn::Generics, - name: String, - description: Option, - context: Option, - scalar: ScalarValueType, - fields: Vec, - implementers: Vec, -} - -impl Definition { - fn type_impl_tokens(&self) -> TokenStream { + fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar_ty = self.scalar.ty_tokens_default(); let generics = self.ty.impl_generics(&self.scalar); let (impl_generics, _, where_clause) = generics.split_for_impl(); + let mut where_clause = where_clause + .cloned() + .unwrap_or_else(|| parse_quote! { where }); + where_clause.predicates.push(parse_quote! { Self: Sync }); + if self.scalar.is_generic() { + where_clause + .predicates + .push(parse_quote! { #scalar_ty: Send + Sync }); + } let ty = self.ty.ty_tokens(); + let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); - let name = &self.name; - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); - impler_tys.sort_unstable_by(|a, b| { - let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); - a.cmp(&b) - }); + let fields_resolvers = self + .fields + .iter() + .map(InterfaceFieldDefinition::resolve_field_async_method_tokens); + let no_field_panic = self.no_field_panic_tokens(); - let fields_meta = self.fields.iter().map(InterfaceFieldDefinition::meta_method_tokens); + let custom_downcasts = self + .implementers + .iter() + .filter_map(ImplementerDefinition::resolve_into_type_async_method_tokens); + let regular_downcast = self.ty.resolve_into_type_async_method_tokens(); quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar_ty> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar_ty> for #ty #where_clause { - fn name(_ : &Self::TypeInfo) -> Option<&'static str> { - Some(#name) + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<#scalar_ty>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar_ty>> { + match field { + #( #fields_resolvers )* + _ => #no_field_panic, + } } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #scalar_ty> - ) -> ::juniper::meta::MetaType<'r, #scalar_ty> - where #scalar_ty: 'r, - { - // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_tys>(info); )* - - let fields = [ - #( #fields_meta, )* - ]; - registry.build_interface_type::<#ty>(info, &fields) - #description - .into_meta() + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + _: Option<&'b [::juniper::Selection<'b, #scalar_ty>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar_ty> + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar_ty>> { + #( #custom_downcasts )* + #regular_downcast } } } } - fn interface_impl_tokens(&self) -> TokenStream { + fn impl_graphql_interface_tokens(&self) -> TokenStream { let scalar_ty = self.scalar.ty_tokens_default(); let generics = self.ty.impl_generics(&self.scalar); @@ -1392,7 +1173,7 @@ impl Definition { } } - fn output_type_impl_tokens(&self) -> TokenStream { + fn impl_output_type_tokens(&self) -> TokenStream { let scalar_ty = self.scalar.ty_tokens_default(); let generics = self.ty.impl_generics(&self.scalar); @@ -1434,6 +1215,19 @@ impl Definition { } } +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + into.append_all(&[ + self.ty.to_token_stream(), + self.impl_graphql_interface_tokens(), + self.impl_output_type_tokens(), + self.impl_graphql_type_tokens(), + self.impl_graphql_value_tokens(), + self.impl_graphql_value_async_tokens(), + ]); + } +} + struct EnumType { ident: syn::Ident, visibility: syn::Visibility, @@ -1521,7 +1315,7 @@ impl EnumType { quote! { #ty } } - fn to_type_definition_tokens(&self) -> TokenStream { + fn type_definition_tokens(&self) -> TokenStream { let enum_ty = &self.ident; let vis = &self.visibility; @@ -1547,7 +1341,7 @@ impl EnumType { } } - fn to_from_impls_tokens(&self) -> impl Iterator + '_ { + fn impl_from_tokens(&self) -> impl Iterator + '_ { let enum_ty = &self.ident; self.variants.iter().enumerate().map(move |(n, ty)| { @@ -1564,7 +1358,7 @@ impl EnumType { }) } - fn to_trait_impl_tokens(&self) -> TokenStream { + fn impl_trait_tokens(&self) -> TokenStream { let enum_ty = &self.ident; let trait_ident = &self.trait_ident; @@ -1652,7 +1446,7 @@ impl EnumType { impl_tokens } - fn to_concrete_type_name_tokens(&self) -> TokenStream { + fn concrete_type_name_method_tokens(&self) -> TokenStream { let match_arms = self.variants.iter().enumerate().map(|(n, ty)| { let variant = Self::variant_ident(n); @@ -1669,7 +1463,7 @@ impl EnumType { } } - fn to_resolve_into_type_tokens(&self) -> TokenStream { + fn resolve_into_type_method_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); let match_arms = self.variants.iter().enumerate().map(|(n, _)| { @@ -1687,7 +1481,7 @@ impl EnumType { } } - fn to_resolve_into_type_async_tokens(&self) -> TokenStream { + fn to_resolve_into_type_async_method_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); let match_arms = self.variants.iter().enumerate().map(|(n, _)| { @@ -1711,9 +1505,9 @@ impl EnumType { impl ToTokens for EnumType { fn to_tokens(&self, into: &mut TokenStream) { - into.append_all(&[self.to_type_definition_tokens()]); - into.append_all(self.to_from_impls_tokens()); - into.append_all(&[self.to_trait_impl_tokens()]); + into.append_all(&[self.type_definition_tokens()]); + into.append_all(self.impl_from_tokens()); + into.append_all(&[self.impl_trait_tokens()]); } } @@ -1764,13 +1558,13 @@ impl TraitObjectType { } } - fn to_concrete_type_name_tokens(&self) -> TokenStream { + fn concrete_type_name_method_tokens(&self) -> TokenStream { quote! { self.as_dyn_graphql_value().concrete_type_name(context, info) } } - fn to_resolve_into_type_tokens(&self) -> TokenStream { + fn resolve_into_type_method_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); quote! { @@ -1779,7 +1573,7 @@ impl TraitObjectType { } } - fn to_resolve_into_type_async_tokens(&self) -> TokenStream { + fn to_resolve_into_type_async_method_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); quote! { @@ -1855,24 +1649,33 @@ impl Type { } } - fn to_concrete_type_name_tokens(&self) -> TokenStream { + fn concrete_type_name_method_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.to_concrete_type_name_tokens(), - Self::TraitObject(o) => o.to_concrete_type_name_tokens(), + Self::Enum(e) => e.concrete_type_name_method_tokens(), + Self::TraitObject(o) => o.concrete_type_name_method_tokens(), } } - fn to_resolve_into_type_tokens(&self) -> TokenStream { + fn resolve_into_type_method_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.to_resolve_into_type_tokens(), - Self::TraitObject(o) => o.to_resolve_into_type_tokens(), + Self::Enum(e) => e.resolve_into_type_method_tokens(), + Self::TraitObject(o) => o.resolve_into_type_method_tokens(), } } - fn to_resolve_into_type_async_tokens(&self) -> TokenStream { + fn to_resolve_into_type_async_method_tokens(&self) -> TokenStream { + match self { + Self::Enum(e) => e.to_resolve_into_type_async_method_tokens(), + Self::TraitObject(o) => o.to_resolve_into_type_async_method_tokens(), + } + } +} + +impl ToTokens for Type { + fn to_tokens(&self, into: &mut TokenStream) { match self { - Self::Enum(e) => e.to_resolve_into_type_async_tokens(), - Self::TraitObject(o) => o.to_resolve_into_type_async_tokens(), + Self::Enum(e) => e.to_tokens(into), + Self::TraitObject(o) => o.to_tokens(into), } } } From cbf811618c97a55e9c2ac60d7f69ad00cd3d6a88 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 17 Sep 2020 17:10:51 +0300 Subject: [PATCH 50/79] Generating enum, vol.3 --- juniper_codegen/src/common/gen.rs | 2 +- juniper_codegen/src/common/mod.rs | 21 +- juniper_codegen/src/graphql_interface/attr.rs | 153 ++++++------- juniper_codegen/src/graphql_interface/mod.rs | 207 +++++++++--------- 4 files changed, 178 insertions(+), 205 deletions(-) diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs index c2002c21c..4688ca5f8 100644 --- a/juniper_codegen/src/common/gen.rs +++ b/juniper_codegen/src/common/gen.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::quote; pub(crate) fn sync_resolving_code() -> TokenStream { quote! { diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 88cd07d50..50f33d143 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -2,8 +2,8 @@ pub(crate) mod parse; pub(crate) mod gen; use proc_macro2::{Span, TokenStream}; -use quote::quote; use syn::parse_quote; +use quote::ToTokens; pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { use syn::{GenericArgument as GA, Type as T}; @@ -104,19 +104,14 @@ impl ScalarValueType { } #[must_use] - pub(crate) fn ty_tokens(&self) -> Option { + pub(crate) fn ty(&self) -> syn::Type { match self { - Self::Concrete(ty) => Some(quote! { #ty }), - Self::ExplicitGeneric(ty_param) => Some(quote! { #ty_param }), - Self::ImplicitGeneric => None, + Self::Concrete(ty) => ty.clone(), + Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, + Self::ImplicitGeneric => parse_quote! { __S }, } } - #[must_use] - pub(crate) fn ty_tokens_default(&self) -> TokenStream { - self.ty_tokens().unwrap_or_else(|| quote! { __S }) - } - #[must_use] pub(crate) fn default_ty(&self) -> syn::Type { match self { @@ -127,3 +122,9 @@ impl ScalarValueType { } } } + +impl ToTokens for ScalarValueType { + fn to_tokens(&self, into: &mut TokenStream) { + self.ty().to_tokens(into) + } +} \ No newline at end of file diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index f6fb90aac..f87243f33 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, ToTokens as _}; +use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -19,10 +19,10 @@ use crate::{ }; use super::{ - inject_async_trait, ArgumentMeta, TraitObjectType, EnumType, ImplementerDefinition, - ImplementerDowncastDefinition, ImplementerMeta, InterfaceDefinition, - InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, InterfaceMeta, MethodArgument, - TraitMethodMeta, + inject_async_trait, ArgumentMeta, Definition, EnumType, ImplementerDefinition, + ImplementerDowncastDefinition, ImplementerMeta, InterfaceFieldArgumentDefinition, + InterfaceFieldDefinition, InterfaceMeta, MethodArgument, TraitMethodMeta, TraitObjectType, + Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -72,17 +72,36 @@ pub fn expand_on_trait( ); } + let scalar = meta + .scalar + .as_ref() + .map(|sc| { + ast.generics + .params + .iter() + .find_map(|p| { + if let syn::GenericParam::Type(tp) = p { + let ident = &tp.ident; + let ty: syn::Type = parse_quote! { #ident }; + if &ty == sc.as_ref() { + return Some(&tp.ident); + } + } + None + }) + .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) + .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) + }) + .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); + let mut implementers: Vec<_> = meta .implementers .iter() - .map(|ty| { - let span = ty.span_ident(); - ImplementerDefinition { - ty: ty.as_ref().clone(), - downcast: None, - context_ty: None, - span, - } + .map(|ty| ImplementerDefinition { + ty: ty.as_ref().clone(), + downcast: None, + context_ty: None, + scalar: scalar.clone(), }) .collect(); for (ty, downcast) in &meta.external_downcasts { @@ -113,7 +132,7 @@ pub fn expand_on_trait( impler.context_ty = d.context_ty; } } - None => err_only_implementer_downcast(&d.span), + None => err_only_implementer_downcast(&m.sig), } } _ => {} @@ -125,7 +144,8 @@ pub fn expand_on_trait( let context = meta .context - .map(SpanContainer::into_inner) + .as_ref() + .map(|c| c.as_ref().clone()) .or_else(|| { fields.iter().find_map(|f| { f.arguments @@ -141,27 +161,7 @@ pub fn expand_on_trait( .cloned() }); - let scalar_ty = meta - .scalar - .as_ref() - .map(|sc| { - ast.generics - .params - .iter() - .find_map(|p| { - if let syn::GenericParam::Type(tp) = p { - let ident = &tp.ident; - let ty: syn::Type = parse_quote! { #ident }; - if &ty == sc.as_ref() { - return Some(&tp.ident); - } - } - None - }) - .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) - .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) - }) - .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); + let is_trait_object = meta.r#dyn.is_some(); let is_async_trait = meta.asyncness.is_some() || ast @@ -181,50 +181,25 @@ pub fn expand_on_trait( }) .is_some(); - let is_trait_object = meta.as_dyn.is_some(); - let ty = if is_trait_object { - trait_ident.clone() - } else if let Some(enum_ident) = &meta.as_enum { - enum_ident.as_ref().clone() - } else { - format_ident!("{}Value", trait_ident) - }; - - let generated_code = InterfaceDefinition { - name, - ty, - trait_object: meta.as_dyn.as_ref().map(|a| a.as_ref().clone()), - visibility: ast.vis.clone(), - description: meta.description.map(SpanContainer::into_inner), - context: context.clone(), - scalar: scalar_ty.clone(), - generics: ast.generics.clone(), - fields, - implementers: implementers.clone(), - }; - + // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. if is_trait_object { ast.attrs.push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); - let scalar = if scalar_ty.is_explicit_generic() { - scalar_ty.ty_tokens().unwrap() - } else { - quote! { GraphQLScalarValue } - }; - let default_scalar = scalar_ty.default_ty(); - if !scalar_ty.is_explicit_generic() { + let scalar_ty = scalar.ty(); + let default_scalar_ty = scalar.default_ty(); + if !scalar.is_explicit_generic() { ast.generics .params - .push(parse_quote! { #scalar = #default_scalar }); + .push(parse_quote! { #scalar_ty = #default_scalar_ty }); } ast.generics .make_where_clause() .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); ast.supertraits - .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar> }); + .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); } if is_async_trait { @@ -245,19 +220,31 @@ pub fn expand_on_trait( ); } - let value_type = if is_trait_object { - let dyn_alias = TraitObjectType::new(&ast, &meta, context); - quote! { #dyn_alias } + let ty = if is_trait_object { + Type::TraitObject(TraitObjectType::new(&ast, &meta, context.clone())) } else { - let enum_type = EnumType::new(&ast, &meta, &implementers); - quote! { #enum_type } + Type::Enum(EnumType::new(&ast, &meta, &implementers)) + }; + + let generated_code = Definition { + ty, + + trait_ident: trait_ident.clone(), + trait_generics: ast.generics.clone(), + + name, + description: meta.description.map(SpanContainer::into_inner), + + context, + scalar, + + fields, + implementers, }; Ok(quote! { #ast - #value_type - #generated_code }) } @@ -279,10 +266,10 @@ pub fn expand_on_impl( }) .is_some(); - let is_trait_object = meta.as_dyn.is_some(); + let is_trait_object = meta.r#dyn.is_some(); if is_trait_object { - let scalar_ty = meta + let scalar = meta .scalar .as_ref() .map(|sc| { @@ -308,20 +295,17 @@ pub fn expand_on_impl( #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); - let scalar = scalar_ty - .ty_tokens() - .unwrap_or_else(|| quote! { GraphQLScalarValue }); - if scalar_ty.is_implicit_generic() { + if scalar.is_implicit_generic() { ast.generics.params.push(parse_quote! { #scalar }); } - if scalar_ty.is_generic() { + if scalar.is_generic() { ast.generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); } - if !scalar_ty.is_explicit_generic() { + if !scalar.is_explicit_generic() { let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; if let syn::PathArguments::None = trait_params { @@ -381,7 +365,6 @@ impl TraitMethod { } fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { - let method_span = method.sig.span(); let method_ident = &method.sig.ident; let ty = parse::downcaster::output_type(&method.sig.output) @@ -417,7 +400,7 @@ impl TraitMethod { ty, downcast: Some(downcast), context_ty, - span: method_span, + scalar: ScalarValueType::ImplicitGeneric, }) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 870ee08c0..a42d1d51a 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -6,7 +6,7 @@ pub mod attr; use std::collections::{HashMap, HashSet}; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; use syn::{ parse::{Parse, ParseStream}, @@ -51,9 +51,9 @@ struct InterfaceMeta { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions pub description: Option>, - pub as_enum: Option>, + pub r#enum: Option>, - pub as_dyn: Option>, + pub r#dyn: Option>, /// Explicitly specified Rust types of [GraphQL objects][2] implementing this /// [GraphQL interface][1] type. @@ -162,7 +162,7 @@ impl Parse for InterfaceMeta { input.parse::()?; let alias = input.parse::()?; output - .as_dyn + .r#dyn .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -170,7 +170,7 @@ impl Parse for InterfaceMeta { input.parse::()?; let alias = input.parse::()?; output - .as_enum + .r#enum .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -215,8 +215,8 @@ impl InterfaceMeta { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), - as_dyn: try_merge_opt!(as_dyn: self, another), - as_enum: try_merge_opt!(as_enum: self, another), + r#dyn: try_merge_opt!(r#dyn: self, another), + r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), external_downcasts: try_merge_hashmap!( external_downcasts: self, another => span_joined @@ -232,8 +232,8 @@ impl InterfaceMeta { .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - if let Some(as_dyn) = &meta.as_dyn { - if meta.as_enum.is_some() { + if let Some(as_dyn) = &meta.r#dyn { + if meta.r#enum.is_some() { return Err(syn::Error::new( as_dyn.span(), "`dyn` attribute argument is not composable with `enum` attribute argument", @@ -257,7 +257,7 @@ impl InterfaceMeta { struct ImplementerMeta { pub scalar: Option>, pub asyncness: Option>, - pub as_dyn: Option>, + pub r#dyn: Option>, } impl Parse for ImplementerMeta { @@ -278,7 +278,7 @@ impl Parse for ImplementerMeta { "dyn" => { let span = ident.span(); output - .as_dyn + .r#dyn .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } @@ -306,7 +306,7 @@ impl ImplementerMeta { fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { scalar: try_merge_opt!(scalar: self, another), - as_dyn: try_merge_opt!(as_dyn: self, another), + r#dyn: try_merge_opt!(r#dyn: self, another), asyncness: try_merge_opt!(asyncness: self, another), }) } @@ -655,7 +655,6 @@ impl MethodArgument { struct InterfaceFieldDefinition { pub name: String, pub ty: syn::Type, - pub trait_ty: syn::Type, pub description: Option, pub deprecated: Option>, pub method: syn::Ident, @@ -693,13 +692,12 @@ impl InterfaceFieldDefinition { } } - fn resolve_field_method_tokens(&self) -> Option { + fn resolve_field_method_tokens(&self, trait_ty: &syn::Type) -> Option { if self.is_async { return None; } let (name, ty, method) = (&self.name, &self.ty, &self.method); - let interface_ty = &self.interface_ty; let arguments = self .arguments @@ -710,22 +708,21 @@ impl InterfaceFieldDefinition { Some(quote! { #name => { - let res: #ty = ::#method(self #( , #arguments )*); + let res: #ty = ::#method(self #( , #arguments )*); #resolving_code } }) } - fn resolve_field_async_method_tokens(&self) -> TokenStream { + fn resolve_field_async_method_tokens(&self, trait_ty: &syn::Type) -> TokenStream { let (name, ty, method) = (&self.name, &self.ty, &self.method); - let interface_ty = &self.interface_ty; let arguments = self .arguments .iter() .map(MethodArgument::resolve_field_method_tokens); - let mut fut = quote! { ::#method(self #( , #arguments )*) }; + let mut fut = quote! { ::#method(self #( , #arguments )*) }; if !self.is_async { fut = quote! { ::juniper::futures::future::ready(#fut) }; } @@ -774,14 +771,10 @@ struct ImplementerDefinition { pub context_ty: Option, pub scalar: ScalarValueType, - - pub interface_ty: syn::Type, } impl ImplementerDefinition { - fn downcast_call_tokens(&self) -> Option { - let interface_ty = &self.interface_ty; - + fn downcast_call_tokens(&self, trait_ty: &syn::Type) -> Option { let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); let fn_path = match self.downcast.as_ref()? { @@ -789,7 +782,7 @@ impl ImplementerDefinition { if !with_context { ctx_arg = None; } - quote! { #interface_ty::#name } + quote! { <#trait_ty>::#name } } ImplementerDowncastDefinition::External { path } => { quote! { #path } @@ -801,60 +794,60 @@ impl ImplementerDefinition { }) } - fn concrete_type_name_method_tokens(&self) -> Option { + fn concrete_type_name_method_tokens(&self, trait_ty: &syn::Type) -> Option { if self.downcast.is_none() { return None; } let ty = &self.ty; - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(); + let downcast = self.downcast_call_tokens(trait_ty); // Doing this may be quite an expensive, because resolving may contain some heavy // computation, so we're preforming it twice. Unfortunately, we have no other options here, // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. Some(quote! { if (#downcast as ::std::option::Option<&#ty>).is_some() { - return <#ty as ::juniper::GraphQLType<#scalar_ty>>::name(info).unwrap().to_string(); + return <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap().to_string(); } }) } - fn resolve_into_type_method_tokens(&self) -> Option { + fn resolve_into_type_method_tokens(&self, trait_ty: &syn::Type) -> Option { if self.downcast.is_none() { return None; } let ty = &self.ty; - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(); + let downcast = self.downcast_call_tokens(trait_ty); let resolving_code = gen::sync_resolving_code(); Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar_ty>>::name(info).unwrap() { + if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { let res = #downcast; return #resolving_code; } }) } - fn resolve_into_type_async_method_tokens(&self) -> Option { + fn resolve_into_type_async_method_tokens(&self, trait_ty: &syn::Type) -> Option { if self.downcast.is_none() { return None; } let ty = &self.ty; - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(); + let downcast = self.downcast_call_tokens(trait_ty); let resolving_code = gen::async_resolving_code(None); Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar_ty>>::name(info).unwrap() { + if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { let fut = ::juniper::futures::future::ready(#downcast); return #resolving_code; } @@ -919,21 +912,21 @@ impl Definition { } fn no_field_panic_tokens(&self) -> TokenStream { - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; quote! { panic!( "Field `{}` not found on type `{}`", field, - >::name(info).unwrap(), + >::name(info).unwrap(), ) } } fn impl_graphql_type_tokens(&self) -> TokenStream { - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let generics = self.ty.impl_generics(&self.scalar); + let generics = self.ty.impl_generics(scalar); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); @@ -958,7 +951,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar_ty> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -966,9 +959,9 @@ impl Definition { fn meta<'r>( info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #scalar_ty> - ) -> ::juniper::meta::MetaType<'r, #scalar_ty> - where #scalar_ty: 'r, + registry: &mut ::juniper::Registry<'r, #scalar> + ) -> ::juniper::meta::MetaType<'r, #scalar> + where #scalar: 'r, { // Ensure all implementer types are registered. #( let _ = registry.get_type::<#impler_tys>(info); )* @@ -985,18 +978,19 @@ impl Definition { } fn impl_graphql_value_tokens(&self) -> TokenStream { - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let generics = self.ty.impl_generics(&self.scalar); + let generics = self.ty.impl_generics(scalar); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); - let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let trait_ty = self.trait_ty(); + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); let fields_resolvers = self .fields .iter() - .filter_map(InterfaceFieldDefinition::resolve_field_method_tokens); + .filter_map(|f| f.resolve_field_method_tokens(&trait_ty)); let async_fields_panic = { let names = self .fields @@ -1016,7 +1010,7 @@ impl Definition { #( #names )|* => panic!( "Tried to resolve async field `{}` on type `{}` with a sync resolver", field, - >::name(info).unwrap(), + >::name(info).unwrap(), ), }) } @@ -1026,33 +1020,33 @@ impl Definition { let custom_downcast_checks = self .implementers .iter() - .filter_map(ImplementerDefinition::concrete_type_name_method_tokens); - let regular_downcast_check = self.ty.concrete_type_name_method_tokens(); + .filter_map(|i| i.concrete_type_name_method_tokens(&trait_ty)); + let regular_downcast_check = self.ty.concrete_type_name_method_tokens(scalar); let custom_downcasts = self .implementers .iter() - .filter_map(ImplementerDefinition::resolve_into_type_method_tokens); + .filter_map(|i| i.resolve_into_type_method_tokens(&trait_ty)); let regular_downcast = self.ty.resolve_into_type_method_tokens(); quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar_ty> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { - type Context = #context_ty; + type Context = #context; type TypeInfo = (); fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) + >::name(info) } fn resolve_field( &self, info: &Self::TypeInfo, field: &str, - args: &::juniper::Arguments<#scalar_ty>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar_ty> { + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* #async_fields_panic @@ -1084,9 +1078,9 @@ impl Definition { } fn impl_graphql_value_async_tokens(&self) -> TokenStream { - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let generics = self.ty.impl_generics(&self.scalar); + let generics = self.ty.impl_generics(scalar); let (impl_generics, _, where_clause) = generics.split_for_impl(); let mut where_clause = where_clause .cloned() @@ -1095,35 +1089,35 @@ impl Definition { if self.scalar.is_generic() { where_clause .predicates - .push(parse_quote! { #scalar_ty: Send + Sync }); + .push(parse_quote! { #scalar: Send + Sync }); } let ty = self.ty.ty_tokens(); - let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let trait_ty = self.trait_ty(); let fields_resolvers = self .fields .iter() - .map(InterfaceFieldDefinition::resolve_field_async_method_tokens); + .map(|f| f.resolve_field_async_method_tokens(&trait_ty)); let no_field_panic = self.no_field_panic_tokens(); let custom_downcasts = self .implementers .iter() - .filter_map(ImplementerDefinition::resolve_into_type_async_method_tokens); + .filter_map(|i| i.resolve_into_type_async_method_tokens(&trait_ty)); let regular_downcast = self.ty.resolve_into_type_async_method_tokens(); quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar_ty> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { fn resolve_field_async<'b>( &'b self, info: &'b Self::TypeInfo, field: &'b str, - args: &'b ::juniper::Arguments<#scalar_ty>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar_ty>> { + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match field { #( #fields_resolvers )* _ => #no_field_panic, @@ -1134,9 +1128,9 @@ impl Definition { &'b self, info: &'b Self::TypeInfo, type_name: &str, - _: Option<&'b [::juniper::Selection<'b, #scalar_ty>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar_ty> - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar_ty>> { + _: Option<&'b [::juniper::Selection<'b, #scalar>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { #( #custom_downcasts )* #regular_downcast } @@ -1145,16 +1139,16 @@ impl Definition { } fn impl_graphql_interface_tokens(&self) -> TokenStream { - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let generics = self.ty.impl_generics(&self.scalar); + let generics = self.ty.impl_generics(scalar); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); - let all_implers_unique = if impler_types.len() > 1 { + let all_implers_unique = if impler_tys.len() > 1 { Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) } else { None @@ -1162,21 +1156,21 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar_ty> for #ty #where_clause + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause { fn mark() { #all_implers_unique - #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#scalar_ty>>::mark(); )* + #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* } } } } fn impl_output_type_tokens(&self) -> TokenStream { - let scalar_ty = self.scalar.ty_tokens_default(); + let scalar = &self.scalar; - let generics = self.ty.impl_generics(&self.scalar); + let generics = self.ty.impl_generics(scalar); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); @@ -1184,19 +1178,19 @@ impl Definition { let fields_marks = self.fields.iter().map(|field| { let arguments_marks = field.arguments.iter().filter_map(|arg| { let arg_ty = &arg.as_regular()?.ty; - Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar_ty>>::mark(); }) + Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) }); let field_ty = &field.ty; let resolved_ty = quote! { <#field_ty as ::juniper::IntoResolvable< - '_, #scalar_ty, _, >::Context, + '_, #scalar, _, >::Context, >>::Type }; quote! { #( #arguments_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar_ty>>::mark(); + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); @@ -1204,11 +1198,11 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar_ty> for #ty #where_clause + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { fn mark() { #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar_ty>>::mark(); )* + #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* } } } @@ -1247,7 +1241,7 @@ impl EnumType { ) -> Self { Self { ident: meta - .as_enum + .r#enum .as_ref() .map(SpanContainer::as_ref) .cloned() @@ -1299,12 +1293,11 @@ impl EnumType { fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { let mut generics = syn::Generics::default(); if scalar.is_generic() { - let scalar_ty = scalar.ty_tokens_default(); - generics.params.push(parse_quote! { #scalar_ty }); + generics.params.push(parse_quote! { #scalar }); generics .make_where_clause() .predicates - .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } generics } @@ -1446,13 +1439,14 @@ impl EnumType { impl_tokens } - fn concrete_type_name_method_tokens(&self) -> TokenStream { + fn concrete_type_name_method_tokens(&self, scalar: &ScalarValueType) -> TokenStream { let match_arms = self.variants.iter().enumerate().map(|(n, ty)| { let variant = Self::variant_ident(n); quote! { - Self::#variant(v) => - <#ty as ::juniper::GraphQLValue<_>>::concrete_type_name(v, context, info), + Self::#variant(v) => < + #ty as ::juniper::GraphQLValue<#scalar> + >::concrete_type_name(v, context, info), } }); @@ -1481,7 +1475,7 @@ impl EnumType { } } - fn to_resolve_into_type_async_method_tokens(&self) -> TokenStream { + fn resolve_into_type_async_method_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); let match_arms = self.variants.iter().enumerate().map(|(n, _)| { @@ -1522,7 +1516,7 @@ struct TraitObjectType { impl TraitObjectType { fn new(r#trait: &syn::ItemTrait, meta: &InterfaceMeta, context: Option) -> Self { Self { - ident: meta.as_dyn.as_ref().unwrap().as_ref().clone(), + ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(), visibility: r#trait.vis.clone(), trait_ident: r#trait.ident.clone(), trait_generics: r#trait.generics.clone(), @@ -1534,11 +1528,10 @@ impl TraitObjectType { let mut generics = self.trait_generics.clone(); generics.params.push(parse_quote! { '__obj }); if scalar.is_generic() { - let scalar_ty = scalar.ty_tokens_default(); generics .make_where_clause() .predicates - .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } generics } @@ -1551,10 +1544,10 @@ impl TraitObjectType { generics.move_bounds_to_where_clause(); let ty_params = &generics.params; - let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); quote! { - dyn #ty<#ty_params, Context = #context_ty, TypeInfo = ()> + '__obj + Send + Sync + dyn #ty<#ty_params, Context = #context, TypeInfo = ()> + '__obj + Send + Sync } } @@ -1573,7 +1566,7 @@ impl TraitObjectType { } } - fn to_resolve_into_type_async_method_tokens(&self) -> TokenStream { + fn resolve_into_type_async_method_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); quote! { @@ -1611,13 +1604,13 @@ impl ToTokens for TraitObjectType { ty_params_right = Some(quote! { #params, }); }; - let context_ty = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); let dyn_alias = quote! { #[automatically_derived] #[doc = #doc] #vis type #dyn_ty<'a #ty_params_left> = - dyn #trait_ident<#ty_params_right Context = #context_ty, TypeInfo = ()> + + dyn #trait_ident<#ty_params_right Context = #context, TypeInfo = ()> + 'a + Send + Sync; }; @@ -1631,10 +1624,6 @@ enum Type { } impl Type { - fn is_trait_object(&self) -> bool { - matches!(self, Self::TraitObject(_)) - } - fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { match self { Self::Enum(e) => e.impl_generics(scalar), @@ -1649,9 +1638,9 @@ impl Type { } } - fn concrete_type_name_method_tokens(&self) -> TokenStream { + fn concrete_type_name_method_tokens(&self, scalar: &ScalarValueType) -> TokenStream { match self { - Self::Enum(e) => e.concrete_type_name_method_tokens(), + Self::Enum(e) => e.concrete_type_name_method_tokens(scalar), Self::TraitObject(o) => o.concrete_type_name_method_tokens(), } } @@ -1663,10 +1652,10 @@ impl Type { } } - fn to_resolve_into_type_async_method_tokens(&self) -> TokenStream { + fn resolve_into_type_async_method_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.to_resolve_into_type_async_method_tokens(), - Self::TraitObject(o) => o.to_resolve_into_type_async_method_tokens(), + Self::Enum(e) => e.resolve_into_type_async_method_tokens(), + Self::TraitObject(o) => o.resolve_into_type_async_method_tokens(), } } } From e475fd2392f444f6d418d7923506509fedcd1a7d Mon Sep 17 00:00:00 2001 From: tyranron Date: Sun, 20 Sep 2020 18:52:14 +0200 Subject: [PATCH 51/79] Generating enum, vol.4 --- .../src/codegen/interface_attr.rs | 124 ++++++++++++++++-- juniper_codegen/src/graphql_interface/attr.rs | 1 + juniper_codegen/src/graphql_interface/mod.rs | 2 +- juniper_codegen/src/util/mod.rs | 4 +- 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 5123f5948..bd999a167 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -21,13 +21,18 @@ where mod trivial { use super::*; - #[graphql_interface(enum = Actor, for = [Human, Droid])] - trait ActorInterface { + #[graphql_interface(for = [Human, Droid])] + trait Character { fn id(&self) -> &str; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + fn info(&self) -> &str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -40,8 +45,15 @@ mod trivial { } } + #[graphql_interface(dyn)] + impl Hero for Human { + fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -54,8 +66,12 @@ mod trivial { } } - type DynCharacter<'a, S = DefaultScalarValue> = - dyn Character + 'a + Send + Sync; + #[graphql_interface(dyn)] + impl Hero for Droid { + fn info(&self) -> &str { + &self.primary_function + } + } #[derive(Clone, Copy)] enum QueryRoot { @@ -65,8 +81,23 @@ mod trivial { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -81,7 +112,7 @@ mod trivial { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -103,7 +134,7 @@ mod trivial { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -125,7 +156,51 @@ mod trivial { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -146,6 +221,28 @@ mod trivial { } } + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[ + (QueryRoot::Human, "earth"), + (QueryRoot::Droid, "run"), + ] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } +/* #[tokio::test] async fn is_graphql_interface() { const DOC: &str = r#"{ @@ -247,8 +344,10 @@ mod trivial { Ok((graphql_value!({"__type": {"description": None}}), vec![])), ); } -} + */ +} +/* mod dyn_alias { use super::*; @@ -3554,3 +3653,4 @@ mod external_downcast { } } } +*/ \ No newline at end of file diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index f87243f33..821f0c42a 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -163,6 +163,7 @@ pub fn expand_on_trait( let is_trait_object = meta.r#dyn.is_some(); + let is_async_trait = meta.asyncness.is_some() || ast .items diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index a42d1d51a..0c45d48e6 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1109,7 +1109,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_field_async<'b>( &'b self, diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index c07682126..2c08db475 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -831,6 +831,7 @@ impl GraphQLTypeDefiniton { .as_ref() .map(|description| quote!( .description(#description) )); + let mut has_dyn_interfaces = false; let interfaces = if !self.interfaces.is_empty() { let interfaces_ty = self.interfaces.iter().map(|ty| { let mut ty: syn::Type = ty.unparenthesized().clone(); @@ -853,6 +854,7 @@ impl GraphQLTypeDefiniton { dyn_ty.bounds.push(parse_quote! { Send }); dyn_ty.bounds.push(parse_quote! { Sync }); + has_dyn_interfaces = true; ty = dyn_ty.into(); } @@ -975,7 +977,7 @@ impl GraphQLTypeDefiniton { .push(parse_quote!( #scalar: Send + Sync )); where_async.predicates.push(parse_quote!(Self: Sync)); - let as_dyn_value = if !self.interfaces.is_empty() { + let as_dyn_value = if has_dyn_interfaces { Some(quote! { #[automatically_derived] impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens From 7f54ba7d53704eeada7d407ca526197e5ba3d849 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 21 Sep 2020 18:49:10 +0200 Subject: [PATCH 52/79] Generating enum, vol.5 --- .../src/codegen/interface_attr.rs | 2851 ++++++++++++----- juniper_codegen/src/common/mod.rs | 8 + juniper_codegen/src/graphql_interface/attr.rs | 51 +- juniper_codegen/src/graphql_interface/mod.rs | 126 +- 4 files changed, 2250 insertions(+), 786 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index bd999a167..5671803f9 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,9 +1,9 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, - EmptySubscription, Executor, FieldError, FieldResult, GraphQLObject, GraphQLType, - IntoFieldError, RootNode, ScalarValue, Variables, + execute, graphql_interface, graphql_object, graphql_value, sa, AsDynGraphQLValue, + DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, + GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, Variables, }; fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> @@ -229,10 +229,7 @@ mod trivial { } }"#; - for (root, expected_info) in &[ - (QueryRoot::Human, "earth"), - (QueryRoot::Droid, "run"), - ] { + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { let schema = schema(*root); let expected_info: &str = *expected_info; @@ -242,70 +239,81 @@ mod trivial { ); } } -/* + #[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, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn registers_itself_in_implementers() { - for object in &["Human", "Droid"] { + for interface in &["Character", "Hero"] { let doc = format!( r#"{{ __type(name: "{}") {{ - interfaces {{ + possibleTypes {{ kind name }} }} }}"#, - object, + interface, ); - let schema = schema(QueryRoot::Human); + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, + {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -315,54 +323,65 @@ mod trivial { #[tokio::test] async fn uses_trait_name() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; - let schema = schema(QueryRoot::Human); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + interface, + ); + + let expected_name: &str = *interface; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), 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, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"description": None}}), vec![])), - ); - } + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + description + }} + }}"#, + interface, + ); - */ + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } + } } -/* -mod dyn_alias { + +mod explicit_alias { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] trait Character { fn id(&self) -> &str; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterEnum)] struct Human { id: String, home_planet: String, } + sa::assert_not_impl_all!(Human: AsDynGraphQLValue); + #[graphql_interface] impl Character for Human { fn id(&self) -> &str { @@ -371,12 +390,14 @@ mod dyn_alias { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterEnum)] struct Droid { id: String, primary_function: String, } + sa::assert_not_impl_all!(Droid: AsDynGraphQLValue); + #[graphql_interface] impl Character for Droid { fn id(&self) -> &str { @@ -392,18 +413,19 @@ mod dyn_alias { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { + fn character(&self) -> CharacterEnum { + match self { + Self::Human => Human { id: "human-32".to_string(), home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { + } + .into(), + Self::Droid => Droid { id: "droid-99".to_string(), primary_function: "run".to_string(), - }), - }; - ch + } + .into(), + } } } @@ -525,13 +547,18 @@ mod dyn_alias { mod trivial_async { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + async fn info(&self) -> &str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -544,8 +571,15 @@ mod trivial_async { } } + #[graphql_interface(dyn)] + impl Hero for Human { + async fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -558,6 +592,13 @@ mod trivial_async { } } + #[graphql_interface(dyn)] + impl Hero for Droid { + async fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -566,8 +607,23 @@ mod trivial_async { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -582,7 +638,7 @@ mod trivial_async { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -604,7 +660,7 @@ mod trivial_async { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -626,7 +682,51 @@ mod trivial_async { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -648,68 +748,98 @@ mod trivial_async { } #[tokio::test] - async fn is_graphql_interface() { + async fn dyn_resolves_info_field() { const DOC: &str = r#"{ - __type(name: "Character") { - kind + hero { + info } }"#; - let schema = schema(QueryRoot::Human); + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } } #[tokio::test] - async fn registers_all_implementers() { - const DOC: &str = r#"{ - __type(name: "Character") { - possibleTypes { - kind - name - } - } - }"#; - + async fn is_graphql_interface() { let schema = schema(QueryRoot::Human); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } } #[tokio::test] - async fn registers_itself_in_implementers() { - for object in &["Human", "Droid"] { + async fn registers_all_implementers() { + let schema = schema(QueryRoot::Human); + + for interface in &["Character", "Hero"] { let doc = format!( r#"{{ __type(name: "{}") {{ - interfaces {{ + possibleTypes {{ kind name }} }} }}"#, - object, + interface, ); - let schema = schema(QueryRoot::Human); + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).await, Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, + {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -719,41 +849,52 @@ mod trivial_async { #[tokio::test] async fn uses_trait_name() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; - let schema = schema(QueryRoot::Human); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + interface, + ); + + let expected_name: &str = *interface; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), 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, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"description": None}}), vec![])), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + description + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } } } mod explicit_async { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; @@ -762,8 +903,17 @@ mod explicit_async { } } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + async fn id(&self) -> &str { + "Non-identified" + } + + fn info(&self) -> &str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -780,8 +930,15 @@ mod explicit_async { } } + #[graphql_interface(async, dyn)] + impl Hero for Human { + fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -794,6 +951,17 @@ mod explicit_async { } } + #[graphql_interface(dyn)] + impl Hero for Droid { + async fn id(&self) -> &str { + &self.id + } + + fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -802,8 +970,23 @@ mod explicit_async { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -818,7 +1001,7 @@ mod explicit_async { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -840,7 +1023,7 @@ mod explicit_async { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -862,45 +1045,105 @@ mod explicit_async { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_fields() { const DOC: &str = r#"{ character { id + info } }"#; - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "human-32", "Home planet is earth"), + (QueryRoot::Droid, "droid-99", "None available"), ] { let schema = schema(*root); let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + }}), + vec![], + )), ); } } #[tokio::test] - async fn resolves_info_field() { + async fn dyn_resolves_fields() { const DOC: &str = r#"{ - character { + hero { + id info } }"#; - for (root, expected) in &[ - (QueryRoot::Human, "Home planet is earth"), - (QueryRoot::Droid, "None available"), + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "Non-identified", "earth"), + (QueryRoot::Droid, "droid-99", "run"), ] { let schema = schema(*root); - let expected: &str = *expected; + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"info": expected}}), vec![])), + Ok(( + graphql_value!({"hero": { + "id": expected_id, + "info": expected_info, + }}), + vec![], + )), ); } } @@ -917,13 +1160,18 @@ mod fallible_field { } } - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Result<&str, CustomError>; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + fn info(&self) -> Result<&str, CustomError>; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -936,8 +1184,15 @@ mod fallible_field { } } + #[graphql_interface(dyn)] + impl Hero for Human { + fn info(&self) -> Result<&str, CustomError> { + Ok(&self.home_planet) + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -950,6 +1205,13 @@ mod fallible_field { } } + #[graphql_interface(dyn)] + impl Hero for Droid { + fn info(&self) -> Result<&str, CustomError> { + Ok(&self.primary_function) + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -958,8 +1220,23 @@ mod fallible_field { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -974,7 +1251,7 @@ mod fallible_field { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -996,7 +1273,7 @@ mod fallible_field { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1018,127 +1295,9 @@ mod fallible_field { } #[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, &Variables::new(), &()).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, &Variables::new(), &()).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], dyn = DynCharacter)] - trait Character { - fn id(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn resolves_human() { + async fn dyn_resolves_human() { const DOC: &str = r#"{ - character { + hero { ... on Human { humanId: id homePlanet @@ -1151,16 +1310,16 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), vec![], )), ); } #[tokio::test] - async fn resolves_droid() { + async fn dyn_resolves_droid() { const DOC: &str = r#"{ - character { + hero { ... on Droid { droidId: id primaryFunction @@ -1173,14 +1332,14 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), vec![], )), ); } #[tokio::test] - async fn resolves_id_field() { + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -1202,102 +1361,83 @@ mod generic { } #[tokio::test] - async fn is_graphql_interface() { + async fn dyn_resolves_info_field() { const DOC: &str = r#"{ - __type(name: "Character") { - kind + hero { + info } }"#; - let schema = schema(QueryRoot::Human); + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } } #[tokio::test] - async fn registers_all_implementers() { - const DOC: &str = r#"{ - __type(name: "Character") { - possibleTypes { - kind - name - } - } - }"#; - + async fn has_correct_graphql_type() { let schema = schema(QueryRoot::Human); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn registers_itself_in_implementers() { - for object in &["Human", "Droid"] { + for (interface, field) in &[("Character", "id"), ("Hero", "info")] { let doc = format!( r#"{{ __type(name: "{}") {{ - interfaces {{ - kind + name + kind + fields {{ name + type {{ + kind + ofType {{ + name + }} + }} }} }} }}"#, - object, + interface, ); - let schema = schema(QueryRoot::Human); - + let expected_name: &str = *interface; + let expected_field_name: &str = *field; assert_eq!( execute(&doc, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": {"interfaces": [ - {"kind": "INTERFACE", "name": "Character"}, - ]}}), + graphql_value!({"__type": { + "name": expected_name, + "kind": "INTERFACE", + "fields": [{ + "name": expected_field_name, + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }] + }}), vec![], )), ); } } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } } - -mod generic_async { +/* +mod generic { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] - trait Character { - async fn id(&self) -> &str; + #[graphql_interface(for = [Human, Droid])] + trait Character { + fn id(&self) -> &str; + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + fn info(&self) -> &str; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -1305,13 +1445,20 @@ mod generic_async { #[graphql_interface] impl Character for Human { - async fn id(&self) -> &str { + fn id(&self) -> &str { &self.id } } + #[graphql_interface(dyn)] + impl Hero for Human { + fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -1319,11 +1466,18 @@ mod generic_async { #[graphql_interface] impl Character for Droid { - async fn id(&self) -> &str { + fn id(&self) -> &str { &self.id } } + #[graphql_interface(dyn)] + impl Hero for Droid { + fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1332,8 +1486,23 @@ mod generic_async { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -1348,7 +1517,7 @@ mod generic_async { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -1370,7 +1539,7 @@ mod generic_async { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1392,12 +1561,222 @@ mod generic_async { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { const DOC: &str = r#"{ - character { - id - } - }"#; + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + interface, + ); + + let schema = schema(QueryRoot::Human); + + let expected_name: &str = *interface; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + ); + } + } +} + +mod generic_async { + use super::*; + + #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + trait Character { + async fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface] + impl Character for Human { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = dyn Character)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface] + impl Character for Droid { + async fn id(&self) -> &str { + &self.id + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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"), @@ -1658,16 +2037,23 @@ mod generic_lifetime_async { } } + */ + mod argument { use super::*; - #[graphql_interface(for = Human, dyn = DynCharacter)] + #[graphql_interface(for = Human)] trait Character { - fn id_wide(&self, is_planet: bool) -> &str; + fn id_wide(&self, is_number: bool) -> &str; + } + + #[graphql_interface(dyn = DynHero, for = Human)] + trait Hero { + fn info_wide(&self, is_planet: bool) -> &str; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -1675,7 +2061,18 @@ mod argument { #[graphql_interface] impl Character for Human { - fn id_wide(&self, is_planet: bool) -> &str { + fn id_wide(&self, is_number: bool) -> &str { + if is_number { + &self.id + } else { + "none" + } + } + } + + #[graphql_interface(dyn)] + impl Hero for Human { + fn info_wide(&self, is_planet: bool) -> &str { if is_planet { &self.home_planet } else { @@ -1688,7 +2085,15 @@ mod argument { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + + fn hero(&self) -> Box> { Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -1697,12 +2102,12 @@ mod argument { } #[tokio::test] - async fn resolves_id_field() { + async fn enum_resolves_id_field() { let schema = schema(QueryRoot); for (input, expected) in &[ - ("{ character { idWide(isPlanet: true) } }", "earth"), - ("{ character { idWide(isPlanet: false) } }", "human-32"), + ("{ character { idWide(isNumber: true) } }", "human-32"), + ("{ character { idWide(isNumber: false) } }", "none"), ] { let expected: &str = *expected; @@ -1714,82 +2119,119 @@ mod argument { } #[tokio::test] - async fn camelcases_name() { - const DOC: &str = r#"{ - __type(name: "Character") { - fields { - name - args { - name - } - } - } - }"#; - + async fn dyn_resolves_info_field() { let schema = schema(QueryRoot); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"name": "idWide", "args": [{"name": "isPlanet"}]}, - ]}}), - vec![], - )), - ); - } - + for (input, expected) in &[ + ("{ hero { infoWide(isPlanet: true) } }", "earth"), + ("{ hero { infoWide(isPlanet: false) } }", "human-32"), + ] { + let expected: &str = *expected; + + assert_eq!( + execute(*input, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"infoWide": expected}}), vec![])), + ); + } + } + #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Character") { - fields { - args { - description - } - } - } - }"#; + async fn camelcases_name() { + let schema = schema(QueryRoot); + + for (interface, field, arg) in &[ + ("Character", "idWide", "isNumber"), + ("Hero", "infoWide", "isPlanet"), + ] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + fields {{ + name + args {{ + name + }} + }} + }} + }}"#, + interface, + ); + let expected_field_name: &str = *field; + let expected_arg_name: &str = *arg; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": expected_field_name, "args": [{"name": expected_arg_name}]}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn has_no_description() { let schema = schema(QueryRoot); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": { "fields": [{"args": [{"description": None}]}]}}), - vec![], - )), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + fields {{ + args {{ + description + }} + }} + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { "fields": [{"args": [{"description": None}]}]}}), + vec![], + )), + ); + } } #[tokio::test] async fn has_no_defaults() { - const DOC: &str = r#"{ - __type(name: "Character") { - fields { - args { - defaultValue - } - } - } - }"#; - let schema = schema(QueryRoot); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": { "fields": [{"args": [{"defaultValue": None}]}]}}), - vec![], - )), - ); + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + fields {{ + args {{ + defaultValue + }} + }} + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { "fields": [{"args": [{"defaultValue": None}]}]}}), + vec![], + )), + ); + } } } mod default_argument { use super::*; - #[graphql_interface(for = Human, dyn = DynCharacter)] + #[graphql_interface(for = Human)] trait Character { async fn id( &self, @@ -1800,7 +2242,7 @@ mod default_argument { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterValue)] struct Human { id: String, } @@ -1816,10 +2258,11 @@ mod default_argument { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - Box::new(Human { + fn character(&self) -> CharacterValue { + Human { id: "human-32".to_string(), - }) + } + .into() } } @@ -1882,14 +2325,14 @@ mod description_from_doc_comment { use super::*; /// Rust docs. - #[graphql_interface(for = Human, dyn = DynCharacter)] + #[graphql_interface(for = Human)] trait Character { /// Rust `id` docs. fn id(&self) -> &str; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -1906,11 +2349,12 @@ mod description_from_doc_comment { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - Box::new(Human { + fn character(&self) -> CharacterValue { + Human { id: "human-32".to_string(), home_planet: "earth".to_string(), - }) + } + .into() } } @@ -1944,7 +2388,7 @@ mod deprecation_from_attr { use super::*; - #[graphql_interface(for = Human, dyn = DynCharacter)] + #[graphql_interface(for = Human)] trait Character { fn id(&self) -> &str; @@ -1960,7 +2404,7 @@ mod deprecation_from_attr { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -1977,11 +2421,12 @@ mod deprecation_from_attr { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - Box::new(Human { + fn character(&self) -> CharacterValue { + Human { id: "human-32".to_string(), home_planet: "earth".to_string(), - }) + } + .into() } } @@ -2077,7 +2522,7 @@ mod explicit_name_description_and_deprecation { use super::*; /// Rust docs. - #[graphql_interface(name = "MyChar", desc = "My character.", for = Human, dyn = DynCharacter)] + #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] trait Character { /// Rust `id` docs. #[graphql_interface(name = "myId", desc = "My character ID.", deprecated = "Not used.")] @@ -2099,7 +2544,7 @@ mod explicit_name_description_and_deprecation { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -2116,11 +2561,12 @@ mod explicit_name_description_and_deprecation { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - Box::new(Human { + fn character(&self) -> CharacterValue { + Human { id: "human-32".to_string(), home_planet: "earth".to_string(), - }) + } + .into() } } @@ -2259,14 +2705,20 @@ mod explicit_name_description_and_deprecation { mod explicit_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] #[graphql_interface(scalar = DefaultScalarValue)] trait Character { fn id(&self) -> &str; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + #[graphql_interface(scalar = DefaultScalarValue)] + trait Hero { + async fn info(&self) -> &str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, scalar = DefaultScalarValue)] + #[graphql(impl = [CharacterValue, dyn Hero], scalar = DefaultScalarValue)] struct Human { id: String, home_planet: String, @@ -2279,8 +2731,15 @@ mod explicit_scalar { } } + #[graphql_interface(dyn, scalar = DefaultScalarValue)] + impl Hero for Human { + async fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, scalar = DefaultScalarValue)] + #[graphql(impl = [CharacterValue, dyn Hero], scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, @@ -2293,6 +2752,13 @@ mod explicit_scalar { } } + #[graphql_interface(dyn, scalar = DefaultScalarValue)] + impl Hero for Droid { + async fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2301,8 +2767,23 @@ mod explicit_scalar { #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -2317,7 +2798,7 @@ mod explicit_scalar { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2339,7 +2820,7 @@ mod explicit_scalar { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2361,7 +2842,51 @@ mod explicit_scalar { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema::<_, DefaultScalarValue, _>(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema::<_, DefaultScalarValue, _>(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2381,6 +2906,25 @@ mod explicit_scalar { ); } } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema::<_, DefaultScalarValue, _>(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } } mod custom_scalar { @@ -2388,14 +2932,19 @@ mod custom_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] - #[graphql_interface(scalar = MyScalarValue)] + #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] trait Character { async fn id(&self) -> &str; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + #[graphql_interface(scalar = MyScalarValue)] + trait Hero { + async fn info(&self) -> &str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, scalar = MyScalarValue)] + #[graphql(impl = [CharacterValue, dyn Hero], scalar = MyScalarValue)] struct Human { id: String, home_planet: String, @@ -2408,8 +2957,15 @@ mod custom_scalar { } } + #[graphql_interface(dyn, scalar = MyScalarValue)] + impl Hero for Human { + async fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, scalar = MyScalarValue)] + #[graphql(impl = [CharacterValue, dyn Hero], scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, @@ -2422,6 +2978,13 @@ mod custom_scalar { } } + #[graphql_interface(dyn, scalar = MyScalarValue)] + impl Hero for Droid { + async fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2430,8 +2993,23 @@ mod custom_scalar { #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -2446,7 +3024,7 @@ mod custom_scalar { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2468,7 +3046,7 @@ mod custom_scalar { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2490,7 +3068,51 @@ mod custom_scalar { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2510,18 +3132,42 @@ mod custom_scalar { ); } } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema::<_, MyScalarValue, _>(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } } mod explicit_generic_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, scalar = S)] + #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { fn id(&self) -> FieldResult<&str, S>; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)] + trait Hero { + async fn info(&self) -> FieldResult<&str, S>; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -2534,8 +3180,15 @@ mod explicit_generic_scalar { } } + #[graphql_interface(dyn, scalar = S)] + impl Hero for Human { + async fn info(&self) -> FieldResult<&str, S> { + Ok(&self.home_planet) + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -2548,6 +3201,13 @@ mod explicit_generic_scalar { } } + #[graphql_interface(dyn, scalar = S)] + impl Hero for Droid { + async fn info(&self) -> FieldResult<&str, S> { + Ok(&self.primary_function) + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2556,8 +3216,23 @@ mod explicit_generic_scalar { #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -2572,7 +3247,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2594,7 +3269,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2616,7 +3291,51 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2636,6 +3355,25 @@ mod explicit_generic_scalar { ); } } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } } mod explicit_custom_context { @@ -2645,7 +3383,7 @@ mod explicit_custom_context { impl juniper::Context for CustomContext {} - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, context = CustomContext)] + #[graphql_interface(for = [Human, Droid], context = CustomContext)] trait Character { async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; @@ -2654,8 +3392,18 @@ mod explicit_custom_context { fn more<'c>(&'c self, #[graphql_interface(context)] custom: &CustomContext) -> &'c str; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + #[graphql_interface(context = CustomContext)] + trait Hero { + async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; + + async fn info<'b>(&'b self, ctx: &()) -> &'b str; + + fn more<'c>(&'c self, #[graphql_interface(context)] custom: &CustomContext) -> &'c str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = CustomContext)] + #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] struct Human { id: String, home_planet: String, @@ -2676,8 +3424,23 @@ mod explicit_custom_context { } } + #[graphql_interface(dyn)] + impl Hero for Human { + async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + &self.id + } + + async fn info<'b>(&'b self, _: &()) -> &'b str { + &self.home_planet + } + + fn more(&self, _: &CustomContext) -> &'static str { + "human" + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = CustomContext)] + #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] struct Droid { id: String, primary_function: String, @@ -2698,6 +3461,21 @@ mod explicit_custom_context { } } + #[graphql_interface(dyn)] + impl Hero for Droid { + async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + &self.id + } + + async fn info<'b>(&'b self, _: &()) -> &'b str { + &self.primary_function + } + + fn more(&self, _: &CustomContext) -> &'static str { + "droid" + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2706,8 +3484,23 @@ mod explicit_custom_context { #[graphql_object(context = CustomContext)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -2722,7 +3515,7 @@ mod explicit_custom_context { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2744,7 +3537,7 @@ mod explicit_custom_context { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2766,36 +3559,87 @@ mod explicit_custom_context { } #[tokio::test] - async fn resolves_fields() { + async fn dyn_resolves_human() { const DOC: &str = r#"{ - character { - id - info - more + hero { + ... on Human { + humanId: id + homePlanet + } } }"#; - for (root, expected_id, expected_info, expexted_more) in &[ - (QueryRoot::Human, "human-32", "earth", "human"), - (QueryRoot::Droid, "droid-99", "run", "droid"), - ] { - let schema = schema(*root); + let schema = schema(QueryRoot::Human); - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - let expexted_more: &str = *expexted_more; - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &CustomContext).await, - Ok(( - graphql_value!({"character": { - "id": expected_id, - "info": expected_info, - "more": expexted_more, - }}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &CustomContext).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &CustomContext).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + for interface in &["character", "hero"] { + let doc = format!( + r#"{{ + {} {{ + id + info + more + }} + }}"#, + interface, + ); + + let expected_interface: &str = *interface; + + for (root, expected_id, expected_info, expexted_more) in &[ + (QueryRoot::Human, "human-32", "earth", "human"), + (QueryRoot::Droid, "droid-99", "run", "droid"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + let expexted_more: &str = *expexted_more; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &CustomContext).await, + Ok(( + graphql_value!({expected_interface: { + "id": expected_id, + "info": expected_info, + "more": expexted_more, + }}), + vec![], + )), + ); + } + } } } @@ -2806,15 +3650,22 @@ mod inferred_custom_context_from_field { impl juniper::Context for CustomContext {} - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] trait Character { + fn id<'a>(&self, context: &'a CustomContext) -> &'a str; + + fn info<'b>(&'b self, context: &()) -> &'b str; + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { async fn id<'a>(&self, context: &'a CustomContext) -> &'a str; async fn info<'b>(&'b self, context: &()) -> &'b str; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = CustomContext)] + #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] struct Human { id: String, home_planet: String, @@ -2822,6 +3673,17 @@ mod inferred_custom_context_from_field { #[graphql_interface] impl Character for Human { + fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { + &ctx.0 + } + + fn info<'b>(&'b self, _: &()) -> &'b str { + &self.home_planet + } + } + + #[graphql_interface(dyn)] + impl Hero for Human { async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } @@ -2832,7 +3694,7 @@ mod inferred_custom_context_from_field { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = CustomContext)] + #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] struct Droid { id: String, primary_function: String, @@ -2840,6 +3702,17 @@ mod inferred_custom_context_from_field { #[graphql_interface] impl Character for Droid { + fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { + &ctx.0 + } + + fn info<'b>(&'b self, _: &()) -> &'b str { + &self.primary_function + } + } + + #[graphql_interface(dyn)] + impl Hero for Droid { async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } @@ -2857,8 +3730,23 @@ mod inferred_custom_context_from_field { #[graphql_object(context = CustomContext)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -2873,7 +3761,7 @@ mod inferred_custom_context_from_field { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2896,7 +3784,7 @@ mod inferred_custom_context_from_field { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2919,30 +3807,86 @@ mod inferred_custom_context_from_field { } #[tokio::test] - async fn resolves_fields() { + async fn dyn_resolves_human() { const DOC: &str = r#"{ - character { - id - info + hero { + ... on Human { + humanId: id + homePlanet + } } }"#; - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-ctx", "earth"), - (QueryRoot::Droid, "droid-ctx", "run"), - ] { - let schema = schema(*root); - let ctx = CustomContext(expected_id.to_string()); + let schema = schema(QueryRoot::Human); + let ctx = CustomContext("in-ctx".into()); - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &ctx).await, - Ok(( - graphql_value!({"character": {"id": expected_id, "info": expected_info}}), - vec![], - )), + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + let ctx = CustomContext("in-droid".into()); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + for interface in &["character", "hero"] { + let doc = format!( + r#"{{ + {} {{ + id + info + }} + }}"#, + interface, ); + + let expected_interface: &str = *interface; + + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "human-ctx", "earth"), + (QueryRoot::Droid, "droid-ctx", "run"), + ] { + let schema = schema(*root); + let ctx = CustomContext(expected_id.to_string()); + + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({expected_interface: { + "id": expected_id, + "info": expected_info, + }}), + vec![], + )), + ); + } } } } @@ -2956,8 +3900,16 @@ mod inferred_custom_context_from_downcast { impl juniper::Context for Database {} - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] trait Character { + #[graphql_interface(downcast)] + fn as_human<'db>(&self, db: &'db Database) -> Option<&'db Human>; + + async fn id(&self) -> &str; + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { #[graphql_interface(downcast)] fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid>; @@ -2965,7 +3917,7 @@ mod inferred_custom_context_from_downcast { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = Database)] + #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] struct Human { id: String, home_planet: String, @@ -2973,6 +3925,17 @@ mod inferred_custom_context_from_downcast { #[graphql_interface] impl Character for Human { + fn as_human<'db>(&self, _: &'db Database) -> Option<&'db Human> { + None + } + + async fn id(&self) -> &str { + &self.id + } + } + + #[graphql_interface(dyn)] + impl Hero for Human { fn as_droid<'db>(&self, _: &'db Database) -> Option<&'db Droid> { None } @@ -2983,7 +3946,7 @@ mod inferred_custom_context_from_downcast { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = Database)] + #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] struct Droid { id: String, primary_function: String, @@ -2991,6 +3954,17 @@ mod inferred_custom_context_from_downcast { #[graphql_interface] impl Character for Droid { + fn as_human<'db>(&self, _: &'db Database) -> Option<&'db Human> { + None + } + + async fn id(&self) -> &str { + &self.id + } + } + + #[graphql_interface(dyn)] + impl Hero for Droid { fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { db.droid.as_ref() } @@ -3000,6 +3974,12 @@ mod inferred_custom_context_from_downcast { } } + //#[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + /* #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -3008,8 +3988,23 @@ mod inferred_custom_context_from_downcast { #[graphql_object(context = Database)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -3023,8 +4018,9 @@ mod inferred_custom_context_from_downcast { } } + #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3047,7 +4043,7 @@ mod inferred_custom_context_from_downcast { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3075,53 +4071,130 @@ mod inferred_custom_context_from_downcast { } #[tokio::test] - async fn resolves_info_field() { + async fn dyn_resolves_human() { const DOC: &str = r#"{ - character { - info + hero { + ... on Human { + humanId: id + homePlanet + } } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - let db = Database { droid: None }; + let schema = schema(QueryRoot::Human); + let db = Database { droid: None }; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &db).await, - Ok(( - graphql_value!({"character": {"info": expected_info}}), - vec![], - )), - ); - } + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); } -} - -mod executor { - use juniper::LookAheadMethods as _; - use super::*; + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter, scalar = S)] - trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync, - { - executor.look_ahead().field_name() - } + let schema = schema(QueryRoot::Droid); + let db = Database { + droid: Some(Droid { + id: "droid-88".to_string(), + primary_function: "sit".to_string(), + }), + }; - async fn info<'b>( - &'b self, - #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>, + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-88", "primaryFunction": "sit"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_info_field() { + for interface in &["character", "hero"] { + let doc = format!( + r#"{{ + {} {{ + info + }} + }}"#, + interface, + ); + + let expected_interface: &str = *interface; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + let db = Database { droid: None }; + + let expected_info: &str = *expected_info; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({expected_interface: { + "info": expected_info, + }}), + vec![], + )), + ); + } + } + }*/ +} + +mod executor { + use juniper::LookAheadMethods as _; + + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = S)] + trait Character { + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync, + { + executor.look_ahead().field_name() + } + + async fn info<'b>( + &'b self, + #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)] + trait Hero { + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync, + { + executor.look_ahead().field_name() + } + + async fn info<'b>( + &'b self, + #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>, ) -> &'b str where S: Send + Sync; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -3137,8 +4210,18 @@ mod executor { } } + #[graphql_interface(dyn, scalar = S)] + impl Hero for Human { + async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + where + S: Send + Sync, + { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -3154,6 +4237,16 @@ mod executor { } } + #[graphql_interface(dyn, scalar = S)] + impl Hero for Droid { + async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + where + S: Send + Sync, + { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -3162,8 +4255,23 @@ mod executor { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -3178,7 +4286,7 @@ mod executor { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3200,7 +4308,7 @@ mod executor { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3222,25 +4330,76 @@ mod executor { } #[tokio::test] - async fn resolves_fields() { + async fn dyn_resolves_human() { const DOC: &str = r#"{ - character { - id - info + hero { + ... on Human { + humanId: id + homePlanet + } } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); + let schema = schema(QueryRoot::Human); - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"id": "id", "info": expected_info}}), - vec![], - )), + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + for interface in &["character", "hero"] { + let doc = format!( + r#"{{ + {} {{ + id + info + }} + }}"#, + interface, ); + + let expected_interface: &str = *interface; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({expected_interface: {"id": "id", "info": expected_info}}), + vec![], + )), + ); + } } } } @@ -3248,7 +4407,7 @@ mod executor { mod ignored_method { use super::*; - #[graphql_interface(for = Human, dyn = DynCharacter)] + #[graphql_interface(for = Human)] trait Character { fn id(&self) -> &str; @@ -3262,7 +4421,7 @@ mod ignored_method { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -3279,11 +4438,12 @@ mod ignored_method { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - Box::new(Human { + fn character(&self) -> CharacterValue { + Human { id: "human-32".to_string(), home_planet: "earth".to_string(), - }) + } + .into() } } @@ -3350,10 +4510,20 @@ mod ignored_method { mod downcast_method { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; + #[graphql_interface(downcast)] + fn as_human(&self) -> Option<&Human> { + None + } + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + fn info(&self) -> &str; + #[graphql_interface(downcast)] fn as_droid(&self) -> Option<&Droid> { None @@ -3361,7 +4531,7 @@ mod downcast_method { } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, @@ -3372,10 +4542,21 @@ mod downcast_method { fn id(&self) -> &str { &self.id } + + fn as_human(&self) -> Option<&Human> { + Some(self) + } + } + + #[graphql_interface(dyn)] + impl Hero for Human { + fn info(&self) -> &str { + &self.home_planet + } } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -3386,147 +4567,245 @@ mod downcast_method { fn id(&self) -> &str { &self.id } + } + + #[graphql_interface(dyn)] + impl Hero for Droid { + fn info(&self) -> &str { + &self.primary_function + } fn as_droid(&self) -> Option<&Droid> { Some(self) } } - #[derive(Clone, Copy)] + //#[derive(Clone, Copy)] enum QueryRoot { Human, Droid, } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).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, &Variables::new(), &()).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, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_not_field() { - const DOC: &str = r#"{ - __type(name: "Character") { - fields { - name - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{"name": "id"}]}}), - vec![], - )), - ); - } + /* + #[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(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_not_field() { + let schema = schema(QueryRoot::Human); + + for (doc, field) in &[ + (r#"{__type(name: "Character") { fields { name } } }"#, "id"), + (r#"{__type(name: "Hero") { fields { name } } }"#, "info"), + ] { + let expected_field: &str = *field; + assert_eq!( + execute(*doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": expected_field}]}}), + vec![], + )), + ); + } + } + + */ } mod external_downcast { use super::*; struct Database { + human: Option, droid: Option, } impl juniper::Context for Database {} - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] - #[graphql_interface(context = Database)] - #[graphql_interface(on Droid = DynCharacter::as_droid)] + #[graphql_interface(for = [Human, Droid])] + #[graphql_interface(context = Database, on Human = CharacterValue::as_human)] trait Character { fn id(&self) -> &str; } - impl<'a, S: ScalarValue> DynCharacter<'a, S> { + impl CharacterValue { + fn as_human<'db>(&self, db: &'db Database) -> Option<&'db Human> { + db.human.as_ref() + } + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + #[graphql_interface(context = Database)] + #[graphql_interface(on Droid = DynHero::as_droid)] + trait Hero { + fn info(&self) -> &str; + } + + impl<'a, S: ScalarValue> DynHero<'a, S> { fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { db.droid.as_ref() } } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = Database)] + #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] struct Human { id: String, home_planet: String, @@ -3539,8 +4818,15 @@ mod external_downcast { } } + #[graphql_interface(dyn)] + impl Hero for Human { + fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character, context = Database)] + #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] struct Droid { id: String, primary_function: String, @@ -3553,6 +4839,13 @@ mod external_downcast { } } + #[graphql_interface(dyn)] + impl Hero for Droid { + fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -3561,15 +4854,30 @@ mod external_downcast { #[graphql_object(context = Database)] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), }), Self::Droid => Box::new(Droid { - id: "?????".to_string(), - primary_function: "???".to_string(), + id: "droid-99".to_string(), + primary_function: "run".to_string(), }), }; ch @@ -3577,7 +4885,7 @@ mod external_downcast { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3588,19 +4896,77 @@ mod external_downcast { }"#; let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; + let db = Database { + human: Some(Human { + id: "human-64".to_string(), + home_planet: "mars".to_string(), + }), + droid: None, + }; assert_eq!( execute(DOC, None, &schema, &Variables::new(), &db).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "human-64", "homePlanet": "mars"}}), vec![], )), ); } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + let db = Database { + human: None, + droid: None, + }; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + let db = Database { + human: None, + droid: None, + }; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3612,23 +4978,24 @@ mod external_downcast { let schema = schema(QueryRoot::Droid); let db = Database { + human: None, droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + id: "droid-01".to_string(), + primary_function: "swim".to_string(), }), }; assert_eq!( execute(DOC, None, &schema, &Variables::new(), &db).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "droid-01", "primaryFunction": "swim"}}), vec![], )), ); } #[tokio::test] - async fn resolves_id_field() { + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -3636,13 +5003,17 @@ mod external_downcast { }"#; let db = Database { - droid: Some(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), + human: Some(Human { + id: "human-64".to_string(), + home_planet: "mars".to_string(), }), + droid: None, }; - for (root, expected_id) in &[(QueryRoot::Human, "human-32"), (QueryRoot::Droid, "?????")] { + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { let schema = schema(*root); let expected_id: &str = *expected_id; @@ -3652,5 +5023,31 @@ mod external_downcast { ); } } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + let db = Database { + human: None, + droid: Some(Droid { + id: "droid-01".to_string(), + primary_function: "swim".to_string(), + }), + }; + + for (root, expected_id) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok((graphql_value!({"hero": {"info": expected_id}}), vec![])), + ); + } + } } -*/ \ No newline at end of file diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 50f33d143..82706b93f 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -112,6 +112,14 @@ impl ScalarValueType { } } + #[must_use] + pub(crate) fn generic_ty(&self) -> syn::Type { + match self { + Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, + Self::ImplicitGeneric | Self::Concrete(_) => parse_quote! { __S }, + } + } + #[must_use] pub(crate) fn default_ty(&self) -> syn::Type { match self { diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 821f0c42a..2ecabb14d 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -163,7 +163,6 @@ pub fn expand_on_trait( let is_trait_object = meta.r#dyn.is_some(); - let is_async_trait = meta.asyncness.is_some() || ast .items @@ -182,18 +181,40 @@ pub fn expand_on_trait( }) .is_some(); + let ty = if is_trait_object { + Type::TraitObject(TraitObjectType::new(&ast, &meta, scalar.clone(), context.clone())) + } else { + Type::Enum(EnumType::new(&ast, &meta, &implementers, scalar.clone())) + }; + + let generated_code = Definition { + ty, + + trait_ident: trait_ident.clone(), + trait_generics: ast.generics.clone(), + + name, + description: meta.description.map(SpanContainer::into_inner), + + context, + scalar: scalar.clone(), + + fields, + implementers, + }; + // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. if is_trait_object { ast.attrs.push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] }); - let scalar_ty = scalar.ty(); - let default_scalar_ty = scalar.default_ty(); + let scalar_ty = scalar.generic_ty(); if !scalar.is_explicit_generic() { + let default_ty = scalar.default_ty(); ast.generics .params - .push(parse_quote! { #scalar_ty = #default_scalar_ty }); + .push(parse_quote! { #scalar_ty = #default_ty }); } ast.generics .make_where_clause() @@ -221,28 +242,6 @@ pub fn expand_on_trait( ); } - let ty = if is_trait_object { - Type::TraitObject(TraitObjectType::new(&ast, &meta, context.clone())) - } else { - Type::Enum(EnumType::new(&ast, &meta, &implementers)) - }; - - let generated_code = Definition { - ty, - - trait_ident: trait_ident.clone(), - trait_generics: ast.generics.clone(), - - name, - description: meta.description.map(SpanContainer::into_inner), - - context, - scalar, - - fields, - implementers, - }; - Ok(quote! { #ast diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0c45d48e6..9141764e8 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -774,15 +774,20 @@ struct ImplementerDefinition { } impl ImplementerDefinition { - fn downcast_call_tokens(&self, trait_ty: &syn::Type) -> Option { - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(context) }); + fn downcast_call_tokens( + &self, + trait_ty: &syn::Type, + ctx: Option, + ) -> Option { + let ctx = ctx.unwrap_or_else(|| parse_quote! { executor.context() }); + let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(#ctx) }); let fn_path = match self.downcast.as_ref()? { ImplementerDowncastDefinition::Method { name, with_context } => { if !with_context { ctx_arg = None; } - quote! { <#trait_ty>::#name } + quote! { ::#name } } ImplementerDowncastDefinition::External { path } => { quote! { #path } @@ -802,7 +807,7 @@ impl ImplementerDefinition { let ty = &self.ty; let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(trait_ty); + let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context })); // Doing this may be quite an expensive, because resolving may contain some heavy // computation, so we're preforming it twice. Unfortunately, we have no other options here, @@ -822,7 +827,7 @@ impl ImplementerDefinition { let ty = &self.ty; let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(trait_ty); + let downcast = self.downcast_call_tokens(trait_ty, None); let resolving_code = gen::sync_resolving_code(); @@ -842,7 +847,7 @@ impl ImplementerDefinition { let ty = &self.ty; let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(trait_ty); + let downcast = self.downcast_call_tokens(trait_ty, None); let resolving_code = gen::async_resolving_code(None); @@ -904,13 +909,6 @@ struct Definition { } impl Definition { - fn trait_ty(&self) -> syn::Type { - let ty = &self.trait_ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); - - parse_quote! { #ty#generics } - } - fn no_field_panic_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -926,7 +924,7 @@ impl Definition { fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(scalar); + let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); @@ -980,11 +978,11 @@ impl Definition { fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(scalar); + let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); - let trait_ty = self.trait_ty(); + let trait_ty = self.ty.trait_ty(); let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); let fields_resolvers = self @@ -1021,7 +1019,7 @@ impl Definition { .implementers .iter() .filter_map(|i| i.concrete_type_name_method_tokens(&trait_ty)); - let regular_downcast_check = self.ty.concrete_type_name_method_tokens(scalar); + let regular_downcast_check = self.ty.concrete_type_name_method_tokens(); let custom_downcasts = self .implementers @@ -1080,7 +1078,7 @@ impl Definition { fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(scalar); + let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let mut where_clause = where_clause .cloned() @@ -1093,7 +1091,7 @@ impl Definition { } let ty = self.ty.ty_tokens(); - let trait_ty = self.trait_ty(); + let trait_ty = self.ty.trait_ty(); let fields_resolvers = self .fields @@ -1141,7 +1139,7 @@ impl Definition { fn impl_graphql_interface_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(scalar); + let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); @@ -1170,7 +1168,7 @@ impl Definition { fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(scalar); + let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let ty = self.ty.ty_tokens(); @@ -1231,6 +1229,7 @@ struct EnumType { trait_types: Vec<(syn::Ident, syn::Generics)>, trait_consts: Vec<(syn::Ident, syn::Type)>, trait_methods: Vec, + scalar: ScalarValueType, } impl EnumType { @@ -1238,6 +1237,7 @@ impl EnumType { r#trait: &syn::ItemTrait, meta: &InterfaceMeta, implers: &Vec, + scalar: ScalarValueType, ) -> Self { Self { ident: meta @@ -1283,6 +1283,7 @@ impl EnumType { } }) .collect(), + scalar, } } @@ -1290,18 +1291,29 @@ impl EnumType { format_ident!("Impl{}", num) } - fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { + fn impl_generics(&self) -> syn::Generics { let mut generics = syn::Generics::default(); - if scalar.is_generic() { + + if self.scalar.is_generic() { + let scalar = &self.scalar; generics.params.push(parse_quote! { #scalar }); generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } + generics } + fn trait_ty(&self) -> syn::Type { + let ty = &self.trait_ident; + + let (_, generics, _) = self.trait_generics.split_for_impl(); + + parse_quote! { #ty#generics } + } + fn ty_tokens(&self) -> TokenStream { let ty = &self.ident; @@ -1439,7 +1451,9 @@ impl EnumType { impl_tokens } - fn concrete_type_name_method_tokens(&self, scalar: &ScalarValueType) -> TokenStream { + fn concrete_type_name_method_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + let match_arms = self.variants.iter().enumerate().map(|(n, ty)| { let variant = Self::variant_ident(n); @@ -1510,38 +1524,69 @@ struct TraitObjectType { pub visibility: syn::Visibility, pub trait_ident: syn::Ident, pub trait_generics: syn::Generics, + pub scalar: ScalarValueType, pub context: Option, } impl TraitObjectType { - fn new(r#trait: &syn::ItemTrait, meta: &InterfaceMeta, context: Option) -> Self { + fn new( + r#trait: &syn::ItemTrait, + meta: &InterfaceMeta, + scalar: ScalarValueType, + context: Option, + ) -> Self { Self { ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(), visibility: r#trait.vis.clone(), trait_ident: r#trait.ident.clone(), trait_generics: r#trait.generics.clone(), + scalar, context, } } - fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { + fn impl_generics(&self) -> syn::Generics { let mut generics = self.trait_generics.clone(); + generics.params.push(parse_quote! { '__obj }); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } if scalar.is_generic() { generics .make_where_clause() .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } + generics } + fn trait_ty(&self) -> syn::Type { + let ty = &self.trait_ident; + + let mut generics = self.trait_generics.clone(); + if !self.scalar.is_explicit_generic() { + let scalar = &self.scalar; + generics.params.push(parse_quote! { #scalar }); + } + let (_, generics, _) = generics.split_for_impl(); + + parse_quote! { #ty#generics } + } + fn ty_tokens(&self) -> TokenStream { let ty = &self.trait_ident; let mut generics = self.trait_generics.clone(); generics.remove_defaults(); generics.move_bounds_to_where_clause(); + if !self.scalar.is_explicit_generic() { + let scalar = &self.scalar; + generics.params.push(parse_quote! { #scalar }); + } let ty_params = &generics.params; let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); @@ -1591,10 +1636,18 @@ impl ToTokens for TraitObjectType { let trait_ident = &self.trait_ident; + let mut generics = self.trait_generics.clone(); + if !self.scalar.is_explicit_generic() { + let scalar_ty = self.scalar.generic_ty(); + let default_ty = self.scalar.default_ty(); + generics + .params + .push(parse_quote! { #scalar_ty = #default_ty }); + } + let (mut ty_params_left, mut ty_params_right) = (None, None); - if !self.trait_generics.params.is_empty() { + if !generics.params.is_empty() { // We should preserve defaults for left side. - let mut generics = self.trait_generics.clone(); generics.move_bounds_to_where_clause(); let params = &generics.params; ty_params_left = Some(quote! { , #params }); @@ -1624,10 +1677,17 @@ enum Type { } impl Type { - fn impl_generics(&self, scalar: &ScalarValueType) -> syn::Generics { + fn impl_generics(&self) -> syn::Generics { match self { - Self::Enum(e) => e.impl_generics(scalar), - Self::TraitObject(o) => o.impl_generics(scalar), + Self::Enum(e) => e.impl_generics(), + Self::TraitObject(o) => o.impl_generics(), + } + } + + fn trait_ty(&self) -> syn::Type { + match self { + Self::Enum(e) => e.trait_ty(), + Self::TraitObject(o) => o.trait_ty(), } } @@ -1638,9 +1698,9 @@ impl Type { } } - fn concrete_type_name_method_tokens(&self, scalar: &ScalarValueType) -> TokenStream { + fn concrete_type_name_method_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.concrete_type_name_method_tokens(scalar), + Self::Enum(e) => e.concrete_type_name_method_tokens(), Self::TraitObject(o) => o.concrete_type_name_method_tokens(), } } From 3a99794ea229f6c0a763c5b628e05f363be8b611 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 22 Sep 2020 13:12:22 +0200 Subject: [PATCH 53/79] Generating enum, vol.6 --- .../src/codegen/interface_attr.rs | 462 +++++++++--------- juniper_codegen/src/graphql_interface/mod.rs | 18 +- 2 files changed, 247 insertions(+), 233 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 5671803f9..e35dbaf79 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1422,6 +1422,7 @@ mod fallible_field { } } } + /* mod generic { use super::*; @@ -3903,7 +3904,7 @@ mod inferred_custom_context_from_downcast { #[graphql_interface(for = [Human, Droid])] trait Character { #[graphql_interface(downcast)] - fn as_human<'db>(&self, db: &'db Database) -> Option<&'db Human>; + fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human>; async fn id(&self) -> &str; } @@ -3925,8 +3926,8 @@ mod inferred_custom_context_from_downcast { #[graphql_interface] impl Character for Human { - fn as_human<'db>(&self, _: &'db Database) -> Option<&'db Human> { - None + fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { + Some(self) } async fn id(&self) -> &str { @@ -3954,7 +3955,7 @@ mod inferred_custom_context_from_downcast { #[graphql_interface] impl Character for Droid { - fn as_human<'db>(&self, _: &'db Database) -> Option<&'db Human> { + fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { None } @@ -3974,13 +3975,7 @@ mod inferred_custom_context_from_downcast { } } - //#[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - /* - #[derive(Clone, Copy)] + #[derive(Clone)] enum QueryRoot { Human, Droid, @@ -4018,7 +4013,6 @@ mod inferred_custom_context_from_downcast { } } - #[tokio::test] async fn enum_resolves_human() { const DOC: &str = r#"{ @@ -4064,7 +4058,7 @@ mod inferred_custom_context_from_downcast { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &db).await, Ok(( - graphql_value!({"character": {"droidId": "droid-88", "primaryFunction": "sit"}}), + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), vec![], )), ); @@ -4122,36 +4116,47 @@ mod inferred_custom_context_from_downcast { } #[tokio::test] - async fn resolves_info_field() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - info - }} - }}"#, - interface, - ); + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; - let expected_interface: &str = *interface; + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(root.clone()); + let db = Database { droid: None }; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - let db = Database { droid: None }; + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } - let expected_info: &str = *expected_info; - assert_eq!( - execute(&doc, None, &schema, &Variables::new(), &db).await, - Ok(( - graphql_value!({expected_interface: { - "info": expected_info, - }}), - vec![], - )), - ); + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(root.clone()); + let db = Database { droid: None }; + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &db).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); } - }*/ + } } mod executor { @@ -4580,193 +4585,192 @@ mod downcast_method { } } - //#[derive(Clone, Copy)] + #[derive(Clone)] 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(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_not_field() { - let schema = schema(QueryRoot::Human); - - for (doc, field) in &[ - (r#"{__type(name: "Character") { fields { name } } }"#, "id"), - (r#"{__type(name: "Hero") { fields { name } } }"#, "info"), - ] { - let expected_field: &str = *field; - assert_eq!( - execute(*doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{"name": expected_field}]}}), - vec![], - )), - ); - } - } - - */ + + #[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(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(root.clone()); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(root.clone()); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_not_field() { + let schema = schema(QueryRoot::Human); + + for (doc, field) in &[ + (r#"{__type(name: "Character") { fields { name } } }"#, "id"), + (r#"{__type(name: "Hero") { fields { name } } }"#, "info"), + ] { + let expected_field: &str = *field; + + assert_eq!( + execute(*doc, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": expected_field}]}}), + vec![], + )), + ); + } + } } mod external_downcast { @@ -4846,7 +4850,7 @@ mod external_downcast { } } - #[derive(Clone, Copy)] + #[derive(Clone)] enum QueryRoot { Human, Droid, @@ -4968,7 +4972,7 @@ mod external_downcast { #[tokio::test] async fn dyn_resolves_droid() { const DOC: &str = r#"{ - character { + hero { ... on Droid { droidId: id primaryFunction @@ -4988,7 +4992,7 @@ mod external_downcast { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &db).await, Ok(( - graphql_value!({"character": {"droidId": "droid-01", "primaryFunction": "swim"}}), + graphql_value!({"hero": {"droidId": "droid-01", "primaryFunction": "swim"}}), vec![], )), ); @@ -5014,7 +5018,7 @@ mod external_downcast { (QueryRoot::Human, "human-32"), (QueryRoot::Droid, "droid-99"), ] { - let schema = schema(*root); + let schema = schema(root.clone()); let expected_id: &str = *expected_id; assert_eq!( @@ -5040,13 +5044,13 @@ mod external_downcast { }), }; - for (root, expected_id) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(root.clone()); - let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; assert_eq!( execute(DOC, None, &schema, &Variables::new(), &db).await, - Ok((graphql_value!({"hero": {"info": expected_id}}), vec![])), + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), ); } } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 9141764e8..16baa7901 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1386,10 +1386,20 @@ impl EnumType { let methods = self.trait_methods.iter().map(|sig| { let method = &sig.ident; - let args = sig.inputs.iter().filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(a) => Some(&a.pat), - }); + let mut sig = sig.clone(); + let mut args = vec![]; + for (n, arg) in sig.inputs.iter_mut().enumerate() { + match arg { + syn::FnArg::Receiver(_) => {}, + syn::FnArg::Typed(a) => { + if !matches!(&*a.pat, syn::Pat::Ident(_)) { + let ident = format_ident!("__arg{}", n); + a.pat = parse_quote! { #ident }; + } + args.push(a.pat.clone()); + } + } + } let and_await = if sig.asyncness.is_some() { Some(quote! { .await }) From 5bc9f2b923c6a4cd307cc5eba14a99b25f7599dd Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 22 Sep 2020 18:33:41 +0200 Subject: [PATCH 54/79] Generating enum, vol.7 --- .../src/codegen/interface_attr.rs | 365 ++++++++++++------ juniper_codegen/src/graphql_interface/attr.rs | 3 - juniper_codegen/src/graphql_interface/mod.rs | 81 +++- 3 files changed, 306 insertions(+), 143 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index e35dbaf79..a47c8590a 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1423,17 +1423,16 @@ mod fallible_field { } } -/* mod generic { use super::*; #[graphql_interface(for = [Human, Droid])] - trait Character { + trait Character { fn id(&self) -> &str; } #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { + trait Hero { fn info(&self) -> &str; } @@ -1445,35 +1444,35 @@ mod generic { } #[graphql_interface] - impl Character for Human { + impl Character for Human { fn id(&self) -> &str { &self.id } } #[graphql_interface(dyn)] - impl Hero for Human { + impl Hero for Human { fn info(&self) -> &str { &self.home_planet } } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue<(), u8>, dyn Hero])] struct Droid { id: String, primary_function: String, } #[graphql_interface] - impl Character for Droid { + impl Character for Droid { fn id(&self) -> &str { &self.id } } #[graphql_interface(dyn)] - impl Hero for Droid { + impl Hero for Droid { fn info(&self) -> &str { &self.primary_function } @@ -1671,39 +1670,58 @@ mod generic { mod generic_async { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] - trait Character { + #[graphql_interface(for = [Human, Droid])] + trait Character { async fn id(&self) -> &str; } + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero { + async fn info(&self) -> &str; + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue, dyn Hero])] struct Human { id: String, home_planet: String, } #[graphql_interface] - impl Character for Human { + impl Character for Human { async fn id(&self) -> &str { &self.id } } + #[graphql_interface(dyn)] + impl Hero for Human { + async fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character)] + #[graphql(impl = [CharacterValue<(), u8>, dyn Hero])] struct Droid { id: String, primary_function: String, } #[graphql_interface] - impl Character for Droid { + impl Character for Droid { async fn id(&self) -> &str { &self.id } } + #[graphql_interface(dyn)] + impl Hero for Droid { + async fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1712,8 +1730,23 @@ mod generic_async { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -1728,7 +1761,7 @@ mod generic_async { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -1750,7 +1783,7 @@ mod generic_async { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1772,7 +1805,51 @@ mod generic_async { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -1792,130 +1869,107 @@ mod generic_async { ); } } - - #[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, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } - #[tokio::test] - async fn registers_all_implementers() { + async fn dyn_resolves_info_field() { const DOC: &str = r#"{ - __type(name: "Character") { - possibleTypes { - kind - name - } + hero { + info } }"#; - let schema = schema(QueryRoot::Human); + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } } #[tokio::test] - async fn registers_itself_in_implementers() { - for object in &["Human", "Droid"] { + async fn uses_trait_name_without_type_params() { + for interface in &["Character", "Hero"] { let doc = format!( r#"{{ __type(name: "{}") {{ - interfaces {{ - kind - name - }} + name }} }}"#, - object, + interface, ); let schema = schema(QueryRoot::Human); + let expected_name: &str = *interface; assert_eq!( execute(&doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"interfaces": [ - {"kind": "INTERFACE", "name": "Character"}, - ]}}), - vec![], - )), + Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), ); } } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } } mod generic_lifetime_async { use super::*; - #[graphql_interface(for = [Human, Droid], dyn = DynCharacter)] - trait Character<'me, A, B> { - async fn id(&self) -> &str; + #[graphql_interface(for = [Human, Droid])] + trait Character<'me, A> { + async fn id(&self) -> &str + where + 'me: 'async_trait; + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid])] + trait Hero<'me, A> { + async fn info(&self) -> &str; } #[derive(GraphQLObject)] - #[graphql(impl = dyn Character<'_, u8, ()>)] + #[graphql(impl = [CharacterValue<'_, ()>, dyn Hero<'_, ()>])] struct Human { id: String, home_planet: String, } #[graphql_interface] - impl<'me, A, B> Character<'me, A, B> for Human { - async fn id(&self) -> &str { + impl<'me, A> Character<'me, A> for Human { + async fn id(&self) -> &str where + 'me: 'async_trait{ &self.id } } + #[graphql_interface(dyn)] + impl<'me, A> Hero<'me, A> for Human { + async fn info(&self) -> &str { + &self.home_planet + } + } + #[derive(GraphQLObject)] - #[graphql(impl = dyn Character<'_, u8, ()>)] + #[graphql(impl = [CharacterValue<'_, ()>, dyn Hero<'_, ()>])] struct Droid { id: String, primary_function: String, } #[graphql_interface] - impl<'me, A, B> Character<'me, A, B> for Droid { - async fn id(&self) -> &str { + impl<'me, A> Character<'me, A> for Droid { + async fn id(&self) -> &str where + 'me: 'async_trait { &self.id } } + #[graphql_interface(dyn)] + impl<'me, A> Hero<'me, A> for Droid { + async fn info(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1924,8 +1978,23 @@ mod generic_lifetime_async { #[graphql_object] impl QueryRoot { - fn character(&self) -> Box> { - let ch: Box> = match self { + fn character(&self) -> CharacterValue<'_, ()> { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -1940,7 +2009,7 @@ mod generic_lifetime_async { } #[tokio::test] - async fn resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -1962,7 +2031,7 @@ mod generic_lifetime_async { } #[tokio::test] - async fn resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1984,7 +2053,51 @@ mod generic_lifetime_async { } #[tokio::test] - async fn resolves_id_field() { + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2004,42 +2117,48 @@ mod generic_lifetime_async { ); } } - #[tokio::test] - async fn is_graphql_interface() { + async fn dyn_resolves_info_field() { const DOC: &str = r#"{ - __type(name: "Character") { - kind + hero { + info } }"#; - let schema = schema(QueryRoot::Human); + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } } #[tokio::test] async fn uses_trait_name_without_type_params() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + interface, + ); - let schema = schema(QueryRoot::Human); + let schema = schema(QueryRoot::Human); - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); + let expected_name: &str = *interface; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + ); + } } } - */ - mod argument { use super::*; @@ -3168,7 +3287,7 @@ mod explicit_generic_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, dyn Hero])] struct Human { id: String, home_planet: String, @@ -3189,7 +3308,7 @@ mod explicit_generic_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -4199,7 +4318,7 @@ mod executor { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, dyn Hero])] struct Human { id: String, home_planet: String, @@ -4226,7 +4345,7 @@ mod executor { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, dyn Hero])] struct Droid { id: String, primary_function: String, @@ -4258,9 +4377,9 @@ mod executor { Droid, } - #[graphql_object] + #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn character(&self) -> CharacterValue { + fn character(&self) -> CharacterValue { match self { Self::Human => Human { id: "human-32".to_string(), diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 2ecabb14d..93e495916 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -190,9 +190,6 @@ pub fn expand_on_trait( let generated_code = Definition { ty, - trait_ident: trait_ident.clone(), - trait_generics: ast.generics.clone(), - name, description: meta.description.map(SpanContainer::into_inner), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 16baa7901..4837c1e28 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -866,10 +866,6 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces ty: Type, - trait_ident: syn::Ident, - - trait_generics: syn::Generics, - /// Name of this [GraphQL interface][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -1291,12 +1287,26 @@ impl EnumType { format_ident!("Impl{}", num) } + fn has_phantom_variant(&self) -> bool { + !self.trait_generics.params.is_empty() + } + + fn non_exhaustive_match_arm_tokens(&self) -> Option { + if self.has_phantom_variant() { + Some(quote! { _ => unreachable!(), }) + } else { + None + } + } + fn impl_generics(&self) -> syn::Generics { - let mut generics = syn::Generics::default(); + let mut generics = self.trait_generics.clone(); - if self.scalar.is_generic() { - let scalar = &self.scalar; + let scalar = &self.scalar; + if self.scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); + } + if self.scalar.is_generic() { generics .make_where_clause() .predicates @@ -1308,7 +1318,6 @@ impl EnumType { fn trait_ty(&self) -> syn::Type { let ty = &self.trait_ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); parse_quote! { #ty#generics } @@ -1316,12 +1325,14 @@ impl EnumType { fn ty_tokens(&self) -> TokenStream { let ty = &self.ident; + let (_, generics, _) = self.trait_generics.split_for_impl(); - quote! { #ty } + quote! { #ty#generics } } fn type_definition_tokens(&self) -> TokenStream { let enum_ty = &self.ident; + let generics = &self.trait_generics; let vis = &self.visibility; let doc = format!( @@ -1337,24 +1348,52 @@ impl EnumType { quote! { #variant(#ty), } }); + let phantom_variant = if self.has_phantom_variant() { + let ty_params = generics.params.iter().map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => unimplemented!(), + }; + quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + } + }); + + Some(quote! { + #[doc(hidden)] + __Phantom(#( #ty_params ),*), + }) + } else { + None + }; + quote! { #[automatically_derived] #[doc = #doc] - #vis enum #enum_ty { + #vis enum #enum_ty#generics { #( #variants )* + #phantom_variant } } } fn impl_from_tokens(&self) -> impl Iterator + '_ { let enum_ty = &self.ident; + let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); self.variants.iter().enumerate().map(move |(n, ty)| { let variant = Self::variant_ident(n); quote! { #[automatically_derived] - impl From<#ty> for #enum_ty { + impl#impl_generics From<#ty> for #enum_ty#generics #where_clause { fn from(v: #ty) -> Self { Self::#variant(v) } @@ -1367,19 +1406,19 @@ impl EnumType { let enum_ty = &self.ident; let trait_ident = &self.trait_ident; - let (trait_params, trait_generics, where_clause) = self.trait_generics.split_for_impl(); + let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); let var_ty = self.variants.first().unwrap(); let assoc_types = self.trait_types.iter().map(|(ty, ty_gen)| { quote! { - type #ty#ty_gen = <#var_ty as #trait_ident#trait_generics>::#ty#ty_gen; + type #ty#ty_gen = <#var_ty as #trait_ident#generics>::#ty#ty_gen; } }); let assoc_consts = self.trait_consts.iter().map(|(ident, ty)| { quote! { - const #ident: #ty = <#var_ty as #trait_ident#trait_generics>::#ident; + const #ident: #ty = <#var_ty as #trait_ident#generics>::#ident; } }); @@ -1390,7 +1429,7 @@ impl EnumType { let mut args = vec![]; for (n, arg) in sig.inputs.iter_mut().enumerate() { match arg { - syn::FnArg::Receiver(_) => {}, + syn::FnArg::Receiver(_) => {} syn::FnArg::Typed(a) => { if !matches!(&*a.pat, syn::Pat::Ident(_)) { let ident = format_ident!("__arg{}", n); @@ -1413,14 +1452,16 @@ impl EnumType { quote! { Self::#variant(v) => - <#ty as #trait_ident#trait_generics>::#method(v #( , #args )* )#and_await, + <#ty as #trait_ident#generics>::#method(v #( , #args )* )#and_await, } }); + let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); quote! { #sig { match self { #( #match_arms )* + #non_exhaustive_match_arm } } } @@ -1428,7 +1469,7 @@ impl EnumType { let mut impl_tokens = quote! { #[automatically_derived] - impl#trait_params #trait_ident#trait_generics for #enum_ty #where_clause { + impl#impl_generics #trait_ident#generics for #enum_ty#generics #where_clause { #( #assoc_types )* #( #assoc_consts )* @@ -1473,10 +1514,12 @@ impl EnumType { >::concrete_type_name(v, context, info), } }); + let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); quote! { match self { #( #match_arms )* + #non_exhaustive_match_arm } } } @@ -1491,10 +1534,12 @@ impl EnumType { Self::#variant(res) => #resolving_code, } }); + let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); quote! { match self { #( #match_arms )* + #non_exhaustive_match_arm } } } @@ -1512,10 +1557,12 @@ impl EnumType { } } }); + let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); quote! { match self { #( #match_arms )* + #non_exhaustive_match_arm } } } From 5dfe2506dd2e2713d2a2ff213bad88c3beba9f8f Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 23 Sep 2020 12:15:36 +0200 Subject: [PATCH 55/79] Generating enum, vol.8 --- .../juniper_tests/src/codegen/interface_attr.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index a47c8590a..5fbebe95c 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1916,9 +1916,7 @@ mod generic_lifetime_async { #[graphql_interface(for = [Human, Droid])] trait Character<'me, A> { - async fn id(&self) -> &str - where - 'me: 'async_trait; + async fn id<'a>(&'a self) -> &'a str; } #[graphql_interface(dyn = DynHero, for = [Human, Droid])] @@ -1935,8 +1933,7 @@ mod generic_lifetime_async { #[graphql_interface] impl<'me, A> Character<'me, A> for Human { - async fn id(&self) -> &str where - 'me: 'async_trait{ + async fn id<'a>(&'a self) -> &'a str { &self.id } } @@ -1957,8 +1954,7 @@ mod generic_lifetime_async { #[graphql_interface] impl<'me, A> Character<'me, A> for Droid { - async fn id(&self) -> &str where - 'me: 'async_trait { + async fn id<'a>(&'a self) -> &'a str { &self.id } } From d50258e6f132b935fdaac78fd669cb02886ce23b Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 23 Sep 2020 16:19:50 +0200 Subject: [PATCH 56/79] Refactor juniper stuff --- juniper/Cargo.toml | 9 +- juniper/src/executor/mod.rs | 12 - juniper/src/macros/common.rs | 741 ------------------ juniper/src/macros/interface.rs | 302 ------- juniper/src/macros/mod.rs | 8 +- juniper/src/macros/tests/impl_subscription.rs | 4 +- juniper/src/macros/tests/interface.rs | 303 ++----- juniper/src/schema/meta.rs | 45 -- juniper/src/tests/fixtures/mod.rs | 4 +- juniper/src/tests/fixtures/starwars/mod.rs | 2 +- juniper/src/tests/fixtures/starwars/model.rs | 319 -------- juniper/src/tests/fixtures/starwars/schema.rs | 395 +++++++--- 12 files changed, 384 insertions(+), 1760 deletions(-) delete mode 100644 juniper/src/macros/common.rs delete mode 100644 juniper/src/macros/interface.rs delete mode 100644 juniper/src/tests/fixtures/starwars/model.rs diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 07e601285..332454a61 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -27,8 +27,9 @@ default = [ "uuid", ] expose-test-schema = ["anyhow", "serde_json"] -schema-language = ["graphql-parser-integration"] graphql-parser-integration = ["graphql-parser"] +scalar-naivetime = [] +schema-language = ["graphql-parser-integration"] [dependencies] juniper_codegen = { version = "0.14.2", path = "../juniper_codegen" } @@ -42,11 +43,11 @@ futures = { default-features = false, features = ["alloc"], version = "0.3.1" } futures-enum = "0.1.12" graphql-parser = { version = "0.3", optional = true } indexmap = { version = "1.0", features = ["serde-1"] } -serde = { version = "1.0.8", features = ["derive"] } -serde_json = { version = "1.0.2", optional = true } +serde = { version = "1.0.8", features = ["derive"], default-features = false } +serde_json = { version = "1.0.2", default-features = false, optional = true } static_assertions = "1.1" url = { version = "2.0", optional = true } -uuid = { version = "0.8", optional = true } +uuid = { version = "0.8", default-features = false, optional = true } [dev-dependencies] bencher = "0.1.2" diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 657addc2a..07be7ac9e 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -80,18 +80,6 @@ where field_path: Arc>, } -pub trait FromExecutor { - /// Performs the conversion. - fn from(value: &T) -> &Self; -} - -/* -impl<'r, 'a, CtxA, CtxB, ScA, ScB> FromExecutor> for Executor<'r, 'a, CtxB, ScB> { - fn from(a: Executor<'r, 'a, CtxA, ScA>) -> Self { - todo!() - } -}*/ - /// Error type for errors that occur during query execution /// /// All execution errors contain the source position in the query of the field diff --git a/juniper/src/macros/common.rs b/juniper/src/macros/common.rs deleted file mode 100644 index 9178f5a78..000000000 --- a/juniper/src/macros/common.rs +++ /dev/null @@ -1,741 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! __juniper_impl_trait { - ( - impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty { - $($body:tt)+ - } - ) => { - impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name { - $($body)* - } - }; - ( - impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty - where ( $($where:tt)* ) - { - $($body:tt)+ - } - ) => { - impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name - where $($where)* - { - $($body)* - } - }; - - ( - impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty { - $($body:tt)* - } - ) => { - impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name - where - $generic: $crate::ScalarValue, - { - $($body)* - } - }; - ( - impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty - where ( $($where:tt)* ) - { - $($body:tt)* - } - ) => { - impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name - where - $($where)* - $generic: $crate::ScalarValue, - { - $($body)* - } - }; - - ( - impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty { - $($body:tt)* - } - ) => { - impl<$($other, )*> $crate::$impl_trait<$scalar> for $name { - $($body)* - } - }; - ( - impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty - where ( $($where:tt)* ) - { - $($body:tt)* - } - ) => { - impl<$($other, )*> $crate::$impl_trait<$scalar> for $name - where $($where)* - { - $($body)* - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __juniper_insert_generic { - () => { - $crate::DefaultScalarValue - }; - ( - <$generic:tt $(: $bound: tt)*> - ) => { - $generic - }; - ( - $scalar: ty - ) => { - $scalar - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __juniper_parse_object_header { - ( - callback = $callback:ident, - rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt - where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {$outname}, - scalar = {<$generic $(: $bound)*>}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback:ident, - rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt - where Scalar = $scalar: ty $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {$outname}, - scalar = {$scalar}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {$outname}, - scalar = {}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = $name: ty $(: $ctxt: ty)* as $outname: tt - where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {$outname}, - scalar = {<$generic $(:$bound)*>}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = $name: ty $(: $ctxt: ty)* as $outname: tt - where Scalar = $scalar: ty $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {$outname}, - scalar = {$scalar}, - }, - rest = $($items)* - ); - }; - - - ( - callback = $callback: ident, - rest = $name: ty $(: $ctxt: ty)* as $outname: tt $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {$outname}, - scalar = {}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* - where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {stringify!($name)}, - scalar = {<$generic $(:$bounds)*>}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* - where Scalar = $scalar: ty $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {stringify!($name)}, - scalar = {$scalar}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {stringify!($name)}, - scalar = {}, - }, - rest = $($items)* - ); - }; - - - ( - callback = $callback: ident, - rest = $name: ty $(: $ctxt: ty)* - where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* - { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {stringify!($name)}, - scalar = {<$generic $(: $bound)*>}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = $name: ty $(: $ctxt: ty)* where Scalar = $scalar: ty $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {stringify!($name)}, - scalar = {$scalar}, - }, - rest = $($items)* - ); - }; - - ( - callback = $callback: ident, - rest = $name: ty $(: $ctxt: ty)* $(| &$mainself:ident |)* { - $($items: tt)* - } - ) => { - $crate::$callback!( - @parse, - meta = { - lifetimes = [], - name = $name, - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - outname = {stringify!($name)}, - scalar = {}, - }, - rest = $($items)* - ); - }; - ( - callback = $callback: ident, - rest = $($rest:tt)* - ) => { - compile_error!("Invalid syntax"); - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __juniper_parse_field_list { - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = - ) => { - $crate::$success_callback!( - @generate, - meta = {$($meta)*}, - items = [$({$($items)*},)*], - ); - }; - - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = , $($rest: tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = {$($meta)*}, - items = [$({$($items)*},)*], - rest = $($rest)* - ); - }; - - - ( - @parse_description, - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = { - $(lifetimes = [$($lifetime:tt,)*],)* - $(name = $name:ty,)* - $(ctx = $ctxt: ty,)* - $(main_self = $mainself: ident,)* - $(outname = {$($outname:tt)*},)* - $(scalar = {$($scalar:tt)*},)* - $(description = $_desciption: tt,)* - $(additional = {$($other: tt)*},)* - }, - items = [$({$($items: tt)*},)*], - rest = $desc: tt $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = { - $(lifetimes = [$($lifetime,)*],)* - $(name = $name,)* - $(ctx = $ctxt,)* - $(main_self = $mainself,)* - $(outname = {$($outname)*},)* - $(scalar = {$($scalar)*},)* - description = $desc, - $(additional = {$($other)*},)* - - }, - items = [$({$($items)*},)*], - rest = $($rest)* - ); - }; - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = { $($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = description: $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - @parse_description, - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = {$($meta)*}, - items = [$({$($items)*},)*], - rest = $($rest)* - ); - }; - - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = $(#[doc = $desc: tt])* - #[deprecated $(( $(since = $since: tt,)* note = $reason: tt ))* ] - field $name: ident ( - $(&$executor: tt)* $(,)* - $($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)* - ) -> $return_ty: ty $body: block - $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = {$($meta)*}, - items = [$({$($items)*},)* { - name = $name, - body = $body, - return_ty = $return_ty, - args = [ - $({ - arg_name = $arg_name, - arg_ty = $arg_ty, - $(arg_default = $arg_default,)* - $(arg_docstring = $arg_desc,)* - },)* - ], - $(docstring = $desc,)* - deprecated = None$(.unwrap_or_else(|| Some($reason)))*, - $(executor_var = $executor,)* - },], - rest = $($rest)* - ); - }; - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = $(#[doc = $desc: tt])* - field $name: ident ( - $(&$executor: ident)* $(,)* - $($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)* - ) -> $return_ty: ty $body: block - $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = {$($meta)*}, - items = [$({$($items)*},)* { - name = $name, - body = $body, - return_ty = $return_ty, - args = [ - $({ - arg_name = $arg_name, - arg_ty = $arg_ty, - $(arg_default = $arg_default,)* - $(arg_docstring = $arg_desc,)* - },)* - ], - $(docstring = $desc,)* - $(executor_var = $executor,)* - },], - rest = $($rest)* - ); - }; - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = field deprecated $reason:tt $name: ident ( - $(&$executor: tt)* $(,)* - $($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)* - ) -> $return_ty: ty $(as $desc: tt)* $body: block - $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = {$($meta)*}, - items = [$({$($items)*},)* { - name = $name, - body = $body, - return_ty = $return_ty, - args = [ - $({ - arg_name = $arg_name, - arg_ty = $arg_ty, - $(arg_default = $arg_default,)* - $(arg_description = $arg_desc,)* - },)* - ], - $(decs = $desc,)* - deprecated = Some($reason), - $(executor_var = $executor,)* - },], - rest = $($rest)* - ); - }; - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = field $name: ident ( - $(&$executor: ident)* $(,)* - $($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)* - ) -> $return_ty: ty $(as $desc: tt)* $body: block - $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = {$($meta)*}, - items = [$({$($items)*},)* { - name = $name, - body = $body, - return_ty = $return_ty, - args = [ - $({ - arg_name = $arg_name, - arg_ty = $arg_ty, - $(arg_default = $arg_default,)* - $(arg_description = $arg_desc,)* - },)* - ], - $(decs = $desc,)* - $(executor_var = $executor,)* - },], - rest = $($rest)* - ); - }; - - ( - success_callback = $success_callback: ident, - additional_parser = { - callback = $callback: ident, - header = {$($header:tt)*}, - }, - meta = {$($meta:tt)*}, - items = [$({$($items: tt)*},)*], - rest = $($rest:tt)* - ) => { - $crate::$callback!( - $($header)* - success_callback = $success_callback, - additional_parser = { - callback = $callback, - header = {$($header)*}, - }, - meta = {$($meta)*}, - items = [$({$($items)*},)*], - rest = $($rest)* - ); - } - -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __juniper_parse_instance_resolver { - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = { - lifetimes = [$($lifetime:tt,)*], - name = $name:ty, - ctx = $ctxt:ty, - main_self = $mainself:ident, - outname = {$($outname:tt)*}, - scalar = {$($scalar:tt)*}, - $(description = $desciption:tt,)* - $(additional = { - $(resolver = {$($ignored_resolver:tt)*},)* - },)* - - }, - items = [$({$($items: tt)*},)*], - rest = instance_resolvers: |&$context: ident| { - $( $srctype:ty => $resolver:expr ),* $(,)* - } $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - ctx = $ctxt, - main_self = $mainself, - outname = {$($outname)*}, - scalar = {$($scalar)*}, - $(description = $desciption,)* - additional = { - resolver = { - context = $context, - items = [ - $({ - src = $srctype, - resolver = $resolver, - },)* - ], - }, - }, - }, - items = [$({$($items)*},)*], - rest = $($rest)* - ); - }; - - ( - success_callback = $success_callback: ident, - additional_parser = {$($additional:tt)*}, - meta = { - lifetimes = [$($lifetime:tt,)*], - name = $name:ty, - ctx = $ctxt:ty, - main_self = $mainself:ident, - outname = {$($outname:tt)*}, - scalar = {$($scalar:tt)*}, - $(description = $desciption:tt,)* - $(additional = { - $(resolver = {$($ignored_resolver:tt)*},)* - },)* - - }, - items = [$({$($items: tt)*},)*], - rest = instance_resolvers: |$(&)* _| {$( $srctype:ty => $resolver:expr ),* $(,)*} $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = $success_callback, - additional_parser = {$($additional)*}, - meta = { - lifetimes = [$($lifetime,)*], - name = $name, - ctx = $ctxt, - main_self = $mainself, - outname = {$($outname)*}, - scalar = {$($scalar)*}, - $(description = $desciption,)* - additional = { - resolver = { - items = [ - $({ - src = $srctype, - resolver = $resolver, - },)* - ], - }, - }, - }, - items = [$({$($items)*},)*], - rest = $($rest)* - ); - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __juniper_create_arg { - ( - registry = $reg: ident, - info = $info: ident, - arg_ty = $arg_ty: ty, - arg_name = $arg_name: ident, - $(description = $arg_description: expr,)* - $(docstring = $arg_docstring: expr,)* - ) => { - $reg.arg::<$arg_ty>( - &$crate::to_camel_case(stringify!($arg_name)), - $info, - ) - $(.description($arg_description))* - .push_docstring(&[$($arg_docstring,)*]) - }; - - ( - registry = $reg: ident, - info = $info: ident, - arg_ty = $arg_ty: ty, - arg_name = $arg_name: ident, - default = $arg_default: expr, - $(description = $arg_description: expr,)* - $(docstring = $arg_docstring: expr,)* - ) => { - $reg.arg_with_default::<$arg_ty>( - &$crate::to_camel_case(stringify!($arg_name)), - &($arg_default), - $info, - ) - $(.description($arg_description))* - .push_docstring(&[$($arg_docstring,)*]) - }; -} diff --git a/juniper/src/macros/interface.rs b/juniper/src/macros/interface.rs deleted file mode 100644 index bca44882b..000000000 --- a/juniper/src/macros/interface.rs +++ /dev/null @@ -1,302 +0,0 @@ -/** -Expose GraphQL interfaces - -Mapping interfaces to GraphQL can be tricky: there is no direct counterpart to -GraphQL interfaces in Rust, and downcasting is not possible in the general case. -Many other GraphQL implementations in other languages use instance checks and -either dynamic typing or forced downcasts to support these features. - -A GraphQL interface defines fields that the implementing types also need to -implement. A GraphQL interface also needs to be able to determine the concrete -type name as well as downcast the general type to the actual concrete type. - -## Syntax - -See the documentation for [`graphql_object!`][1] on the general item and type -syntax. `graphql_interface!` requires an additional `instance_resolvers` item, -and does _not_ support the `interfaces` item. - -`instance_resolvers` is a match like structure used to resolve the concrete -instance type of the interface. It starts with a context argument and continues -with a number of match arms; on the left side is the indicated type, and on the -right an expression that resolve into `Option` of the type indicated: - -```rust,ignore -instance_resolvers: |&context| { - &Human => context.get_human(self.id()), // returns Option<&Human> - &Droid => context.get_droid(self.id()), // returns Option<&Droid> -}, -``` - -This is used for both the `__typename` field and when resolving a specialized -fragment, e.g. `...on Human`. For `__typename`, the resolvers will be executed -in order - the first one returning `Some` will be the determined type name. When -resolving fragment type conditions, only the corresponding match arm will be -executed. - -## Example - -A simplified extract from the StarWars schema example shows how to use the -shared context to implement downcasts. - -```rust -# extern crate juniper; -# use std::collections::HashMap; -struct Human { id: String } -struct Droid { id: String } -struct Database { - humans: HashMap, - droids: HashMap, -} - -trait Character { - fn id(&self) -> &str; -} - -impl Character for Human { - fn id(&self) -> &str { &self.id } -} - -impl Character for Droid { - fn id(&self) -> &str { &self.id } -} - -#[juniper::graphql_object(Context = Database)] -impl Human { - fn id(&self) -> &str { &self.id } -} - -#[juniper::graphql_object( - name = "Droid", - Context = Database, -)] -impl Droid { - fn id(&self) -> &str { &self.id } -} - -// You can introduce lifetimes or generic parameters by < > before the name. -juniper::graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| { - field id() -> &str { self.id() } - - instance_resolvers: |&context| { - &Human => context.humans.get(self.id()), - &Droid => context.droids.get(self.id()), - } -}); - -# fn main() { } -``` - -[1]: macro.graphql_object!.html - -*/ -#[macro_export] -macro_rules! graphql_interface { - - ( - @generate, - meta = { - lifetimes = [$($lifetimes:tt,)*], - name = $name:ty, - ctx = $ctx:ty, - main_self = $main_self:ident, - outname = {$($outname:tt)*}, - scalar = {$($scalar:tt)*}, - $(description = $desciption:tt,)* - additional = { - resolver = { - $(context = $resolver_ctx: ident,)* - items = [ - $({ - src = $resolver_src: ty, - resolver = $resolver_expr: expr, - },)* - ], - }, - }, - }, - items = [$({ - name = $fn_name: ident, - body = $body: block, - return_ty = $return_ty: ty, - args = [$({ - arg_name = $arg_name : ident, - arg_ty = $arg_ty: ty, - $(arg_default = $arg_default: expr,)* - $(arg_description = $arg_description: expr,)* - $(arg_docstring = $arg_docstring: expr,)* - },)*], - $(decs = $fn_description: expr,)* - $(docstring = $docstring: expr,)* - $(deprecated = $deprecated: expr,)* - $(executor_var = $executor: ident,)* - },)*], - ) => { - $crate::__juniper_impl_trait!( - impl<$($scalar)* $(, $lifetimes)* > GraphQLType for $name { - fn name(_ : &Self::TypeInfo) -> Option<&'static str> { - Some($($outname)*) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)> - ) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)> - where - $crate::__juniper_insert_generic!($($scalar)+): 'r - { - // Ensure all child types are registered - $( - let _ = registry.get_type::<$resolver_src>(info); - )* - let fields = &[$( - registry.field_convert::<$return_ty, _, Self::Context>( - &$crate::to_camel_case(stringify!($fn_name)), - info - ) - $(.description($fn_description))* - .push_docstring(&[$($docstring,)*]) - $(.deprecated($deprecated))* - $(.argument( - $crate::__juniper_create_arg!( - registry = registry, - info = info, - arg_ty = $arg_ty, - arg_name = $arg_name, - $(default = $arg_default,)* - $(description = $arg_description,)* - $(docstring = $arg_docstring,)* - ) - ))*, - )*]; - registry.build_interface_type::<$name>( - info, fields - ) - $(.description($desciption))* - .into_meta() - } - } - ); - - $crate::__juniper_impl_trait!( - impl<$($scalar)* $(, $lifetimes)* > IsOutputType for $name { } - ); - - $crate::__juniper_impl_trait!( - impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name { - type Context = $ctx; - type TypeInfo = (); - - fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> { - Some($($outname)*) - } - - #[allow(unused_variables)] - fn resolve_field( - &$main_self, - info: &Self::TypeInfo, - field: &str, - args: &$crate::Arguments<$crate::__juniper_insert_generic!($($scalar)+)>, - executor: &$crate::Executor - ) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)+)> { - $( - if field == &$crate::to_camel_case(stringify!($fn_name)) { - let f = (|| { - $( - let $arg_name: $arg_ty = args.get(&$crate::to_camel_case(stringify!($arg_name))) - .expect(concat!( - "Argument ", - stringify!($arg_name), - " missing - validation must have failed" - )); - )* - $( - let $executor = &executor; - )* - $body - }); - let result: $return_ty = f(); - - return $crate::IntoResolvable::into(result, executor.context()) - .and_then(|res| { - match res { - Some((ctx, r)) => { - executor.replaced_context(ctx) - .resolve_with_ctx(&(), &r) - } - None => Ok($crate::Value::null()) - } - }); - } - )* - - panic!("Field {} not found on type {}", field, $($outname)*) - } - - #[allow(unused_variables)] - fn concrete_type_name(&$main_self, context: &Self::Context, _info: &Self::TypeInfo) -> String { - $(let $resolver_ctx = &context;)* - - $( - if ($resolver_expr as ::std::option::Option<$resolver_src>).is_some() { - return - <$resolver_src as $crate::GraphQLType<_>>::name(&()).unwrap().to_owned(); - } - )* - - panic!("Concrete type not handled by instance resolvers on {}", $($outname)*); - } - - fn resolve_into_type( - &$main_self, - _info: &Self::TypeInfo, - type_name: &str, - _: Option<&[$crate::Selection<$crate::__juniper_insert_generic!($($scalar)*)>]>, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)*)> { - $(let $resolver_ctx = &executor.context();)* - - $( - if type_name == (<$resolver_src as $crate::GraphQLType<_>>::name(&())).unwrap() { - return executor.resolve(&(), &$resolver_expr); - } - )* - - panic!("Concrete type not handled by instance resolvers on {}", $($outname)*); - } - } - ); - }; - - ( - @parse, - meta = {$($meta:tt)*}, - rest = $($rest:tt)* - ) => { - $crate::__juniper_parse_field_list!( - success_callback = graphql_interface, - additional_parser = { - callback = __juniper_parse_instance_resolver, - header = {}, - }, - meta = {$($meta)*}, - items = [], - rest = $($rest)* - ); - }; - - (@$($stuff:tt)*) => { - compile_error!("Invalid syntax for `graphql_interface!`"); - }; - - ( - $($rest:tt)* - ) => { - $crate::__juniper_parse_object_header!( - callback = graphql_interface, - rest = $($rest)* - ); - } - - -} diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index ff4f38caf..ef9675bd3 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,10 +1,4 @@ -// Wrapper macros which allows built-in macros to be recognized as "crate-local" -// and helper traits for #[juniper::graphql_subscription] macro. - -#[macro_use] -mod common; -//#[macro_use] -//mod interface; +//! Helper traits for macros. #[cfg(test)] mod tests; diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs index d802a9d8d..de56473c2 100644 --- a/juniper/src/macros/tests/impl_subscription.rs +++ b/juniper/src/macros/tests/impl_subscription.rs @@ -17,7 +17,7 @@ struct WithLifetime<'a> { value: &'a str, } -#[crate::graphql_object(Context=Context)] +#[crate::graphql_object(Context = Context)] impl<'a> WithLifetime<'a> { fn value(&'a self) -> &'a str { self.value @@ -26,7 +26,7 @@ impl<'a> WithLifetime<'a> { struct WithContext; -#[crate::graphql_object(Context=Context)] +#[crate::graphql_object(Context = Context)] impl WithContext { fn ctx(ctx: &Context) -> bool { ctx.flag1 diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index 3e45f3cb7..56684bb0b 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -1,12 +1,3 @@ -use std::marker::PhantomData; - -use crate::{ - ast::InputValue, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, Value}, -}; - /* Syntax to validate: @@ -19,135 +10,84 @@ Syntax to validate: */ -struct Concrete; - -struct CustomName; - -#[allow(dead_code)] -struct WithLifetime<'a> { - data: PhantomData<&'a i32>, -} - -#[allow(dead_code)] -struct WithGenerics { - data: T, -} - -struct DescriptionFirst; -struct FieldsFirst; -struct InterfacesFirst; - -struct CommasWithTrailing; -struct CommasOnMeta; - -struct ResolversWithTrailingComma; +use crate::{ + ast::InputValue, + graphql_interface, graphql_object, + schema::model::RootNode, + types::scalars::{EmptyMutation, EmptySubscription}, + value::{DefaultScalarValue, Object, Value}, +}; -struct Root; +struct Concrete; -#[crate::graphql_object] +#[graphql_object(impl = [CustomNameValue, DescriptionValue, WithLifetime<'_>, WithGenerics<()>])] impl Concrete { fn simple() -> i32 { 0 } } -graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| { - field simple() -> i32 { 0 } - - instance_resolvers: |_| { Concrete => Some(Concrete) } -}); - -graphql_interface!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| { - field simple() -> i32 { 0 } - instance_resolvers: |_| { Concrete => Some(Concrete) } -}); - -graphql_interface!( WithGenerics: () as "WithGenerics" |&self| { - field simple() -> i32 { 0 } - instance_resolvers: |_| { Concrete => Some(Concrete) } -}); - -graphql_interface!(DescriptionFirst: () |&self| { - description: "A description" - - field simple() -> i32 { 0 } - - instance_resolvers: |_| { Concrete => Some(Concrete) } -}); - -graphql_interface!(FieldsFirst: () |&self| { - field simple() -> i32 { 0 } - - description: "A description" - - instance_resolvers: |_| { Concrete => Some(Concrete) } -}); - -graphql_interface!(InterfacesFirst: () |&self| { - instance_resolvers: |_| { Concrete => Some(Concrete) } - - field simple() -> i32 { 0 } - - description: "A description" -}); - -graphql_interface!(CommasWithTrailing: () |&self| { - instance_resolvers: |_| { Concrete => Some(Concrete) }, - - field simple() -> i32 { 0 }, - - description: "A description", -}); - -graphql_interface!(CommasOnMeta: () |&self| { - instance_resolvers: |_| { Concrete => Some(Concrete) } - description: "A description", - - field simple() -> i32 { 0 } -}); - -graphql_interface!(ResolversWithTrailingComma: () |&self| { - instance_resolvers: |_| { Concrete => Some(Concrete), } - description: "A description", - - field simple() -> i32 { 0 } -}); - -#[crate::graphql_object( - // FIXME: make async work - noasync -)] -impl<'a> Root { - fn custom_name() -> CustomName { - CustomName {} +#[graphql_interface(for = Concrete, name = "ACustomNamedInterface")] +trait CustomName { + fn simple(&self) -> i32; +} +#[graphql_interface] +impl CustomName for Concrete { + fn simple(&self) -> i32 { + Self::simple() } +} - fn with_lifetime() -> WithLifetime<'a> { - WithLifetime { data: PhantomData } - } - fn with_generics() -> WithGenerics { - WithGenerics { data: 123 } +#[graphql_interface(for = Concrete)] +trait WithLifetime<'a> { + fn simple(&self) -> i32; +} +#[graphql_interface] +impl<'a> WithLifetime<'a> for Concrete { + fn simple(&self) -> i32 { + Self::simple() } +} - fn description_first() -> DescriptionFirst { - DescriptionFirst {} +#[graphql_interface(for = Concrete)] +trait WithGenerics { + fn simple(&self) -> i32; +} +#[graphql_interface] +impl WithGenerics for Concrete { + fn simple(&self) -> i32 { + Self::simple() } - fn fields_first() -> FieldsFirst { - FieldsFirst {} +} + +#[graphql_interface(for = Concrete, desc = "A description")] +trait Description { + fn simple(&self) -> i32; +} +#[graphql_interface] +impl Description for Concrete { + fn simple(&self) -> i32 { + Self::simple() } - fn interfaces_first() -> InterfacesFirst { - InterfacesFirst {} +} + +struct Root; + +#[crate::graphql_object] +impl<'a> Root { + fn custom_name() -> CustomNameValue { + Concrete.into() } - fn commas_with_trailing() -> CommasWithTrailing { - CommasWithTrailing {} + fn with_lifetime() -> WithLifetimeValue<'a> { + Concrete.into() } - fn commas_on_meta() -> CommasOnMeta { - CommasOnMeta {} + fn with_generics() -> WithGenericsValue { + Concrete.into() } - fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma { - ResolversWithTrailingComma {} + fn description() -> DescriptionValue { + Concrete.into() } } @@ -156,16 +96,16 @@ where F: Fn(&Object, &Vec>) -> (), { let doc = r#" - query ($typeName: String!) { - __type(name: $typeName) { - name - description - fields(includeDeprecated: true) { + query ($typeName: String!) { + __type(name: $typeName) { name + description + fields(includeDeprecated: true) { + name + } } - } - } - "#; + }"#; + let schema = RootNode::new( Root {}, EmptyMutation::<()>::new(), @@ -256,115 +196,10 @@ async fn introspect_with_generics() { #[tokio::test] async fn introspect_description_first() { - run_type_info_query("DescriptionFirst", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("DescriptionFirst")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_fields_first() { - run_type_info_query("FieldsFirst", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("FieldsFirst")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_interfaces_first() { - run_type_info_query("InterfacesFirst", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("InterfacesFirst")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_commas_with_trailing() { - run_type_info_query("CommasWithTrailing", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("CommasWithTrailing")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_commas_on_meta() { - run_type_info_query("CommasOnMeta", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("CommasOnMeta")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_resolvers_with_trailing_comma() { - run_type_info_query("ResolversWithTrailingComma", |object, fields| { + run_type_info_query("Description", |object, fields| { assert_eq!( object.get_field_value("name"), - Some(&Value::scalar("ResolversWithTrailingComma")) + Some(&Value::scalar("Description")) ); assert_eq!( object.get_field_value("description"), diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 938da61db..85b61a502 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -639,30 +639,6 @@ impl<'a, S> Field<'a, S> { self } - /// Adds a (multi)line doc string to the description of the field. - /// Any leading or trailing newlines will be removed. - /// - /// If the docstring contains newlines, repeated leading tab and space characters - /// will be removed from the beginning of each line. - /// - /// If the description hasn't been set, the description is set to the provided line. - /// Otherwise, the doc string is added to the current description after a newline. - // TODO: remove after full proc macros - pub fn push_docstring(mut self, multiline: &[&str]) -> Field<'a, S> { - if let Some(docstring) = clean_docstring(multiline) { - match &mut self.description { - &mut Some(ref mut desc) => { - desc.push('\n'); - desc.push_str(&docstring); - } - desc @ &mut None => { - *desc = Some(docstring); - } - } - } - self - } - /// Add an argument to the field /// /// Arguments are unordered and can't contain duplicates by name. @@ -707,27 +683,6 @@ impl<'a, S> Argument<'a, S> { self } - /// Adds a (multi)line doc string to the description of the field. - /// Any leading or trailing newlines will be removed. - /// - /// If the docstring contains newlines, repeated leading tab and space characters - /// will be removed from the beginning of each line. - /// - /// If the description hasn't been set, the description is set to the provided line. - /// Otherwise, the doc string is added to the current description after a newline. - pub fn push_docstring(mut self, multiline: &[&str]) -> Argument<'a, S> { - if let Some(docstring) = clean_docstring(multiline) { - match &mut self.description { - &mut Some(ref mut desc) => { - desc.push('\n'); - desc.push_str(&docstring); - } - desc @ &mut None => *desc = Some(docstring), - } - } - self - } - /// Set the default value of the argument /// /// This overwrites the default value if any was previously set. diff --git a/juniper/src/tests/fixtures/mod.rs b/juniper/src/tests/fixtures/mod.rs index 90bd64e68..71b5bec84 100644 --- a/juniper/src/tests/fixtures/mod.rs +++ b/juniper/src/tests/fixtures/mod.rs @@ -1,4 +1,4 @@ //! Library fixtures -///// GraphQL schema and data from Star Wars. -//pub mod starwars; +/// GraphQL schema and data from Star Wars. +pub mod starwars; diff --git a/juniper/src/tests/fixtures/starwars/mod.rs b/juniper/src/tests/fixtures/starwars/mod.rs index 2e271ab15..9ae9b47fd 100644 --- a/juniper/src/tests/fixtures/starwars/mod.rs +++ b/juniper/src/tests/fixtures/starwars/mod.rs @@ -1,4 +1,4 @@ -pub mod model; + pub mod schema; #[cfg(feature = "schema-language")] pub mod schema_language; diff --git a/juniper/src/tests/fixtures/starwars/model.rs b/juniper/src/tests/fixtures/starwars/model.rs deleted file mode 100644 index ae250607a..000000000 --- a/juniper/src/tests/fixtures/starwars/model.rs +++ /dev/null @@ -1,319 +0,0 @@ -#![allow(missing_docs)] - -use std::collections::HashMap; - -use crate::GraphQLEnum; - -#[derive(GraphQLEnum, Copy, Clone, Eq, PartialEq, Debug)] -pub enum Episode { - #[graphql(name = "NEW_HOPE")] - NewHope, - Empire, - Jedi, -} - -pub trait Character { - fn id(&self) -> &str; - fn name(&self) -> &str; - fn friend_ids(&self) -> &[String]; - fn appears_in(&self) -> &[Episode]; - fn secret_backstory(&self) -> &Option; - fn as_character(&self) -> &dyn Character; -} - -pub trait Human: Character { - fn home_planet(&self) -> &Option; -} - -pub trait Droid: Character { - fn primary_function(&self) -> &Option; -} - -#[derive(Clone)] -struct HumanData { - id: String, - name: String, - friend_ids: Vec, - appears_in: Vec, - secret_backstory: Option, - home_planet: Option, -} - -#[derive(Clone)] -struct DroidData { - id: String, - name: String, - friend_ids: Vec, - appears_in: Vec, - secret_backstory: Option, - primary_function: Option, -} - -impl Character for HumanData { - fn id(&self) -> &str { - &self.id - } - fn name(&self) -> &str { - &self.name - } - fn friend_ids(&self) -> &[String] { - &self.friend_ids - } - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - fn secret_backstory(&self) -> &Option { - &self.secret_backstory - } - fn as_character(&self) -> &dyn Character { - self - } -} - -impl Human for HumanData { - fn home_planet(&self) -> &Option { - &self.home_planet - } -} - -impl Character for DroidData { - fn id(&self) -> &str { - &self.id - } - fn name(&self) -> &str { - &self.name - } - fn friend_ids(&self) -> &[String] { - &self.friend_ids - } - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - fn secret_backstory(&self) -> &Option { - &self.secret_backstory - } - fn as_character(&self) -> &dyn Character { - self - } -} - -impl Droid for DroidData { - fn primary_function(&self) -> &Option { - &self.primary_function - } -} - -#[derive(Default, Clone)] -pub struct Database { - humans: HashMap, - droids: HashMap, -} - -use crate::{ - executor::Registry, - schema::meta::MetaType, - types::base::{GraphQLType, GraphQLValue}, - value::ScalarValue, -}; - -impl GraphQLType for Database -where - S: ScalarValue, -{ - fn name(_: &()) -> Option<&str> { - Some("_Database") - } - - fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> - where - S: 'r, - { - registry.build_object_type::(&(), &[]).into_meta() - } -} - -impl GraphQLValue for Database -where - S: ScalarValue, -{ - type Context = Self; - type TypeInfo = (); - - fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { - >::name(info) - } -} - -impl HumanData { - pub fn new( - id: &str, - name: &str, - friend_ids: &[&str], - appears_in: &[Episode], - secret_backstory: Option<&str>, - home_planet: Option<&str>, - ) -> HumanData { - HumanData { - id: id.to_owned(), - name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(|f| f.to_owned()) - .collect(), - appears_in: appears_in.to_vec(), - secret_backstory: secret_backstory.map(|b| b.to_owned()), - home_planet: home_planet.map(|p| p.to_owned()), - } - } -} - -impl DroidData { - pub fn new( - id: &str, - name: &str, - friend_ids: &[&str], - appears_in: &[Episode], - secret_backstory: Option<&str>, - primary_function: Option<&str>, - ) -> DroidData { - DroidData { - id: id.to_owned(), - name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(|f| f.to_owned()) - .collect(), - appears_in: appears_in.to_vec(), - secret_backstory: secret_backstory.map(|b| b.to_owned()), - primary_function: primary_function.map(|p| p.to_owned()), - } - } -} - -impl Database { - pub fn new() -> Database { - let mut humans = HashMap::new(); - let mut droids = HashMap::new(); - - humans.insert( - "1000".to_owned(), - HumanData::new( - "1000", - "Luke Skywalker", - &["1002", "1003", "2000", "2001"], - &[Episode::NewHope, Episode::Empire, Episode::Jedi], - None, - Some("Tatooine"), - ), - ); - - humans.insert( - "1001".to_owned(), - HumanData::new( - "1001", - "Darth Vader", - &["1004"], - &[Episode::NewHope, Episode::Empire, Episode::Jedi], - None, - Some("Tatooine"), - ), - ); - - humans.insert( - "1002".to_owned(), - HumanData::new( - "1002", - "Han Solo", - &["1000", "1003", "2001"], - &[Episode::NewHope, Episode::Empire, Episode::Jedi], - None, - None, - ), - ); - - humans.insert( - "1003".to_owned(), - HumanData::new( - "1003", - "Leia Organa", - &["1000", "1002", "2000", "2001"], - &[Episode::NewHope, Episode::Empire, Episode::Jedi], - None, - Some("Alderaan"), - ), - ); - - humans.insert( - "1004".to_owned(), - HumanData::new( - "1004", - "Wilhuff Tarkin", - &["1001"], - &[Episode::NewHope], - None, - None, - ), - ); - - droids.insert( - "2000".to_owned(), - DroidData::new( - "2000", - "C-3PO", - &["1000", "1002", "1003", "2001"], - &[Episode::NewHope, Episode::Empire, Episode::Jedi], - None, - Some("Protocol"), - ), - ); - - droids.insert( - "2001".to_owned(), - DroidData::new( - "2001", - "R2-D2", - &["1000", "1002", "1003"], - &[Episode::NewHope, Episode::Empire, Episode::Jedi], - None, - Some("Astromech"), - ), - ); - - Database { humans, droids } - } - - pub fn get_hero(&self, episode: Option) -> &dyn Character { - if episode == Some(Episode::Empire) { - self.get_human("1000").unwrap().as_character() - } else { - self.get_droid("2001").unwrap().as_character() - } - } - - pub fn get_human(&self, id: &str) -> Option<&dyn Human> { - self.humans.get(id).map(|h| h as &dyn Human) - } - - pub fn get_droid(&self, id: &str) -> Option<&dyn Droid> { - self.droids.get(id).map(|d| d as &dyn Droid) - } - - pub fn get_character(&self, id: &str) -> Option<&dyn Character> { - if let Some(h) = self.humans.get(id) { - Some(h) - } else if let Some(d) = self.droids.get(id) { - Some(d) - } else { - None - } - } - - pub fn get_friends(&self, c: &dyn Character) -> Vec<&dyn Character> { - c.friend_ids() - .iter() - .flat_map(|id| self.get_character(id)) - .collect() - } -} diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index e0818e57b..b32026524 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -1,164 +1,377 @@ #![allow(missing_docs)] +use std::{collections::HashMap, pin::Pin}; + use crate::{ - executor::Context, - graphql_subscription, - tests::fixtures::starwars::model::{Character, Database, Droid, Episode, Human}, - GraphQLObject, + executor::Registry, graphql_interface, graphql_object, graphql_subscription, + schema::meta::MetaType, value::ScalarValue, Context, DefaultScalarValue, GraphQLEnum, + GraphQLObject, GraphQLType, GraphQLValue, }; -use std::pin::Pin; - -impl Context for Database {} -graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| { - description: "A character in the Star Wars Trilogy" +pub struct Query; - field id() -> &str as "The id of the character" { - self.id() +#[crate::graphql_object( + context = Database, + scalar = crate::DefaultScalarValue, +)] +/// The root query object of the schema +impl Query { + #[graphql(arguments(id(description = "id of the human")))] + fn human(database: &Database, id: String) -> Option<&Human> { + database.get_human(&id) } - field name() -> Option<&str> as "The name of the character" { - Some(self.name()) + #[graphql(arguments(id(description = "id of the droid")))] + fn droid(database: &Database, id: String) -> Option<&Droid> { + database.get_droid(&id) } - field friends(&executor) -> Vec<&dyn Character> - as "The friends of the character" { - executor.context().get_friends(self.as_character()) + #[graphql(arguments(episode( + description = "If omitted, returns the hero of the whole saga. \ + If provided, returns the hero of that particular episode" + )))] + fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { + Some(database.get_hero(episode).as_character()) } +} + +pub struct Subscription; - field appears_in() -> &[Episode] as "Which movies they appear in" { - self.appears_in() +type HumanStream = Pin + Send>>; + +#[graphql_subscription(context = Database)] +/// Super basic subscription fixture +impl Subscription { + async fn async_human(context: &Database) -> HumanStream { + let human = context.get_human("1000").unwrap().clone(); + Box::pin(futures::stream::once(futures::future::ready(human))) } +} +#[derive(GraphQLEnum, Clone, Copy, Debug, Eq, PartialEq)] +pub enum Episode { + #[graphql(name = "NEW_HOPE")] + NewHope, + Empire, + Jedi, +} - instance_resolvers: |&context| { - &dyn Human => context.get_human(&self.id()), - &dyn Droid => context.get_droid(&self.id()), +#[graphql_interface(for = [Human], context = Database)] +/// A character in the Star Wars Trilogy +pub trait Character { + /// The id of the character + fn id(&self) -> &str; + + /// The name of the character + fn name(&self) -> Option<&str>; + + /// The friends of the character + fn friends(&self, ctx: &Database) -> Vec; + + /// Which movies they appear in + fn appears_in(&self) -> &[Episode]; +} + +#[derive(GraphQLObject, Clone)] +struct Human { + id: String, + name: String, + friend_ids: Vec, + appears_in: Vec, + secret_backstory: Option, + home_planet: Option, +} + +impl Human { + pub fn new( + id: &str, + name: &str, + friend_ids: &[&str], + appears_in: &[Episode], + secret_backstory: Option<&str>, + home_planet: Option<&str>, + ) -> Self { + Self { + id: id.to_owned(), + name: name.to_owned(), + friend_ids: friend_ids + .to_owned() + .into_iter() + .map(ToOwned::to_owned) + .collect(), + appears_in: appears_in.to_vec(), + secret_backstory: secret_backstory.map(ToOwned::to_owned), + home_planet: home_planet.map(|p| p.to_owned()), + } } -}); +} -#[crate::graphql_object( - Context = Database, - Scalar = crate::DefaultScalarValue, - interfaces = [&dyn Character], - // FIXME: make async work - noasync +#[graphql_object( + context = Database, + scalar = DefaultScalarValue, + interfaces = CharacterValue, )] -/// A humanoid creature in the Star Wars universe. -impl<'a> &'a dyn Human { +impl Human { /// The id of the human fn id(&self) -> &str { - self.id() + &self.id } /// The name of the human fn name(&self) -> Option<&str> { - Some(self.name()) + Some(&self.name) } /// The friends of the human - fn friends(&self, ctx: &Database) -> Vec<&dyn Character> { - ctx.get_friends(self.as_character()) + fn friends(&self, ctx: &Database) -> Vec { + ctx.get_friends(self) } /// Which movies they appear in fn appears_in(&self) -> &[Episode] { - self.appears_in() + &self.appears_in } /// The home planet of the human fn home_planet(&self) -> &Option { - self.home_planet() + &self.home_planet } } -#[crate::graphql_object( - Context = Database, - Scalar = crate::DefaultScalarValue, - interfaces = [&dyn Character], - // FIXME: make async work - noasync +#[graphql_interface] +impl Character for Human { + fn id(&self) -> &str { + &self.id + } + + fn name(&self) -> Option<&str> { + Some(&self.name) + } + + fn friends(&self, ctx: &Database) -> Vec { + ctx.get_friends(self) + } + + fn appears_in(&self) -> &[Episode] { + &self.appears_in + } +} + +#[derive(Clone)] +struct Droid { + id: String, + name: String, + friend_ids: Vec, + appears_in: Vec, + secret_backstory: Option, + primary_function: Option, +} + +impl Droid { + pub fn new( + id: &str, + name: &str, + friend_ids: &[&str], + appears_in: &[Episode], + secret_backstory: Option<&str>, + primary_function: Option<&str>, + ) -> Self { + Self { + id: id.to_owned(), + name: name.to_owned(), + friend_ids: friend_ids + .to_owned() + .into_iter() + .map(ToOwned::to_owned) + .collect(), + appears_in: appears_in.to_vec(), + secret_backstory: secret_backstory.map(ToOwned::to_owned), + primary_function: primary_function.map(ToOwned::to_owned), + } + } +} + +#[graphql_object( + context = Database, + scalar = crate::DefaultScalarValue, + interfaces = CharacterValue, )] /// A mechanical creature in the Star Wars universe. -impl<'a> &'a dyn Droid { +impl Droid { /// The id of the droid fn id(&self) -> &str { - self.id() + &self.id } /// The name of the droid fn name(&self) -> Option<&str> { - Some(self.name()) + Some(&self.name) } /// The friends of the droid - fn friends(&self, ctx: &Database) -> Vec<&dyn Character> { - ctx.get_friends(self.as_character()) + fn friends(&self, ctx: &Database) -> Vec { + ctx.get_friends(self) } /// Which movies they appear in fn appears_in(&self) -> &[Episode] { - self.appears_in() + &self.appears_in } /// The primary function of the droid fn primary_function(&self) -> &Option { - self.primary_function() + &self.primary_function } } -pub struct Query; +#[graphql_interface] +impl Character for Droid { + fn id(&self) -> &str { + &self.id + } -#[crate::graphql_object( - Context = Database, - Scalar = crate::DefaultScalarValue, - // FIXME: make async work - noasync -)] -/// The root query object of the schema -impl Query { - #[graphql(arguments(id(description = "id of the human")))] - fn human(database: &Database, id: String) -> Option<&dyn Human> { - database.get_human(&id) + fn name(&self) -> Option<&str> { + Some(&self.name) } - #[graphql(arguments(id(description = "id of the droid")))] - fn droid(database: &Database, id: String) -> Option<&dyn Droid> { - database.get_droid(&id) + fn friends(&self, ctx: &Database) -> Vec { + ctx.get_friends(self) } - #[graphql(arguments(episode( - description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" - )))] - fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { - Some(database.get_hero(episode).as_character()) + fn appears_in(&self) -> &[Episode] { + &self.appears_in } } -#[derive(GraphQLObject)] -#[graphql(description = "A humanoid creature in the Star Wars universe")] -#[derive(Clone)] -/// A humanoid creature in the Star Wars universe. -/// TODO: remove this when async interfaces are merged -struct HumanSubscription { - id: String, - name: String, - home_planet: String, +#[derive(Default, Clone)] +pub struct Database { + humans: HashMap, + droids: HashMap, } -pub struct Subscription; +impl Context for Database {} -type HumanStream = Pin + Send>>; +impl Database { + pub fn new() -> Database { + let mut humans = HashMap::new(); + let mut droids = HashMap::new(); -#[graphql_subscription(context = Database)] -/// Super basic subscription fixture -impl Subscription { - async fn async_human() -> HumanStream { - Box::pin(futures::stream::once(async { - HumanSubscription { - id: "stream id".to_string(), - name: "stream name".to_string(), - home_planet: "stream home planet".to_string(), - } - })) + humans.insert( + "1000".to_owned(), + HumanData::new( + "1000", + "Luke Skywalker", + &["1002", "1003", "2000", "2001"], + &[Episode::NewHope, Episode::Empire, Episode::Jedi], + None, + Some("Tatooine"), + ), + ); + + humans.insert( + "1001".to_owned(), + HumanData::new( + "1001", + "Darth Vader", + &["1004"], + &[Episode::NewHope, Episode::Empire, Episode::Jedi], + None, + Some("Tatooine"), + ), + ); + + humans.insert( + "1002".to_owned(), + HumanData::new( + "1002", + "Han Solo", + &["1000", "1003", "2001"], + &[Episode::NewHope, Episode::Empire, Episode::Jedi], + None, + None, + ), + ); + + humans.insert( + "1003".to_owned(), + HumanData::new( + "1003", + "Leia Organa", + &["1000", "1002", "2000", "2001"], + &[Episode::NewHope, Episode::Empire, Episode::Jedi], + None, + Some("Alderaan"), + ), + ); + + humans.insert( + "1004".to_owned(), + HumanData::new( + "1004", + "Wilhuff Tarkin", + &["1001"], + &[Episode::NewHope], + None, + None, + ), + ); + + droids.insert( + "2000".to_owned(), + DroidData::new( + "2000", + "C-3PO", + &["1000", "1002", "1003", "2001"], + &[Episode::NewHope, Episode::Empire, Episode::Jedi], + None, + Some("Protocol"), + ), + ); + + droids.insert( + "2001".to_owned(), + DroidData::new( + "2001", + "R2-D2", + &["1000", "1002", "1003"], + &[Episode::NewHope, Episode::Empire, Episode::Jedi], + None, + Some("Astromech"), + ), + ); + + Database { humans, droids } + } + + pub fn get_hero(&self, episode: Option) -> &dyn Character { + if episode == Some(Episode::Empire) { + self.get_human("1000").unwrap().as_character() + } else { + self.get_droid("2001").unwrap().as_character() + } + } + + pub fn get_human(&self, id: &str) -> Option<&Human> { + self.humans.get(id) + } + + pub fn get_droid(&self, id: &str) -> Option<&Droid> { + self.droids.get(id) + } + + pub fn get_character(&self, id: &str) -> Option { + if let Some(h) = self.humans.get(id) { + Some(h.into()) + } else if let Some(d) = self.droids.get(id) { + Some(d.into()) + } else { + None + } + } + + pub fn get_friends(&self, c: &dyn Character) -> Vec { + c.friend_ids() + .iter() + .flat_map(|id| self.get_character(id)) + .collect() } } From 038fd672825032eb9e5ff5bbdb542721765bade3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 24 Sep 2020 20:11:11 +0200 Subject: [PATCH 57/79] Fix juniper tests, vol.1 --- juniper/src/macros/tests/field.rs | 62 +++++++++---------- juniper/src/macros/tests/interface.rs | 4 +- juniper/src/tests/fixtures/starwars/schema.rs | 20 +++--- .../fixtures/starwars/schema_language.rs | 3 +- juniper/src/tests/introspection_tests.rs | 5 +- juniper/src/tests/query_tests.rs | 3 +- juniper/src/tests/schema_introspection.rs | 1 + 7 files changed, 50 insertions(+), 48 deletions(-) diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index cffa7dcf4..db8dc36eb 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -1,15 +1,3 @@ -use crate::{ - ast::InputValue, - executor::FieldResult, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, Value}, -}; - -struct Interface; -#[derive(Debug)] -struct Root; - /* Syntax to validate: @@ -22,9 +10,19 @@ Syntax to validate: */ -#[crate::graphql_object( - interfaces = [&Interface], -)] +use crate::{ + ast::InputValue, + executor::FieldResult, + schema::model::RootNode, + types::scalars::{EmptyMutation, EmptySubscription}, + value::{DefaultScalarValue, Object, Value}, + graphql_object, graphql_interface +}; + +#[derive(Debug)] +struct Root; + +#[graphql_object(interfaces = [InterfaceValue])] impl Root { fn simple() -> i32 { 0 @@ -104,44 +102,42 @@ impl Root { } } -graphql_interface!(Interface: () |&self| { - field simple() -> i32 { 0 } +#[graphql_interface(for = Root)] +trait Interface { + fn simple(&self) -> i32; - field description() -> i32 as "Field description" { 0 } + #[graphql_interface(desc = "Field description")] + fn description(&self) -> i32; - field deprecated "Deprecation reason" - deprecated() -> i32 { 0 } + #[graphql_interface(deprecated = "Deprecation reason")] + fn deprecated() -> i32; - field deprecated "Deprecation reason" - deprecated_descr() -> i32 as "Field description" { 0 } + #[graphql_interface(desc = "Field description", deprecated = "Deprecation reason")] + fn deprecated_descr() -> i32; /// Field description - field attr_description() -> i32 { 0 } + fn attr_description(&self) -> i32; /// Field description /// with `collapse_docs` behavior - field attr_description_collapse() -> i32 { 0 } + fn attr_description_collapse(&self) -> i32; /// Get the i32 representation of 0. /// /// - This comment is longer. /// - These two lines are rendered as bullets by GraphiQL. - field attr_description_long() -> i32 { 0 } + fn attr_description_long(&self) -> i32; #[deprecated] - field attr_deprecated() -> i32 { 0 } + fn attr_deprecated() -> i32; #[deprecated(note = "Deprecation reason")] - field attr_deprecated_reason() -> i32 { 0 } + fn attr_deprecated_reason() -> i32; /// Field description #[deprecated(note = "Deprecation reason")] - field attr_deprecated_descr() -> i32 { 0 } - - instance_resolvers: |&_| { - Root => Some(Root {}), - } -}); + fn attr_deprecated_descr() -> i32; +} async fn run_field_info_query(type_name: &str, field_name: &str, f: F) where diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index 56684bb0b..8718b0aa5 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -20,7 +20,9 @@ use crate::{ struct Concrete; -#[graphql_object(impl = [CustomNameValue, DescriptionValue, WithLifetime<'_>, WithGenerics<()>])] +#[graphql_object(impl = [ + CustomNameValue, DescriptionValue, WithLifetimeValue<'_>, WithGenericsValue<()>, +])] impl Concrete { fn simple() -> i32 { 0 diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index b32026524..ed4cf492b 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -71,7 +71,7 @@ pub trait Character { fn appears_in(&self) -> &[Episode]; } -#[derive(GraphQLObject, Clone)] +#[derive(Clone)] struct Human { id: String, name: String, @@ -244,8 +244,8 @@ impl Character for Droid { #[derive(Default, Clone)] pub struct Database { - humans: HashMap, - droids: HashMap, + humans: HashMap, + droids: HashMap, } impl Context for Database {} @@ -257,7 +257,7 @@ impl Database { humans.insert( "1000".to_owned(), - HumanData::new( + Human::new( "1000", "Luke Skywalker", &["1002", "1003", "2000", "2001"], @@ -269,7 +269,7 @@ impl Database { humans.insert( "1001".to_owned(), - HumanData::new( + Human::new( "1001", "Darth Vader", &["1004"], @@ -281,7 +281,7 @@ impl Database { humans.insert( "1002".to_owned(), - HumanData::new( + Human::new( "1002", "Han Solo", &["1000", "1003", "2001"], @@ -293,7 +293,7 @@ impl Database { humans.insert( "1003".to_owned(), - HumanData::new( + Human::new( "1003", "Leia Organa", &["1000", "1002", "2000", "2001"], @@ -305,7 +305,7 @@ impl Database { humans.insert( "1004".to_owned(), - HumanData::new( + Human::new( "1004", "Wilhuff Tarkin", &["1001"], @@ -317,7 +317,7 @@ impl Database { droids.insert( "2000".to_owned(), - DroidData::new( + Droid::new( "2000", "C-3PO", &["1000", "1002", "1003", "2001"], @@ -329,7 +329,7 @@ impl Database { droids.insert( "2001".to_owned(), - DroidData::new( + Droid::new( "2001", "R2-D2", &["1000", "1002", "1003"], diff --git a/juniper/src/tests/fixtures/starwars/schema_language.rs b/juniper/src/tests/fixtures/starwars/schema_language.rs index c2f16023c..7a71c8d7a 100644 --- a/juniper/src/tests/fixtures/starwars/schema_language.rs +++ b/juniper/src/tests/fixtures/starwars/schema_language.rs @@ -8,7 +8,8 @@ mod tests { use crate::{ schema::model::RootNode, tests::fixtures::starwars::{ - model::Database, schema::Query, schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION, + schema::{Database, Query}, + schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION, }, types::scalars::{EmptyMutation, EmptySubscription}, }; diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs index 46b7a21c2..38259eead 100644 --- a/juniper/src/tests/introspection_tests.rs +++ b/juniper/src/tests/introspection_tests.rs @@ -1,14 +1,15 @@ use std::collections::HashSet; -use super::schema_introspection::*; use crate::{ executor::Variables, introspection::IntrospectionFormat, schema::model::RootNode, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, }; +use super::schema_introspection::*; + #[tokio::test] async fn test_introspection_query_type_name() { let doc = r#" diff --git a/juniper/src/tests/query_tests.rs b/juniper/src/tests/query_tests.rs index c68e3a529..ac5efafff 100644 --- a/juniper/src/tests/query_tests.rs +++ b/juniper/src/tests/query_tests.rs @@ -2,11 +2,12 @@ use crate::{ ast::InputValue, executor::Variables, schema::model::RootNode, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, types::scalars::{EmptyMutation, EmptySubscription}, value::Value, }; + #[tokio::test] async fn test_hero_name() { let doc = r#" diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index 4303c6762..d7f340256 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -3,6 +3,7 @@ use crate::value::{ Value::{self, Null}, }; + // Sort a nested schema Value. // In particular, lists are sorted by the "name" key of children, if present. // Only needed for comparisons. From f64a0bcc78639841f314d0cbdec17ad89fb04bd4 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 25 Sep 2020 12:28:10 +0200 Subject: [PATCH 58/79] Fix juniper tests, vol.2 --- .../src/executor_tests/introspection/mod.rs | 31 ++++------ juniper/src/macros/tests/field.rs | 59 ++++++++++++++++--- juniper/src/macros/tests/interface.rs | 28 ++++----- juniper/src/schema/meta.rs | 32 ---------- juniper/src/tests/fixtures/starwars/schema.rs | 56 ++++++++++-------- juniper/src/tests/query_tests.rs | 6 +- 6 files changed, 114 insertions(+), 98 deletions(-) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index aacb341f5..8c1b3b6c2 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -10,7 +10,7 @@ use crate::{ schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, Value}, - GraphQLEnum, + GraphQLEnum, graphql_object, graphql_interface, }; #[derive(GraphQLEnum)] @@ -22,10 +22,6 @@ enum Sample { struct Scalar(i32); -struct Interface; - -struct Root; - #[crate::graphql_scalar(name = "SampleScalar")] impl GraphQLScalar for Scalar { fn resolve(&self) -> Value { @@ -41,23 +37,19 @@ impl GraphQLScalar for Scalar { } } -graphql_interface!(Interface: () as "SampleInterface" |&self| { - description: "A sample interface" - - field sample_enum() -> Sample as "A sample field in the interface" { +/// A sample interface +#[graphql_interface(name = "SampleInterface", for = Root, scalar = DefaultScalarValue)] +trait Interface { + /// A sample field in the interface + fn sample_enum(&self) -> Sample { Sample::One } +} - instance_resolvers: |&_| { - Root => Some(Root), - } -}); +struct Root; /// The root query object in the schema -#[crate::graphql_object( - interfaces = [&Interface] - Scalar = crate::DefaultScalarValue, -)] +#[graphql_object(interfaces = InterfaceValue, scalar = DefaultScalarValue)] impl Root { fn sample_enum() -> Sample { Sample::One @@ -65,7 +57,7 @@ impl Root { #[graphql(arguments( first(description = "The first number",), - second(description = "The second number", default = 123,), + second(description = "The second number", default = 123), ))] /// A sample scalar field on the object @@ -74,6 +66,9 @@ impl Root { } } +#[graphql_interface(scalar = DefaultScalarValue)] +impl Interface for Root {} + #[tokio::test] async fn test_execution() { let doc = r#" diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index db8dc36eb..1104454fa 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -10,13 +10,15 @@ Syntax to validate: */ +#![allow(deprecated)] + use crate::{ ast::InputValue, executor::FieldResult, + graphql_interface, graphql_object, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, Object, Value}, - graphql_object, graphql_interface }; #[derive(Debug)] @@ -102,7 +104,50 @@ impl Root { } } -#[graphql_interface(for = Root)] +#[graphql_interface(scalar = DefaultScalarValue)] +impl Interface for Root { + fn simple(&self) -> i32 { + 0 + } + + fn description(&self) -> i32 { + 0 + } + + fn deprecated(&self) -> i32 { + 0 + } + + fn deprecated_descr(&self) -> i32 { + 0 + } + + fn attr_description(&self) -> i32 { + 0 + } + + fn attr_description_collapse(&self) -> i32 { + 0 + } + + fn attr_description_long(&self) -> i32 { + 0 + } + + fn attr_deprecated(&self) -> i32 { + 0 + } + + fn attr_deprecated_reason(&self) -> i32 { + 0 + } + + fn attr_deprecated_descr(&self) -> i32 { + 0 + } +} + +#[graphql_interface(for = Root, scalar = DefaultScalarValue)] trait Interface { fn simple(&self) -> i32; @@ -110,10 +155,10 @@ trait Interface { fn description(&self) -> i32; #[graphql_interface(deprecated = "Deprecation reason")] - fn deprecated() -> i32; + fn deprecated(&self) -> i32; #[graphql_interface(desc = "Field description", deprecated = "Deprecation reason")] - fn deprecated_descr() -> i32; + fn deprecated_descr(&self) -> i32; /// Field description fn attr_description(&self) -> i32; @@ -129,14 +174,14 @@ trait Interface { fn attr_description_long(&self) -> i32; #[deprecated] - fn attr_deprecated() -> i32; + fn attr_deprecated(&self) -> i32; #[deprecated(note = "Deprecation reason")] - fn attr_deprecated_reason() -> i32; + fn attr_deprecated_reason(&self) -> i32; /// Field description #[deprecated(note = "Deprecation reason")] - fn attr_deprecated_descr() -> i32; + fn attr_deprecated_descr(&self) -> i32; } async fn run_field_info_query(type_name: &str, field_name: &str, f: F) diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index 8718b0aa5..f02874d50 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -29,59 +29,59 @@ impl Concrete { } } -#[graphql_interface(for = Concrete, name = "ACustomNamedInterface")] +#[graphql_interface(for = Concrete, name = "ACustomNamedInterface", scalar = DefaultScalarValue)] trait CustomName { fn simple(&self) -> i32; } -#[graphql_interface] +#[graphql_interface(scalar = DefaultScalarValue)] impl CustomName for Concrete { fn simple(&self) -> i32 { - Self::simple() + 0 } } -#[graphql_interface(for = Concrete)] +#[graphql_interface(for = Concrete, scalar = DefaultScalarValue)] trait WithLifetime<'a> { fn simple(&self) -> i32; } -#[graphql_interface] +#[graphql_interface(scalar = DefaultScalarValue)] impl<'a> WithLifetime<'a> for Concrete { fn simple(&self) -> i32 { - Self::simple() + 0 } } -#[graphql_interface(for = Concrete)] +#[graphql_interface(for = Concrete, scalar = DefaultScalarValue)] trait WithGenerics { fn simple(&self) -> i32; } -#[graphql_interface] +#[graphql_interface(scalar = DefaultScalarValue)] impl WithGenerics for Concrete { fn simple(&self) -> i32 { - Self::simple() + 0 } } -#[graphql_interface(for = Concrete, desc = "A description")] +#[graphql_interface(for = Concrete, desc = "A description", scalar = DefaultScalarValue)] trait Description { fn simple(&self) -> i32; } -#[graphql_interface] +#[graphql_interface(scalar = DefaultScalarValue)] impl Description for Concrete { fn simple(&self) -> i32 { - Self::simple() + 0 } } struct Root; #[crate::graphql_object] -impl<'a> Root { +impl Root { fn custom_name() -> CustomNameValue { Concrete.into() } - fn with_lifetime() -> WithLifetimeValue<'a> { + fn with_lifetime() -> WithLifetimeValue<'static> { Concrete.into() } fn with_generics() -> WithGenericsValue { diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 85b61a502..be64e06f8 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -754,35 +754,3 @@ where { >::from_input_value(v).is_some() } - -fn clean_docstring(multiline: &[&str]) -> Option { - if multiline.is_empty() { - return None; - } - let trim_start = multiline - .iter() - .filter_map(|ln| ln.chars().position(|ch| !ch.is_whitespace())) - .min() - .unwrap_or(0); - Some( - multiline - .iter() - .enumerate() - .flat_map(|(line, ln)| { - let new_ln = if !ln.chars().next().map(char::is_whitespace).unwrap_or(false) { - ln.trim_end() // skip trimming the first line - } else if ln.len() >= trim_start { - ln[trim_start..].trim_end() - } else { - "" - }; - new_ln.chars().chain( - ['\n'] - .iter() - .take_while(move |_| line < multiline.len() - 1) - .cloned(), - ) - }) - .collect::(), - ) -} diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index ed4cf492b..8693f4b2c 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -3,17 +3,13 @@ use std::{collections::HashMap, pin::Pin}; use crate::{ - executor::Registry, graphql_interface, graphql_object, graphql_subscription, - schema::meta::MetaType, value::ScalarValue, Context, DefaultScalarValue, GraphQLEnum, - GraphQLObject, GraphQLType, GraphQLValue, + graphql_interface, graphql_object, graphql_subscription, Context, DefaultScalarValue, + GraphQLEnum, }; pub struct Query; -#[crate::graphql_object( - context = Database, - scalar = crate::DefaultScalarValue, -)] +#[graphql_object(context = Database, scalar = DefaultScalarValue)] /// The root query object of the schema impl Query { #[graphql(arguments(id(description = "id of the human")))] @@ -30,8 +26,8 @@ impl Query { description = "If omitted, returns the hero of the whole saga. \ If provided, returns the hero of that particular episode" )))] - fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { - Some(database.get_hero(episode).as_character()) + fn hero(database: &Database, episode: Option) -> Option { + Some(database.get_hero(episode)) } } @@ -55,7 +51,7 @@ pub enum Episode { Jedi, } -#[graphql_interface(for = [Human], context = Database)] +#[graphql_interface(for = [Human, Droid], context = Database, scalar = DefaultScalarValue)] /// A character in the Star Wars Trilogy pub trait Character { /// The id of the character @@ -69,10 +65,13 @@ pub trait Character { /// Which movies they appear in fn appears_in(&self) -> &[Episode]; + + #[graphql_interface(ignore)] + fn friends_ids(&self) -> &[String]; } #[derive(Clone)] -struct Human { +pub struct Human { id: String, name: String, friend_ids: Vec, @@ -105,6 +104,7 @@ impl Human { } } +/// A humanoid creature in the Star Wars universe. #[graphql_object( context = Database, scalar = DefaultScalarValue, @@ -118,7 +118,7 @@ impl Human { /// The name of the human fn name(&self) -> Option<&str> { - Some(&self.name) + Some(self.name.as_str()) } /// The friends of the human @@ -137,7 +137,7 @@ impl Human { } } -#[graphql_interface] +#[graphql_interface(scalar = DefaultScalarValue)] impl Character for Human { fn id(&self) -> &str { &self.id @@ -154,10 +154,14 @@ impl Character for Human { fn appears_in(&self) -> &[Episode] { &self.appears_in } + + fn friends_ids(&self) -> &[String] { + &self.friend_ids + } } #[derive(Clone)] -struct Droid { +pub struct Droid { id: String, name: String, friend_ids: Vec, @@ -190,12 +194,12 @@ impl Droid { } } +/// A mechanical creature in the Star Wars universe. #[graphql_object( context = Database, - scalar = crate::DefaultScalarValue, + scalar = DefaultScalarValue, interfaces = CharacterValue, )] -/// A mechanical creature in the Star Wars universe. impl Droid { /// The id of the droid fn id(&self) -> &str { @@ -204,7 +208,7 @@ impl Droid { /// The name of the droid fn name(&self) -> Option<&str> { - Some(&self.name) + Some(self.name.as_str()) } /// The friends of the droid @@ -223,7 +227,7 @@ impl Droid { } } -#[graphql_interface] +#[graphql_interface(scalar = DefaultScalarValue)] impl Character for Droid { fn id(&self) -> &str { &self.id @@ -240,6 +244,10 @@ impl Character for Droid { fn appears_in(&self) -> &[Episode] { &self.appears_in } + + fn friends_ids(&self) -> &[String] { + &self.friend_ids + } } #[derive(Default, Clone)] @@ -342,11 +350,11 @@ impl Database { Database { humans, droids } } - pub fn get_hero(&self, episode: Option) -> &dyn Character { + pub fn get_hero(&self, episode: Option) -> CharacterValue { if episode == Some(Episode::Empire) { - self.get_human("1000").unwrap().as_character() + self.get_human("1000").unwrap().clone().into() } else { - self.get_droid("2001").unwrap().as_character() + self.get_droid("2001").unwrap().clone().into() } } @@ -360,16 +368,16 @@ impl Database { pub fn get_character(&self, id: &str) -> Option { if let Some(h) = self.humans.get(id) { - Some(h.into()) + Some(h.clone().into()) } else if let Some(d) = self.droids.get(id) { - Some(d.into()) + Some(d.clone().into()) } else { None } } pub fn get_friends(&self, c: &dyn Character) -> Vec { - c.friend_ids() + c.friends_ids() .iter() .flat_map(|id| self.get_character(id)) .collect() diff --git a/juniper/src/tests/query_tests.rs b/juniper/src/tests/query_tests.rs index ac5efafff..b0ed712a9 100644 --- a/juniper/src/tests/query_tests.rs +++ b/juniper/src/tests/query_tests.rs @@ -642,8 +642,8 @@ async fn test_query_inline_fragments_droid() { "hero", Value::object( vec![ - ("name", Value::scalar("R2-D2")), ("__typename", Value::scalar("Droid")), + ("name", Value::scalar("R2-D2")), ("primaryFunction", Value::scalar("Astromech")), ] .into_iter() @@ -663,8 +663,8 @@ async fn test_query_inline_fragments_human() { let doc = r#" query InlineFragments { hero(episode: EMPIRE) { - name __typename + name } } "#; @@ -683,8 +683,8 @@ async fn test_query_inline_fragments_human() { "hero", Value::object( vec![ - ("name", Value::scalar("Luke Skywalker")), ("__typename", Value::scalar("Human")), + ("name", Value::scalar("Luke Skywalker")), ] .into_iter() .collect(), From f860ab8016296d549a0c94331126b38e05c25a5a Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 25 Sep 2020 17:39:29 +0200 Subject: [PATCH 59/79] Polish 'juniper' crate changes, vol.1 --- juniper/src/lib.rs | 13 +++---- juniper/src/macros/helper/mod.rs | 34 +++++++++++++++++++ .../subscription.rs} | 0 juniper/src/macros/mod.rs | 4 +-- juniper/src/types/async_await.rs | 5 ++- juniper/src/types/base.rs | 15 ++------ 6 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 juniper/src/macros/helper/mod.rs rename juniper/src/macros/{subscription_helpers.rs => helper/subscription.rs} (100%) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 9632fde9e..5ac999b7b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -128,7 +128,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - graphql_object, graphql_scalar, graphql_subscription, graphql_union, graphql_interface, + graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, }; @@ -175,16 +175,17 @@ pub use crate::{ LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables, }, introspection::IntrospectionFormat, - macros::subscription_helpers::{ExtractTypeFromStream, IntoFieldResult}, + macros::helper::{ + subscription::{ExtractTypeFromStream, IntoFieldResult}, + AsDynGraphQLValue, + }, schema::{ meta, model::{RootNode, SchemaType}, }, types::{ - async_await::{GraphQLTypeAsync, GraphQLValueAsync, DynGraphQLValueAsync}, - base::{ - Arguments, AsDynGraphQLValue, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind, - }, + async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync}, + base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind}, marker::{self, GraphQLInterface, GraphQLUnion}, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs new file mode 100644 index 000000000..8162b7a9c --- /dev/null +++ b/juniper/src/macros/helper/mod.rs @@ -0,0 +1,34 @@ +//! Helper traits and definitions for macros. + +pub mod subscription; + +use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ScalarValue}; + +/// Conversion of a [`GraphQLValue`] to its [trait object][1]. +/// +/// [`GraphQLValue`]: crate::GraphQLValue +/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html +pub trait AsDynGraphQLValue { + /// Context type of this [`GraphQLValue`]. + /// + /// [`GraphQLValue`]: crate::GraphQLValue + type Context; + + /// Schema information type of this [`GraphQLValue`]. + /// + /// [`GraphQLValue`]: crate::GraphQLValue + type TypeInfo; + + /// Converts this value to a [`DynGraphQLValue`] [trait object][1]. + /// + /// [1]: https://doc.rust-lang.org/reference/types/trait-object.html + fn as_dyn_graphql_value(&self) -> &DynGraphQLValue; + + /// Converts this value to a [`DynGraphQLValueAsync`] [trait object][1]. + /// + /// [1]: https://doc.rust-lang.org/reference/types/trait-object.html + fn as_dyn_graphql_value_async(&self) + -> &DynGraphQLValueAsync; +} + +crate::sa::assert_obj_safe!(AsDynGraphQLValue); diff --git a/juniper/src/macros/subscription_helpers.rs b/juniper/src/macros/helper/subscription.rs similarity index 100% rename from juniper/src/macros/subscription_helpers.rs rename to juniper/src/macros/helper/subscription.rs diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index ef9675bd3..ec8e3b702 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,6 +1,6 @@ //! Helper traits for macros. +pub mod helper; + #[cfg(test)] mod tests; - -pub mod subscription_helpers; diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index b02bc089d..395faf5ef 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -113,8 +113,11 @@ where crate::sa::assert_obj_safe!(GraphQLValueAsync); +/// Helper alias for naming [trait objects][1] of [`GraphQLValueAsync`]. +/// +/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html pub type DynGraphQLValueAsync = -dyn GraphQLValueAsync + Send + 'static; + dyn GraphQLValueAsync + Send + 'static; /// Extension of [`GraphQLType`] trait with asynchronous queries/mutations resolvers. /// diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 15c6c7f22..46072ee69 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -7,7 +7,6 @@ use crate::{ schema::meta::{Argument, MetaType}, value::{DefaultScalarValue, Object, ScalarValue, Value}, GraphQLEnum, - types::async_await::DynGraphQLValueAsync, }; /// GraphQL type kind @@ -291,20 +290,12 @@ where crate::sa::assert_obj_safe!(GraphQLValue); +/// Helper alias for naming [trait objects][1] of [`GraphQLValue`]. +/// +/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html pub type DynGraphQLValue = dyn GraphQLValue + Send + Sync + 'static; -pub trait AsDynGraphQLValue { - type Context; - type TypeInfo; - - fn as_dyn_graphql_value(&self) -> &DynGraphQLValue; - - fn as_dyn_graphql_value_async(&self) -> &DynGraphQLValueAsync; -} - -crate::sa::assert_obj_safe!(AsDynGraphQLValue); - /// Primary trait used to expose Rust types in a GraphQL schema. /// /// All of the convenience macros ultimately expand into an implementation of From 8753a6e140f1c5abfebfe75cba301d0cc6b90d3c Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 25 Sep 2020 17:56:24 +0200 Subject: [PATCH 60/79] Polish 'juniper' crate changes, vol.2 --- juniper/src/executor_tests/introspection/mod.rs | 5 +++-- juniper/src/macros/mod.rs | 2 +- juniper/src/macros/tests/interface.rs | 16 ++++++++-------- juniper/src/tests/fixtures/starwars/mod.rs | 1 - juniper/src/tests/query_tests.rs | 1 - juniper/src/tests/schema_introspection.rs | 1 - 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 8c1b3b6c2..eeb4e144d 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -7,10 +7,11 @@ use self::input_object::{NamedPublic, NamedPublicWithDescription}; use crate::{ executor::Variables, + graphql_interface, graphql_object, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, Value}, - GraphQLEnum, graphql_object, graphql_interface, + GraphQLEnum, }; #[derive(GraphQLEnum)] @@ -49,7 +50,7 @@ trait Interface { struct Root; /// The root query object in the schema -#[graphql_object(interfaces = InterfaceValue, scalar = DefaultScalarValue)] +#[graphql_object(interfaces = InterfaceValue)] impl Root { fn sample_enum() -> Sample { Sample::One diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index ec8e3b702..b38958dae 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,4 +1,4 @@ -//! Helper traits for macros. +//! Helper definitions for macros. pub mod helper; diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index f02874d50..fffeded65 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -98,16 +98,16 @@ where F: Fn(&Object, &Vec>) -> (), { let doc = r#" - query ($typeName: String!) { - __type(name: $typeName) { + query ($typeName: String!) { + __type(name: $typeName) { + name + description + fields(includeDeprecated: true) { name - description - fields(includeDeprecated: true) { - name - } } - }"#; - + } + } + "#; let schema = RootNode::new( Root {}, EmptyMutation::<()>::new(), diff --git a/juniper/src/tests/fixtures/starwars/mod.rs b/juniper/src/tests/fixtures/starwars/mod.rs index 9ae9b47fd..7207bd96a 100644 --- a/juniper/src/tests/fixtures/starwars/mod.rs +++ b/juniper/src/tests/fixtures/starwars/mod.rs @@ -1,4 +1,3 @@ - pub mod schema; #[cfg(feature = "schema-language")] pub mod schema_language; diff --git a/juniper/src/tests/query_tests.rs b/juniper/src/tests/query_tests.rs index b0ed712a9..d671c5bf6 100644 --- a/juniper/src/tests/query_tests.rs +++ b/juniper/src/tests/query_tests.rs @@ -7,7 +7,6 @@ use crate::{ value::Value, }; - #[tokio::test] async fn test_hero_name() { let doc = r#" diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index d7f340256..4303c6762 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -3,7 +3,6 @@ use crate::value::{ Value::{self, Null}, }; - // Sort a nested schema Value. // In particular, lists are sorted by the "name" key of children, if present. // Only needed for comparisons. From 2112a83679862c75278766f8340eae6cfeecf8a5 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 25 Sep 2020 19:52:28 +0200 Subject: [PATCH 61/79] Remove redundant stuf --- .../src/codegen/interface_attr.rs | 76 +++++++++---------- juniper_codegen/src/result.rs | 5 +- juniper_codegen/src/util/mod.rs | 60 +-------------- 3 files changed, 42 insertions(+), 99 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 5fbebe95c..b03fd20cc 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,9 +1,9 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface, graphql_object, graphql_value, sa, AsDynGraphQLValue, - DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, Variables, + execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, + EmptySubscription, Executor, FieldError, FieldResult, GraphQLObject, GraphQLType, + IntoFieldError, RootNode, ScalarValue, Variables, }; fn schema<'q, C, S, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> @@ -32,7 +32,7 @@ mod trivial { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -53,7 +53,7 @@ mod trivial { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -380,8 +380,6 @@ mod explicit_alias { home_planet: String, } - sa::assert_not_impl_all!(Human: AsDynGraphQLValue); - #[graphql_interface] impl Character for Human { fn id(&self) -> &str { @@ -396,8 +394,6 @@ mod explicit_alias { primary_function: String, } - sa::assert_not_impl_all!(Droid: AsDynGraphQLValue); - #[graphql_interface] impl Character for Droid { fn id(&self) -> &str { @@ -558,7 +554,7 @@ mod trivial_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -579,7 +575,7 @@ mod trivial_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -913,7 +909,7 @@ mod explicit_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -938,7 +934,7 @@ mod explicit_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -1171,7 +1167,7 @@ mod fallible_field { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -1192,7 +1188,7 @@ mod fallible_field { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -1437,7 +1433,7 @@ mod generic { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero])] struct Human { id: String, home_planet: String, @@ -1458,7 +1454,7 @@ mod generic { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, dyn Hero])] + #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, @@ -1681,7 +1677,7 @@ mod generic_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero])] struct Human { id: String, home_planet: String, @@ -1702,7 +1698,7 @@ mod generic_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, dyn Hero])] + #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, @@ -1925,7 +1921,7 @@ mod generic_lifetime_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<'_, ()>, dyn Hero<'_, ()>])] + #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] struct Human { id: String, home_planet: String, @@ -1946,7 +1942,7 @@ mod generic_lifetime_async { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<'_, ()>, dyn Hero<'_, ()>])] + #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] struct Droid { id: String, primary_function: String, @@ -2169,7 +2165,7 @@ mod argument { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -2834,7 +2830,7 @@ mod explicit_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], scalar = DefaultScalarValue)] + #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] struct Human { id: String, home_planet: String, @@ -2855,7 +2851,7 @@ mod explicit_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], scalar = DefaultScalarValue)] + #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, @@ -3060,7 +3056,7 @@ mod custom_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], scalar = MyScalarValue)] + #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] struct Human { id: String, home_planet: String, @@ -3081,7 +3077,7 @@ mod custom_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], scalar = MyScalarValue)] + #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, @@ -3283,7 +3279,7 @@ mod explicit_generic_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -3304,7 +3300,7 @@ mod explicit_generic_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -3519,7 +3515,7 @@ mod explicit_custom_context { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, @@ -3556,7 +3552,7 @@ mod explicit_custom_context { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, @@ -3781,7 +3777,7 @@ mod inferred_custom_context_from_field { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, @@ -3810,7 +3806,7 @@ mod inferred_custom_context_from_field { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = CustomContext)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, @@ -4033,7 +4029,7 @@ mod inferred_custom_context_from_downcast { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Human { id: String, home_planet: String, @@ -4062,7 +4058,7 @@ mod inferred_custom_context_from_downcast { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Droid { id: String, primary_function: String, @@ -4314,7 +4310,7 @@ mod executor { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -4341,7 +4337,7 @@ mod executor { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, dyn Hero])] + #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -4651,7 +4647,7 @@ mod downcast_method { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Human { id: String, home_planet: String, @@ -4676,7 +4672,7 @@ mod downcast_method { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero])] + #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, @@ -4924,7 +4920,7 @@ mod external_downcast { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Human { id: String, home_planet: String, @@ -4945,7 +4941,7 @@ mod external_downcast { } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, dyn Hero], context = Database)] + #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Droid { id: String, primary_function: String, diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index 022541384..68c878ffa 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -11,7 +11,6 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; #[allow(unused_variables)] pub enum GraphQLScope { InterfaceAttr, - InterfaceDerive, UnionAttr, UnionDerive, DeriveObject, @@ -25,7 +24,7 @@ pub enum GraphQLScope { impl GraphQLScope { pub fn spec_section(&self) -> &str { match self { - Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", + Self::InterfaceAttr => "#sec-Interfaces", Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveObject | Self::ImplObject => "#sec-Objects", Self::DeriveInputObject => "#sec-Input-Objects", @@ -38,7 +37,7 @@ impl GraphQLScope { impl fmt::Display for GraphQLScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { - Self::InterfaceAttr | Self::InterfaceDerive => "interface", + Self::InterfaceAttr => "interface", Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveObject | Self::ImplObject => "object", Self::DeriveInputObject => "input object", diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 2c08db475..ad23a3ec3 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -18,7 +18,7 @@ use syn::{ token, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta, }; -use crate::common::parse::{ParseBufferExt as _, TypeExt as _}; +use crate::common::parse::ParseBufferExt as _; /// Returns the name of a type. /// If the type does not end in a simple ident, `None` is returned. @@ -831,35 +831,8 @@ impl GraphQLTypeDefiniton { .as_ref() .map(|description| quote!( .description(#description) )); - let mut has_dyn_interfaces = false; let interfaces = if !self.interfaces.is_empty() { - let interfaces_ty = self.interfaces.iter().map(|ty| { - let mut ty: syn::Type = ty.unparenthesized().clone(); - - if let syn::Type::TraitObject(dyn_ty) = &mut ty { - let mut dyn_ty = dyn_ty.clone(); - if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = - dyn_ty.bounds.first_mut().unwrap() - { - let trait_params = &mut path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); - } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { #scalar }); - a.args.push(parse_quote! { Context = Self::Context }); - a.args.push(parse_quote! { TypeInfo = Self::TypeInfo }); - } - } - dyn_ty.bounds.push(parse_quote! { Send }); - dyn_ty.bounds.push(parse_quote! { Sync }); - - has_dyn_interfaces = true; - ty = dyn_ty.into(); - } - - ty - }); + let interfaces_ty = &self.interfaces; Some(quote!( .interfaces(&[ @@ -977,7 +950,7 @@ impl GraphQLTypeDefiniton { .push(parse_quote!( #scalar: Send + Sync )); where_async.predicates.push(parse_quote!(Self: Sync)); - let as_dyn_value = if has_dyn_interfaces { + let as_dyn_value = if !self.interfaces.is_empty() { Some(quote! { #[automatically_derived] impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens @@ -1218,32 +1191,7 @@ impl GraphQLTypeDefiniton { .map(|description| quote!( .description(#description) )); let interfaces = if !self.interfaces.is_empty() { - let interfaces_ty = self.interfaces.iter().map(|ty| { - let mut ty: syn::Type = ty.unparenthesized().clone(); - - if let syn::Type::TraitObject(dyn_ty) = &mut ty { - let mut dyn_ty = dyn_ty.clone(); - if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = - dyn_ty.bounds.first_mut().unwrap() - { - let trait_params = &mut path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); - } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { #scalar }); - a.args.push(parse_quote! { Context = Self::Context }); - a.args.push(parse_quote! { TypeInfo = Self::TypeInfo }); - } - } - dyn_ty.bounds.push(parse_quote! { Send }); - dyn_ty.bounds.push(parse_quote! { Sync }); - - ty = dyn_ty.into(); - } - - ty - }); + let interfaces_ty = &self.interfaces; Some(quote!( .interfaces(&[ From a83202e2e3884eafb4ef1cd7b0745caa9e158235 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 25 Sep 2020 20:32:18 +0200 Subject: [PATCH 62/79] Polishing 'juniper_codegen', vol.1 --- juniper_codegen/src/common/mod.rs | 6 +-- juniper_codegen/src/common/parse/attr.rs | 37 +++++++++++++++++++ .../src/common/parse/downcaster.rs | 2 +- juniper_codegen/src/common/parse/mod.rs | 4 +- juniper_codegen/src/derive_scalar_value.rs | 2 +- juniper_codegen/src/graphql_interface/attr.rs | 19 ++++++---- juniper_codegen/src/graphql_union/attr.rs | 6 +-- juniper_codegen/src/graphql_union/derive.rs | 4 +- juniper_codegen/src/lib.rs | 2 +- juniper_codegen/src/util/mod.rs | 32 ---------------- 10 files changed, 60 insertions(+), 54 deletions(-) diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 82706b93f..e1f8330b1 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,9 +1,9 @@ -pub(crate) mod parse; pub(crate) mod gen; +pub(crate) mod parse; use proc_macro2::{Span, TokenStream}; -use syn::parse_quote; use quote::ToTokens; +use syn::parse_quote; pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { use syn::{GenericArgument as GA, Type as T}; @@ -135,4 +135,4 @@ impl ToTokens for ScalarValueType { fn to_tokens(&self, into: &mut TokenStream) { self.ty().to_tokens(into) } -} \ No newline at end of file +} diff --git a/juniper_codegen/src/common/parse/attr.rs b/juniper_codegen/src/common/parse/attr.rs index 8d39a885f..03e410a1f 100644 --- a/juniper_codegen/src/common/parse/attr.rs +++ b/juniper_codegen/src/common/parse/attr.rs @@ -1,3 +1,40 @@ +use proc_macro2::{Span, TokenStream}; +use syn::parse_quote; + +use crate::util::path_eq_single; + +/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given +/// `attr_path` and `attr_args`. +/// +/// This function is generally used for uniting `proc_macro_attribute` with its body attributes. +pub(crate) fn unite( + (attr_path, attr_args): (&str, &TokenStream), + attrs: &Vec, +) -> Vec { + let mut full_attrs = Vec::with_capacity(attrs.len() + 1); + let attr_path = syn::Ident::new(attr_path, Span::call_site()); + full_attrs.push(parse_quote! { #[#attr_path(#attr_args)] }); + full_attrs.extend_from_slice(attrs); + full_attrs +} + +/// Strips all `attr_path` attributes from the given `attrs` collection. +/// +/// This function is generally used for removing duplicate attributes during `proc_macro_attribute` +/// expansion, so avoid unnecessary expansion duplication. +pub(crate) fn strip(attr_path: &str, attrs: Vec) -> Vec { + attrs + .into_iter() + .filter_map(|attr| { + if path_eq_single(&attr.path, attr_path) { + None + } else { + Some(attr) + } + }) + .collect() +} + pub(crate) mod err { use proc_macro2::Span; use syn::spanned::Spanned; diff --git a/juniper_codegen/src/common/parse/downcaster.rs b/juniper_codegen/src/common/parse/downcaster.rs index 9882a7f9a..c790959cc 100644 --- a/juniper_codegen/src/common/parse/downcaster.rs +++ b/juniper_codegen/src/common/parse/downcaster.rs @@ -85,4 +85,4 @@ pub(crate) fn context_ty(sig: &syn::Signature) -> Result, Span } ty => Err(ty.span()), } -} \ No newline at end of file +} diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 1fc916a83..16f4639dc 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -2,17 +2,17 @@ pub(crate) mod attr; pub(crate) mod downcaster; use std::{ - mem, any::TypeId, iter::{self, FromIterator as _}, + mem, }; use syn::{ ext::IdentExt as _, parse::{Parse, ParseBuffer}, + parse_quote, punctuated::Punctuated, token::{self, Token}, - parse_quote, }; pub(crate) trait ParseBufferExt { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 1c4f2f326..b725cfa14 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -1,6 +1,6 @@ use crate::{ - result::GraphQLScope, common::parse::ParseBufferExt as _, + result::GraphQLScope, util::{self, span_container::SpanContainer}, }; use proc_macro2::TokenStream; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 93e495916..c8f4c666a 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -13,9 +13,7 @@ use crate::{ ScalarValueType, }, result::GraphQLScope, - util::{ - path_eq_single, span_container::SpanContainer, strip_attrs, to_camel_case, unite_attrs, - }, + util::{path_eq_single, span_container::SpanContainer, to_camel_case}, }; use super::{ @@ -31,13 +29,13 @@ const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body.clone()) { - let trait_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = strip_attrs("graphql_interface", ast.attrs); + let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); return expand_on_trait(trait_attrs, ast); } else if let Ok(mut ast) = syn::parse2::(body) { if ast.trait_.is_some() { - let impl_attrs = unite_attrs(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = strip_attrs("graphql_interface", ast.attrs); + let impl_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); return expand_on_impl(impl_attrs, ast); } } @@ -182,7 +180,12 @@ pub fn expand_on_trait( .is_some(); let ty = if is_trait_object { - Type::TraitObject(TraitObjectType::new(&ast, &meta, scalar.clone(), context.clone())) + Type::TraitObject(TraitObjectType::new( + &ast, + &meta, + scalar.clone(), + context.clone(), + )) } else { Type::Enum(EnumType::new(&ast, &meta, &implementers, scalar.clone())) }; diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index 02afeaadd..c7b29ed24 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -9,7 +9,7 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ common::parse, result::GraphQLScope, - util::{path_eq_single, span_container::SpanContainer, strip_attrs, unite_attrs}, + util::{path_eq_single, span_container::SpanContainer}, }; use super::{ @@ -28,8 +28,8 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result Option { .map(|segment| segment.ident.clone()) } -/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given -/// `attr_path` and `attr_args`. -/// -/// This function is generally used for uniting `proc_macro_attribute` with its body attributes. -pub fn unite_attrs( - (attr_path, attr_args): (&str, &TokenStream), - attrs: &Vec, -) -> Vec { - let mut full_attrs = Vec::with_capacity(attrs.len() + 1); - let attr_path = syn::Ident::new(attr_path, Span::call_site()); - full_attrs.push(parse_quote! { #[#attr_path(#attr_args)] }); - full_attrs.extend_from_slice(attrs); - full_attrs -} - -/// Strips all `attr_path` attributes from the given `attrs` collection. -/// -/// This function is generally used for removing duplicate attributes during `proc_macro_attribute` -/// expansion, so avoid unnecessary expansion duplication. -pub fn strip_attrs(attr_path: &str, attrs: Vec) -> Vec { - attrs - .into_iter() - .filter_map(|attr| { - if path_eq_single(&attr.path, attr_path) { - None - } else { - Some(attr) - } - }) - .collect() -} - /// Compares a path to a one-segment string value, /// return true if equal. pub fn path_eq_single(path: &syn::Path, value: &str) -> bool { From 084619c7473ee0e69d9eb05564b37a37fe14a948 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 28 Sep 2020 13:35:19 +0200 Subject: [PATCH 63/79] Polishing 'juniper_codegen', vol.2 --- juniper_codegen/src/common/mod.rs | 80 +--------------- juniper_codegen/src/common/parse/attr.rs | 8 +- .../src/common/parse/downcaster.rs | 6 ++ juniper_codegen/src/common/parse/mod.rs | 94 ++++++++++++++++++- juniper_codegen/src/graphql_interface/attr.rs | 3 +- 5 files changed, 110 insertions(+), 81 deletions(-) diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index e1f8330b1..b580987af 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,85 +1,13 @@ +//! Common functions, definitions and extensions for code generation, used by this crate. + pub(crate) mod gen; pub(crate) mod parse; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::ToTokens; use syn::parse_quote; -pub(crate) fn anonymize_lifetimes(ty: &mut syn::Type) { - use syn::{GenericArgument as GA, Type as T}; - - match ty { - T::Array(syn::TypeArray { elem, .. }) - | T::Group(syn::TypeGroup { elem, .. }) - | T::Paren(syn::TypeParen { elem, .. }) - | T::Ptr(syn::TypePtr { elem, .. }) - | T::Slice(syn::TypeSlice { elem, .. }) => anonymize_lifetimes(&mut *elem), - - T::Tuple(syn::TypeTuple { elems, .. }) => { - for ty in elems.iter_mut() { - anonymize_lifetimes(ty); - } - } - - T::ImplTrait(syn::TypeImplTrait { bounds, .. }) - | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { - for bound in bounds.iter_mut() { - match bound { - syn::TypeParamBound::Lifetime(lt) => { - lt.ident = syn::Ident::new("_", Span::call_site()) - } - syn::TypeParamBound::Trait(_) => { - todo!("Anonymizing lifetimes in trait is not yet supported") - } - } - } - } - - T::Reference(ref_ty) => { - if let Some(lt) = ref_ty.lifetime.as_mut() { - lt.ident = syn::Ident::new("_", Span::call_site()); - } - anonymize_lifetimes(&mut *ref_ty.elem); - } - - T::Path(ty) => { - for seg in ty.path.segments.iter_mut() { - match &mut seg.arguments { - syn::PathArguments::AngleBracketed(angle) => { - for arg in angle.args.iter_mut() { - match arg { - GA::Lifetime(lt) => { - lt.ident = syn::Ident::new("_", Span::call_site()); - } - GA::Type(ty) => anonymize_lifetimes(ty), - GA::Binding(b) => anonymize_lifetimes(&mut b.ty), - GA::Constraint(_) | GA::Const(_) => {} - } - } - } - syn::PathArguments::Parenthesized(args) => { - for ty in args.inputs.iter_mut() { - anonymize_lifetimes(ty); - } - if let syn::ReturnType::Type(_, ty) = &mut args.output { - anonymize_lifetimes(&mut *ty); - } - } - syn::PathArguments::None => {} - } - } - } - - // These types unlikely will be used as GraphQL types. - T::BareFn(_) - | T::Infer(_) - | T::Macro(_) - | T::Never(_) - | T::Verbatim(_) - | T::__Nonexhaustive => {} - } -} - +/// #[derive(Clone, Debug)] pub(crate) enum ScalarValueType { Concrete(syn::Type), diff --git a/juniper_codegen/src/common/parse/attr.rs b/juniper_codegen/src/common/parse/attr.rs index 03e410a1f..4e03b833f 100644 --- a/juniper_codegen/src/common/parse/attr.rs +++ b/juniper_codegen/src/common/parse/attr.rs @@ -1,3 +1,6 @@ +//! Common functions, definitions and extensions for parsing and modifying Rust attributes, used by +//! this crate. + use proc_macro2::{Span, TokenStream}; use syn::parse_quote; @@ -35,6 +38,7 @@ pub(crate) fn strip(attr_path: &str, attrs: Vec) -> Vec Span; } @@ -75,7 +81,7 @@ pub(crate) mod err { } } -/// Handy extension of [`Option`] methods used in this crate. +/// Handy extension of [`Option`] methods, used in this crate. pub(crate) trait OptionExt { type Inner; diff --git a/juniper_codegen/src/common/parse/downcaster.rs b/juniper_codegen/src/common/parse/downcaster.rs index c790959cc..89dd67cdc 100644 --- a/juniper_codegen/src/common/parse/downcaster.rs +++ b/juniper_codegen/src/common/parse/downcaster.rs @@ -1,3 +1,9 @@ +//! Common functions, definitions and extensions for parsing downcasting functions, used by GraphQL +//! [interfaces][1] and [unions][2] definitions to downcast its type to a concrete implementer type. +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces +//! [2]: https://spec.graphql.org/June2018/#sec-Unions + use proc_macro2::Span; use syn::{ext::IdentExt as _, spanned::Spanned as _}; diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 16f4639dc..fce5f0cf2 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -1,3 +1,6 @@ +//! Common functions, definitions and extensions for parsing, normalizing and modifying Rust syntax, +//! used by this crate. + pub(crate) mod attr; pub(crate) mod downcaster; @@ -7,6 +10,7 @@ use std::{ mem, }; +use proc_macro2::Span; use syn::{ ext::IdentExt as _, parse::{Parse, ParseBuffer}, @@ -15,6 +19,7 @@ use syn::{ token::{self, Token}, }; +/// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing. pub(crate) trait ParseBufferExt { /// Tries to parse `T` as the next token. /// @@ -24,6 +29,7 @@ pub(crate) trait ParseBufferExt { /// Checks whether next token is `T`. /// /// Doesn't move [`ParseStream`]'s cursor. + #[must_use] fn is_next(&self) -> bool; /// Parses next token as [`syn::Ident`] _allowing_ Rust keywords, while default [`Parse`] @@ -75,7 +81,7 @@ impl<'a> ParseBufferExt for ParseBuffer<'a> { } else if TypeId::of::() == TypeId::of::() { let _ = syn::parenthesized!(inner in self); } else { - panic!( + unimplemented!( "ParseBufferExt::parse_maybe_wrapped_and_punctuated supports only brackets, \ braces and parentheses as wrappers.", ); @@ -87,16 +93,21 @@ impl<'a> ParseBufferExt for ParseBuffer<'a> { } } +/// Extension of [`syn::Type`] providing common function widely used by this crate for parsing. pub(crate) trait TypeExt { /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested /// [`syn::TypeParen`]s asap). + #[must_use] fn unparenthesized(&self) -> &Self; /// Retrieves the inner [`syn::Type`] from the given reference type, or just returns "as is" if /// the type is not a reference. /// - /// Also, unparenthesizes the type, if required. + /// Also, makes the type [`TypeExt::unparenthesized`], if possible. + #[must_use] fn unreferenced(&self) -> &Self; + + fn lifetimes_anonymized(&mut self); } impl TypeExt for syn::Type { @@ -113,11 +124,90 @@ impl TypeExt for syn::Type { ty => ty, } } + + fn lifetimes_anonymized(&mut self) { + use syn::{GenericArgument as GA, Type as T}; + + match self { + T::Array(syn::TypeArray { elem, .. }) + | T::Group(syn::TypeGroup { elem, .. }) + | T::Paren(syn::TypeParen { elem, .. }) + | T::Ptr(syn::TypePtr { elem, .. }) + | T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_anonymized(), + + T::Tuple(syn::TypeTuple { elems, .. }) => { + for ty in elems.iter_mut() { + ty.lifetimes_anonymized(); + } + } + + T::ImplTrait(syn::TypeImplTrait { bounds, .. }) + | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { + for bound in bounds.iter_mut() { + match bound { + syn::TypeParamBound::Lifetime(lt) => { + lt.ident = syn::Ident::new("_", Span::call_site()) + } + syn::TypeParamBound::Trait(_) => { + todo!("Anonymizing lifetimes in trait is not yet supported") + } + } + } + } + + T::Reference(ref_ty) => { + if let Some(lt) = ref_ty.lifetime.as_mut() { + lt.ident = syn::Ident::new("_", Span::call_site()); + } + (&mut *ref_ty.elem).lifetimes_anonymized(); + } + + T::Path(ty) => { + for seg in ty.path.segments.iter_mut() { + match &mut seg.arguments { + syn::PathArguments::AngleBracketed(angle) => { + for arg in angle.args.iter_mut() { + match arg { + GA::Lifetime(lt) => { + lt.ident = syn::Ident::new("_", Span::call_site()); + } + GA::Type(ty) => ty.lifetimes_anonymized(), + GA::Binding(b) => b.ty.lifetimes_anonymized(), + GA::Constraint(_) | GA::Const(_) => {} + } + } + } + syn::PathArguments::Parenthesized(args) => { + for ty in args.inputs.iter_mut() { + ty.lifetimes_anonymized(); + } + if let syn::ReturnType::Type(_, ty) = &mut args.output { + (&mut *ty).lifetimes_anonymized(); + } + } + syn::PathArguments::None => {} + } + } + } + + // These types unlikely will be used as GraphQL types. + T::BareFn(_) + | T::Infer(_) + | T::Macro(_) + | T::Never(_) + | T::Verbatim(_) + | T::__Nonexhaustive => {} + } + } } +/// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing. pub(crate) trait GenericsExt { + /// Removes all default types out of type parameters and const parameters in these + /// [`syn::Generics`]. fn remove_defaults(&mut self); + /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`]. fn move_bounds_to_where_clause(&mut self); } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c8f4c666a..c5bd6d03c 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -8,7 +8,6 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ common::{ - anonymize_lifetimes, parse::{self, TypeExt as _}, ScalarValueType, }, @@ -457,7 +456,7 @@ impl TraitMethod { syn::ReturnType::Default => parse_quote! { () }, syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), }; - anonymize_lifetimes(&mut ty); + ty.lifetimes_anonymized(); let description = meta.description.as_ref().map(|d| d.as_ref().value()); let deprecated = meta From ab52be5379cb9b39d3b1642e0712032d5a623a6e Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 28 Sep 2020 14:53:01 +0200 Subject: [PATCH 64/79] Polishing 'juniper_codegen', vol.3 --- juniper_codegen/src/common/gen.rs | 19 +++++++++++++++++++ juniper_codegen/src/common/mod.rs | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/juniper_codegen/src/common/gen.rs b/juniper_codegen/src/common/gen.rs index 4688ca5f8..dc5adc139 100644 --- a/juniper_codegen/src/common/gen.rs +++ b/juniper_codegen/src/common/gen.rs @@ -1,6 +1,14 @@ +//! Common code generated parts, used by this crate. + use proc_macro2::TokenStream; use quote::quote; +/// Generate the code resolving some [GraphQL type][1] in a synchronous manner. +/// +/// Value of a [GraphQL type][1] should be stored in a `res` binding in the generated code, before +/// including this piece of code. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Types pub(crate) fn sync_resolving_code() -> TokenStream { quote! { ::juniper::IntoResolvable::into(res, executor.context()) @@ -11,6 +19,17 @@ pub(crate) fn sync_resolving_code() -> TokenStream { } } +/// Generate the code resolving some [GraphQL type][1] in an asynchronous manner. +/// +/// Value of a [GraphQL type][1] should be resolvable with `fut` binding representing a [`Future`] +/// in the generated code, before including this piece of code. +/// +/// Optional `ty` argument may be used to annotate a concrete type of the resolving +/// [GraphQL type][1] (the [`Future::Output`]). +/// +/// [`Future`]: std::future::Future +/// [`Future::Output`]: std::future::Future::Output +/// [1]: https://spec.graphql.org/June2018/#sec-Types pub(crate) fn async_resolving_code(ty: Option<&syn::Type>) -> TokenStream { let ty = ty.map(|t| quote! { : #t }); diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index b580987af..88498c1e8 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -7,30 +7,49 @@ use proc_macro2::TokenStream; use quote::ToTokens; use syn::parse_quote; +/// [`ScalarValue`] parametrization of the code generation. /// +/// [`ScalarValue`]: juniper::ScalarValue #[derive(Clone, Debug)] pub(crate) enum ScalarValueType { + /// Concrete Rust type is specified as [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue Concrete(syn::Type), + + /// One of type parameters of the original type is specified as [`ScalarValue`]. + /// + /// The original type is the type that the code is generated for. + /// + /// [`ScalarValue`]: juniper::ScalarValue ExplicitGeneric(syn::Ident), + + /// [`ScalarValue`] parametrization is assumed to be a generic and is not specified explicitly. + /// + /// [`ScalarValue`]: juniper::ScalarValue ImplicitGeneric, } impl ScalarValueType { + /// Indicates whether this [`ScalarValueType`] is generic. #[must_use] pub(crate) fn is_generic(&self) -> bool { matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric) } + /// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ExplicitGeneric`]. #[must_use] pub(crate) fn is_explicit_generic(&self) -> bool { matches!(self, Self::ExplicitGeneric(_)) } + /// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ImplicitGeneric`]. #[must_use] pub(crate) fn is_implicit_generic(&self) -> bool { matches!(self, Self::ImplicitGeneric) } + /// Returns a type identifier which represents this [`ScalarValueType`]. #[must_use] pub(crate) fn ty(&self) -> syn::Type { match self { @@ -40,6 +59,7 @@ impl ScalarValueType { } } + /// Returns a type parameter identifier that suits this [`ScalarValueType`]. #[must_use] pub(crate) fn generic_ty(&self) -> syn::Type { match self { @@ -48,6 +68,9 @@ impl ScalarValueType { } } + /// Returns a default [`ScalarValue`] type that is compatible with this [`ScalarValueType`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue #[must_use] pub(crate) fn default_ty(&self) -> syn::Type { match self { From d703926b4c18d7c4c1e0363e199d610b1fc16997 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 28 Sep 2020 16:30:39 +0200 Subject: [PATCH 65/79] Polishing 'juniper_codegen', vol.4 --- integration_tests/juniper_tests/Cargo.toml | 1 + juniper_codegen/src/graphql_interface/attr.rs | 15 +- juniper_codegen/src/graphql_interface/mod.rs | 205 +++++++++++++----- 3 files changed, 153 insertions(+), 68 deletions(-) diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml index 893571478..7fedd6d81 100644 --- a/integration_tests/juniper_tests/Cargo.toml +++ b/integration_tests/juniper_tests/Cargo.toml @@ -10,6 +10,7 @@ futures = "0.3.1" juniper = { path = "../../juniper" } [dev-dependencies] +async-trait = "0.1.39" serde_json = { version = "1" } fnv = "1.0.3" tokio = { version = "0.2", features = ["rt-core", "time", "macros"] } \ No newline at end of file diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c5bd6d03c..863124b94 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -16,10 +16,9 @@ use crate::{ }; use super::{ - inject_async_trait, ArgumentMeta, Definition, EnumType, ImplementerDefinition, - ImplementerDowncastDefinition, ImplementerMeta, InterfaceFieldArgumentDefinition, - InterfaceFieldDefinition, InterfaceMeta, MethodArgument, TraitMethodMeta, TraitObjectType, - Type, + inject_async_trait, ArgumentMeta, Definition, EnumType, ImplMeta, ImplementerDefinition, + ImplementerDowncastDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, + MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -51,7 +50,7 @@ pub fn expand_on_trait( attrs: Vec, mut ast: syn::ItemTrait, ) -> syn::Result { - let meta = InterfaceMeta::from_attrs("graphql_interface", &attrs)?; + let meta = TraitMeta::from_attrs("graphql_interface", &attrs)?; let trait_ident = &ast.ident; @@ -253,7 +252,7 @@ pub fn expand_on_impl( attrs: Vec, mut ast: syn::ItemImpl, ) -> syn::Result { - let meta = ImplementerMeta::from_attrs("graphql_interface", &attrs)?; + let meta = ImplMeta::from_attrs("graphql_interface", &attrs)?; let is_async_trait = meta.asyncness.is_some() || ast @@ -348,7 +347,7 @@ impl TraitMethod { .filter(|attr| !path_eq_single(&attr.path, "graphql_interface")) .collect(); - let meta = TraitMethodMeta::from_attrs("graphql_interface", &method_attrs) + let meta = MethodMeta::from_attrs("graphql_interface", &method_attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; @@ -405,7 +404,7 @@ impl TraitMethod { fn parse_field( method: &mut syn::TraitItemMethod, - meta: TraitMethodMeta, + meta: MethodMeta, ) -> Option { let method_ident = &method.sig.ident; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 4837c1e28..06a62eb86 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -27,21 +27,18 @@ use crate::{ util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, }; -/// Helper alias for the type of [`InterfaceMeta::external_downcasts`] field. -type InterfaceMetaDowncasts = HashMap>; - -/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_interface]`) attribute placed -/// on a trait definition, when generating code for [GraphQL interface][1] type. +/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait +/// definition, when generating code for [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] -struct InterfaceMeta { +struct TraitMeta { /// Explicitly specified name of [GraphQL interface][1] type. /// - /// If absent, then Rust type name is used by default. + /// If absent, then Rust trait name is used by default. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub name: Option>, + name: Option>, /// Explicitly specified [description][2] of [GraphQL interface][1] type. /// @@ -49,57 +46,82 @@ struct InterfaceMeta { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub description: Option>, + description: Option>, - pub r#enum: Option>, + /// Explicitly specified identifier of the enum Rust type behind the trait, being an actual + /// implementation of a [GraphQL interface][1] type. + /// + /// If absent, then `{trait_name}Value` identifier will be used. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + r#enum: Option>, - pub r#dyn: Option>, + /// Explicitly specified identifier of the Rust type alias of the [trait object][2], being an + /// actual implementation of a [GraphQL interface][1] type. + /// + /// Effectively makes code generation to use a [trait object][2] as a [GraphQL interface][1] + /// type rather than an enum. If absent, then enum is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html + r#dyn: Option>, /// Explicitly specified Rust types of [GraphQL objects][2] implementing this /// [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - pub implementers: HashSet>, + implementers: HashSet>, - /// Explicitly specified type of `juniper::Context` to use for resolving this - /// [GraphQL interface][1] type with. + /// Explicitly specified type of [`Context`] to use for resolving this [GraphQL interface][1] + /// type with. /// - /// If absent, then unit type `()` is assumed as type of `juniper::Context`. + /// If absent, then unit type `()` is assumed as type of [`Context`]. /// + /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub context: Option>, + context: Option>, - /// Explicitly specified type of `juniper::ScalarValue` to use for resolving this + /// Explicitly specified type of [`ScalarValue`] to use for resolving this /// [GraphQL interface][1] type with. /// - /// If absent, then generated code will be generic over any `juniper::ScalarValue` type, which, - /// in turn, requires all [interface][1] implementers to be generic over any - /// `juniper::ScalarValue` type too. That's why this type should be specified only if one of the - /// implementers implements `juniper::GraphQLType` in a non-generic way over - /// `juniper::ScalarValue` type. + /// If absent, then generated code will be generic over any [`ScalarValue`] type, which, in + /// turn, requires all [interface][1] implementers to be generic over any [`ScalarValue`] type + /// too. That's why this type should be specified only if one of the implementers implements + /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub scalar: Option>, + scalar: Option>, - pub asyncness: Option>, + /// Explicitly specified marker indicating that the Rust trait should be transformed into + /// [`async_trait`]. + /// + /// If absent, then trait will be transformed into [`async_trait`] only if it contains async + /// methods. + asyncness: Option>, /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers. /// - /// If absent, then macro will try to auto-infer all the possible variants from the type - /// declaration, if possible. That's why specifying an external resolver function has sense, - /// when some custom [union][1] variant resolving logic is involved, or variants cannot be - /// inferred. + /// If absent, then macro will downcast to the implementers via enum dispatch or dynamic + /// dispatch (if the one is chosen). That's why specifying an external resolver function has + /// sense, when some custom [interface][1] implementer resolving logic is involved. + /// + /// Once the downcasting function is specified for some [GraphQL object][2] implementer type, it + /// cannot be downcast another such function or trait method marked with a + /// [`MethodMeta::downcast`] marker. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub external_downcasts: InterfaceMetaDowncasts, + /// [2]: https://spec.graphql.org/June2018/#sec-Objects + external_downcasts: HashMap>, - /// Indicator whether the generated code is intended to be used only inside the `juniper` + /// Indicator whether the generated code is intended to be used only inside the [`juniper`] /// library. - pub is_internal: bool, + is_internal: bool, } -impl Parse for InterfaceMeta { +impl Parse for TraitMeta { fn parse(input: ParseStream) -> syn::Result { let mut output = Self::default(); @@ -206,8 +228,8 @@ impl Parse for InterfaceMeta { } } -impl InterfaceMeta { - /// Tries to merge two [`InterfaceMeta`]s into a single one, reporting about duplicates, if any. +impl TraitMeta { + /// Tries to merge two [`TraitMeta`]s into a single one, reporting about duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), @@ -225,9 +247,9 @@ impl InterfaceMeta { }) } - /// Parses [`InterfaceMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a - /// trait definition. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + /// Parses [`TraitMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait + /// definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut meta = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; @@ -254,13 +276,39 @@ impl InterfaceMeta { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] -struct ImplementerMeta { - pub scalar: Option>, - pub asyncness: Option>, - pub r#dyn: Option>, +struct ImplMeta { + /// Explicitly specified type of [`ScalarValue`] to use for implementing the + /// [GraphQL interface][1] type. + /// + /// If absent, then generated code will be generic over any [`ScalarValue`] type, which, in + /// turn, requires all [interface][1] implementers to be generic over any [`ScalarValue`] type + /// too. That's why this type should be specified only if the implementer itself implements + /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + scalar: Option>, + + /// Explicitly specified marker indicating that the trait implementation block should be + /// transformed with applying [`async_trait`]. + /// + /// If absent, then trait will be transformed with applying [`async_trait`] only if it contains + /// async methods. + /// + /// This marker is especially useful when Rust trait contains async default methods, while the + /// implementation block doesn't. + asyncness: Option>, + + /// Explicitly specified marker indicating that the implemented [GraphQL interface][1] type is + /// represented as a [trait object][2] in Rust type system rather then an enum (default mode, + /// when the marker is absent). + /// + /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html + r#dyn: Option>, } -impl Parse for ImplementerMeta { +impl Parse for ImplMeta { fn parse(input: ParseStream) -> syn::Result { let mut output = Self::default(); @@ -300,9 +348,8 @@ impl Parse for ImplementerMeta { } } -impl ImplementerMeta { - /// Tries to merge two [`ImplementerMeta`]s into a single one, reporting about duplicates, if - /// any. +impl ImplMeta { + /// Tries to merge two [`ImplMeta`]s into a single one, reporting about duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { scalar: try_merge_opt!(scalar: self, another), @@ -311,8 +358,8 @@ impl ImplementerMeta { }) } - /// Parses [`ImplementerMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a - /// trait implementation block. + /// Parses [`ImplMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait + /// implementation block. pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { filter_attrs(name, attrs) .map(|attr| attr.parse_args()) @@ -320,16 +367,54 @@ impl ImplementerMeta { } } +/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait method +/// definition, when generating code for [GraphQL interface][1] type. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] -struct TraitMethodMeta { - pub name: Option>, - pub description: Option>, - pub deprecated: Option>>, - pub ignore: Option>, - pub downcast: Option>, +struct MethodMeta { + /// Explicitly specified name of a [GraphQL field][1] represented by this trait method. + /// + /// If absent, then `camelCased` Rust method name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL field][1]. + /// + /// If absent, then Rust doc comment is used as the [description][2], if any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + description: Option>, + + /// Explicitly specified [deprecation][2] of this [GraphQL field][1]. + /// + /// If absent, then Rust `#[deprecated]` attribute is used as the [deprecation][2], if any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation + deprecated: Option>>, + + /// Explicitly specified marker indicating that this trait method should be omitted by code + /// generation and not considered in the [GraphQL interface][1] type definition. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + ignore: Option>, + + /// Explicitly specified marker indicating that this trait method doesn't represent a + /// [GraphQL field][1], but is a downcasting function into the [GraphQL object][2] implementer + /// type returned by this trait method. + /// + /// Once this marker is specified, the [GraphQL object][2] implementer type cannot be downcast + /// via another trait method or [`TraitMeta::external_downcasts`] function. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Objects + downcast: Option>, } -impl Parse for TraitMethodMeta { +impl Parse for MethodMeta { fn parse(input: ParseStream) -> syn::Result { let mut output = Self::default(); @@ -386,8 +471,8 @@ impl Parse for TraitMethodMeta { } } -impl TraitMethodMeta { - /// Tries to merge two [`FieldMeta`]s into a single one, reporting about duplicates, if any. +impl MethodMeta { + /// Tries to merge two [`MethodMeta`]s into a single one, reporting about duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), @@ -398,8 +483,8 @@ impl TraitMethodMeta { }) } - /// Parses [`FieldMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a - /// function/method definition. + /// Parses [`MethodMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a + /// method definition. pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut meta = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) @@ -1231,7 +1316,7 @@ struct EnumType { impl EnumType { fn new( r#trait: &syn::ItemTrait, - meta: &InterfaceMeta, + meta: &TraitMeta, implers: &Vec, scalar: ScalarValueType, ) -> Self { @@ -1588,7 +1673,7 @@ struct TraitObjectType { impl TraitObjectType { fn new( r#trait: &syn::ItemTrait, - meta: &InterfaceMeta, + meta: &TraitMeta, scalar: ScalarValueType, context: Option, ) -> Self { From 85ee3bd1af88bb6319c56a41a7f8a2595044eca3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 29 Sep 2020 13:52:00 +0200 Subject: [PATCH 66/79] Polishing 'juniper_codegen', vol.5 --- juniper_codegen/src/graphql_interface/attr.rs | 17 +- juniper_codegen/src/graphql_interface/mod.rs | 228 +++++++++++++++--- 2 files changed, 197 insertions(+), 48 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 863124b94..45ac70bb1 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -16,9 +16,9 @@ use crate::{ }; use super::{ - inject_async_trait, ArgumentMeta, Definition, EnumType, ImplMeta, ImplementerDefinition, - ImplementerDowncastDefinition, InterfaceFieldArgumentDefinition, InterfaceFieldDefinition, - MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type, + inject_async_trait, ArgumentMeta, Definition, EnumType, Field, FieldArgument, ImplMeta, + ImplementerDefinition, ImplementerDowncastDefinition, MethodArgument, MethodMeta, TraitMeta, + TraitObjectType, Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -333,7 +333,7 @@ pub fn expand_on_impl( } enum TraitMethod { - Field(InterfaceFieldDefinition), + Field(Field), Downcast(ImplementerDefinition), } @@ -402,10 +402,7 @@ impl TraitMethod { }) } - fn parse_field( - method: &mut syn::TraitItemMethod, - meta: MethodMeta, - ) -> Option { + fn parse_field(method: &mut syn::TraitItemMethod, meta: MethodMeta) -> Option { let method_ident = &method.sig.ident; let name = meta @@ -463,7 +460,7 @@ impl TraitMethod { .as_ref() .map(|d| d.as_ref().as_ref().map(syn::LitStr::value)); - Some(InterfaceFieldDefinition { + Some(Field { name, ty, description, @@ -533,7 +530,7 @@ impl TraitMethod { return None; } - Some(MethodArgument::Regular(InterfaceFieldArgumentDefinition { + Some(MethodArgument::Regular(FieldArgument { name, ty: argument.ty.as_ref().clone(), description: meta.description.as_ref().map(|d| d.as_ref().value()), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 06a62eb86..3adf26b2c 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -534,13 +534,59 @@ impl MethodMeta { } } +/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait method +/// argument, when generating code for [GraphQL interface][1] type. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] struct ArgumentMeta { - pub name: Option>, - pub description: Option>, - pub default: Option>>, - pub context: Option>, - pub executor: Option>, + /// Explicitly specified name of a [GraphQL argument][1] represented by this method argument. + /// + /// If absent, then `camelCased` Rust argument name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL argument][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + description: Option>, + + /// Explicitly specified [default value][2] of this [GraphQL argument][1]. + /// + /// If the exact default expression is not specified, then the [`Default::default`] value is + /// used. + /// + /// If absent, then this [GraphQL argument][1] is considered as [required][2]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments + default: Option>>, + + /// Explicitly specified marker indicating that this method argument doesn't represent a + /// [GraphQL argument][1], but is a [`Context`] being injected into a [GraphQL field][2] + /// resolving function. + /// + /// If absent, then the method argument still is considered as [`Context`] if it's named + /// `context` or `ctx`. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + context: Option>, + + /// Explicitly specified marker indicating that this method argument doesn't represent a + /// [GraphQL argument][1], but is a [`Executor`] being injected into a [GraphQL field][2] + /// resolving function. + /// + /// If absent, then the method argument still is considered as [`Context`] if it's named + /// `executor`. + /// + /// [`Executor`]: juniper::Executor + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + executor: Option>, } impl Parse for ArgumentMeta { @@ -624,7 +670,7 @@ impl ArgumentMeta { /// Parses [`ArgumentMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a /// function argument. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let meta = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; @@ -659,22 +705,64 @@ impl ArgumentMeta { } } -struct InterfaceFieldArgumentDefinition { - pub name: String, - pub ty: syn::Type, - pub description: Option, - pub default: Option>, +/// Representation of [GraphQL interface][1] field [argument][2] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments +#[derive(Debug)] +struct FieldArgument { + /// Name of this [GraphQL field argument][2]. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + name: String, + + /// Rust type that this [GraphQL field argument][2] is represented by. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + ty: syn::Type, + + /// [Description][1] of this [GraphQL field argument][2]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + description: Option, + + /// Default value of this [GraphQL field argument][2]. + /// + /// If outer [`Option`] is [`None`], then this [argument][2] is a [required][3] one. + /// + /// If inner [`Option`] is [`None`], then the [`Default::default`] value is used. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [3]: https://spec.graphql.org/June2018/#sec-Required-Arguments + default: Option>, } +/// Possible kinds of Rust trait method arguments for code generation. +#[derive(Debug)] enum MethodArgument { - Regular(InterfaceFieldArgumentDefinition), + /// Regular [GraphQL field argument][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + Regular(FieldArgument), + + /// [`Context`] passed into a [GraphQL field][2] resolving method. + /// + /// [`Context`]: juniper::Context + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields Context(syn::Type), + + /// [`Executor`] passed into a [GraphQL field][2] resolving method. + /// + /// [`Executor`]: juniper::Executor + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields Executor, } impl MethodArgument { + /// Returns this [`MethodArgument`] as a [`FieldArgument`], if it represents one. #[must_use] - pub fn as_regular(&self) -> Option<&InterfaceFieldArgumentDefinition> { + fn as_regular(&self) -> Option<&FieldArgument> { if let Self::Regular(arg) = self { Some(arg) } else { @@ -682,6 +770,7 @@ impl MethodArgument { } } + /// Returns [`syn::Type`] of this [`MethodArgument::Context`], if it represents one. #[must_use] fn context_ty(&self) -> Option<&syn::Type> { if let Self::Context(ty) = self { @@ -691,7 +780,13 @@ impl MethodArgument { } } - fn meta_method_tokens(&self) -> Option { + /// Returns generated code for the [`GraphQLType::meta`] method, which registers this + /// [`MethodArgument`] in [`Registry`], if it represents a [`FieldArgument`]. + /// + /// [`GraphQLType::meta`]: juniper::GraphQLType::meta + /// [`Registry`]: juniper::Registry + #[must_use] + fn method_meta_tokens(&self) -> Option { let arg = self.as_regular()?; let (name, ty) = (&arg.name, &arg.ty); @@ -714,7 +809,12 @@ impl MethodArgument { Some(quote! { .argument(registry#method#description) }) } - fn resolve_field_method_tokens(&self) -> TokenStream { + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which provides the + /// value of this [`MethodArgument`] to be passed into a trait method call. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + #[must_use] + fn method_resolve_field_tokens(&self) -> TokenStream { match self { Self::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); @@ -737,18 +837,61 @@ impl MethodArgument { } } -struct InterfaceFieldDefinition { - pub name: String, - pub ty: syn::Type, - pub description: Option, - pub deprecated: Option>, - pub method: syn::Ident, - pub arguments: Vec, - pub is_async: bool, +/// Representation of [GraphQL interface][1] [field][2] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields +#[derive(Debug)] +struct Field { + /// Name of this [GraphQL field][2]. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + name: String, + + /// Rust type that this [GraphQL field][2] is represented by (method return type). + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + ty: syn::Type, + + /// [Description][1] of this [GraphQL field][2]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + description: Option, + + /// [Deprecation][1] of this [GraphQL field][2]. + /// + /// If inner [`Option`] is [`None`], then deprecation has no message attached. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Deprecation + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + deprecated: Option>, + + /// Name of Rust trait method representing this [GraphQL field][2]. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + method: syn::Ident, + + /// Rust trait [`MethodArgument`]s required to call the trait method representing this + /// [GraphQL field][2]. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + arguments: Vec, + + /// Indicator whether this [GraphQL field][2] should be resolved asynchronously. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + is_async: bool, } -impl InterfaceFieldDefinition { - fn meta_method_tokens(&self) -> TokenStream { +impl Field { + /// Returns generated code for the [`GraphQLType::meta`] method, which registers this + /// [`Field`] in [`Registry`]. + /// + /// [`GraphQLType::meta`]: juniper::GraphQLType::meta + /// [`Registry`]: juniper::Registry + #[must_use] + fn method_meta_tokens(&self) -> TokenStream { let (name, ty) = (&self.name, &self.ty); let description = self @@ -767,7 +910,7 @@ impl InterfaceFieldDefinition { let arguments = self .arguments .iter() - .filter_map(MethodArgument::meta_method_tokens); + .filter_map(MethodArgument::method_meta_tokens); quote! { registry.field_convert::<#ty, _, Self::Context>(#name, info) @@ -777,7 +920,14 @@ impl InterfaceFieldDefinition { } } - fn resolve_field_method_tokens(&self, trait_ty: &syn::Type) -> Option { + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this + /// [`Field`] synchronously. + /// + /// Returns [`None`] if this [`Field::is_async`]. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + #[must_use] + fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option { if self.is_async { return None; } @@ -787,7 +937,7 @@ impl InterfaceFieldDefinition { let arguments = self .arguments .iter() - .map(MethodArgument::resolve_field_method_tokens); + .map(MethodArgument::method_resolve_field_tokens); let resolving_code = gen::sync_resolving_code(); @@ -799,13 +949,18 @@ impl InterfaceFieldDefinition { }) } - fn resolve_field_async_method_tokens(&self, trait_ty: &syn::Type) -> TokenStream { + /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which + /// resolves this [`Field`] asynchronously. + /// + /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async + #[must_use] + fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream { let (name, ty, method) = (&self.name, &self.ty, &self.method); let arguments = self .arguments .iter() - .map(MethodArgument::resolve_field_method_tokens); + .map(MethodArgument::method_resolve_field_tokens); let mut fut = quote! { ::#method(self #( , #arguments )*) }; if !self.is_async { @@ -823,7 +978,7 @@ impl InterfaceFieldDefinition { } } -#[derive(Clone)] +#[derive(Clone, Debug)] enum ImplementerDowncastDefinition { Method { name: syn::Ident, @@ -981,7 +1136,7 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces scalar: ScalarValueType, - fields: Vec, + fields: Vec, /// Implementers definitions of this [GraphQL interface][1]. /// @@ -1023,10 +1178,7 @@ impl Definition { a.cmp(&b) }); - let fields_meta = self - .fields - .iter() - .map(InterfaceFieldDefinition::meta_method_tokens); + let fields_meta = self.fields.iter().map(Field::method_meta_tokens); quote! { #[automatically_derived] @@ -1069,7 +1221,7 @@ impl Definition { let fields_resolvers = self .fields .iter() - .filter_map(|f| f.resolve_field_method_tokens(&trait_ty)); + .filter_map(|f| f.method_resolve_field_tokens(&trait_ty)); let async_fields_panic = { let names = self .fields @@ -1177,7 +1329,7 @@ impl Definition { let fields_resolvers = self .fields .iter() - .map(|f| f.resolve_field_async_method_tokens(&trait_ty)); + .map(|f| f.method_resolve_field_async_tokens(&trait_ty)); let no_field_panic = self.no_field_panic_tokens(); let custom_downcasts = self From 427d1cc439398812f3aad272a91e5bd04888aed9 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 29 Sep 2020 16:02:19 +0200 Subject: [PATCH 67/79] Polishing 'juniper_codegen', vol.6 --- juniper_codegen/src/graphql_interface/attr.rs | 19 +- juniper_codegen/src/graphql_interface/mod.rs | 1263 +++++++++-------- 2 files changed, 684 insertions(+), 598 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 45ac70bb1..89182a43d 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -17,8 +17,7 @@ use crate::{ use super::{ inject_async_trait, ArgumentMeta, Definition, EnumType, Field, FieldArgument, ImplMeta, - ImplementerDefinition, ImplementerDowncastDefinition, MethodArgument, MethodMeta, TraitMeta, - TraitObjectType, Type, + Implementer, ImplementerDowncast, MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -93,7 +92,7 @@ pub fn expand_on_trait( let mut implementers: Vec<_> = meta .implementers .iter() - .map(|ty| ImplementerDefinition { + .map(|ty| Implementer { ty: ty.as_ref().clone(), downcast: None, context_ty: None, @@ -103,7 +102,7 @@ pub fn expand_on_trait( for (ty, downcast) in &meta.external_downcasts { match implementers.iter_mut().find(|i| &i.ty == ty) { Some(impler) => { - impler.downcast = Some(ImplementerDowncastDefinition::External { + impler.downcast = Some(ImplementerDowncast::External { path: downcast.inner().clone(), }); } @@ -334,7 +333,7 @@ pub fn expand_on_impl( enum TraitMethod { Field(Field), - Downcast(ImplementerDefinition), + Downcast(Implementer), } impl TraitMethod { @@ -362,7 +361,7 @@ impl TraitMethod { Some(Self::Field(Self::parse_field(method, meta)?)) } - fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { + fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { let method_ident = &method.sig.ident; let ty = parse::downcaster::output_type(&method.sig.output) @@ -389,12 +388,12 @@ impl TraitMethod { return None; } - let downcast = ImplementerDowncastDefinition::Method { + let downcast = ImplementerDowncast::Method { name: method_ident.clone(), with_context: context_ty.is_some(), }; - Some(ImplementerDefinition { + Some(Implementer { ty, downcast: Some(downcast), context_ty, @@ -592,11 +591,11 @@ fn err_only_implementer_downcast(span: &S) { fn err_duplicate_downcast( method: &syn::TraitItemMethod, - external: &ImplementerDowncastDefinition, + external: &ImplementerDowncast, impler_ty: &syn::Type, ) { let external = match external { - ImplementerDowncastDefinition::External { path } => path, + ImplementerDowncast::External { path } => path, _ => unreachable!(), }; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 3adf26b2c..d82895317 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -705,751 +705,838 @@ impl ArgumentMeta { } } -/// Representation of [GraphQL interface][1] field [argument][2] for code generation. +/// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments -#[derive(Debug)] -struct FieldArgument { - /// Name of this [GraphQL field argument][2]. +struct Definition { + /// Rust type that this [GraphQL interface][1] is represented with. /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - name: String, + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + ty: Type, - /// Rust type that this [GraphQL field argument][2] is represented by. + /// Name of this [GraphQL interface][1] in GraphQL schema. /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - ty: syn::Type, + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + name: String, - /// [Description][1] of this [GraphQL field argument][2]. + /// Description of this [GraphQL interface][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces description: Option, - /// Default value of this [GraphQL field argument][2]. + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with for this + /// [GraphQL interface][1]. /// - /// If outer [`Option`] is [`None`], then this [argument][2] is a [required][3] one. - /// - /// If inner [`Option`] is [`None`], then the [`Default::default`] value is used. + /// If [`None`] then generated code will use unit type `()` as [`Context`]. /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [3]: https://spec.graphql.org/June2018/#sec-Required-Arguments - default: Option>, -} + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + context: Option, -/// Possible kinds of Rust trait method arguments for code generation. -#[derive(Debug)] -enum MethodArgument { - /// Regular [GraphQL field argument][1]. + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this + /// [GraphQL interface][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - Regular(FieldArgument), + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + scalar: ScalarValueType, - /// [`Context`] passed into a [GraphQL field][2] resolving method. + /// Defined [`Field`]s of this [GraphQL interface][1]. /// - /// [`Context`]: juniper::Context - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - Context(syn::Type), + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fields: Vec, - /// [`Executor`] passed into a [GraphQL field][2] resolving method. + /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// - /// [`Executor`]: juniper::Executor - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - Executor, + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + implementers: Vec, } -impl MethodArgument { - /// Returns this [`MethodArgument`] as a [`FieldArgument`], if it represents one. +impl Definition { + /// Returns generated code that panics about unknown field tried to be resolved on this + /// [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn as_regular(&self) -> Option<&FieldArgument> { - if let Self::Regular(arg) = self { - Some(arg) - } else { - None - } - } + fn panic_no_field_tokens(&self) -> TokenStream { + let scalar = &self.scalar; - /// Returns [`syn::Type`] of this [`MethodArgument::Context`], if it represents one. - #[must_use] - fn context_ty(&self) -> Option<&syn::Type> { - if let Self::Context(ty) = self { - Some(ty) - } else { - None + quote! { + panic!( + "Field `{}` not found on type `{}`", + field, + >::name(info).unwrap(), + ) } } - /// Returns generated code for the [`GraphQLType::meta`] method, which registers this - /// [`MethodArgument`] in [`Registry`], if it represents a [`FieldArgument`]. + /// Returns generated code implementing [`GraphQLType`] trait for this [GraphQL interface][1]. /// - /// [`GraphQLType::meta`]: juniper::GraphQLType::meta - /// [`Registry`]: juniper::Registry + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn method_meta_tokens(&self) -> Option { - let arg = self.as_regular()?; + fn impl_graphql_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; - let (name, ty) = (&arg.name, &arg.ty); + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); - let description = arg + let ty = self.ty.ty_tokens(); + + let name = &self.name; + let description = self .description .as_ref() .map(|desc| quote! { .description(#desc) }); - let method = if let Some(val) = &arg.default { - let val = val - .as_ref() - .map(|v| quote! { (#v).into() }) - .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote! { .arg_with_default::<#ty>(#name, &#val, info) } - } else { - quote! { .arg::<#ty>(#name, info) } - }; - - Some(quote! { .argument(registry#method#description) }) - } + // Sorting is required to preserve/guarantee the order of implementers registered in schema. + let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + impler_tys.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); + a.cmp(&b) + }); - /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which provides the - /// value of this [`MethodArgument`] to be passed into a trait method call. - /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - #[must_use] - fn method_resolve_field_tokens(&self) -> TokenStream { - match self { - Self::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - let err_text = format!( - "Internal error: missing argument `{}` - validation must have failed", - &name, - ); + let fields_meta = self.fields.iter().map(Field::method_meta_tokens); - quote! { - args.get::<#ty>(#name).expect(#err_text) + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) } - } - Self::Context(_) => quote! { - ::juniper::FromContext::from(executor.context()) - }, + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar> + ) -> ::juniper::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + // Ensure all implementer types are registered. + #( let _ = registry.get_type::<#impler_tys>(info); )* - Self::Executor => quote! { &executor }, + let fields = [ + #( #fields_meta, )* + ]; + registry.build_interface_type::<#ty>(info, &fields) + #description + .into_meta() + } + } } } -} -/// Representation of [GraphQL interface][1] [field][2] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields -#[derive(Debug)] -struct Field { - /// Name of this [GraphQL field][2]. + /// Returns generated code implementing [`GraphQLValue`] trait for this [GraphQL interface][1]. /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - name: String, + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; - /// Rust type that this [GraphQL field][2] is represented by (method return type). - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - ty: syn::Type, + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); - /// [Description][1] of this [GraphQL field][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - description: Option, + let ty = self.ty.ty_tokens(); + let trait_ty = self.ty.trait_ty(); + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); - /// [Deprecation][1] of this [GraphQL field][2]. - /// - /// If inner [`Option`] is [`None`], then deprecation has no message attached. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Deprecation - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - deprecated: Option>, + let fields_resolvers = self + .fields + .iter() + .filter_map(|f| f.method_resolve_field_tokens(&trait_ty)); + let async_fields_panic = { + let names = self + .fields + .iter() + .filter_map(|field| { + if field.is_async { + Some(&field.name) + } else { + None + } + }) + .collect::>(); + if names.is_empty() { + None + } else { + Some(quote! { + #( #names )|* => panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + field, + >::name(info).unwrap(), + ), + }) + } + }; + let no_field_panic = self.panic_no_field_tokens(); - /// Name of Rust trait method representing this [GraphQL field][2]. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - method: syn::Ident, + let custom_downcast_checks = self + .implementers + .iter() + .filter_map(|i| i.method_concrete_type_name_tokens(&trait_ty)); + let regular_downcast_check = self.ty.method_concrete_type_name_tokens(); - /// Rust trait [`MethodArgument`]s required to call the trait method representing this - /// [GraphQL field][2]. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - arguments: Vec, + let custom_downcasts = self + .implementers + .iter() + .filter_map(|i| i.method_resolve_into_type_tokens(&trait_ty)); + let regular_downcast = self.ty.method_resolve_into_type_tokens(); - /// Indicator whether this [GraphQL field][2] should be resolved asynchronously. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - is_async: bool, -} + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + { + type Context = #context; + type TypeInfo = (); -impl Field { - /// Returns generated code for the [`GraphQLType::meta`] method, which registers this - /// [`Field`] in [`Registry`]. - /// - /// [`GraphQLType::meta`]: juniper::GraphQLType::meta - /// [`Registry`]: juniper::Registry - #[must_use] - fn method_meta_tokens(&self) -> TokenStream { - let (name, ty) = (&self.name, &self.ty); + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - let deprecated = self.deprecated.as_ref().map(|reason| { - let reason = reason - .as_ref() - .map(|rsn| quote! { Some(#rsn) }) - .unwrap_or_else(|| quote! { None }); - quote! { .deprecated(#reason) } - }); + fn resolve_field( + &self, + info: &Self::TypeInfo, + field: &str, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match field { + #( #fields_resolvers )* + #async_fields_panic + _ => #no_field_panic, + } + } - let arguments = self - .arguments - .iter() - .filter_map(MethodArgument::method_meta_tokens); + fn concrete_type_name( + &self, + context: &Self::Context, + info: &Self::TypeInfo, + ) -> String { + #( #custom_downcast_checks )* + #regular_downcast_check + } - quote! { - registry.field_convert::<#ty, _, Self::Context>(#name, info) - #( #arguments )* - #description - #deprecated + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<#scalar>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + #( #custom_downcasts )* + #regular_downcast + } + } } } - /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this - /// [`Field`] synchronously. - /// - /// Returns [`None`] if this [`Field::is_async`]. + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL interface][1]. /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.is_async { - return None; + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let mut where_clause = where_clause + .cloned() + .unwrap_or_else(|| parse_quote! { where }); + where_clause.predicates.push(parse_quote! { Self: Sync }); + if self.scalar.is_generic() { + where_clause + .predicates + .push(parse_quote! { #scalar: Send + Sync }); } - let (name, ty, method) = (&self.name, &self.ty, &self.method); + let ty = self.ty.ty_tokens(); + let trait_ty = self.ty.trait_ty(); - let arguments = self - .arguments + let fields_resolvers = self + .fields .iter() - .map(MethodArgument::method_resolve_field_tokens); + .map(|f| f.method_resolve_field_async_tokens(&trait_ty)); + let no_field_panic = self.panic_no_field_tokens(); - let resolving_code = gen::sync_resolving_code(); + let custom_downcasts = self + .implementers + .iter() + .filter_map(|i| i.method_resolve_into_type_async_tokens(&trait_ty)); + let regular_downcast = self.ty.method_resolve_into_type_async_tokens(); - Some(quote! { - #name => { - let res: #ty = ::#method(self #( , #arguments )*); - #resolving_code + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause + { + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match field { + #( #fields_resolvers )* + _ => #no_field_panic, + } + } + + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + _: Option<&'b [::juniper::Selection<'b, #scalar>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + #( #custom_downcasts )* + #regular_downcast + } } - }) + } } - /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which - /// resolves this [`Field`] asynchronously. + /// Returns generated code implementing [`GraphQLInterface`] trait for this + /// [GraphQL interface][1]. /// - /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async + /// [`GraphQLInterface`]: juniper::GraphQLInterface + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream { - let (name, ty, method) = (&self.name, &self.ty, &self.method); + fn impl_graphql_interface_tokens(&self) -> TokenStream { + let scalar = &self.scalar; - let arguments = self - .arguments - .iter() - .map(MethodArgument::method_resolve_field_tokens); + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); - let mut fut = quote! { ::#method(self #( , #arguments )*) }; - if !self.is_async { - fut = quote! { ::juniper::futures::future::ready(#fut) }; - } + let ty = self.ty.ty_tokens(); - let resolving_code = gen::async_resolving_code(Some(ty)); + let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + + let all_implers_unique = if impler_tys.len() > 1 { + Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) + } else { + None + }; quote! { - #name => { - let fut = #fut; - #resolving_code + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause + { + fn mark() { + #all_implers_unique + + #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + } } } } -} - -#[derive(Clone, Debug)] -enum ImplementerDowncastDefinition { - Method { - name: syn::Ident, - with_context: bool, - }, - External { - path: syn::ExprPath, - }, -} -/// Definition of [GraphQL interface][1] implementer for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Clone)] -struct ImplementerDefinition { - /// Rust type that this [GraphQL interface][1] implementer resolves into. + /// Returns generated code implementing [`marker::IsOutputType`] trait for this + /// [GraphQL interface][1]. /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub ty: syn::Type, + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; - pub downcast: Option, + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); - /// Rust type of `juniper::Context` that this [GraphQL interface][1] implementer requires for - /// downcasting. - /// - /// It's available only when code generation happens for Rust traits and a trait method contains - /// context argument. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub context_ty: Option, + let ty = self.ty.ty_tokens(); - pub scalar: ScalarValueType, -} + let fields_marks = self.fields.iter().map(|field| { + let arguments_marks = field.arguments.iter().filter_map(|arg| { + let arg_ty = &arg.as_regular()?.ty; + Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) + }); -impl ImplementerDefinition { - fn downcast_call_tokens( - &self, - trait_ty: &syn::Type, - ctx: Option, - ) -> Option { - let ctx = ctx.unwrap_or_else(|| parse_quote! { executor.context() }); - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(#ctx) }); + let field_ty = &field.ty; + let resolved_ty = quote! { + <#field_ty as ::juniper::IntoResolvable< + '_, #scalar, _, >::Context, + >>::Type + }; - let fn_path = match self.downcast.as_ref()? { - ImplementerDowncastDefinition::Method { name, with_context } => { - if !with_context { - ctx_arg = None; - } - quote! { ::#name } - } - ImplementerDowncastDefinition::External { path } => { - quote! { #path } + quote! { + #( #arguments_marks )* + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } - }; - - Some(quote! { - #fn_path(self #ctx_arg) - }) - } - - fn concrete_type_name_method_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.downcast.is_none() { - return None; - } - - let ty = &self.ty; - let scalar = &self.scalar; + }); - let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context })); + let impler_tys = self.implementers.iter().map(|impler| &impler.ty); - // Doing this may be quite an expensive, because resolving may contain some heavy - // computation, so we're preforming it twice. Unfortunately, we have no other options here, - // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. - Some(quote! { - if (#downcast as ::std::option::Option<&#ty>).is_some() { - return <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap().to_string(); + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } } - }) - } - - fn resolve_into_type_method_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.downcast.is_none() { - return None; } + } +} - let ty = &self.ty; - let scalar = &self.scalar; - - let downcast = self.downcast_call_tokens(trait_ty, None); - - let resolving_code = gen::sync_resolving_code(); - - Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { - let res = #downcast; - return #resolving_code; - } - }) - } - - fn resolve_into_type_async_method_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.downcast.is_none() { - return None; - } - - let ty = &self.ty; - let scalar = &self.scalar; - - let downcast = self.downcast_call_tokens(trait_ty, None); - - let resolving_code = gen::async_resolving_code(None); - - Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { - let fut = ::juniper::futures::future::ready(#downcast); - return #resolving_code; - } - }) +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + into.append_all(&[ + self.ty.to_token_stream(), + self.impl_graphql_interface_tokens(), + self.impl_output_type_tokens(), + self.impl_graphql_type_tokens(), + self.impl_graphql_value_tokens(), + self.impl_graphql_value_async_tokens(), + ]); } } -struct Definition { - /// Rust type that this [GraphQL interface][1] is represented with. +/// Representation of [GraphQL interface][1] field [argument][2] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments +#[derive(Debug)] +struct FieldArgument { + /// Rust type that this [GraphQL field argument][2] is represented by. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ty: Type, + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + ty: syn::Type, - /// Name of this [GraphQL interface][1] in GraphQL schema. + /// Name of this [GraphQL field argument][2] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments name: String, - /// Description of this [GraphQL interface][1] to put into GraphQL schema. + /// [Description][1] of this [GraphQL field argument][2] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments description: Option, - /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` implementation with - /// for this [GraphQL interface][1]. + /// Default value of this [GraphQL field argument][2] in GraphQL schema. /// - /// If [`None`] then generated code will use unit type `()` as `juniper::Context`. + /// If outer [`Option`] is [`None`], then this [argument][2] is a [required][3] one. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - context: Option, - - /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` implementation with - /// for this [GraphQL interface][1]. + /// If inner [`Option`] is [`None`], then the [`Default::default`] value is used. /// - /// If [`None`] then generated code will be generic over any `juniper::ScalarValue` type, which, - /// in turn, requires all [interface][1] implementers to be generic over any - /// `juniper::ScalarValue` type too. That's why this type should be specified only if one of the - /// implementers implements `juniper::GraphQLType` in a non-generic way over - /// `juniper::ScalarValue` type. + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [3]: https://spec.graphql.org/June2018/#sec-Required-Arguments + default: Option>, +} + +/// Possible kinds of Rust trait method arguments for code generation. +#[derive(Debug)] +enum MethodArgument { + /// Regular [GraphQL field argument][1]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - scalar: ScalarValueType, + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + Regular(FieldArgument), - fields: Vec, + /// [`Context`] passed into a [GraphQL field][2] resolving method. + /// + /// [`Context`]: juniper::Context + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + Context(syn::Type), - /// Implementers definitions of this [GraphQL interface][1]. + /// [`Executor`] passed into a [GraphQL field][2] resolving method. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + /// [`Executor`]: juniper::Executor + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + Executor, } -impl Definition { - fn no_field_panic_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - quote! { - panic!( - "Field `{}` not found on type `{}`", - field, - >::name(info).unwrap(), - ) +impl MethodArgument { + /// Returns this [`MethodArgument`] as a [`FieldArgument`], if it represents one. + #[must_use] + fn as_regular(&self) -> Option<&FieldArgument> { + if let Self::Regular(arg) = self { + Some(arg) + } else { + None } } - fn impl_graphql_type_tokens(&self) -> TokenStream { - let scalar = &self.scalar; + /// Returns [`syn::Type`] of this [`MethodArgument::Context`], if it represents one. + #[must_use] + fn context_ty(&self) -> Option<&syn::Type> { + if let Self::Context(ty) = self { + Some(ty) + } else { + None + } + } - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + /// Returns generated code for the [`GraphQLType::meta`] method, which registers this + /// [`MethodArgument`] in [`Registry`], if it represents a [`FieldArgument`]. + /// + /// [`GraphQLType::meta`]: juniper::GraphQLType::meta + /// [`Registry`]: juniper::Registry + #[must_use] + fn method_meta_tokens(&self) -> Option { + let arg = self.as_regular()?; - let ty = self.ty.ty_tokens(); + let (name, ty) = (&arg.name, &arg.ty); - let name = &self.name; - let description = self + let description = arg .description .as_ref() .map(|desc| quote! { .description(#desc) }); - // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); - impler_tys.sort_unstable_by(|a, b| { - let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); - a.cmp(&b) - }); - - let fields_meta = self.fields.iter().map(Field::method_meta_tokens); + let method = if let Some(val) = &arg.default { + let val = val + .as_ref() + .map(|v| quote! { (#v).into() }) + .unwrap_or_else(|| quote! { <#ty as Default>::default() }); + quote! { .arg_with_default::<#ty>(#name, &#val, info) } + } else { + quote! { .arg::<#ty>(#name, info) } + }; - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause - { - fn name(_ : &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } + Some(quote! { .argument(registry#method#description) }) + } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_tys>(info); )* + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which provides the + /// value of this [`MethodArgument`] to be passed into a trait method call. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + #[must_use] + fn method_resolve_field_tokens(&self) -> TokenStream { + match self { + Self::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + let err_text = format!( + "Internal error: missing argument `{}` - validation must have failed", + &name, + ); - let fields = [ - #( #fields_meta, )* - ]; - registry.build_interface_type::<#ty>(info, &fields) - #description - .into_meta() + quote! { + args.get::<#ty>(#name).expect(#err_text) } } + + Self::Context(_) => quote! { + ::juniper::FromContext::from(executor.context()) + }, + + Self::Executor => quote! { &executor }, } } +} - fn impl_graphql_value_tokens(&self) -> TokenStream { - let scalar = &self.scalar; +/// Representation of [GraphQL interface][1] [field][2] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields +#[derive(Debug)] +struct Field { + /// Rust type that this [GraphQL field][2] is represented by (method return type). + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + ty: syn::Type, - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + /// Name of this [GraphQL field][2] in GraphQL schema. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + name: String, - let ty = self.ty.ty_tokens(); - let trait_ty = self.ty.trait_ty(); - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + /// [Description][1] of this [GraphQL field][2] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + description: Option, - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(&trait_ty)); - let async_fields_panic = { - let names = self - .fields - .iter() - .filter_map(|field| { - if field.is_async { - Some(&field.name) - } else { - None - } - }) - .collect::>(); - if names.is_empty() { - None - } else { - Some(quote! { - #( #names )|* => panic!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - field, - >::name(info).unwrap(), - ), - }) - } - }; - let no_field_panic = self.no_field_panic_tokens(); + /// [Deprecation][1] of this [GraphQL field][2] to put into GraphQL schema. + /// + /// If inner [`Option`] is [`None`], then deprecation has no message attached. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Deprecation + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + deprecated: Option>, - let custom_downcast_checks = self - .implementers - .iter() - .filter_map(|i| i.concrete_type_name_method_tokens(&trait_ty)); - let regular_downcast_check = self.ty.concrete_type_name_method_tokens(); + /// Name of Rust trait method representing this [GraphQL field][2]. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + method: syn::Ident, - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.resolve_into_type_method_tokens(&trait_ty)); - let regular_downcast = self.ty.resolve_into_type_method_tokens(); + /// Rust trait [`MethodArgument`]s required to call the trait method representing this + /// [GraphQL field][2]. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + arguments: Vec, - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause - { - type Context = #context; - type TypeInfo = (); + /// Indicator whether this [GraphQL field][2] should be resolved asynchronously. + /// + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + is_async: bool, +} - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } +impl Field { + /// Returns generated code for the [`GraphQLType::meta`] method, which registers this + /// [`Field`] in [`Registry`]. + /// + /// [`GraphQLType::meta`]: juniper::GraphQLType::meta + /// [`Registry`]: juniper::Registry + #[must_use] + fn method_meta_tokens(&self) -> TokenStream { + let (name, ty) = (&self.name, &self.ty); - fn resolve_field( - &self, - info: &Self::TypeInfo, - field: &str, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - match field { - #( #fields_resolvers )* - #async_fields_panic - _ => #no_field_panic, - } - } + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); - fn concrete_type_name( - &self, - context: &Self::Context, - info: &Self::TypeInfo, - ) -> String { - #( #custom_downcast_checks )* - #regular_downcast_check - } + let deprecated = self.deprecated.as_ref().map(|reason| { + let reason = reason + .as_ref() + .map(|rsn| quote! { Some(#rsn) }) + .unwrap_or_else(|| quote! { None }); + quote! { .deprecated(#reason) } + }); - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - #( #custom_downcasts )* - #regular_downcast - } - } + let arguments = self + .arguments + .iter() + .filter_map(MethodArgument::method_meta_tokens); + + quote! { + registry.field_convert::<#ty, _, Self::Context>(#name, info) + #( #arguments )* + #description + #deprecated } } - fn impl_graphql_value_async_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let mut where_clause = where_clause - .cloned() - .unwrap_or_else(|| parse_quote! { where }); - where_clause.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_generic() { - where_clause - .predicates - .push(parse_quote! { #scalar: Send + Sync }); + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this + /// [`Field`] synchronously. + /// + /// Returns [`None`] if this [`Field::is_async`]. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + #[must_use] + fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option { + if self.is_async { + return None; } - let ty = self.ty.ty_tokens(); - let trait_ty = self.ty.trait_ty(); + let (name, ty, method) = (&self.name, &self.ty, &self.method); - let fields_resolvers = self - .fields + let arguments = self + .arguments .iter() - .map(|f| f.method_resolve_field_async_tokens(&trait_ty)); - let no_field_panic = self.no_field_panic_tokens(); + .map(MethodArgument::method_resolve_field_tokens); - let custom_downcasts = self - .implementers + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #name => { + let res: #ty = ::#method(self #( , #arguments )*); + #resolving_code + } + }) + } + + /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which + /// resolves this [`Field`] asynchronously. + /// + /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async + #[must_use] + fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream { + let (name, ty, method) = (&self.name, &self.ty, &self.method); + + let arguments = self + .arguments .iter() - .filter_map(|i| i.resolve_into_type_async_method_tokens(&trait_ty)); - let regular_downcast = self.ty.resolve_into_type_async_method_tokens(); + .map(MethodArgument::method_resolve_field_tokens); - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause - { - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field: &'b str, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - match field { - #( #fields_resolvers )* - _ => #no_field_panic, - } - } + let mut fut = quote! { ::#method(self #( , #arguments )*) }; + if !self.is_async { + fut = quote! { ::juniper::futures::future::ready(#fut) }; + } - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - _: Option<&'b [::juniper::Selection<'b, #scalar>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - #( #custom_downcasts )* - #regular_downcast - } + let resolving_code = gen::async_resolving_code(Some(ty)); + + quote! { + #name => { + let fut = #fut; + #resolving_code } } } +} - fn impl_graphql_interface_tokens(&self) -> TokenStream { - let scalar = &self.scalar; +/// Representation of custom downcast into an [`Implementer`] from a [GraphQL interface][1] type for +/// code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Clone, Debug)] +enum ImplementerDowncast { + /// Downcast is performed via a method of trait describing a [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + Method { + /// Name of trait method which performs this [`ImplementerDowncast`]. + name: syn::Ident, - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + /// Indicator whether the trait method accepts a [`Context`] as its second argument. + /// + /// [`Context`]: juniper::Context + with_context: bool, + }, - let ty = self.ty.ty_tokens(); + /// Downcast is performed via some external function. + External { + /// Path of the external function to be called with. + path: syn::ExprPath, + }, +} - let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); +/// Representation of [GraphQL interface][1] implementer for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Clone, Debug)] +struct Implementer { + /// Rust type that this [GraphQL interface][1] [`Implementer`] is represented by. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + ty: syn::Type, - let all_implers_unique = if impler_tys.len() > 1 { - Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) - } else { - None - }; + /// Custom [`ImplementerDowncast`] for this [`Implementer`]. + /// + /// If absent, then [`Implementer`] is downcast from an enum variant or a trait object. + downcast: Option, - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause - { - fn mark() { - #all_implers_unique + /// Rust type of [`Context`] that this [GraphQL interface][1] [`Implementer`] requires for + /// downcasting. + /// + /// It's available only when code generation happens for Rust traits and a trait method contains + /// context argument. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + context_ty: Option, - #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + /// [`ScalarValue`] parametrization of this [`Implementer`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + scalar: ScalarValueType, +} + +impl Implementer { + /// Returns generated code of downcasting this [`Implementer`] via custom + /// [`ImplementerDowncast`]. + /// + /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + #[must_use] + fn downcast_call_tokens( + &self, + trait_ty: &syn::Type, + ctx: Option, + ) -> Option { + let ctx = ctx.unwrap_or_else(|| parse_quote! { executor.context() }); + let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(#ctx) }); + + let fn_path = match self.downcast.as_ref()? { + ImplementerDowncast::Method { name, with_context } => { + if !with_context { + ctx_arg = None; } + quote! { ::#name } } - } + ImplementerDowncast::External { path } => { + quote! { #path } + } + }; + + Some(quote! { + #fn_path(self #ctx_arg) + }) } - fn impl_output_type_tokens(&self) -> TokenStream { + /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns + /// name of the GraphQL type represented by this [`Implementer`]. + /// + /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + /// + /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + #[must_use] + fn method_concrete_type_name_tokens(&self, trait_ty: &syn::Type) -> Option { + if self.downcast.is_none() { + return None; + } + + let ty = &self.ty; let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context })); - let ty = self.ty.ty_tokens(); + // Doing this may be quite an expensive, because resolving may contain some heavy + // computation, so we're preforming it twice. Unfortunately, we have no other options here, + // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. + Some(quote! { + if (#downcast as ::std::option::Option<&#ty>).is_some() { + return <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap().to_string(); + } + }) + } - let fields_marks = self.fields.iter().map(|field| { - let arguments_marks = field.arguments.iter().filter_map(|arg| { - let arg_ty = &arg.as_regular()?.ty; - Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) - }); + /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts + /// the [GraphQL interface][1] type into this [`Implementer`] synchronously. + /// + /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + /// + /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option { + if self.downcast.is_none() { + return None; + } - let field_ty = &field.ty; - let resolved_ty = quote! { - <#field_ty as ::juniper::IntoResolvable< - '_, #scalar, _, >::Context, - >>::Type - }; + let ty = &self.ty; + let scalar = &self.scalar; - quote! { - #( #arguments_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - } - }); + let downcast = self.downcast_call_tokens(trait_ty, None); - let impler_tys = self.implementers.iter().map(|impler| &impler.ty); + let resolving_code = gen::sync_resolving_code(); - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause - { - fn mark() { - #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* - } + Some(quote! { + if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { + let res = #downcast; + return #resolving_code; } - } + }) } -} -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - into.append_all(&[ - self.ty.to_token_stream(), - self.impl_graphql_interface_tokens(), - self.impl_output_type_tokens(), - self.impl_graphql_type_tokens(), - self.impl_graphql_value_tokens(), - self.impl_graphql_value_async_tokens(), - ]); + /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, + /// which downcasts the [GraphQL interface][1] type into this [`Implementer`] asynchronously. + /// + /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn method_resolve_into_type_async_tokens(&self, trait_ty: &syn::Type) -> Option { + if self.downcast.is_none() { + return None; + } + + let ty = &self.ty; + let scalar = &self.scalar; + + let downcast = self.downcast_call_tokens(trait_ty, None); + + let resolving_code = gen::async_resolving_code(None); + + Some(quote! { + if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { + let fut = ::juniper::futures::future::ready(#downcast); + return #resolving_code; + } + }) } } @@ -1469,7 +1556,7 @@ impl EnumType { fn new( r#trait: &syn::ItemTrait, meta: &TraitMeta, - implers: &Vec, + implers: &Vec, scalar: ScalarValueType, ) -> Self { Self { @@ -1739,7 +1826,7 @@ impl EnumType { impl_tokens } - fn concrete_type_name_method_tokens(&self) -> TokenStream { + fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; let match_arms = self.variants.iter().enumerate().map(|(n, ty)| { @@ -1761,7 +1848,7 @@ impl EnumType { } } - fn resolve_into_type_method_tokens(&self) -> TokenStream { + fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); let match_arms = self.variants.iter().enumerate().map(|(n, _)| { @@ -1781,7 +1868,7 @@ impl EnumType { } } - fn resolve_into_type_async_method_tokens(&self) -> TokenStream { + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); let match_arms = self.variants.iter().enumerate().map(|(n, _)| { @@ -1890,13 +1977,13 @@ impl TraitObjectType { } } - fn concrete_type_name_method_tokens(&self) -> TokenStream { + fn method_concrete_type_name_tokens(&self) -> TokenStream { quote! { self.as_dyn_graphql_value().concrete_type_name(context, info) } } - fn resolve_into_type_method_tokens(&self) -> TokenStream { + fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); quote! { @@ -1905,7 +1992,7 @@ impl TraitObjectType { } } - fn resolve_into_type_async_method_tokens(&self) -> TokenStream { + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); quote! { @@ -1992,24 +2079,24 @@ impl Type { } } - fn concrete_type_name_method_tokens(&self) -> TokenStream { + fn method_concrete_type_name_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.concrete_type_name_method_tokens(), - Self::TraitObject(o) => o.concrete_type_name_method_tokens(), + Self::Enum(e) => e.method_concrete_type_name_tokens(), + Self::TraitObject(o) => o.method_concrete_type_name_tokens(), } } - fn resolve_into_type_method_tokens(&self) -> TokenStream { + fn method_resolve_into_type_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.resolve_into_type_method_tokens(), - Self::TraitObject(o) => o.resolve_into_type_method_tokens(), + Self::Enum(e) => e.method_resolve_into_type_tokens(), + Self::TraitObject(o) => o.method_resolve_into_type_tokens(), } } - fn resolve_into_type_async_method_tokens(&self) -> TokenStream { + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { match self { - Self::Enum(e) => e.resolve_into_type_async_method_tokens(), - Self::TraitObject(o) => o.resolve_into_type_async_method_tokens(), + Self::Enum(e) => e.method_resolve_into_type_async_tokens(), + Self::TraitObject(o) => o.method_resolve_into_type_async_tokens(), } } } From ad046de031c3ddfc810396503095c794a63bef3a Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 29 Sep 2020 18:07:21 +0200 Subject: [PATCH 68/79] Polishing 'juniper_codegen', vol.7 --- juniper_codegen/src/graphql_interface/mod.rs | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index d82895317..558f7dee2 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1540,19 +1540,51 @@ impl Implementer { } } +/// Representation of Rust enum implementing [GraphQL interface][1] type for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces struct EnumType { + /// Name of this [`EnumType`] to generate it with. ident: syn::Ident, + + /// [`syn::Visibility`] of this [`EnumType`] to generate it with. visibility: syn::Visibility, + + /// Rust types of all [GraphQL interface][1] implements to represent variants of this + /// [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces variants: Vec, + + /// Name of the trait describing the [GraphQL interface][1] represented by this [`EnumType`]. trait_ident: syn::Ident, + + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] represented by this + /// [`EnumType`]. trait_generics: syn::Generics, + + /// Associated types of the trait describing the [GraphQL interface][1] represented by this + /// [`EnumType`]. trait_types: Vec<(syn::Ident, syn::Generics)>, + + /// Associated constants of the trait describing the [GraphQL interface][1] represented by this + /// [`EnumType`]. trait_consts: Vec<(syn::Ident, syn::Type)>, + + /// Methods of the trait describing the [GraphQL interface][1] represented by this [`EnumType`]. trait_methods: Vec, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this + /// [`EnumType`]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue scalar: ScalarValueType, } impl EnumType { + /// Constructs new [`EnumType`] out of the given parameters. + #[must_use] fn new( r#trait: &syn::ItemTrait, meta: &TraitMeta, @@ -1607,14 +1639,25 @@ impl EnumType { } } + /// Returns name of a single variant of this [`EnumType`] by the given positional `num` in the + /// enum type definition. + #[must_use] fn variant_ident(num: usize) -> syn::Ident { format_ident!("Impl{}", num) } + /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant to hold type + /// parameters. + #[must_use] fn has_phantom_variant(&self) -> bool { !self.trait_generics.params.is_empty() } + /// Returns generate code for dispatching non-exhaustive phantom variant of this [`EnumType`] + /// in `match` expressions. + /// + /// Returns [`None`] if this [`EnumType`] is exhaustive. + #[must_use] fn non_exhaustive_match_arm_tokens(&self) -> Option { if self.has_phantom_variant() { Some(quote! { _ => unreachable!(), }) @@ -1623,6 +1666,11 @@ impl EnumType { } } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation + /// for this [`EnumType`] + /// + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] fn impl_generics(&self) -> syn::Generics { let mut generics = self.trait_generics.clone(); @@ -1640,6 +1688,11 @@ impl EnumType { generics } + /// Returns full type signature of the original trait describing the [GraphQL interface][1] for + /// this [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] fn trait_ty(&self) -> syn::Type { let ty = &self.trait_ident; let (_, generics, _) = self.trait_generics.split_for_impl(); @@ -1647,6 +1700,8 @@ impl EnumType { parse_quote! { #ty#generics } } + /// Returns generated code of the full type signature of this [`EnumType`]. + #[must_use] fn ty_tokens(&self) -> TokenStream { let ty = &self.ident; let (_, generics, _) = self.trait_generics.split_for_impl(); @@ -1654,6 +1709,11 @@ impl EnumType { quote! { #ty#generics } } + /// Returns generate code of the Rust type definitions of this [`EnumType`]. + /// + /// If the [`EnumType::trait_generics`] are not empty, then they are contained in the generated + /// enum too. + #[must_use] fn type_definition_tokens(&self) -> TokenStream { let enum_ty = &self.ident; let generics = &self.trait_generics; @@ -1708,6 +1768,9 @@ impl EnumType { } } + /// Returns generated code implementing [`From`] trait for this [`EnumType`] from its + /// [`EnumType::variants`]. + #[must_use] fn impl_from_tokens(&self) -> impl Iterator + '_ { let enum_ty = &self.ident; let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); @@ -1726,6 +1789,11 @@ impl EnumType { }) } + /// Returns generated code implementing the original trait describing the [GraphQL interface][1] + /// for this [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] fn impl_trait_tokens(&self) -> TokenStream { let enum_ty = &self.ident; @@ -1826,6 +1894,11 @@ impl EnumType { impl_tokens } + /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns + /// name of the underlying [`Implementer`] GraphQL type contained in this [`EnumType`]. + /// + /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -1848,6 +1921,11 @@ impl EnumType { } } + /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts + /// this [`EnumType`] into its underlying [`Implementer`] type synchronously. + /// + /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); @@ -1868,6 +1946,11 @@ impl EnumType { } } + /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, + /// which downcasts this [`EnumType`] into its underlying [`Implementer`] type asynchronously. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + #[must_use] fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); From c45f2906901feedfe047670dc2d9c20d6dd54b27 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 30 Sep 2020 13:00:01 +0200 Subject: [PATCH 69/79] Polishing 'juniper_codegen', vol.8 --- juniper_codegen/src/graphql_interface/mod.rs | 118 +++++++++++++++++-- 1 file changed, 111 insertions(+), 7 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 558f7dee2..77034d940 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1557,10 +1557,14 @@ struct EnumType { variants: Vec, /// Name of the trait describing the [GraphQL interface][1] represented by this [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces trait_ident: syn::Ident, /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] represented by this /// [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces trait_generics: syn::Generics, /// Associated types of the trait describing the [GraphQL interface][1] represented by this @@ -1569,9 +1573,13 @@ struct EnumType { /// Associated constants of the trait describing the [GraphQL interface][1] represented by this /// [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces trait_consts: Vec<(syn::Ident, syn::Type)>, /// Methods of the trait describing the [GraphQL interface][1] represented by this [`EnumType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces trait_methods: Vec, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this @@ -1667,7 +1675,7 @@ impl EnumType { } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation - /// for this [`EnumType`] + /// for this [`EnumType`]. /// /// [`GraphQLType`]: juniper::GraphQLType #[must_use] @@ -1983,16 +1991,46 @@ impl ToTokens for EnumType { } } +/// Representation of Rust [trait object][2] implementing [GraphQL interface][1] type for code +/// generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [2]: https://doc.rust-lang.org/reference/types/trait-object.html struct TraitObjectType { - pub ident: syn::Ident, - pub visibility: syn::Visibility, - pub trait_ident: syn::Ident, - pub trait_generics: syn::Generics, - pub scalar: ScalarValueType, - pub context: Option, + /// Name of this [`TraitObjectType`] to generate it with. + ident: syn::Ident, + + /// [`syn::Visibility`] of this [`TraitObjectType`] to generate it with. + visibility: syn::Visibility, + + /// Name of the trait describing the [GraphQL interface][1] represented by this + /// [`TraitObjectType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + trait_ident: syn::Ident, + + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] represented by this + /// [`TraitObjectType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + trait_generics: syn::Generics, + + /// [`ScalarValue`] parametrization of this [`TraitObjectType`] to generate it with. + /// + /// [`ScalarValue`]: juniper::ScalarValue + scalar: ScalarValueType, + + /// Rust type of [`Context`] to generate this [`TraitObjectType`] with. + /// + /// If [`None`] then generated code will use unit type `()` as [`Context`]. + /// + /// [`Context`]: juniper::Context + context: Option, } impl TraitObjectType { + /// Constructs new [`TraitObjectType`] out of the given parameters. + #[must_use] fn new( r#trait: &syn::ItemTrait, meta: &TraitMeta, @@ -2009,6 +2047,11 @@ impl TraitObjectType { } } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation + /// for this [`TraitObjectType`]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] fn impl_generics(&self) -> syn::Generics { let mut generics = self.trait_generics.clone(); @@ -2028,6 +2071,11 @@ impl TraitObjectType { generics } + /// Returns full type signature of the original trait describing the [GraphQL interface][1] for + /// this [`TraitObjectType`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] fn trait_ty(&self) -> syn::Type { let ty = &self.trait_ident; @@ -2041,6 +2089,8 @@ impl TraitObjectType { parse_quote! { #ty#generics } } + /// Returns generated code of the full type signature of this [`TraitObjectType`]. + #[must_use] fn ty_tokens(&self) -> TokenStream { let ty = &self.trait_ident; @@ -2060,12 +2110,22 @@ impl TraitObjectType { } } + /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns + /// name of the underlying [`Implementer`] GraphQL type contained in this [`TraitObjectType`]. + /// + /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { quote! { self.as_dyn_graphql_value().concrete_type_name(context, info) } } + /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts + /// this [`TraitObjectType`] into its underlying [`Implementer`] type synchronously. + /// + /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); @@ -2075,6 +2135,12 @@ impl TraitObjectType { } } + /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, + /// which downcasts this [`TraitObjectType`] into its underlying [`Implementer`] type + /// asynchronously. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + #[must_use] fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); @@ -2135,12 +2201,29 @@ impl ToTokens for TraitObjectType { } } +/// Representation of possible Rust types implementing [GraphQL interface][1] type for code +/// generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces enum Type { + /// [GraphQL interface][1] type implementation as Rust enum. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces Enum(EnumType), + + /// [GraphQL interface][1] type implementation as Rust [trait object][2]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html TraitObject(TraitObjectType), } impl Type { + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation + /// for this [`Type`]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] fn impl_generics(&self) -> syn::Generics { match self { Self::Enum(e) => e.impl_generics(), @@ -2148,6 +2231,11 @@ impl Type { } } + /// Returns full type signature of the original trait describing the [GraphQL interface][1] for + /// this [`Type`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] fn trait_ty(&self) -> syn::Type { match self { Self::Enum(e) => e.trait_ty(), @@ -2155,6 +2243,8 @@ impl Type { } } + /// Returns generated code of the full type signature of this [`Type`]. + #[must_use] fn ty_tokens(&self) -> TokenStream { match self { Self::Enum(e) => e.ty_tokens(), @@ -2162,6 +2252,11 @@ impl Type { } } + /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns + /// name of the underlying [`Implementer`] GraphQL type contained in this [`Type`]. + /// + /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { match self { Self::Enum(e) => e.method_concrete_type_name_tokens(), @@ -2169,6 +2264,11 @@ impl Type { } } + /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts + /// this [`Type`] into its underlying [`Implementer`] type synchronously. + /// + /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { match self { Self::Enum(e) => e.method_resolve_into_type_tokens(), @@ -2176,6 +2276,10 @@ impl Type { } } + /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, + /// which downcasts this [`Type`] into its underlying [`Implementer`] type asynchronously. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async fn method_resolve_into_type_async_tokens(&self) -> TokenStream { match self { Self::Enum(e) => e.method_resolve_into_type_async_tokens(), From f75df503e6552c97c804d4c20744affafe663024 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 30 Sep 2020 13:34:16 +0200 Subject: [PATCH 70/79] Polishing 'juniper_codegen', vol.9 --- juniper_codegen/src/graphql_interface/attr.rs | 52 ++++++++++++++++--- juniper_codegen/src/graphql_interface/mod.rs | 3 ++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 89182a43d..d18d92bcf 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -331,12 +331,26 @@ pub fn expand_on_impl( Ok(quote! { #ast }) } +/// Representation of parsed Rust trait method for `#[graphql_interface]` macro code generation. enum TraitMethod { + /// Method represents a [`Field`] of [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces Field(Field), + + /// Method represents a custom downcasting function into the [`Implementer`] of + /// [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces Downcast(Implementer), } impl TraitMethod { + /// Parses this [`TraitMethod`] from the given trait method definition. + /// + /// Returns [`None`] if the trait method marked with `#[graphql_interface(ignore)]` attribute, + /// or parsing fails. + #[must_use] fn parse(method: &mut syn::TraitItemMethod) -> Option { let method_attrs = method.attrs.clone(); @@ -361,6 +375,10 @@ impl TraitMethod { Some(Self::Field(Self::parse_field(method, meta)?)) } + /// Parses [`TraitMethod::Downcast`] from the given trait method definition. + /// + /// Returns [`None`] if parsing fails. + #[must_use] fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { let method_ident = &method.sig.ident; @@ -401,6 +419,10 @@ impl TraitMethod { }) } + /// Parses [`TraitMethod::Field`] from the given trait method definition. + /// + /// Returns [`None`] if parsing fails. + #[must_use] fn parse_field(method: &mut syn::TraitItemMethod, meta: MethodMeta) -> Option { let method_ident = &method.sig.ident; @@ -470,6 +492,10 @@ impl TraitMethod { }) } + /// Parses [`MethodArgument`] from the given trait method argument definition. + /// + /// Returns [`None`] if parsing fails. + #[must_use] fn parse_field_argument(argument: &mut syn::PatType) -> Option { let argument_attrs = argument.attrs.clone(); @@ -538,6 +564,9 @@ impl TraitMethod { } } +/// Checks whether the given [`ArgumentMeta`] doesn't contain arguments related to +/// [`FieldArgument`]. +#[must_use] fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> { if let Some(span) = &meta.name { return err_disallowed_attr(&span, "name"); @@ -551,18 +580,22 @@ fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> { Some(()) } -fn err_disallowed_attr(span: &S, attr: &str) -> Option { +/// Emits "argument is not allowed" [`syn::Error`] for the given `arg` pointing to the given `span`. +#[must_use] +fn err_disallowed_attr(span: &S, arg: &str) -> Option { ERR.custom( span.span(), format!( - "attribute `#[graphql_interface({} = ...)]` is not allowed here", - attr, + "attribute argument `#[graphql_interface({} = ...)]` is not allowed here", + arg, ), ) .emit(); return None; } +/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`. +#[must_use] fn err_invalid_method_receiver(span: &S) -> Option { ERR.custom( span.span(), @@ -572,6 +605,8 @@ fn err_invalid_method_receiver(span: &S) -> Option { return None; } +/// Emits "no trait method receiver" [`syn::Error`] pointing to the given `span`. +#[must_use] fn err_no_method_receiver(span: &S) -> Option { ERR.custom( span.span(), @@ -581,6 +616,7 @@ fn err_no_method_receiver(span: &S) -> Option { return None; } +/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given `span`. fn err_only_implementer_downcast(span: &S) { ERR.custom( span.span(), @@ -589,6 +625,8 @@ fn err_only_implementer_downcast(span: &S) { .emit(); } +/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and `external` +/// [`ImplementerDowncast`] function. fn err_duplicate_downcast( method: &syn::TraitItemMethod, external: &ImplementerDowncast, @@ -602,16 +640,16 @@ fn err_duplicate_downcast( ERR.custom( method.span(), format!( - "trait method `{}` conflicts with the external downcast function `{}` declared \ - on the trait to downcast into the implementer type `{}`", + "trait method `{}` conflicts with the external downcast function `{}` declared on the \ + trait to downcast into the implementer type `{}`", method.sig.ident, external.to_token_stream(), impler_ty.to_token_stream(), ), ) .note(String::from( - "use `#[graphql_interface(ignore)]` attribute to ignore this trait method for interface \ - implementers downcasting", + "use `#[graphql_interface(ignore)]` attribute argument to ignore this trait method for \ + interface implementers downcasting", )) .emit() } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 77034d940..75608f327 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -2297,6 +2297,9 @@ impl ToTokens for Type { } } +/// Injects [`async_trait`] implementation into the given trait definition or trait implementation +/// block, correctly restricting type and lifetime parameters with `'async_trait` lifetime, if +/// required. fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) where M: IntoIterator, From 49c8d917e0a41f79a88f36ce09f7e33888f19c1d Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 30 Sep 2020 17:02:36 +0200 Subject: [PATCH 71/79] Fix other crates tests and make Clippy happier --- juniper/src/executor/mod.rs | 7 ++-- juniper_actix/examples/actix_server.rs | 2 +- juniper_actix/src/lib.rs | 4 +- juniper_codegen/src/common/parse/attr.rs | 10 +---- juniper_codegen/src/graphql_interface/attr.rs | 38 ++++++++++--------- juniper_codegen/src/graphql_interface/mod.rs | 26 ++++--------- juniper_codegen/src/impl_scalar.rs | 37 ++++++------------ juniper_hyper/examples/hyper_server.rs | 2 +- juniper_hyper/src/lib.rs | 2 +- juniper_iron/examples/iron_server.rs | 2 +- juniper_iron/src/lib.rs | 2 +- juniper_rocket/examples/rocket_server.rs | 2 +- juniper_rocket/src/lib.rs | 5 +-- .../examples/rocket_server.rs | 2 +- juniper_rocket_async/src/lib.rs | 5 +-- juniper_warp/examples/warp_server.rs | 2 +- juniper_warp/src/lib.rs | 6 +-- 17 files changed, 62 insertions(+), 92 deletions(-) diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 07be7ac9e..92a9648fa 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -1025,10 +1025,9 @@ where let mut fragments = vec![]; for def in document.iter() { - match def { - Definition::Fragment(f) => fragments.push(f), - _ => (), - }; + if let Definition::Fragment(f) = def { + fragments.push(f) + } } let default_variable_values = operation.item.variable_definitions.as_ref().map(|defs| { diff --git a/juniper_actix/examples/actix_server.rs b/juniper_actix/examples/actix_server.rs index b3b7584c3..ab32f315c 100644 --- a/juniper_actix/examples/actix_server.rs +++ b/juniper_actix/examples/actix_server.rs @@ -5,7 +5,7 @@ use std::env; use actix_cors::Cors; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use juniper_actix::{ diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs index bfb86b270..5265e8912 100644 --- a/juniper_actix/src/lib.rs +++ b/juniper_actix/src/lib.rs @@ -489,7 +489,7 @@ mod tests { use juniper::{ futures::stream::StreamExt, http::tests::{run_http_test_suite, HttpIntegration, TestResponse}, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; @@ -666,7 +666,7 @@ mod tests { #[actix_web::rt::test] async fn batch_request_works() { use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; diff --git a/juniper_codegen/src/common/parse/attr.rs b/juniper_codegen/src/common/parse/attr.rs index 4e03b833f..9e42cc975 100644 --- a/juniper_codegen/src/common/parse/attr.rs +++ b/juniper_codegen/src/common/parse/attr.rs @@ -12,7 +12,7 @@ use crate::util::path_eq_single; /// This function is generally used for uniting `proc_macro_attribute` with its body attributes. pub(crate) fn unite( (attr_path, attr_args): (&str, &TokenStream), - attrs: &Vec, + attrs: &[syn::Attribute], ) -> Vec { let mut full_attrs = Vec::with_capacity(attrs.len() + 1); let attr_path = syn::Ident::new(attr_path, Span::call_site()); @@ -28,13 +28,7 @@ pub(crate) fn unite( pub(crate) fn strip(attr_path: &str, attrs: Vec) -> Vec { attrs .into_iter() - .filter_map(|attr| { - if path_eq_single(&attr.path, attr_path) { - None - } else { - Some(attr) - } - }) + .filter(|attr| !path_eq_single(&attr.path, attr_path)) .collect() } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index d18d92bcf..c6576eeca 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -167,24 +167,25 @@ pub fn expand_on_trait( _ => None, }) .is_some(); - let has_default_async_methods = ast - .items - .iter() - .find(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), - _ => false, - }) - .is_some(); + let has_default_async_methods = ast.items.iter().any(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), + _ => false, + }); let ty = if is_trait_object { - Type::TraitObject(TraitObjectType::new( + Type::TraitObject(Box::new(TraitObjectType::new( &ast, &meta, scalar.clone(), context.clone(), - )) + ))) } else { - Type::Enum(EnumType::new(&ast, &meta, &implementers, scalar.clone())) + Type::Enum(Box::new(EnumType::new( + &ast, + &meta, + &implementers, + scalar.clone(), + ))) }; let generated_code = Definition { @@ -342,7 +343,7 @@ enum TraitMethod { /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Downcast(Implementer), + Downcast(Box), } impl TraitMethod { @@ -369,7 +370,7 @@ impl TraitMethod { } if meta.downcast.is_some() { - return Some(Self::Downcast(Self::parse_downcast(method)?)); + return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?))); } Some(Self::Field(Self::parse_field(method, meta)?)) @@ -448,7 +449,7 @@ impl TraitMethod { let mut args_iter = method.sig.inputs.iter_mut(); match args_iter.next().unwrap() { syn::FnArg::Receiver(rcv) => { - if !rcv.reference.is_some() || rcv.mutability.is_some() { + if rcv.reference.is_none() || rcv.mutability.is_some() { return err_invalid_method_receiver(rcv); } } @@ -591,7 +592,8 @@ fn err_disallowed_attr(span: &S, arg: &str) -> Option { ), ) .emit(); - return None; + + None } /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`. @@ -602,7 +604,8 @@ fn err_invalid_method_receiver(span: &S) -> Option { "trait method receiver can only be a shared reference `&self`", ) .emit(); - return None; + + None } /// Emits "no trait method receiver" [`syn::Error`] pointing to the given `span`. @@ -613,7 +616,8 @@ fn err_no_method_receiver(span: &S) -> Option { "trait method should have a shared reference receiver `&self`", ) .emit(); - return None; + + None } /// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given `span`. diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 75608f327..edc772513 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1464,9 +1464,7 @@ impl Implementer { /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name #[must_use] fn method_concrete_type_name_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.downcast.is_none() { - return None; - } + self.downcast.as_ref()?; let ty = &self.ty; let scalar = &self.scalar; @@ -1492,9 +1490,7 @@ impl Implementer { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.downcast.is_none() { - return None; - } + self.downcast.as_ref()?; let ty = &self.ty; let scalar = &self.scalar; @@ -1520,9 +1516,7 @@ impl Implementer { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn method_resolve_into_type_async_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.downcast.is_none() { - return None; - } + self.downcast.as_ref()?; let ty = &self.ty; let scalar = &self.scalar; @@ -1596,7 +1590,7 @@ impl EnumType { fn new( r#trait: &syn::ItemTrait, meta: &TraitMeta, - implers: &Vec, + implers: &[Implementer], scalar: ScalarValueType, ) -> Self { Self { @@ -1778,7 +1772,6 @@ impl EnumType { /// Returns generated code implementing [`From`] trait for this [`EnumType`] from its /// [`EnumType::variants`]. - #[must_use] fn impl_from_tokens(&self) -> impl Iterator + '_ { let enum_ty = &self.ident; let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); @@ -1878,12 +1871,7 @@ impl EnumType { } }; - if self - .trait_methods - .iter() - .find(|sig| sig.asyncness.is_some()) - .is_some() - { + if self.trait_methods.iter().any(|sig| sig.asyncness.is_some()) { let mut ast: syn::ItemImpl = parse_quote! { #impl_tokens }; inject_async_trait( &mut ast.attrs, @@ -2209,13 +2197,13 @@ enum Type { /// [GraphQL interface][1] type implementation as Rust enum. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Enum(EnumType), + Enum(Box), /// [GraphQL interface][1] type implementation as Rust [trait object][2]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - TraitObject(TraitObjectType), + TraitObject(Box), } impl Type { diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 560dea931..74dc6d7e3 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -26,12 +26,10 @@ fn get_first_method_arg( inputs: syn::punctuated::Punctuated, ) -> Option { if let Some(fn_arg) = inputs.first() { - match fn_arg { - syn::FnArg::Typed(pat_type) => match &*pat_type.pat { - syn::Pat::Ident(pat_ident) => return Some(pat_ident.ident.clone()), - _ => (), - }, - _ => (), + if let syn::FnArg::Typed(pat_type) = fn_arg { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + return Some(pat_ident.ident.clone()); + } } } @@ -70,19 +68,11 @@ fn get_enum_type(return_type: &Option) -> Option { } }); - if let Some(generic_type_arg) = generic_type_arg { - match generic_type_arg { - syn::GenericArgument::Type(the_type) => match the_type { - syn::Type::Path(type_path) => { - if let Some(path_segment) = - type_path.path.segments.first() - { - return Some(path_segment.clone()); - } - } - _ => (), - }, - _ => (), + if let Some(syn::GenericArgument::Type(syn::Type::Path(type_path))) = + generic_type_arg + { + if let Some(path_segment) = type_path.path.segments.first() { + return Some(path_segment.clone()); } } } @@ -115,13 +105,10 @@ impl syn::parse::Parse for ScalarCodegenInput { let custom_data_type_is_struct: bool = !parse_custom_scalar_value_impl.generics.params.is_empty(); - match *parse_custom_scalar_value_impl.self_ty { - syn::Type::Path(type_path) => { - if let Some(path_segment) = type_path.path.segments.first() { - impl_for_type = Some(path_segment.clone()); - } + if let syn::Type::Path(type_path) = *parse_custom_scalar_value_impl.self_ty { + if let Some(path_segment) = type_path.path.segments.first() { + impl_for_type = Some(path_segment.clone()); } - _ => (), } for impl_item in parse_custom_scalar_value_impl.items { diff --git a/juniper_hyper/examples/hyper_server.rs b/juniper_hyper/examples/hyper_server.rs index 588369863..912226498 100644 --- a/juniper_hyper/examples/hyper_server.rs +++ b/juniper_hyper/examples/hyper_server.rs @@ -5,7 +5,7 @@ use hyper::{ Body, Method, Response, Server, StatusCode, }; use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 409e8ef7c..7ead57fe1 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -319,7 +319,7 @@ mod tests { }; use juniper::{ http::tests as http_tests, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use reqwest::{self, blocking::Response as ReqwestResponse}; diff --git a/juniper_iron/examples/iron_server.rs b/juniper_iron/examples/iron_server.rs index ee86b0a06..74ce40548 100644 --- a/juniper_iron/examples/iron_server.rs +++ b/juniper_iron/examples/iron_server.rs @@ -8,7 +8,7 @@ use std::env; use iron::prelude::*; use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, }; use juniper_iron::{GraphQLHandler, GraphiQLHandler}; diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 82077d62b..0e91c8a0e 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -421,7 +421,7 @@ mod tests { use juniper::{ http::tests as http_tests, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, }; diff --git a/juniper_rocket/examples/rocket_server.rs b/juniper_rocket/examples/rocket_server.rs index 8a95d7e40..0a55adadd 100644 --- a/juniper_rocket/examples/rocket_server.rs +++ b/juniper_rocket/examples/rocket_server.rs @@ -3,7 +3,7 @@ use rocket::{response::content, State}; use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index 892695437..65c3b4dc6 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -141,8 +141,7 @@ impl GraphQLResponse { /// # use rocket::response::content; /// # use rocket::State; /// # - /// # use juniper::tests::fixtures::starwars::schema::Query; - /// # use juniper::tests::fixtures::starwars::model::Database; + /// # use juniper::tests::fixtures::starwars::schema::{Database, Query}; /// # use juniper::{EmptyMutation, EmptySubscription, FieldError, RootNode, Value}; /// # /// # type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; @@ -422,7 +421,7 @@ mod fromform_tests { mod tests { use juniper::{ http::tests as http_tests, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use rocket::{ diff --git a/juniper_rocket_async/examples/rocket_server.rs b/juniper_rocket_async/examples/rocket_server.rs index a34899965..def21f336 100644 --- a/juniper_rocket_async/examples/rocket_server.rs +++ b/juniper_rocket_async/examples/rocket_server.rs @@ -1,5 +1,5 @@ use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use rocket::{response::content, State}; diff --git a/juniper_rocket_async/src/lib.rs b/juniper_rocket_async/src/lib.rs index c32b1e709..55b2c448b 100644 --- a/juniper_rocket_async/src/lib.rs +++ b/juniper_rocket_async/src/lib.rs @@ -160,8 +160,7 @@ impl GraphQLResponse { /// # use rocket::response::content; /// # use rocket::State; /// # - /// # use juniper::tests::fixtures::starwars::schema::Query; - /// # use juniper::tests::fixtures::starwars::model::Database; + /// # use juniper::tests::fixtures::starwars::schema::{Database, Query}; /// # use juniper::{EmptyMutation, EmptySubscription, FieldError, RootNode, Value}; /// # /// # type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; @@ -456,7 +455,7 @@ mod tests { use juniper::{ http::tests as http_tests, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use rocket::{ diff --git a/juniper_warp/examples/warp_server.rs b/juniper_warp/examples/warp_server.rs index 9eb14e205..d0f1312c0 100644 --- a/juniper_warp/examples/warp_server.rs +++ b/juniper_warp/examples/warp_server.rs @@ -3,7 +3,7 @@ use std::env; use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use warp::{http::Response, Filter}; diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 495e72498..3d270143f 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -602,7 +602,7 @@ mod tests { #[tokio::test] async fn graphql_handler_works_json_post() { use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; @@ -641,7 +641,7 @@ mod tests { #[tokio::test] async fn batch_requests_work() { use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; @@ -696,7 +696,7 @@ mod tests_http_harness { use super::*; use juniper::{ http::tests::{run_http_test_suite, HttpIntegration, TestResponse}, - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use warp::{ From 6ce1259d33d6218fef333d7d22079f3208931b01 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 30 Sep 2020 17:46:55 +0200 Subject: [PATCH 72/79] Fix examples --- examples/actix_subscriptions/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 152cf22c6..2c2c0d7f1 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -4,7 +4,7 @@ use actix_cors::Cors; use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use juniper::{ - tests::fixtures::starwars::{model::Database, schema::Query}, + tests::fixtures::starwars::schema::{Character as _, Database, Query}, DefaultScalarValue, EmptyMutation, FieldError, RootNode, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; @@ -76,11 +76,11 @@ impl Subscription { )) } else { let random_id = rng.gen_range(1000, 1005).to_string(); - let human = context.get_human(&random_id).unwrap(); + let human = context.get_human(&random_id).unwrap().clone(); Ok(RandomHuman { id: human.id().to_owned(), - name: human.name().to_owned(), + name: human.name().unwrap().to_owned(), }) } }); From 357bc2a17c282152a32b02e27609aa24652cb53c Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 30 Sep 2020 19:15:29 +0200 Subject: [PATCH 73/79] Add codegen failure tests, vol. 1 --- .../interface/argument_double_underscored.rs | 19 ++++++++++++ .../argument_double_underscored.stderr | 16 ++++++++++ .../fail/interface/duplicate_fields.rs | 24 +++++++++++++++ .../fail/interface/duplicate_fields.stderr | 0 ...ncast_fn_conflicts_with_downcast_method.rs | 28 ++++++++++++++++++ ...t_fn_conflicts_with_downcast_method.stderr | 8 +++++ .../interface/field_double_underscored.rs | 19 ++++++++++++ .../interface/field_double_underscored.stderr | 16 ++++++++++ .../fail/interface/impl_argument_no_object.rs | 23 --------------- .../interface/impl_argument_no_object.stderr | 27 ----------------- .../impl_argument_no_underscore.rs.disabled | 23 --------------- .../fail/interface/impl_no_fields.rs.disabled | 12 -------- .../impl_no_input_object.rs.disabled | 23 --------------- .../interface/impl_no_underscore.rs.disabled | 23 --------------- .../interface/impl_unqiue_name.rs.disabled | 29 ------------------- .../codegen_fail/fail/interface/no_fields.rs | 15 ++++++++++ .../fail/interface/no_fields.stderr | 7 +++++ .../interface/non_input_object_argument.rs | 21 ++++++++++++++ .../non_input_object_argument.stderr | 16 ++++++++++ .../fail/interface/non_object_implementer.rs | 20 +++++++++++++ .../interface/non_object_implementer.stderr | 17 +++++++++++ .../codegen_fail/fail/interface/wrong_item.rs | 11 +++++++ .../fail/interface/wrong_item.stderr | 7 +++++ .../fail/union/struct_same_type_pretty.stderr | 2 +- 24 files changed, 245 insertions(+), 161 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/duplicate_fields.rs create mode 100644 integration_tests/codegen_fail/fail/interface/duplicate_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.rs create mode 100644 integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/field_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled delete mode 100644 integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled create mode 100644 integration_tests/codegen_fail/fail/interface/no_fields.rs create mode 100644 integration_tests/codegen_fail/fail/interface/no_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/non_input_object_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/non_object_implementer.rs create mode 100644 integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs new file mode 100644 index 000000000..d7227c381 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA {} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self, __num: i32) -> &str { + "funA" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr new file mode 100644 index 000000000..23d9e46ec --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied + --> $DIR/attr_non_input_object_argument.rs:16:1 + | +16 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::IsInputType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied + --> $DIR/attr_non_input_object_argument.rs:16:1 + | +16 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/duplicate_fields.rs b/integration_tests/codegen_fail/fail/interface/duplicate_fields.rs new file mode 100644 index 000000000..f3ae4673e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/duplicate_fields.rs @@ -0,0 +1,24 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA {} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str { + "funA" + } + + #[graphql_interface(name = "id")] + fn id2(&self) -> &str { + "funB" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/duplicate_fields.stderr b/integration_tests/codegen_fail/fail/interface/duplicate_fields.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.rs b/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.rs new file mode 100644 index 000000000..1454e543a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.rs @@ -0,0 +1,28 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA { + fn as_obja(&self) -> Option<&ObjA> { + Some(self) + } +} + +#[graphql_interface(for = ObjA)] +#[graphql_interface(on ObjA = downcast_obja)] +trait Character { + fn id(&self, __num: i32) -> &str { + "funA" + } + + #[graphql_interface(downcast)] + fn as_obja(&self) -> Option<&ObjA>; +} + +fn main() {} + diff --git a/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr b/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr new file mode 100644 index 000000000..97a2c9014 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr @@ -0,0 +1,8 @@ +error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA` + --> $DIR/external_downcast_fn_conflicts_with_downcast_method.rs:24:5 + | +24 | fn as_obja(&self) -> Option<&ObjA>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: use `#[graphql_interface(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs new file mode 100644 index 000000000..43115d327 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA {} + +#[graphql_interface(for = ObjA)] +trait Character { + fn __id(&self) -> &str { + "funA" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr new file mode 100644 index 000000000..23d9e46ec --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied + --> $DIR/attr_non_input_object_argument.rs:16:1 + | +16 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::IsInputType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied + --> $DIR/attr_non_input_object_argument.rs:16:1 + | +16 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs b/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs deleted file mode 100644 index 1ab0e6e17..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[derive(juniper::GraphQLObject)] -#[graphql(scalar = juniper::DefaultScalarValue)] -pub struct ObjA { - test: String, -} - -enum Character { - A(ObjA), -} - -juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { - field id(__test: ObjA) -> &str { - match *self { - Character::A(_) => "funA", - } - } - - instance_resolvers: |_| { - &ObjA => match *self { Character::A(ref h) => Some(h) }, - } -}); - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr b/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr deleted file mode 100644 index 10bf97ff0..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_argument_no_object.stderr +++ /dev/null @@ -1,27 +0,0 @@ -error[E0277]: the trait bound `ObjA: FromInputValue` is not satisfied - --> $DIR/impl_argument_no_object.rs:11:1 - | -11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { -12 | | field id(__test: ObjA) -> &str { -13 | | match *self { -14 | | Character::A(_) => "funA", -... | -20 | | } -21 | | }); - | |___^ the trait `FromInputValue` is not implemented for `ObjA` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: FromInputValue` is not satisfied - --> $DIR/impl_argument_no_object.rs:11:1 - | -11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { -12 | | field id(__test: ObjA) -> &str { -13 | | match *self { -14 | | Character::A(_) => "funA", -... | -20 | | } -21 | | }); - | |___^ the trait `FromInputValue` is not implemented for `ObjA` - | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled deleted file mode 100644 index 65f8117d0..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_argument_no_underscore.rs.disabled +++ /dev/null @@ -1,23 +0,0 @@ -#[derive(juniper::GraphQLObject)] -#[graphql(scalar = juniper::DefaultScalarValue)] -pub struct ObjA { - test: String, -} - -enum Character { - A(ObjA), -} - -juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { - field id(__test: String) -> &str { - match *self { - Character::A(_) => "funA", - } - } - - instance_resolvers: |_| { - &ObjA => match *self { Character::A(ref h) => Some(h) }, - } -}); - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled deleted file mode 100644 index 4af462fa4..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_no_fields.rs.disabled +++ /dev/null @@ -1,12 +0,0 @@ -enum Character {} - -juniper::graphql_interface!(Character: () where Scalar = |&self| { - field id() -> &str { - match *self { - } - } - - instance_resolvers: |_| {} -}); - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled deleted file mode 100644 index e3075f8ac..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_no_input_object.rs.disabled +++ /dev/null @@ -1,23 +0,0 @@ -#[derive(juniper::GraphQLInputObject)] -#[graphql(scalar = juniper::DefaultScalarValue)] -pub struct ObjA { - test: String, -} - -enum Character { - A(ObjA), -} - -juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { - field id() -> &str { - match *self { - Character::A(_) => "funA", - } - } - - instance_resolvers: |_| { - &ObjA => match *self { Character::A(ref h) => Some(h) }, - } -}); - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled deleted file mode 100644 index 1ff44b942..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_no_underscore.rs.disabled +++ /dev/null @@ -1,23 +0,0 @@ -#[derive(juniper::GraphQLObject)] -#[graphql(scalar = juniper::DefaultScalarValue)] -pub struct ObjA { - test: String, -} - -enum Character { - A(ObjA), -} - -juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { - field __id() -> &str { - match *self { - Character::A(_) => "funA", - } - } - - instance_resolvers: |_| { - &ObjA => match *self { Character::A(ref h) => Some(h) }, - } -}); - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled b/integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled deleted file mode 100644 index c0b88b4b8..000000000 --- a/integration_tests/codegen_fail/fail/interface/impl_unqiue_name.rs.disabled +++ /dev/null @@ -1,29 +0,0 @@ -#[derive(juniper::GraphQLObject)] -#[graphql(scalar = juniper::DefaultScalarValue)] -pub struct ObjA { - test: String, -} - -enum Character { - A(ObjA), -} - -juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| { - field id() -> &str { - match *self { - Character::A(_) => "funA", - } - } - - field id() -> &str { - match *self { - Character::A(_) => "funA", - } - } - - instance_resolvers: |_| { - &ObjA => match *self { Character::A(ref h) => Some(h) }, - } -}); - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/no_fields.rs new file mode 100644 index 000000000..115960359 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/no_fields.rs @@ -0,0 +1,15 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA {} + +#[graphql_interface(for = ObjA)] +trait Character {} + +fn main() {} \ No newline at end of file diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/no_fields.stderr new file mode 100644 index 000000000..8cc9588cf --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/no_fields.stderr @@ -0,0 +1,7 @@ +error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only + --> $DIR/attr_wrong_item.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/non_input_object_argument.rs b/integration_tests/codegen_fail/fail/interface/non_input_object_argument.rs new file mode 100644 index 000000000..db6ad3002 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_input_object_argument.rs @@ -0,0 +1,21 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA { + fn id(&self, obj: Self) -> &str { + "funA" + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self, obj: ObjA) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr b/integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr new file mode 100644 index 000000000..ca2f768d6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied + --> $DIR/non_input_object_argument.rs:16:1 + | +16 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::IsInputType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied + --> $DIR/non_input_object_argument.rs:16:1 + | +16 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/non_object_implementer.rs b/integration_tests/codegen_fail/fail/interface/non_object_implementer.rs new file mode 100644 index 000000000..0aba2508d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_object_implementer.rs @@ -0,0 +1,20 @@ +use juniper::{graphql_interface, GraphQLInputObject}; + +#[derive(GraphQLInputObject)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA { + fn id(&self) -> &str { + "funA" + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr b/integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr new file mode 100644 index 000000000..13719edfc --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied + --> $DIR/non_object_implementer.rs:15:1 + | +15 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::GraphQLObjectType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied + --> $DIR/non_object_implementer.rs:15:1 + | +15 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::IsOutputType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.rs b/integration_tests/codegen_fail/fail/interface/wrong_item.rs new file mode 100644 index 000000000..9b37b9429 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item.rs @@ -0,0 +1,11 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +enum Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item.stderr new file mode 100644 index 000000000..8bb40afa5 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item.stderr @@ -0,0 +1,7 @@ +error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only + --> $DIR/wrong_item.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr b/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr index fca94bfb8..9e3152bd2 100644 --- a/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr +++ b/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr @@ -1,4 +1,4 @@ -error: duplicated attribute +error: duplicated attribute argument found --> $DIR/struct_same_type_pretty.rs:5:14 | 5 | #[graphql(on i32 = Character::b)] From f5f72a34e9be8876f3d756b434a31b312f9956ec Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 1 Oct 2020 15:57:11 +0200 Subject: [PATCH 74/79] Add codegen failure tests, vol. 2 --- ...argument.rs => argument_non_input_type.rs} | 0 ....stderr => argument_non_input_type.stderr} | 4 ++-- ...od_conflicts_with_external_downcast_fn.rs} | 0 ...conflicts_with_external_downcast_fn.stderr | 20 ++++++++++++++++ .../downcast_method_wrong_input_args.rs | 24 +++++++++++++++++++ .../downcast_method_wrong_input_args.stderr | 19 +++++++++++++++ .../downcast_method_wrong_return_type.rs | 24 +++++++++++++++++++ .../downcast_method_wrong_return_type.stderr | 19 +++++++++++++++ ...t_fn_conflicts_with_downcast_method.stderr | 8 ------- .../interface/field_non_output_return_type.rs | 24 +++++++++++++++++++ .../field_non_output_return_type.stderr | 8 +++++++ ...uplicate_fields.rs => fields_duplicate.rs} | 0 ..._fields.stderr => fields_duplicate.stderr} | 0 ...nter.rs => implementer_non_object_type.rs} | 0 ...err => implementer_non_object_type.stderr} | 4 ++-- .../implementers_duplicate_pretty.rs | 21 ++++++++++++++++ .../implementers_duplicate_pretty.stderr | 17 +++++++++++++ .../interface/implementers_duplicate_ugly.rs | 23 ++++++++++++++++++ .../implementers_duplicate_ugly.stderr | 21 ++++++++++++++++ .../fail/interface/name_double_underscored.rs | 10 ++++++++ .../interface/name_double_underscored.stderr | 7 ++++++ 21 files changed, 241 insertions(+), 12 deletions(-) rename integration_tests/codegen_fail/fail/interface/{non_input_object_argument.rs => argument_non_input_type.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{non_input_object_argument.stderr => argument_non_input_type.stderr} (89%) rename integration_tests/codegen_fail/fail/interface/{external_downcast_fn_conflicts_with_downcast_method.rs => downcast_method_conflicts_with_external_downcast_fn.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs create mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr rename integration_tests/codegen_fail/fail/interface/{duplicate_fields.rs => fields_duplicate.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{duplicate_fields.stderr => fields_duplicate.stderr} (100%) rename integration_tests/codegen_fail/fail/interface/{non_object_implementer.rs => implementer_non_object_type.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{non_object_implementer.stderr => implementer_non_object_type.stderr} (89%) create mode 100644 integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs create mode 100644 integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs create mode 100644 integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/name_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/non_input_object_argument.rs b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/non_input_object_argument.rs rename to integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr similarity index 89% rename from integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr rename to integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr index ca2f768d6..c4e89fc48 100644 --- a/integration_tests/codegen_fail/fail/interface/non_input_object_argument.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> $DIR/non_input_object_argument.rs:16:1 + --> $DIR/argument_non_input_type.rs:16:1 | 16 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` @@ -8,7 +8,7 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> $DIR/non_input_object_argument.rs:16:1 + --> $DIR/argument_non_input_type.rs:16:1 | 16 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` diff --git a/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.rs rename to integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr new file mode 100644 index 000000000..5e2d905a7 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr @@ -0,0 +1,20 @@ +error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA` + --> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:24:5 + | +24 | fn as_obja(&self) -> Option<&ObjA>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: use `#[graphql_interface(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting + +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:10:6 + | +10 | impl Character for ObjA { + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs new file mode 100644 index 000000000..c99d6dcfb --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs @@ -0,0 +1,24 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[graphql_interface(for = Human)] +trait Character { + fn id(&self) -> i32 { + 0 + } + + #[graphql_interface(downcast)] + fn a(&self, ctx: &(), rand: u8) -> Option<&Human> { + None + } +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct Human { + id: String, +} + +#[graphql_interface] +impl Character for Human {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr new file mode 100644 index 000000000..718a0a251 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr @@ -0,0 +1,19 @@ +error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context` + --> $DIR/downcast_method_wrong_input_args.rs:10:10 + | +10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/downcast_method_wrong_input_args.rs:16:18 + | +16 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/downcast_method_wrong_input_args.rs:22:6 + | +22 | impl Character for Human {} + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs new file mode 100644 index 000000000..a9949a65d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs @@ -0,0 +1,24 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[graphql_interface(for = Human)] +trait Character { + fn id(&self) -> i32 { + 0 + } + + #[graphql_interface(downcast)] + fn a(&self, ctx: &(), rand: u8) -> &Human { + unimplemented!() + } +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct Human { + id: String, +} + +#[graphql_interface] +impl Character for Human {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr new file mode 100644 index 000000000..8c4594950 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr @@ -0,0 +1,19 @@ +error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only + --> $DIR/downcast_method_wrong_return_type.rs:10:40 + | +10 | fn a(&self, ctx: &(), rand: u8) -> &Human { + | ^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/downcast_method_wrong_return_type.rs:16:18 + | +16 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/downcast_method_wrong_return_type.rs:22:6 + | +22 | impl Character for Human {} + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr b/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr deleted file mode 100644 index 97a2c9014..000000000 --- a/integration_tests/codegen_fail/fail/interface/external_downcast_fn_conflicts_with_downcast_method.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA` - --> $DIR/external_downcast_fn_conflicts_with_downcast_method.rs:24:5 - | -24 | fn as_obja(&self) -> Option<&ObjA>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - = note: use `#[graphql_interface(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs new file mode 100644 index 000000000..a860ace35 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs @@ -0,0 +1,24 @@ +use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject}; + +#[derive(GraphQLInputObject)] +pub struct ObjB { + id: i32, +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA {} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> ObjB { + ObjB { id: 34 } + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr new file mode 100644 index 000000000..73b266d3b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> $DIR/field_non_output_return_type.rs:17:1 + | +17 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: required by `juniper::marker::IsOutputType::mark` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/duplicate_fields.rs b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/duplicate_fields.rs rename to integration_tests/codegen_fail/fail/interface/fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/duplicate_fields.stderr b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/duplicate_fields.stderr rename to integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/interface/non_object_implementer.rs b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/non_object_implementer.rs rename to integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr similarity index 89% rename from integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr rename to integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr index 13719edfc..2de9329c7 100644 --- a/integration_tests/codegen_fail/fail/interface/non_object_implementer.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied - --> $DIR/non_object_implementer.rs:15:1 + --> $DIR/implementer_non_object_type.rs:15:1 | 15 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `ObjA` @@ -8,7 +8,7 @@ error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied - --> $DIR/non_object_implementer.rs:15:1 + --> $DIR/implementer_non_object_type.rs:15:1 | 15 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA` diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs new file mode 100644 index 000000000..fbdc8f12e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs @@ -0,0 +1,21 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface] +impl Character for ObjA { + fn id(&self) -> &str { + "funA" + } +} + +#[graphql_interface(for = [ObjA, ObjA])] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..40db273e9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr @@ -0,0 +1,17 @@ +error: duplicated attribute argument found + --> $DIR/implementers_duplicate_pretty.rs:16:34 + | +16 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/implementers_duplicate_pretty.rs:10:6 + | +10 | impl Character for ObjA { + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs new file mode 100644 index 000000000..1acb56c01 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs @@ -0,0 +1,23 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +type ObjAlias = ObjA; + +#[graphql_interface] +impl Character for ObjA { + fn id(&self) -> &str { + "funA" + } +} + +#[graphql_interface(for = [ObjA, ObjAlias])] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr new file mode 100644 index 000000000..b88ef50cf --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr @@ -0,0 +1,21 @@ +error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `ObjA`: + --> $DIR/implementers_duplicate_ugly.rs:18:1 + | +18 | #[graphql_interface(for = [ObjA, ObjAlias])] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `ObjA` + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValue`: + --> $DIR/implementers_duplicate_ugly.rs:18:1 + | +18 | #[graphql_interface(for = [ObjA, ObjAlias])] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValue` + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs new file mode 100644 index 000000000..b233119f1 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs @@ -0,0 +1,10 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait __Character { + fn id(&self) -> &str { + "funA" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr new file mode 100644 index 000000000..de8c6715f --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/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. + --> $DIR/name_double_underscored.rs:4:7 + | +4 | trait __Character { + | ^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema From 9cd63214818fa8394a95d96c4e41d34eaad5b7ed Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 1 Oct 2020 16:39:30 +0200 Subject: [PATCH 75/79] Add codegen failure tests, vol.3 --- .../argument_double_underscored.stderr | 27 ++++++++++--------- ...hod_conflicts_with_external_downcast_fn.rs | 9 ++++--- ...conflicts_with_external_downcast_fn.stderr | 4 +-- .../interface/field_double_underscored.stderr | 27 ++++++++++--------- .../fail/interface/fields_duplicate.stderr | 25 +++++++++++++++++ .../fail/interface/no_fields.stderr | 24 ++++++++++++----- juniper_codegen/src/graphql_interface/attr.rs | 20 ++++++++++++++ juniper_codegen/src/util/mod.rs | 18 +++++++++---- 8 files changed, 113 insertions(+), 41 deletions(-) diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr index 23d9e46ec..f06c385cc 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -1,16 +1,19 @@ -error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> $DIR/attr_non_input_object_argument.rs:16:1 +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> $DIR/argument_double_underscored.rs:14:18 | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` +14 | fn id(&self, __num: i32) -> &str { + | ^^^^^ | - = note: required by `juniper::marker::IsInputType::mark` - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: https://spec.graphql.org/June2018/#sec-Schema -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> $DIR/attr_non_input_object_argument.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/argument_double_underscored.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/argument_double_underscored.rs:10:6 | - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) +10 | impl Character for ObjA {} + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs index 1454e543a..df2e5b85c 100644 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs @@ -8,6 +8,10 @@ pub struct ObjA { #[graphql_interface] impl Character for ObjA { + fn id(&self, _: i32) -> &str { + "funA" + } + fn as_obja(&self) -> Option<&ObjA> { Some(self) } @@ -16,13 +20,10 @@ impl Character for ObjA { #[graphql_interface(for = ObjA)] #[graphql_interface(on ObjA = downcast_obja)] trait Character { - fn id(&self, __num: i32) -> &str { - "funA" - } + fn id(&self, num: i32) -> &str; #[graphql_interface(downcast)] fn as_obja(&self) -> Option<&ObjA>; } fn main() {} - diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr index 5e2d905a7..29124c3ce 100644 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr +++ b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr @@ -1,7 +1,7 @@ error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA` - --> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:24:5 + --> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:26:5 | -24 | fn as_obja(&self) -> Option<&ObjA>; +26 | fn as_obja(&self) -> Option<&ObjA>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr index 23d9e46ec..adebb09f2 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -1,16 +1,19 @@ -error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> $DIR/attr_non_input_object_argument.rs:16:1 +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> $DIR/field_double_underscored.rs:14:8 | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` +14 | fn __id(&self) -> &str { + | ^^^^ | - = note: required by `juniper::marker::IsInputType::mark` - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: https://spec.graphql.org/June2018/#sec-Schema -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> $DIR/attr_non_input_object_argument.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/field_double_underscored.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/field_double_underscored.rs:10:6 | - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) +10 | impl Character for ObjA {} + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr index e69de29bb..db1333863 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr @@ -0,0 +1,25 @@ +error: GraphQL interface must have a different name for each field + --> $DIR/fields_duplicate.rs:13:1 + | +13 | / trait Character { +14 | | fn id(&self) -> &str { +15 | | "funA" +16 | | } +... | +21 | | } +22 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/fields_duplicate.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/fields_duplicate.rs:10:6 + | +10 | impl Character for ObjA {} + | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/no_fields.stderr index 8cc9588cf..367a5e9bc 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/no_fields.stderr @@ -1,7 +1,19 @@ -error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only - --> $DIR/attr_wrong_item.rs:8:1 +error: GraphQL interface must have at least one field + --> $DIR/no_fields.rs:13:1 + | +13 | trait Character {} + | ^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + +error[E0412]: cannot find type `CharacterValue` in this scope + --> $DIR/no_fields.rs:4:18 | -8 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0405]: cannot find trait `Character` in this scope + --> $DIR/no_fields.rs:10:6 + | +10 | impl Character for ObjA {} + | ^^^^^^^^^ not found in this scope diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c6576eeca..6bda00bb6 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -52,6 +52,7 @@ pub fn expand_on_trait( let meta = TraitMeta::from_attrs("graphql_interface", &attrs)?; let trait_ident = &ast.ident; + let trait_span = ast.span(); let name = meta .name @@ -137,6 +138,16 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); + if fields.is_empty() { + ERR.emit_custom(trait_span, "must have at least one field"); + } + + if !all_fields_different(&fields) { + ERR.emit_custom(trait_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + let context = meta .context .as_ref() @@ -657,3 +668,12 @@ fn err_duplicate_downcast( )) .emit() } + +/// Checks whether all [GraphQL interface][1] fields have different names. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +fn all_fields_different(fields: &[Field]) -> bool { + let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect(); + names.dedup(); + names.len() == fields.len() +} diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index c45387820..e597eb26b 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -224,11 +224,18 @@ fn get_doc_attr(attrs: &[Attribute]) -> Option> { pub fn to_camel_case(s: &str) -> String { let mut dest = String::new(); - // handle '_' to be more friendly with the - // _var convention for unused variables - let s_iter = if s.starts_with('_') { &s[1..] } else { s } - .split('_') - .enumerate(); + // Handle `_` and `__` to be more friendly with the `_var` convention for unused variables, and + // GraphQL introspection identifiers. + let s_iter = if s.starts_with("__") { + dest.push_str("__"); + &s[2..] + } else if s.starts_with('_') { + &s[1..] + } else { + s + } + .split('_') + .enumerate(); for (i, part) in s_iter { if i > 0 && part.len() == 1 { @@ -1935,6 +1942,7 @@ mod test { fn test_to_camel_case() { assert_eq!(&to_camel_case("test")[..], "test"); assert_eq!(&to_camel_case("_test")[..], "test"); + assert_eq!(&to_camel_case("__test")[..], "__test"); assert_eq!(&to_camel_case("first_second")[..], "firstSecond"); assert_eq!(&to_camel_case("first_")[..], "first"); assert_eq!(&to_camel_case("a_b_c")[..], "aBC"); From 8563dbced358a06637187cceb231a0fb12d60579 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 1 Oct 2020 17:33:30 +0200 Subject: [PATCH 76/79] Fix codegen failure tests accordingly to latest nightly Rust --- .../fail/interface/implementers_duplicate_ugly.stderr | 2 +- .../codegen_fail/fail/union/enum_same_type_ugly.stderr | 2 +- .../codegen_fail/fail/union/struct_same_type_ugly.stderr | 2 +- .../codegen_fail/fail/union/trait_same_type_ugly.stderr | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr index b88ef50cf..2a62e2eaa 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `ObjA`: +error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`: --> $DIR/implementers_duplicate_ugly.rs:18:1 | 18 | #[graphql_interface(for = [ObjA, ObjAlias])] diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr index f8c1a9f08..f703e72b9 100644 --- a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`: --> $DIR/enum_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr index 08d350828..81bf349a8 100644 --- a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`: --> $DIR/struct_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr index f1cb64857..979c71421 100644 --- a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`: --> $DIR/trait_same_type_ugly.rs:3:1 | 3 | #[graphql_union] From 8919df9add6007b768285412715cf5deb142f0a8 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 5 Oct 2020 15:19:14 +0200 Subject: [PATCH 77/79] Fix codegen when interface has no implementers --- .../src/codegen/interface_attr.rs | 91 +++++++++++++++++++ juniper_codegen/src/graphql_interface/mod.rs | 4 +- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index b03fd20cc..3cf4f198d 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -18,6 +18,97 @@ where ) } +mod no_implers { + use super::*; + + #[graphql_interface] + trait Character { + fn id(&self) -> &str; + } + + #[graphql_interface(dyn = DynHero)] + trait Hero { + fn info(&self) -> &str; + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() + } + + fn hero(&self) -> Box> { + unimplemented!() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + let schema = schema(QueryRoot); + + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn uses_trait_name() { + let schema = schema(QueryRoot); + + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + interface, + ); + + let expected_name: &str = *interface; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + ); + } + } + + #[tokio::test] + async fn has_no_description() { + let schema = schema(QueryRoot); + + for interface in &["Character", "Hero"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + description + }} + }}"#, + interface, + ); + + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } + } +} + mod trivial { use super::*; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index edc772513..94b07e844 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1661,7 +1661,7 @@ impl EnumType { /// Returns [`None`] if this [`EnumType`] is exhaustive. #[must_use] fn non_exhaustive_match_arm_tokens(&self) -> Option { - if self.has_phantom_variant() { + if self.has_phantom_variant() || self.variants.is_empty() { Some(quote! { _ => unreachable!(), }) } else { None @@ -1801,7 +1801,7 @@ impl EnumType { let trait_ident = &self.trait_ident; let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - let var_ty = self.variants.first().unwrap(); + let var_ty = self.variants.first(); let assoc_types = self.trait_types.iter().map(|(ty, ty_gen)| { quote! { From 3b1e270d346dd63ba719b216187154404de970b3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 5 Oct 2020 15:19:52 +0200 Subject: [PATCH 78/79] Fix warnings in book tests --- docs/book/content/advanced/introspection.md | 1 + docs/book/content/advanced/subscriptions.md | 3 +-- docs/book/content/quickstart.md | 1 + docs/book/content/schema/schemas_and_mutations.md | 2 ++ docs/book/content/types/input_objects.md | 2 ++ docs/book/content/types/objects/complex_fields.md | 3 ++- docs/book/content/types/unions.md | 8 ++++++-- 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/book/content/advanced/introspection.md b/docs/book/content/advanced/introspection.md index 279b96b14..9bf7e8956 100644 --- a/docs/book/content/advanced/introspection.md +++ b/docs/book/content/advanced/introspection.md @@ -30,6 +30,7 @@ result can then be converted to JSON for use with tools and libraries such as [graphql-client](https://github.com/graphql-rust/graphql-client): ```rust +# #![allow(unused_variables)] # extern crate juniper; # extern crate serde_json; use juniper::{EmptyMutation, EmptySubscription, FieldResult, IntrospectionFormat}; diff --git a/docs/book/content/advanced/subscriptions.md b/docs/book/content/advanced/subscriptions.md index 162d57fa8..59f145161 100644 --- a/docs/book/content/advanced/subscriptions.md +++ b/docs/book/content/advanced/subscriptions.md @@ -78,6 +78,7 @@ operation returns a [`Future`][Future] with an `Item` value of a `Result { If some custom logic is needed to resolve a [GraphQL union][1] variant, you may specify an external function to do so: ```rust +# #![allow(dead_code)] # extern crate juniper; use juniper::{GraphQLObject, GraphQLUnion}; @@ -132,6 +133,7 @@ impl Character { With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/June2018/#example-f8163). ```rust +# #![allow(dead_code)] # extern crate juniper; use juniper::{GraphQLObject, GraphQLUnion}; @@ -289,6 +291,7 @@ impl Character for Droid { If a context is required in a trait method to resolve a [GraphQL union][1] variant, specify it as an argument. ```rust +# #![allow(unused_variables)] # extern crate juniper; # use std::collections::HashMap; use juniper::{graphql_union, GraphQLObject}; @@ -451,6 +454,7 @@ fn get_droid<'db>(ch: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droi By default, `#[derive(GraphQLUnion)]` and `#[graphql_union]` macros generate code, which is generic over a [`ScalarValue`][2] type. This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`][2] type in its implementation. To resolve such problem, a concrete [`ScalarValue`][2] type should be specified: ```rust +# #![allow(dead_code)] # extern crate juniper; use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion}; From bfa6a4ce778ddd765dba9043690dbbf895a66dc6 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 5 Oct 2020 16:19:29 +0200 Subject: [PATCH 79/79] Describing new interfaces in Book, vol.1 --- docs/book/content/types/interfaces.md | 289 +++++++++++++++++++++++++- docs/book/tests/Cargo.toml | 2 +- 2 files changed, 282 insertions(+), 9 deletions(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 3b43a7396..921cdb9f5 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -1,16 +1,280 @@ -# Interfaces +Interfaces +========== + +[GraphQL interfaces][1] map well to interfaces known from common object-oriented languages such as Java or C#, but Rust, unfortunately, has no concept that maps perfectly to them. The nearest analogue of [GraphQL interfaces][1] are Rust traits, and the main difference is that in GraphQL [interface type][1] serves both as an _abstraction_ and a _boxed value (downcastable to concrete implementers)_, while in Rust, a trait is an _abstraction only_ and _to represent such a boxed value a separate type is required_, like enum or trait object, because Rust trait does not represent a type itself, and so can have no values. This difference imposes some unintuitive and non-obvious corner cases when we try to express [GraphQL interfaces][1] in Rust, but on the other hand gives you full control over which type is backing your interface, and how it's resolved. + +For implementing [GraphQL interfaces][1] Juniper provides `#[graphql_interface]` macro. + -GraphQL interfaces map well to interfaces known from common object-oriented -languages such as Java or C#, but Rust has unfortunately not a concept that maps -perfectly to them. Because of this, defining interfaces in Juniper can require a -little bit of boilerplate code, but on the other hand gives you full control -over which type is backing your interface. -To highlight a couple of different ways you can implement interfaces in Rust, -let's have a look at the same end-result from a few different implementations: ## Traits +Defining a trait is mandatory for defining a [GraphQL interface][1], because this is the _obvious_ way we describe an _abstraction_ in Rust. All [interface][1] fields are defined as computed ones via trait methods. + +```rust +# extern crate juniper; +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn id(&self) -> &str; +} +# +# fn main() {} +``` + +However, to return values of such [interface][1], we should provide its implementers and the Rust type representing a _boxed value of this trait_. The last one can be represented in two flavors: enum and [trait object][2]. + + +### Enum values (default) + +By default, Juniper generates an enum representing the values of the defined [GraphQL interface][1], and names it straightforwardly, `{Interface}Value`. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[graphql_interface(for = Human)] // enumerating all implementers is mandatory +trait Character { + fn id(&self) -> &str; +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] // notice enum name, NOT trait name +struct Human { + id: String, +} +#[graphql_interface] // implementing requires macro attribute too, (°o°)! +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} +# +# fn main() { +let human = Human { id: "human-32".to_owned() }; +// Values type for interface has `From` implementations for all its implementers, +// so we don't need to bother with enum variant names. +let character: CharacterValue = human.into(); +assert_eq!(character.id(), "human-32"); +# } +``` + +Also, enum name can be specified explicitly, if desired. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[graphql_interface(enum = CharaterInterface, for = Human)] +trait Character { + fn id(&self) -> &str; +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharaterInterface)] +struct Human { + id: String, + home_planet: String, +} +#[graphql_interface] +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} +# +# fn main() {} +``` + + +### Trait object values + +If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, that should be specified explicitly in the trait definition. + +Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood. + +> __NOTICE__: +> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it. + +```rust +# extern crate juniper; +# extern crate tokio; +use juniper::{graphql_interface, GraphQLObject}; + +// `dyn` argument accepts the name of type alias for the required trait object, +// and macro generates this alias automatically +#[graphql_interface(dyn = DynCharacter, for = Human)] +trait Character { + async fn id(&self) -> &str; // async fields are supported natively +} + +#[derive(GraphQLObject)] +#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait, +struct Human { // so it may be specified explicitly when required + id: String, +} +#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too +impl Character for Human { + async fn id(&self) -> &str { + &self.id + } +} +# +# #[tokio::main] +# async fn main() { +let human = Human { id: "human-32".to_owned() }; +let character: Box = Box::new(human); +assert_eq!(character.id().await, "human-32"); +# } +``` + + +### Ignoring trait methods + +We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[graphql_interface(for = Human)] +trait Character { + fn id(&self) -> &str; + + #[graphql_interface(ignore)] // or `#[graphql_interface(skip)]`, your choice + fn ignored(&self) -> u32 { 0 } +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +struct Human { + id: String, +} +#[graphql_interface] // implementing requires macro attribute too, (°o°)! +impl Character for Human { + fn id(&self) -> &str { + &self.id + } +} +# +# fn main() {} +``` + + +### Custom context + +If a context is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument. + +```rust +# extern crate juniper; +# use std::collections::HashMap; +use juniper::{graphql_interface, GraphQLObject}; + +struct Database { + humans: HashMap, +} +impl juniper::Context for Database {} + +#[graphql_interface(for = Human)] // look, ma, context type is inferred! \(^o^)/ +trait Character { // while still can be specified via `Context = ...` attribute argument + // If a field argument is named `context` or `ctx`, it's automatically assumed + // as a context argument. + fn id(&self, context: &Database) -> Option<&str>; + + // Otherwise, you may mark it explicitly as a context argument. + fn name(&self, #[graphql_interface(context)] db: &Database) -> Option<&str>; +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue, Context = Database)] +struct Human { + id: String, + name: String, +} +#[graphql_interface] +impl Character for Human { + fn id(&self, db: &Database) -> Option<&str> { + if db.humans.contains_key(&self.id) { + Some(&self.id) + } else { + None + } + } + + fn name(&self, db: &Database) -> Option<&str> { + if db.humans.contains_key(&self.id) { + Some(&self.name) + } else { + None + } + } +} +# +# fn main() {} +``` + + +### Using executor and explicit generic scalar + +If an executor is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument. + +This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`][4] does so. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; + +#[graphql_interface(for = Human, Scalar = S)] // notice specifying scalar as existing type parameter +trait Character { + // If a field argument is named `executor`, it's automatically assumed + // as an executor argument. + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ + + + // Otherwise, you may mark it explicitly as an executor argument. + async fn name<'b>( + &'b self, + #[graphql_interface(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; +} + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<__S>)] +struct Human { + id: String, + name: String, +} +#[graphql_interface(Scalar = S)] +impl Character for Human { + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync, + { + executor.look_ahead().field_name() + } + + async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + where + S: Send + Sync, + { + &self.name + } +} +# +# fn main() {} +``` + + + + + + Traits are maybe the most obvious concept you want to use when building interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll have to manually specify how to convert a trait into a concrete type. This can @@ -213,3 +477,12 @@ juniper::graphql_interface!(Character: () where Scalar = |&self| { # fn main() {} ``` + + + + + +[1]: https://spec.graphql.org/June2018/#sec-Interfaces +[2]: https://doc.rust-lang.org/reference/types/trait-object.html +[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html +[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html \ No newline at end of file diff --git a/docs/book/tests/Cargo.toml b/docs/book/tests/Cargo.toml index 120f1a894..4cf997e47 100644 --- a/docs/book/tests/Cargo.toml +++ b/docs/book/tests/Cargo.toml @@ -12,7 +12,7 @@ juniper_subscriptions = { path = "../../../juniper_subscriptions" } derive_more = "0.99.7" futures = "0.3" -tokio = { version = "0.2", features = ["rt-core", "blocking", "stream", "rt-util"] } +tokio = { version = "0.2", features = ["blocking", "macros", "rt-core", "rt-util", "stream"] } iron = "0.5.0" mount = "0.4.0"