From 01d7cb8bd5e1a65051441b6c52ad058d1e62e864 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 07:44:12 +0300 Subject: [PATCH 001/122] WIP --- .../src/codegen/interface_attr.rs | 518 ++++++++++++++++++ juniper/src/macros/helper/mod.rs | 19 + 2 files changed, 537 insertions(+) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d8869a04a..aaa213288 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -31,6 +31,524 @@ where ) } +mod new { + use super::*; + + const fn fnv1a128(str: &str) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash + } + + const fn is_superset(superset: &[&str], set: &[&str]) -> bool { + const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true + } + + const fn find(set: &[&str], elem: &str) -> bool { + let mut i = 0; + while i < set.len() { + if str_eq(elem, set[i]) { + return true; + } + i += 1; + } + false + } + + if superset.len() < set.len() { + return false; + } + + let mut i = 0; + while i < set.len() { + if !find(superset, set[i]) { + return false; + } + i += 1; + } + + true + } + + trait Type { + const NAME: &'static str; + } + + trait PossibleFragments { + const POSSIBLE_FRAGMENTS: &'static [&'static str]; + } + + impl<'a, T: PossibleFragments> PossibleFragments for &T { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = T::POSSIBLE_FRAGMENTS; + } + + impl Type for String { + const NAME: &'static str = "String"; + } + + impl PossibleFragments for String { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + impl<'a> Type for &'a str { + const NAME: &'static str = "String"; + } + + impl<'a> PossibleFragments for &'a str { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + trait Field { + type Context; + type TypeInfo; + const POSSIBLE_FRAGMENTS: &'static [&'static str]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult; + } + + // #[derive(GraphQLInterface)] + // #[graphql(for(Human, Droid))] + struct Character { + id: String, + } + + impl Type for Character { + const NAME: &'static str = "Character"; + } + + impl PossibleFragments for Character { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[ + ::NAME, + ::NAME, + ::NAME, + ]; + } + + enum CharacterEnumValue { + Human(I1), + Droid(I2), + } + + type CharacterValue = CharacterEnumValue; + + impl From for CharacterValue { + fn from(v: Human) -> Self { + Self::Human(v) + } + } + + impl From for CharacterValue { + fn from(v: Droid) -> Self { + Self::Droid(v) + } + } + + impl Field for CharacterValue { + type Context = (); + type TypeInfo = (); + const POSSIBLE_FRAGMENTS: &'static [&'static str] = + ::POSSIBLE_FRAGMENTS; + + fn call( + &self, + info: &Self::TypeInfo, + args: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + match self { + CharacterValue::Human(v) => { + const _: () = assert!(is_superset( + ::POSSIBLE_FRAGMENTS, + >::POSSIBLE_FRAGMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + CharacterValue::Droid(v) => { + const _: () = assert!(is_superset( + ::POSSIBLE_FRAGMENTS, + >::POSSIBLE_FRAGMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + } + } + } + + #[automatically_derived] + impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + { + fn mark() { + const _: fn() = || { + trait MutuallyExclusive {} + impl MutuallyExclusive for Droid {} + impl MutuallyExclusive for Human {} + }; + } + } + + #[automatically_derived] + impl<__S> ::juniper::marker::IsOutputType<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + { + fn mark() { + <<&str as ::juniper::IntoResolvable< + '_, + __S, + _, + >::Context, + >>::Type as ::juniper::marker::IsOutputType<__S>>::mark(); + >::mark(); + >::mark(); + } + } + + #[automatically_derived] + impl<__S> ::juniper::GraphQLType<__S> for CharacterValue + 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::(info); + let _ = registry.get_type::(info); + let fields = [registry.field_convert::<&str, _, Self::Context>("id", info)]; + registry + .build_interface_type::(info, &fields) + .into_meta() + } + } + + #[allow(deprecated)] + #[automatically_derived] + impl<__S> ::juniper::GraphQLValue<__S> for CharacterValue + 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, + info: &Self::TypeInfo, + field: &str, + args: &::juniper::Arguments<__S>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<__S> { + match field { + "id" => <_ as Field<__S, { fnv1a128("id") }>>::call(self, info, args, executor), + _ => { + return Err(::juniper::FieldError::from({ + format!("Field `{}` not found on type `{}`", field, "CharacterValue",) + })) + } + } + } + fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { + match self { + Self::Human(v) => { + >::concrete_type_name(v, context, info) + } + Self::Droid(v) => { + >::concrete_type_name(v, context, info) + } + } + } + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<__S>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<__S> { + match self { + Self::Human(res) => ::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()), + }), + Self::Droid(res) => ::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()), + }), + } + } + } + + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl<__S> ::juniper::GraphQLValueAsync<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + Self: Sync, + __S: Send + Sync, + { + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<__S>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + match field { + "id" => Box::pin(::juniper::futures::future::ready(<_ as Field< + __S, + { fnv1a128("id") }, + >>::call( + self, info, args, executor, + ))), + _ => Box::pin(async move { + return Err(::juniper::FieldError::from({ + format!("Field `{}` not found on type `{}`", field, "CharacterValue",) + })); + }), + } + } + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + _: Option<&'b [::juniper::Selection<'b, __S>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + match self { + Self::Droid(v) => { + let fut = ::juniper::futures::future::ready(v); + Box::pin(::juniper::futures::FutureExt::then( + fut, + move |res| 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()), + } + }, + )) + } + Self::Human(v) => { + let fut = ::juniper::futures::future::ready(v); + Box::pin(::juniper::futures::FutureExt::then( + fut, + move |res| 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()), + } + }, + )) + } + } + } + } + + // --------- + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + impl Type for Human { + const NAME: &'static str = "Human"; + } + + impl PossibleFragments for Human { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + impl Field for Human { + type Context = (); + type TypeInfo = (); + const POSSIBLE_FRAGMENTS: &'static [&'static str] = + ::POSSIBLE_FRAGMENTS; + + fn call( + &self, + info: &Self::TypeInfo, + _: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + 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(info, &r), + None => Ok(::juniper::Value::null()), + }) + } + } + + // #[derive(GraphQLObject)] + // #[graphql(impl = CharacterValue)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + impl Type for Droid { + const NAME: &'static str = "Droid"; + } + + impl PossibleFragments for Droid { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + impl Field for Droid { + type Context = (); + type TypeInfo = (); + const POSSIBLE_FRAGMENTS: &'static [&'static str] = + <&str as PossibleFragments>::POSSIBLE_FRAGMENTS; + + fn call( + &self, + info: &Self::TypeInfo, + _: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + 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(info, &r), + None => Ok(::juniper::Value::null()), + }) + } + } + + // ----- + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + // -------------- + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + mod no_implers { use super::*; diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 0f81018da..147b3a15e 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -83,3 +83,22 @@ where { Box::pin(future::err(err_unnamed_type(name))) } + +/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in +/// const generics. See [spec] for more info. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +pub const fn fnv1a128(str: &str) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash +} From 205cfcd993269409d5fd1dff0b52e1ac23e12008 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 10:53:06 +0300 Subject: [PATCH 002/122] WIP --- .../src/codegen/interface_attr.rs | 76 ++++++------------- juniper/src/macros/helper/mod.rs | 44 ++++++++++- juniper/src/types/scalars.rs | 8 ++ juniper_codegen/src/derive_scalar_value.rs | 9 +++ juniper_codegen/src/impl_scalar.rs | 9 +++ 5 files changed, 92 insertions(+), 54 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index aaa213288..92cd9a9e7 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -94,38 +94,10 @@ mod new { true } - trait Type { - const NAME: &'static str; - } - - trait PossibleFragments { - const POSSIBLE_FRAGMENTS: &'static [&'static str]; - } - - impl<'a, T: PossibleFragments> PossibleFragments for &T { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = T::POSSIBLE_FRAGMENTS; - } - - impl Type for String { - const NAME: &'static str = "String"; - } - - impl PossibleFragments for String { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; - } - - impl<'a> Type for &'a str { - const NAME: &'static str = "String"; - } - - impl<'a> PossibleFragments for &'a str { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; - } - trait Field { type Context; type TypeInfo; - const POSSIBLE_FRAGMENTS: &'static [&'static str]; + const SUBTYPES: &'static [&'static str]; fn call( &self, @@ -141,15 +113,15 @@ mod new { id: String, } - impl Type for Character { + impl juniper::macros::helper::Type for Character { const NAME: &'static str = "Character"; } - impl PossibleFragments for Character { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[ - ::NAME, - ::NAME, - ::NAME, + impl juniper::macros::helper::SubTypes for Character { + const NAMES: &'static [&'static str] = &[ + >::NAME, + >::NAME, + >::NAME, ]; } @@ -175,8 +147,8 @@ mod new { impl Field for CharacterValue { type Context = (); type TypeInfo = (); - const POSSIBLE_FRAGMENTS: &'static [&'static str] = - ::POSSIBLE_FRAGMENTS; + const SUBTYPES: &'static [&'static str] = + >::NAMES; fn call( &self, @@ -187,16 +159,16 @@ mod new { match self { CharacterValue::Human(v) => { const _: () = assert!(is_superset( - ::POSSIBLE_FRAGMENTS, - >::POSSIBLE_FRAGMENTS, + ::NAMES, + >::SUBTYPES, )); <_ as Field>::call(v, info, args, executor) } CharacterValue::Droid(v) => { const _: () = assert!(is_superset( - ::POSSIBLE_FRAGMENTS, - >::POSSIBLE_FRAGMENTS, + ::NAMES, + >::SUBTYPES, )); <_ as Field>::call(v, info, args, executor) @@ -400,19 +372,19 @@ mod new { home_planet: String, } - impl Type for Human { + impl juniper::macros::helper::Type for Human { const NAME: &'static str = "Human"; } - impl PossibleFragments for Human { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + impl juniper::macros::helper::SubTypes for Human { + const NAMES: &'static [&'static str] = &[>::NAME]; } impl Field for Human { type Context = (); type TypeInfo = (); - const POSSIBLE_FRAGMENTS: &'static [&'static str] = - ::POSSIBLE_FRAGMENTS; + const SUBTYPES: &'static [&'static str] = + >::NAMES; fn call( &self, @@ -429,8 +401,6 @@ mod new { } } - // #[derive(GraphQLObject)] - // #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, @@ -447,19 +417,19 @@ mod new { } } - impl Type for Droid { + impl juniper::macros::helper::Type for Droid { const NAME: &'static str = "Droid"; } - impl PossibleFragments for Droid { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + impl juniper::macros::helper::SubTypes for Droid { + const NAMES: &'static [&'static str] = &[::NAME]; } impl Field for Droid { type Context = (); type TypeInfo = (); - const POSSIBLE_FRAGMENTS: &'static [&'static str] = - <&str as PossibleFragments>::POSSIBLE_FRAGMENTS; + const SUBTYPES: &'static [&'static str] = + <&str as juniper::macros::helper::SubTypes>::NAMES; fn call( &self, diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 147b3a15e..9d5cc0fc1 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,7 +2,7 @@ pub mod subscription; -use std::fmt; +use std::{fmt, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; @@ -102,3 +102,45 @@ pub const fn fnv1a128(str: &str) -> u128 { } hash } + +/// TODO +pub trait Type { + const NAME: &'static str; +} + +impl<'a, S, T: Type + ?Sized> Type for &'a T { + const NAME: &'static str = T::NAME; +} + +impl + ?Sized> Type for Box { + const NAME: &'static str = T::NAME; +} + +impl + ?Sized> Type for Arc { + const NAME: &'static str = T::NAME; +} + +impl + ?Sized> Type for Rc { + const NAME: &'static str = T::NAME; +} + +/// TODO +pub trait SubTypes { + const NAMES: &'static [&'static str]; +} + +impl<'a, S, T: SubTypes + ?Sized> SubTypes for &'a T { + const NAMES: &'static [&'static str] = T::NAMES; +} + +impl + ?Sized> SubTypes for Box { + const NAMES: &'static [&'static str] = T::NAMES; +} + +impl + ?Sized> SubTypes for Arc { + const NAMES: &'static [&'static str] = T::NAMES; +} + +impl + ?Sized> SubTypes for Rc { + const NAMES: &'static [&'static str] = T::NAMES; +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index d55c6ac3d..443240251 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -202,6 +202,14 @@ where }) } +impl crate::macros::helper::Type for str { + const NAME: &'static str = "String"; +} + +impl crate::macros::helper::SubTypes for str { + const NAMES: &'static [&'static str] = &[::NAME]; +} + impl GraphQLType for str where S: ScalarValue, diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index a3c87c674..3ea24dcec 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -224,6 +224,15 @@ fn impl_scalar_struct( impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { } + + impl#impl_generics ::juniper::macros::helper::Type<#scalar> for #ident { + const NAME: &'static str = #name; + } + + impl#impl_generics ::juniper::macros::helper::SubTypes<#scalar> for #ident { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } ); Ok(content) diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 94c8481c3..d4676350c 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -325,6 +325,15 @@ pub fn build_scalar( #from_str_body } } + + impl#generic_type_decl ::juniper::macros::helper::Type<#generic_type> for #impl_for_type { + const NAME: &'static str = #name; + } + + impl#generic_type_decl ::juniper::macros::helper::SubTypes<#generic_type> for #impl_for_type { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } ); Ok(content) From 928cb9c07d6ad9b3629613c6da531d218312f20e Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 14:26:16 +0300 Subject: [PATCH 003/122] WIP (with IsSubtype) --- .../src/codegen/interface_attr.rs | 110 ++++++++++++------ 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 92cd9a9e7..279973990 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -94,10 +94,54 @@ mod new { true } + trait IsSubtype { + fn mark() {} + } + + impl IsSubtype for String {} + impl IsSubtype<&'static str> for String {} + impl IsSubtype for &'static str {} + impl IsSubtype<&'static str> for &'static str {} + + impl IsSubtype> for Vec where T: IsSubtype {} + impl IsSubtype> for T where T: IsSubtype {} + + impl IsSubtype<()> for () {} + impl IsSubtype<(Option,)> for () {} + impl IsSubtype<(Option, Option)> for () {} + impl IsSubtype<(Option, Option, Option)> for () {} + + impl IsSubtype<(S1,)> for (T1,) where T1: IsSubtype {} + impl IsSubtype<(S1, Option)> for (T1,) where T1: IsSubtype {} + impl IsSubtype<(S1, Option, Option)> for (T1,) where T1: IsSubtype {} + + impl IsSubtype<(S1, S2)> for (T1, T2) + where + T1: IsSubtype, + T2: IsSubtype, + { + } + impl IsSubtype<(S1, S2, Option)> for (T1, T2) + where + T1: IsSubtype, + T2: IsSubtype, + { + } + + impl IsSubtype<(S1, S2, S3)> for (T1, T2, T3) + where + T1: IsSubtype, + T2: IsSubtype, + T3: IsSubtype, + { + } + trait Field { type Context; type TypeInfo; - const SUBTYPES: &'static [&'static str]; + type Ret; + type ArgTypes; + const ARG_NAMES: &'static [&'static str]; fn call( &self, @@ -113,17 +157,8 @@ mod new { id: String, } - impl juniper::macros::helper::Type for Character { - const NAME: &'static str = "Character"; - } - - impl juniper::macros::helper::SubTypes for Character { - const NAMES: &'static [&'static str] = &[ - >::NAME, - >::NAME, - >::NAME, - ]; - } + impl IsSubtype for Character {} + impl IsSubtype for Character {} enum CharacterEnumValue { Human(I1), @@ -147,8 +182,9 @@ mod new { impl Field for CharacterValue { type Context = (); type TypeInfo = (); - const SUBTYPES: &'static [&'static str] = - >::NAMES; + type Ret = String; + type ArgTypes = (); + const ARG_NAMES: &'static [&'static str] = &[]; fn call( &self, @@ -158,17 +194,29 @@ mod new { ) -> juniper::ExecutionResult { match self { CharacterValue::Human(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; const _: () = assert!(is_superset( - ::NAMES, - >::SUBTYPES, + >::ARG_NAMES, + >::ARG_NAMES, )); <_ as Field>::call(v, info, args, executor) } CharacterValue::Droid(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; const _: () = assert!(is_superset( - ::NAMES, - >::SUBTYPES, + >::ARG_NAMES, + >::ARG_NAMES, )); <_ as Field>::call(v, info, args, executor) @@ -372,19 +420,12 @@ mod new { home_planet: String, } - impl juniper::macros::helper::Type for Human { - const NAME: &'static str = "Human"; - } - - impl juniper::macros::helper::SubTypes for Human { - const NAMES: &'static [&'static str] = &[>::NAME]; - } - impl Field for Human { type Context = (); type TypeInfo = (); - const SUBTYPES: &'static [&'static str] = - >::NAMES; + type Ret = String; + type ArgTypes = (Option,); + const ARG_NAMES: &'static [&'static str] = &["optional"]; fn call( &self, @@ -417,19 +458,12 @@ mod new { } } - impl juniper::macros::helper::Type for Droid { - const NAME: &'static str = "Droid"; - } - - impl juniper::macros::helper::SubTypes for Droid { - const NAMES: &'static [&'static str] = &[::NAME]; - } - impl Field for Droid { type Context = (); type TypeInfo = (); - const SUBTYPES: &'static [&'static str] = - <&str as juniper::macros::helper::SubTypes>::NAMES; + type Ret = &'static str; + type ArgTypes = (); + const ARG_NAMES: &'static [&'static str] = &[]; fn call( &self, From 3dad1622b209c8b8e1d7d6dd4143fe55080e4d56 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 16:20:22 +0300 Subject: [PATCH 004/122] WIP (It looks like we're onto something) --- .../src/codegen/interface_attr.rs | 130 ++++++++---------- 1 file changed, 55 insertions(+), 75 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 279973990..28fbf3e7e 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -49,55 +49,12 @@ mod new { hash } - const fn is_superset(superset: &[&str], set: &[&str]) -> bool { - const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true - } - - const fn find(set: &[&str], elem: &str) -> bool { - let mut i = 0; - while i < set.len() { - if str_eq(elem, set[i]) { - return true; - } - i += 1; - } - false - } - - if superset.len() < set.len() { - return false; - } - - let mut i = 0; - while i < set.len() { - if !find(superset, set[i]) { - return false; - } - i += 1; - } - - true - } - trait IsSubtype { fn mark() {} } + struct Parameter(T); + impl IsSubtype for String {} impl IsSubtype<&'static str> for String {} impl IsSubtype for &'static str {} @@ -107,32 +64,64 @@ mod new { impl IsSubtype> for T where T: IsSubtype {} impl IsSubtype<()> for () {} - impl IsSubtype<(Option,)> for () {} - impl IsSubtype<(Option, Option)> for () {} - impl IsSubtype<(Option, Option, Option)> for () {} - - impl IsSubtype<(S1,)> for (T1,) where T1: IsSubtype {} - impl IsSubtype<(S1, Option)> for (T1,) where T1: IsSubtype {} - impl IsSubtype<(S1, Option, Option)> for (T1,) where T1: IsSubtype {} + impl IsSubtype<(Option>,)> for () {} + impl + IsSubtype<(Option>, Option>)> for () + { + } + impl + IsSubtype<( + Option>, + Option>, + Option>, + )> for () + { + } - impl IsSubtype<(S1, S2)> for (T1, T2) + impl IsSubtype<(Parameter,)> for (Parameter,) where + T1: IsSubtype + { + } + impl + IsSubtype<(Parameter, Option>)> for (Parameter,) where T1: IsSubtype, - T2: IsSubtype, { } - impl IsSubtype<(S1, S2, Option)> for (T1, T2) + impl + IsSubtype<(Option>, Parameter)> for (Parameter,) where T1: IsSubtype, - T2: IsSubtype, { } - - impl IsSubtype<(S1, S2, S3)> for (T1, T2, T3) + impl + IsSubtype<( + Parameter, + Option>, + Option>, + )> for (Parameter,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Parameter, + Option>, + )> for (Parameter,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Option>, + Parameter, + )> for (Parameter,) where T1: IsSubtype, - T2: IsSubtype, - T3: IsSubtype, { } @@ -141,7 +130,6 @@ mod new { type TypeInfo; type Ret; type ArgTypes; - const ARG_NAMES: &'static [&'static str]; fn call( &self, @@ -183,8 +171,7 @@ mod new { type Context = (); type TypeInfo = (); type Ret = String; - type ArgTypes = (); - const ARG_NAMES: &'static [&'static str] = &[]; + type ArgTypes = (Parameter,); fn call( &self, @@ -200,10 +187,6 @@ mod new { let _ = >::ArgTypes, >>::mark; - const _: () = assert!(is_superset( - >::ARG_NAMES, - >::ARG_NAMES, - )); <_ as Field>::call(v, info, args, executor) } @@ -214,10 +197,6 @@ mod new { let _ = >::ArgTypes, >>::mark; - const _: () = assert!(is_superset( - >::ARG_NAMES, - >::ARG_NAMES, - )); <_ as Field>::call(v, info, args, executor) } @@ -424,8 +403,10 @@ mod new { type Context = (); type TypeInfo = (); type Ret = String; - type ArgTypes = (Option,); - const ARG_NAMES: &'static [&'static str] = &["optional"]; + type ArgTypes = ( + Parameter<&'static str, { fnv1a128("required") }>, + Option>, + ); fn call( &self, @@ -462,8 +443,7 @@ mod new { type Context = (); type TypeInfo = (); type Ret = &'static str; - type ArgTypes = (); - const ARG_NAMES: &'static [&'static str] = &[]; + type ArgTypes = (Parameter,); fn call( &self, From af0448693f5edec766a30f6532f967c17b30b66e Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 21 Dec 2021 12:35:11 +0300 Subject: [PATCH 005/122] WIP (return to const assertions) --- .../src/codegen/interface_attr.rs | 613 +++++++++++++----- .../juniper_tests/src/codegen/object_attr.rs | 2 +- 2 files changed, 441 insertions(+), 174 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 28fbf3e7e..c2f7a3c62 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -34,176 +34,6 @@ where mod new { use super::*; - const fn fnv1a128(str: &str) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash - } - - trait IsSubtype { - fn mark() {} - } - - struct Parameter(T); - - impl IsSubtype for String {} - impl IsSubtype<&'static str> for String {} - impl IsSubtype for &'static str {} - impl IsSubtype<&'static str> for &'static str {} - - impl IsSubtype> for Vec where T: IsSubtype {} - impl IsSubtype> for T where T: IsSubtype {} - - impl IsSubtype<()> for () {} - impl IsSubtype<(Option>,)> for () {} - impl - IsSubtype<(Option>, Option>)> for () - { - } - impl - IsSubtype<( - Option>, - Option>, - Option>, - )> for () - { - } - - impl IsSubtype<(Parameter,)> for (Parameter,) where - T1: IsSubtype - { - } - impl - IsSubtype<(Parameter, Option>)> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<(Option>, Parameter)> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Parameter, - Option>, - Option>, - )> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Parameter, - Option>, - )> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Option>, - Parameter, - )> for (Parameter,) - where - T1: IsSubtype, - { - } - - trait Field { - type Context; - type TypeInfo; - type Ret; - type ArgTypes; - - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult; - } - - // #[derive(GraphQLInterface)] - // #[graphql(for(Human, Droid))] - struct Character { - id: String, - } - - impl IsSubtype for Character {} - impl IsSubtype for Character {} - - enum CharacterEnumValue { - Human(I1), - Droid(I2), - } - - type CharacterValue = CharacterEnumValue; - - impl From for CharacterValue { - fn from(v: Human) -> Self { - Self::Human(v) - } - } - - impl From for CharacterValue { - fn from(v: Droid) -> Self { - Self::Droid(v) - } - } - - impl Field for CharacterValue { - type Context = (); - type TypeInfo = (); - type Ret = String; - type ArgTypes = (Parameter,); - - fn call( - &self, - info: &Self::TypeInfo, - args: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - match self { - CharacterValue::Human(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - <_ as Field>::call(v, info, args, executor) - } - CharacterValue::Droid(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - <_ as Field>::call(v, info, args, executor) - } - } - } - } - #[automatically_derived] impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue where @@ -390,6 +220,419 @@ mod new { } } + impl IsSubtype for String {} + impl IsSubtype<&'static str> for String {} + impl IsSubtype for &'static str {} + impl IsSubtype<&'static str> for &'static str {} + + impl IsSubtype> for Vec where T: IsSubtype {} + impl IsSubtype> for T where T: IsSubtype {} + + impl IsSubtype<()> for () {} + impl IsSubtype<(Option>,)> for () {} + impl + IsSubtype<(Option>, Option>)> for () + { + } + impl + IsSubtype<( + Option>, + Option>, + Option>, + )> for () + { + } + + impl IsSubtype<(Argument,)> for (Argument,) where + T1: IsSubtype + { + } + impl + IsSubtype<(Argument, Option>)> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<(Option>, Argument)> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Argument, + Option>, + Option>, + )> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Argument, + Option>, + )> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Option>, + Argument, + )> for (Argument,) + where + T1: IsSubtype, + { + } + + const _: () = assert!(is_subtype(::N, ::N)); + const _: () = assert!(is_subtype( + as WrappedType>::N, + ::N, + )); + const _: () = assert!(!is_subtype( + > as WrappedType>::N, + ::N, + )); + const _: () = assert!(is_subtype( + > as WrappedType>::N, + as WrappedType>::N, + )); + const _: () = assert!(is_subtype( + >>> as WrappedType>::N, + > as WrappedType>::N, + )); + + const fn fnv1a128(str: &str) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash + } + + const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true + } + + const fn is_superset(superset: &[&str], set: &[&str]) -> bool { + const fn find(set: &[&str], elem: &str) -> bool { + let mut i = 0; + while i < set.len() { + if str_eq(elem, set[i]) { + return true; + } + i += 1; + } + false + } + + if superset.len() < set.len() { + return false; + } + + let mut i = 0; + while i < set.len() { + if !find(superset, set[i]) { + return false; + } + i += 1; + } + + true + } + + const fn is_valid_field_args( + base_interface: &'static [(&'static str, &'static str, u128)], + implementation: &'static [(&'static str, &'static str, u128)], + ) -> bool { + const fn find( + base_interface: &'static [(&'static str, &'static str, u128)], + impl_name: &'static str, + impl_ty: &'static str, + impl_wrap_val: u128, + ) -> bool { + let mut i = 0; + while i < base_interface.len() { + let (base_name, base_ty, base_wrap_val) = base_interface[i]; + if str_eq(impl_name, base_name) { + return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; + } + i += 1; + } + false + } + + if base_interface.len() > implementation.len() { + return false; + } + + let mut i = 0; + let mut successfully_implemented_fields = 0; + while i < implementation.len() { + let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; + if find(base_interface, impl_name, impl_ty, impl_wrap_val) { + successfully_implemented_fields += 1; + } else if impl_wrap_val % 10 != 2 { + // Not an optional field. + return false; + } + i += 1; + } + + successfully_implemented_fields == base_interface.len() + } + + const fn is_subtype(ty: u128, subtype: u128) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + is_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + is_subtype(ty / 10, subtype) + } else { + false + } + } + + trait WrappedType { + /// NonNull - 1 + /// Nullable - 2 + /// List - 3 + /// + /// `[[Int]!] - >>> as WrappedType>::N = 12332` + const N: u128; + } + + impl WrappedType for i32 { + const N: u128 = 1; + } + + impl WrappedType for Option { + const N: u128 = T::N * 10 + 2; + } + + impl WrappedType for Vec { + const N: u128 = T::N * 10 + 3; + } + + impl WrappedType for String { + const N: u128 = 1; + } + + impl<'a> WrappedType for &'a str { + const N: u128 = 1; + } + + trait Type { + const NAME: &'static str; + } + + impl Type for Option { + const NAME: &'static str = T::NAME; + } + + impl Type for Vec { + const NAME: &'static str = T::NAME; + } + + impl Type for String { + const NAME: &'static str = "String"; + } + + impl<'a> Type for &'a str { + const NAME: &'static str = "String"; + } + + impl Type for i32 { + const NAME: &'static str = "Int"; + } + + trait SubTypes { + const NAMES: &'static [&'static str]; + } + + impl SubTypes for Option { + const NAMES: &'static [&'static str] = T::NAMES; + } + + impl SubTypes for Vec { + const NAMES: &'static [&'static str] = T::NAMES; + } + + impl SubTypes for String { + const NAMES: &'static [&'static str] = &[::NAME]; + } + + impl<'a> SubTypes for &'a str { + const NAMES: &'static [&'static str] = &[::NAME]; + } + + impl<'a> SubTypes for i32 { + const NAMES: &'static [&'static str] = &[::NAME]; + } + + trait IsSubtype { + fn mark() {} + } + + struct Argument(T); + + trait Field { + type Context; + type TypeInfo; + type Ret; + type ArgTypes; + const SUB_TYPES: &'static [&'static str]; + const WRAPPED_VALUE: u128; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult; + } + + // #[derive(GraphQLInterface)] + // #[graphql(for(Human, Droid))] + struct Character { + id: Option, + } + + impl Type for CharacterValue { + const NAME: &'static str = "Character"; + } + + impl SubTypes for CharacterValue { + const NAMES: &'static [&'static str] = &[ + ::NAME, + ::NAME, + ::NAME, + ]; + } + + impl IsSubtype for Character {} + impl IsSubtype for Character {} + + enum CharacterEnumValue { + Human(I1), + Droid(I2), + } + + type CharacterValue = CharacterEnumValue; + + impl From for CharacterValue { + fn from(v: Human) -> Self { + Self::Human(v) + } + } + + impl From for CharacterValue { + fn from(v: Droid) -> Self { + Self::Droid(v) + } + } + + impl Field for CharacterValue { + type Context = (); + type TypeInfo = (); + type Ret = String; + type ArgTypes = (Argument,); + const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; + const WRAPPED_VALUE: u128 = as WrappedType>::N; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = + &[("required", "String", 1)]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + match self { + CharacterValue::Human(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; + + const _: () = assert!(is_superset( + as SubTypes>::NAMES, + >::SUB_TYPES, + )); + const _: () = assert!(is_subtype( + as WrappedType>::N, + >::WRAPPED_VALUE, + )); + const _: () = assert!(is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + CharacterValue::Droid(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; + + const _: () = assert!(is_superset( + as SubTypes>::NAMES, + >::SUB_TYPES, + )); + const _: () = assert!(is_subtype( + as WrappedType>::N, + >::WRAPPED_VALUE, + )); + const _: () = assert!(is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + } + } + } + // --------- #[derive(GraphQLObject)] @@ -399,14 +642,26 @@ mod new { home_planet: String, } + impl Type for Human { + const NAME: &'static str = "Human"; + } + + impl SubTypes for Human { + const NAMES: &'static [&'static str] = &[::NAME]; + } + impl Field for Human { type Context = (); type TypeInfo = (); type Ret = String; type ArgTypes = ( - Parameter<&'static str, { fnv1a128("required") }>, - Option>, + Argument<&'static str, { fnv1a128("required") }>, + Option>, ); + const SUB_TYPES: &'static [&'static str] = ::NAMES; + const WRAPPED_VALUE: u128 = ::N; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = + &[("required", "String", 1), ("optional", "String", 12)]; fn call( &self, @@ -439,11 +694,23 @@ mod new { } } + impl Type for Droid { + const NAME: &'static str = "Droid"; + } + + impl SubTypes for Droid { + const NAMES: &'static [&'static str] = &[::NAME]; + } + impl Field for Droid { type Context = (); type TypeInfo = (); type Ret = &'static str; - type ArgTypes = (Parameter,); + type ArgTypes = (Argument,); + const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; + const WRAPPED_VALUE: u128 = as WrappedType>::N; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = + &[("required", "String", 1), ("optional", "Int", 12)]; fn call( &self, diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 9161cc55d..304ba34c5 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -1901,7 +1901,7 @@ mod executor { #[graphql_object(scalar = S: ScalarValue)] impl Human { - async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> &'e str + async fn id<'e, 'r, 'a, S>(&self, executor: &'e Executor<'r, 'a, (), S>) -> &'e str where S: ScalarValue, { From 4ae8c5d064eaf764614c1a91fe14da69a98be9bc Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Dec 2021 08:54:20 +0300 Subject: [PATCH 006/122] Make `chrono` scalars similar to the `time` --- juniper/src/integrations/chrono.rs | 768 +++++++++++++++++++++-------- 1 file changed, 569 insertions(+), 199 deletions(-) diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 55cfd50ed..48cbf4ddb 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -1,44 +1,72 @@ -/*! - -# Supported types - -| Rust Type | JSON Serialization | Notes | -|-------------------------|------------------------|-------------------------------------------| -| `DateTime` | RFC3339 string | | -| `DateTime` | RFC3339 string | | -| `NaiveDate` | YYYY-MM-DD | | -| `NaiveDateTime` | float (unix timestamp) | JSON numbers (i.e. IEEE doubles) are not | -| | | precise enough for nanoseconds. | -| | | Values will be truncated to microsecond | -| | | resolution. | -| `NaiveTime` | H:M:S | Optional. Use the `scalar-naivetime` | -| | | feature. | - -*/ -#![allow(clippy::needless_lifetimes)] -use chrono::prelude::*; +//! GraphQL support for [`chrono`] crate types. +//! +//! # Supported types +//! +//! | Rust type | Format | GraphQL scalar | +//! |-----------------------------------|-----------------------|---------------------| +//! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] | +//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | +//! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | +//! | [`DateTime`]`<`[`FixedOffset`]`>` | [RFC 3339] string | [`DateTime`][s4] | +//! | [`FixedOffset`] | `±hh:mm` | [`UtcOffset`][s5] | +//! +//! [`DateTime`]: chrono::DateTime +//! [`FixedOffset`]: chrono::FixedOffset +//! [`NaiveDate`]: chrono::NaiveDate +//! [`NaiveDateTime`]: chrono::NaiveDateTime +//! [`NaiveTime`]: chrono::NaiveTime +//! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 +//! [s1]: https://graphql-scalars.dev/docs/scalars/date +//! [s2]: https://graphql-scalars.dev/docs/scalars/local-time +//! [s4]: https://graphql-scalars.dev/docs/scalars/date-time +//! [s5]: https://graphql-scalars.dev/docs/scalars/utc-offset + +use std::str::FromStr; + +use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; use crate::{ + graphql_scalar, parser::{ParseError, ScalarToken, Token}, - value::{ParseScalarResult, ParseScalarValue}, + value::ParseScalarResult, Value, }; -#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")] -impl GraphQLScalar for DateTime +/// Format of a [`Date` scalar][1]. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/date +const DATE_FORMAT: &str = "%Y-%m-%d"; + +#[graphql_scalar( + description = "Date", + description = "Date in the proleptic Gregorian calendar (without time \ + zone).\ + \n\n\ + Represents a description of the date (as used for birthdays, + for example). It cannot represent an instant on the \ + time-line.\ + \n\n\ + [`Date` scalar][1] compliant.\ + \n\n\ + See also [`time::Date`][2] for details.\ + \n\n\ + [1]: https://graphql-scalars.dev/docs/scalars/date\n\ + [2]: https://docs.rs/time/*/time/struct.Date.html", + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" +)] +impl GraphQLScalar for NaiveDate where S: ScalarValue, { fn resolve(&self) -> Value { - Value::scalar(self.to_rfc3339()) + Value::scalar(self.format(DATE_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map_err(|e| format!("Failed to parse `DateTimeFixedOffset`: {}", e)) + Self::parse_from_str(s, "%Y-%m-%d").map_err(|e| format!("Invalid `Date`: {}", e)) }) } @@ -51,21 +79,64 @@ where } } -#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")] -impl GraphQLScalar for DateTime +/// Full format of a [`LocalTime` scalar][1]. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/local-time +const LOCAL_TIME_FORMAT: &str = "%H:%M:%S%.3f"; + +/// Format of a [`LocalTime` scalar][1] without milliseconds. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/local-time +const LOCAL_TIME_FORMAT_NO_MILLIS: &str = "%H:%M:%S"; + +/// Format of a [`LocalTime` scalar][1] without seconds. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/local-time +const LOCAL_TIME_FORMAT_NO_SECS: &str = "%H:%M"; + +#[graphql_scalar( + description = "LocalTime", + description = "Clock time within a given date (without time zone) in \ + `HH:mm[:ss[.SSS]]` format.\ + \n\n\ + All minutes are assumed to have exactly 60 seconds; no \ + attempt is made to handle leap seconds (either positive or \ + negative).\ + \n\n\ + [`LocalTime` scalar][1] compliant.\ + \n\n\ + See also [`time::Time`][2] for details.\ + \n\n\ + [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\ + [2]: https://docs.rs/time/*/time/struct.Time.html", + specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" +)] +impl GraphQLScalar for NaiveTime where S: ScalarValue, { fn resolve(&self) -> Value { - Value::scalar(self.to_rfc3339()) + Value::scalar( + if self.nanosecond() == 0 { + self.format(LOCAL_TIME_FORMAT_NO_MILLIS) + } else { + self.format(LOCAL_TIME_FORMAT) + } + .to_string(), + ) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - s.parse::>() - .map_err(|e| format!("Failed to parse `DateTimeUtc`: {}", e)) + // First, try to parse the most used format. + // At the end, try to parse the full format for the parsing + // error to be most informative. + Self::parse_from_str(s, LOCAL_TIME_FORMAT_NO_MILLIS) + .or_else(|_| Self::parse_from_str(s, LOCAL_TIME_FORMAT_NO_SECS)) + .or_else(|_| Self::parse_from_str(s, LOCAL_TIME_FORMAT)) + .map_err(|e| format!("Invalid `LocalTime`: {}", e)) }) } @@ -78,54 +149,80 @@ where } } -// Don't use `Date` as the docs say: -// "[Date] should be considered ambiguous at best, due to the " -// inherent lack of precision required for the time zone resolution. -// For serialization and deserialization uses, it is best to use -// `NaiveDate` instead." -#[crate::graphql_scalar(description = "NaiveDate")] -impl GraphQLScalar for NaiveDate +/// Format of a `LocalDateTime` scalar. +const LOCAL_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; + +#[graphql_scalar( + description = "LocalDateTime", + description = "Combined date and time (without time zone) in `yyyy-MM-dd \ + HH:mm:ss` format.\ + \n\n\ + See also [`time::PrimitiveDateTime`][2] for details.\ + \n\n\ + [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html" +)] +impl GraphQLScalar for NaiveDateTime where S: ScalarValue, { fn resolve(&self) -> Value { - Value::scalar(self.format("%Y-%m-%d").to_string()) + Value::scalar(self.format(LOCAL_DATE_TIME_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - NaiveDate::parse_from_str(s, "%Y-%m-%d") - .map_err(|e| format!("Failed to parse `NaiveDate`: {}", e)) + Self::parse_from_str(s, LOCAL_DATE_TIME_FORMAT) + .map_err(|e| format!("Invalid `LocalDateTime`: {}", e)) }) } fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) + if let ScalarToken::String(s) = value { + Ok(S::from(s.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } } -#[cfg(feature = "scalar-naivetime")] -#[crate::graphql_scalar(description = "NaiveTime")] -impl GraphQLScalar for NaiveTime +/// Format of a [`DateTime` scalar][1]. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/date-time +const DATE_TIME_FORMAT: &str = "%FT%TZ"; + +#[graphql_scalar( + name = "DateTime", + description = "Combined date and time (with time zone) in [RFC 3339][0] \ + format.\ + \n\n\ + Represents a description of an exact instant on the \ + time-line (such as the instant that a user account was \ + created).\ + \n\n\ + [`DateTime` scalar][1] compliant.\ + \n\n\ + See also [`time::OffsetDateTime`][2] for details.\ + \n\n\ + [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ + [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ + [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html", + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" +)] +impl GraphQLScalar for DateTime where S: ScalarValue, { fn resolve(&self) -> Value { - Value::scalar(self.format("%H:%M:%S").to_string()) + Value::scalar(self.naive_utc().format(DATE_TIME_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result, String> { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - NaiveTime::parse_from_str(s, "%H:%M:%S") - .map_err(|e| format!("Failed to parse `NaiveTime`: {}", e)) + DateTime::parse_from_rfc3339(s).map_err(|e| format!("Invalid `DateTime`: {}", e)) }) } @@ -138,197 +235,485 @@ where } } -// JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond -// datetimes. Values will be truncated to microsecond resolution. -#[crate::graphql_scalar(description = "NaiveDateTime")] -impl GraphQLScalar for NaiveDateTime -where - S: ScalarValue, -{ +#[graphql_scalar( + name = "UtcOffset", + description = "Offset from UTC in `±hh:mm` format. See [list of database \ + time zones][0].\ + \n\n\ + [`UtcOffset` scalar][1] compliant.\ + \n\n\ + See also [`time::UtcOffset`][2] for details.\ + \n\n\ + [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\ + [1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\ + [2]: https://docs.rs/time/*/time/struct.UtcOffset.html", + specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset" +)] +impl GraphQLScalar for FixedOffset { fn resolve(&self) -> Value { - Value::scalar(self.timestamp() as f64) + let hh = self.local_minus_utc() / 3600; + let mm = (self.local_minus_utc() % 3600) / 60; + + Value::scalar(format!("{:+}:{}", hh, mm.abs())) } - fn from_input_value(v: &InputValue) -> Result { - v.as_float_value() - .ok_or_else(|| format!("Expected `Float`, found: {}", v)) - .and_then(|f| { - let secs = f as i64; - NaiveDateTime::from_timestamp_opt(secs, 0) - .ok_or_else(|| format!("Out-of-range number of seconds: {}", secs)) + fn from_input_value(v: &InputValue) -> Result { + const ERR_PREFIX: &str = "Invalid `UtcOffset`"; + + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s: &str| { + let (hh, mm) = s + .get(1..=2) + .and_then(|hh| s.get(4..=5).map(|mm| (hh, mm))) + .filter(|_| s.chars().count() == 6) + .ok_or_else(|| { + format!("{}: Expected exactly 6 characters: `±hh:mm`", ERR_PREFIX,) + })?; + + let (hh, mm) = u16::from_str(hh) + .and_then(|hh| u16::from_str(mm).map(|mm| (hh, mm))) + .map_err(|e| format!("{}: {}", ERR_PREFIX, e))?; + let offset = i32::from(hh * 3600 + mm * 60); + + match (s.chars().next(), s.chars().skip(3).next()) { + (Some('+'), Some(':')) => FixedOffset::east_opt(offset), + (Some('-'), Some(':')) => FixedOffset::west_opt(offset), + _ => return Err(format!("{}: Expected format `±hh:mm`", ERR_PREFIX)), + } + .ok_or_else(|| format!("{}: out-of-bound offset", ERR_PREFIX)) }) } fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - >::from_str(value) + if let ScalarToken::String(s) = value { + Ok(S::from(s.to_owned())) + } else { + Err(ParseError::UnexpectedToken(Token::Scalar(value))) + } } } #[cfg(test)] -mod test { - use chrono::prelude::*; - - use crate::{graphql_input_value, FromInputValue, InputValue}; +mod date_test { + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - fn datetime_fixedoffset_test(raw: &'static str) { - let input: InputValue = graphql_input_value!((raw)); - - let parsed: DateTime = FromInputValue::from_input_value(&input).unwrap(); - let expected = DateTime::parse_from_rfc3339(raw).unwrap(); - - assert_eq!(parsed, expected); - } + use super::NaiveDate; #[test] - fn datetime_fixedoffset_from_input_value() { - datetime_fixedoffset_test("2014-11-28T21:00:09+09:00"); + fn parses_correct_input() { + for (raw, expected) in [ + ("1996-12-19", NaiveDate::from_ymd(1996, 12, 19)), + ("1564-01-30", NaiveDate::from_ymd(1564, 01, 30)), + ] { + let input: InputValue = graphql_input_value!((raw)); + let parsed = NaiveDate::from_input_value(&input); + + assert!( + parsed.is_ok(), + "failed to parse `{}`: {}", + raw, + parsed.unwrap_err(), + ); + assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + } } #[test] - fn datetime_fixedoffset_from_input_value_with_z_timezone() { - datetime_fixedoffset_test("2014-11-28T21:00:09Z"); + fn fails_on_invalid_input() { + for input in [ + graphql_input_value!("1996-13-19"), + graphql_input_value!("1564-01-61"), + graphql_input_value!("2021-11-31"), + graphql_input_value!("11-31"), + graphql_input_value!("2021-11"), + graphql_input_value!("2021"), + graphql_input_value!("31"), + graphql_input_value!("i'm not even a date"), + graphql_input_value!(2.32), + graphql_input_value!(1), + graphql_input_value!(null), + graphql_input_value!(false), + ] { + let input: InputValue = input; + let parsed = NaiveDate::from_input_value(&input); + + assert!(parsed.is_err(), "allows input: {:?}", input); + } } #[test] - fn datetime_fixedoffset_from_input_value_with_fractional_seconds() { - datetime_fixedoffset_test("2014-11-28T21:00:09.05+09:00"); + fn formats_correctly() { + for (val, expected) in [ + ( + NaiveDate::from_ymd(1996, 12, 19), + graphql_input_value!("1996-12-19"), + ), + ( + NaiveDate::from_ymd(1564, 01, 30), + graphql_input_value!("1564-01-30"), + ), + ( + NaiveDate::from_ymd(2020, 01, 01), + graphql_input_value!("2020-01-01"), + ), + ] { + let actual: InputValue = val.to_input_value(); + + assert_eq!(actual, expected, "on value: {}", val); + } } +} - fn datetime_utc_test(raw: &'static str) { - let input: InputValue = graphql_input_value!((raw)); +#[cfg(test)] +mod naive_time_test { + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - let parsed: DateTime = FromInputValue::from_input_value(&input).unwrap(); - let expected = DateTime::parse_from_rfc3339(raw) - .unwrap() - .with_timezone(&Utc); + use super::NaiveTime; + + #[test] + fn parses_correct_input() { + for (raw, expected) in [ + ("14:23:43", NaiveTime::from_hms(14, 23, 43)), + ("14:00:00", NaiveTime::from_hms(14, 00, 00)), + ("14:00", NaiveTime::from_hms(14, 00, 00)), + ("14:32", NaiveTime::from_hms(14, 32, 00)), + ("14:00:00.000", NaiveTime::from_hms(14, 00, 00)), + ("14:23:43.345", NaiveTime::from_hms_milli(14, 23, 43, 345)), + ] { + let input: InputValue = graphql_input_value!((raw)); + let parsed = NaiveTime::from_input_value(&input); + + assert!( + parsed.is_ok(), + "failed to parse `{}`: {}", + raw, + parsed.unwrap_err(), + ); + assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + } + } - assert_eq!(parsed, expected); + #[test] + fn fails_on_invalid_input() { + for input in [ + graphql_input_value!("12"), + graphql_input_value!("12:"), + graphql_input_value!("56:34:22"), + graphql_input_value!("23:78:43"), + graphql_input_value!("23:78:"), + graphql_input_value!("23:18:99"), + graphql_input_value!("23:18:22."), + graphql_input_value!("22.03"), + graphql_input_value!("24:00"), + graphql_input_value!("24:00:00"), + graphql_input_value!("24:00:00.000"), + graphql_input_value!("i'm not even a time"), + graphql_input_value!(2.32), + graphql_input_value!(1), + graphql_input_value!(null), + graphql_input_value!(false), + ] { + let input: InputValue = input; + let parsed = NaiveTime::from_input_value(&input); + + assert!(parsed.is_err(), "allows input: {:?}", input); + } } #[test] - fn datetime_utc_from_input_value() { - datetime_utc_test("2014-11-28T21:00:09+09:00") + fn formats_correctly() { + for (val, expected) in [ + ( + NaiveTime::from_hms_micro(1, 2, 3, 4005), + graphql_input_value!("01:02:03.004"), + ), + ( + NaiveTime::from_hms(0, 0, 0), + graphql_input_value!("00:00:00"), + ), + ( + NaiveTime::from_hms(12, 0, 0), + graphql_input_value!("12:00:00"), + ), + ( + NaiveTime::from_hms(1, 2, 3), + graphql_input_value!("01:02:03"), + ), + ] { + let actual: InputValue = val.to_input_value(); + + assert_eq!(actual, expected, "on value: {}", val); + } } +} + +#[cfg(test)] +mod naive_date_time_test { + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; + + use super::{NaiveDate, NaiveDateTime, NaiveTime}; #[test] - fn datetime_utc_from_input_value_with_z_timezone() { - datetime_utc_test("2014-11-28T21:00:09Z") + fn parses_correct_input() { + for (raw, expected) in [ + ( + "1996-12-19 14:23:43", + NaiveDateTime::new( + NaiveDate::from_ymd(1996, 12, 19), + NaiveTime::from_hms(14, 23, 43), + ), + ), + ( + "1564-01-30 14:00:00", + NaiveDateTime::new( + NaiveDate::from_ymd(1564, 1, 30), + NaiveTime::from_hms(14, 00, 00), + ), + ), + ] { + let input: InputValue = graphql_input_value!((raw)); + let parsed = NaiveDateTime::from_input_value(&input); + + assert!( + parsed.is_ok(), + "failed to parse `{}`: {}", + raw, + parsed.unwrap_err(), + ); + assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + } } #[test] - fn datetime_utc_from_input_value_with_fractional_seconds() { - datetime_utc_test("2014-11-28T21:00:09.005+09:00"); + fn fails_on_invalid_input() { + for input in [ + graphql_input_value!("12"), + graphql_input_value!("12:"), + graphql_input_value!("56:34:22"), + graphql_input_value!("56:34:22.000"), + graphql_input_value!("1996-12-19T14:23:43"), + graphql_input_value!("1996-12-19 14:23:43Z"), + graphql_input_value!("1996-12-19 14:23:43.543"), + graphql_input_value!("1996-12-19 14:23"), + graphql_input_value!("1996-12-19 14:23:"), + graphql_input_value!("1996-12-19 23:78:43"), + graphql_input_value!("1996-12-19 23:18:99"), + graphql_input_value!("1996-12-19 24:00:00"), + graphql_input_value!("1996-12-19 99:02:13"), + graphql_input_value!("i'm not even a datetime"), + graphql_input_value!(2.32), + graphql_input_value!(1), + graphql_input_value!(null), + graphql_input_value!(false), + ] { + let input: InputValue = input; + let parsed = NaiveDateTime::from_input_value(&input); + + assert!(parsed.is_err(), "allows input: {:?}", input); + } } #[test] - fn naivedate_from_input_value() { - let input: InputValue = graphql_input_value!("1996-12-19"); - let y = 1996; - let m = 12; - let d = 19; + fn formats_correctly() { + for (val, expected) in [ + ( + NaiveDateTime::new( + NaiveDate::from_ymd(1996, 12, 19), + NaiveTime::from_hms(0, 0, 0), + ), + graphql_input_value!("1996-12-19 00:00:00"), + ), + ( + NaiveDateTime::new( + NaiveDate::from_ymd(1564, 1, 30), + NaiveTime::from_hms(14, 0, 0), + ), + graphql_input_value!("1564-01-30 14:00:00"), + ), + ] { + let actual: InputValue = val.to_input_value(); + + assert_eq!(actual, expected, "on value: {}", val); + } + } +} - let parsed: NaiveDate = FromInputValue::from_input_value(&input).unwrap(); - let expected = NaiveDate::from_ymd(y, m, d); +#[cfg(test)] +mod date_time_test { + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - assert_eq!(parsed, expected); + use super::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; - assert_eq!(parsed.year(), y); - assert_eq!(parsed.month(), m); - assert_eq!(parsed.day(), d); + #[test] + fn parses_correct_input() { + for (raw, expected) in [ + ( + "2014-11-28T21:00:09+09:00", + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(2014, 11, 28), + NaiveTime::from_hms(12, 0, 9), + ), + FixedOffset::east(9 * 3600), + ), + ), + ( + "2014-11-28T21:00:09Z", + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(2014, 11, 28), + NaiveTime::from_hms(21, 0, 9), + ), + FixedOffset::east(0), + ), + ), + ( + "2014-11-28T21:00:09+00:00", + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(2014, 11, 28), + NaiveTime::from_hms(21, 0, 9), + ), + FixedOffset::east(0), + ), + ), + ( + "2014-11-28T21:00:09.05+09:00", + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(2014, 11, 28), + NaiveTime::from_hms_milli(12, 0, 9, 50), + ), + FixedOffset::east(0), + ), + ), + ] { + let input: InputValue = graphql_input_value!((raw)); + let parsed = DateTime::from_input_value(&input); + + assert!( + parsed.is_ok(), + "failed to parse `{}`: {}", + raw, + parsed.unwrap_err(), + ); + assert_eq!(parsed.unwrap(), expected, "input: {}", raw); + } } #[test] - #[cfg(feature = "scalar-naivetime")] - fn naivetime_from_input_value() { - let input: InputValue = graphql_input_value!("21:12:19"); - let [h, m, s] = [21, 12, 19]; - let parsed: NaiveTime = FromInputValue::from_input_value(&input).unwrap(); - let expected = NaiveTime::from_hms(h, m, s); - assert_eq!(parsed, expected); - assert_eq!(parsed.hour(), h); - assert_eq!(parsed.minute(), m); - assert_eq!(parsed.second(), s); + fn fails_on_invalid_input() { + for input in [ + graphql_input_value!("12"), + graphql_input_value!("12:"), + graphql_input_value!("56:34:22"), + graphql_input_value!("56:34:22.000"), + graphql_input_value!("1996-12-1914:23:43"), + graphql_input_value!("1996-12-19 14:23:43Z"), + graphql_input_value!("1996-12-19T14:23:43"), + graphql_input_value!("1996-12-19T14:23:43ZZ"), + graphql_input_value!("1996-12-19T14:23:43.543"), + graphql_input_value!("1996-12-19T14:23"), + graphql_input_value!("1996-12-19T14:23:1"), + graphql_input_value!("1996-12-19T14:23:"), + graphql_input_value!("1996-12-19T23:78:43Z"), + graphql_input_value!("1996-12-19T23:18:99Z"), + graphql_input_value!("1996-12-19T24:00:00Z"), + graphql_input_value!("1996-12-19T99:02:13Z"), + graphql_input_value!("1996-12-19T99:02:13Z"), + graphql_input_value!("1996-12-19T12:02:13+4444444"), + graphql_input_value!("i'm not even a datetime"), + graphql_input_value!(2.32), + graphql_input_value!(1), + graphql_input_value!(null), + graphql_input_value!(false), + ] { + let input: InputValue = input; + let parsed = DateTime::::from_input_value(&input); + + assert!(parsed.is_err(), "allows input: {:?}", input); + } } #[test] - fn naivedatetime_from_input_value() { - let raw = 1_000_000_000_f64; - let input: InputValue = graphql_input_value!((raw)); - - let parsed: NaiveDateTime = FromInputValue::from_input_value(&input).unwrap(); - let expected = NaiveDateTime::from_timestamp_opt(raw as i64, 0).unwrap(); - - assert_eq!(parsed, expected); - assert_eq!(raw, expected.timestamp() as f64); + fn formats_correctly() { + for (val, expected) in [ + ( + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(1996, 12, 19), + NaiveTime::from_hms(0, 0, 0), + ), + FixedOffset::east(0), + ), + graphql_input_value!("1996-12-19T00:00:00Z"), + ), + ( + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(1564, 1, 30), + NaiveTime::from_hms(5, 0, 0), + ), + FixedOffset::east(9 * 3600), + ), + graphql_input_value!("1564-01-30T05:00:00Z"), + ), + ] { + let actual: InputValue = val.to_input_value(); + + assert_eq!(actual, expected, "on value: {}", val); + } } } #[cfg(test)] mod integration_test { - use chrono::{prelude::*, Utc}; - use crate::{ - graphql_object, graphql_value, graphql_vars, + execute, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; + use super::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; + #[tokio::test] - async fn test_serialization() { + async fn serializes() { struct Root; #[graphql_object] - #[cfg(feature = "scalar-naivetime")] impl Root { - fn example_naive_date() -> NaiveDate { + fn date() -> NaiveDate { NaiveDate::from_ymd(2015, 3, 14) } - fn example_naive_date_time() -> NaiveDateTime { - NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11) - } - fn example_naive_time() -> NaiveTime { + + fn local_time() -> NaiveTime { NaiveTime::from_hms(16, 7, 8) } - fn example_date_time_fixed_offset() -> DateTime { - DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() - } - fn example_date_time_utc() -> DateTime { - Utc.timestamp(61, 0) - } - } - #[graphql_object] - #[cfg(not(feature = "scalar-naivetime"))] - impl Root { - fn example_naive_date() -> NaiveDate { - NaiveDate::from_ymd(2015, 3, 14) - } - fn example_naive_date_time() -> NaiveDateTime { - NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11) - } - fn example_date_time_fixed_offset() -> DateTime { - DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() + fn local_date_time() -> NaiveDateTime { + NaiveDateTime::new( + NaiveDate::from_ymd(2016, 7, 8), + NaiveTime::from_hms(9, 10, 11), + ) } - fn example_date_time_utc() -> DateTime { - Utc.timestamp(61, 0) + + fn date_time() -> DateTime { + DateTime::::from_utc( + NaiveDateTime::new( + NaiveDate::from_ymd(1996, 12, 20), + NaiveTime::from_hms(0, 39, 57), + ), + FixedOffset::west(8 * 3600), + ) } } - #[cfg(feature = "scalar-naivetime")] - let doc = r#"{ - exampleNaiveDate, - exampleNaiveDateTime, - exampleNaiveTime, - exampleDateTimeFixedOffset, - exampleDateTimeUtc, - }"#; - - #[cfg(not(feature = "scalar-naivetime"))] - let doc = r#"{ - exampleNaiveDate, - exampleNaiveDateTime, - exampleDateTimeFixedOffset, - exampleDateTimeUtc, + const DOC: &str = r#"{ + date + localTime + localDateTime + dateTime, }"#; let schema = RootNode::new( @@ -337,32 +722,17 @@ mod integration_test { EmptySubscription::<()>::new(), ); - let (result, errs) = crate::execute(doc, None, &schema, &graphql_vars! {}, &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - #[cfg(feature = "scalar-naivetime")] - assert_eq!( - result, - graphql_value!({ - "exampleNaiveDate": "2015-03-14", - "exampleNaiveDateTime": 1_467_969_011.0, - "exampleNaiveTime": "16:07:08", - "exampleDateTimeFixedOffset": "1996-12-19T16:39:57-08:00", - "exampleDateTimeUtc": "1970-01-01T00:01:01+00:00", - }), - ); - #[cfg(not(feature = "scalar-naivetime"))] assert_eq!( - result, - graphql_value!({ - "exampleNaiveDate": "2015-03-14", - "exampleNaiveDateTime": 1_467_969_011.0, - "exampleDateTimeFixedOffset": "1996-12-19T16:39:57-08:00", - "exampleDateTimeUtc": "1970-01-01T00:01:01+00:00", - }), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({ + "date": "2015-03-14", + "localTime": "16:07:08", + "localDateTime": "2016-07-08 09:10:11", + "dateTime": "1996-12-20T00:39:57Z", + }), + vec![], + )), ); } } From 279ae2206579dba2dcb4382cdc72f082a9692426 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Dec 2021 09:00:51 +0300 Subject: [PATCH 007/122] Remove FixedOffset scalar --- juniper/src/integrations/chrono.rs | 61 ------------------------------ 1 file changed, 61 deletions(-) diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 48cbf4ddb..fdf928d08 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -21,8 +21,6 @@ //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time //! [s5]: https://graphql-scalars.dev/docs/scalars/utc-offset -use std::str::FromStr; - use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; use crate::{ @@ -235,65 +233,6 @@ where } } -#[graphql_scalar( - name = "UtcOffset", - description = "Offset from UTC in `±hh:mm` format. See [list of database \ - time zones][0].\ - \n\n\ - [`UtcOffset` scalar][1] compliant.\ - \n\n\ - See also [`time::UtcOffset`][2] for details.\ - \n\n\ - [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\ - [1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\ - [2]: https://docs.rs/time/*/time/struct.UtcOffset.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset" -)] -impl GraphQLScalar for FixedOffset { - fn resolve(&self) -> Value { - let hh = self.local_minus_utc() / 3600; - let mm = (self.local_minus_utc() % 3600) / 60; - - Value::scalar(format!("{:+}:{}", hh, mm.abs())) - } - - fn from_input_value(v: &InputValue) -> Result { - const ERR_PREFIX: &str = "Invalid `UtcOffset`"; - - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s: &str| { - let (hh, mm) = s - .get(1..=2) - .and_then(|hh| s.get(4..=5).map(|mm| (hh, mm))) - .filter(|_| s.chars().count() == 6) - .ok_or_else(|| { - format!("{}: Expected exactly 6 characters: `±hh:mm`", ERR_PREFIX,) - })?; - - let (hh, mm) = u16::from_str(hh) - .and_then(|hh| u16::from_str(mm).map(|mm| (hh, mm))) - .map_err(|e| format!("{}: {}", ERR_PREFIX, e))?; - let offset = i32::from(hh * 3600 + mm * 60); - - match (s.chars().next(), s.chars().skip(3).next()) { - (Some('+'), Some(':')) => FixedOffset::east_opt(offset), - (Some('-'), Some(':')) => FixedOffset::west_opt(offset), - _ => return Err(format!("{}: Expected format `±hh:mm`", ERR_PREFIX)), - } - .ok_or_else(|| format!("{}: out-of-bound offset", ERR_PREFIX)) - }) - } - - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } -} - #[cfg(test)] mod date_test { use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; From bba4af844128c70a8414f99f00e8d0c6ce0a133b Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 23 Dec 2021 09:28:42 +0300 Subject: [PATCH 008/122] Corrections --- docs/book/content/types/scalars.md | 2 +- juniper/CHANGELOG.md | 2 ++ juniper/Cargo.toml | 1 - juniper/src/integrations/chrono.rs | 31 +++++++++++++++--------------- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index c8382833f..188b0e7f5 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -35,7 +35,7 @@ Juniper has built-in support for a few additional types from common third party crates. They are enabled via features that are on by default. * uuid::Uuid -* chrono::DateTime +* chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime} * time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset} * url::Url * bson::oid::ObjectId diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 3ab147d65..c5ad96206 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -10,6 +10,8 @@ - Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) +- Change [`chrono` crate](https://docs.rs/chrono) GraphQL scalars according to the [graphql-scalars.dev](https://graphql-scalars.dev). ([#1010](https://github.com/graphql-rust/juniper/pull/1010)) +- Remove `scalar-naivetime` feature. ([#1010](https://github.com/graphql-rust/juniper/pull/1010)) ## Features diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index fc41812ba..7cc9b57d6 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -28,7 +28,6 @@ default = [ ] expose-test-schema = ["anyhow", "serde_json"] graphql-parser-integration = ["graphql-parser"] -scalar-naivetime = [] schema-language = ["graphql-parser-integration"] [dependencies] diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index fdf928d08..2d4bd1b0d 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -2,13 +2,12 @@ //! //! # Supported types //! -//! | Rust type | Format | GraphQL scalar | -//! |-----------------------------------|-----------------------|---------------------| -//! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] | -//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | -//! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | -//! | [`DateTime`]`<`[`FixedOffset`]`>` | [RFC 3339] string | [`DateTime`][s4] | -//! | [`FixedOffset`] | `±hh:mm` | [`UtcOffset`][s5] | +//! | Rust type | Format | GraphQL scalar | +//! |-----------------------------------|-----------------------|-------------------| +//! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] | +//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | +//! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | +//! | [`DateTime`]`<`[`FixedOffset`]`>` | [RFC 3339] string | [`DateTime`][s4] | //! //! [`DateTime`]: chrono::DateTime //! [`FixedOffset`]: chrono::FixedOffset @@ -46,10 +45,10 @@ const DATE_FORMAT: &str = "%Y-%m-%d"; \n\n\ [`Date` scalar][1] compliant.\ \n\n\ - See also [`time::Date`][2] for details.\ + See also [`chrono::NaiveDate`][2] for details.\ \n\n\ [1]: https://graphql-scalars.dev/docs/scalars/date\n\ - [2]: https://docs.rs/time/*/time/struct.Date.html", + [2]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html", specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" )] impl GraphQLScalar for NaiveDate @@ -103,10 +102,10 @@ const LOCAL_TIME_FORMAT_NO_SECS: &str = "%H:%M"; \n\n\ [`LocalTime` scalar][1] compliant.\ \n\n\ - See also [`time::Time`][2] for details.\ + See also [`chrono::NaiveTime`][2] for details.\ \n\n\ [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\ - [2]: https://docs.rs/time/*/time/struct.Time.html", + [2]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveTime.html", specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" )] impl GraphQLScalar for NaiveTime @@ -155,9 +154,9 @@ const LOCAL_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; description = "Combined date and time (without time zone) in `yyyy-MM-dd \ HH:mm:ss` format.\ \n\n\ - See also [`time::PrimitiveDateTime`][2] for details.\ + See also [`chrono::NaiveDateTime`][1] for details.\ \n\n\ - [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html" + [1]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDateTime.html" )] impl GraphQLScalar for NaiveDateTime where @@ -201,11 +200,13 @@ const DATE_TIME_FORMAT: &str = "%FT%TZ"; \n\n\ [`DateTime` scalar][1] compliant.\ \n\n\ - See also [`time::OffsetDateTime`][2] for details.\ + See also [`chrono::DateTime`][2]`<`[`FixedOffset`][3]`>` \ + for details.\ \n\n\ [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ - [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html", + [2]: https://docs.rs/chrono/*/chrono/struct.DateTime.html\n\", + [3]: https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html", specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" )] impl GraphQLScalar for DateTime From 15e527fb564096f2c1acab11f6f2eba378c891a6 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 09:51:56 +0300 Subject: [PATCH 009/122] It's alive! --- .../src/codegen/interface_attr.rs | 527 ++++-------------- juniper/src/macros/helper/mod.rs | 282 +++++++++- juniper/src/types/scalars.rs | 10 +- juniper_codegen/src/common/field/mod.rs | 107 ++++ juniper_codegen/src/derive_scalar_value.rs | 16 +- juniper_codegen/src/graphql_interface/mod.rs | 34 ++ juniper_codegen/src/graphql_object/mod.rs | 49 ++ juniper_codegen/src/graphql_union/mod.rs | 36 ++ juniper_codegen/src/impl_scalar.rs | 16 +- juniper_codegen/src/util/mod.rs | 41 ++ 10 files changed, 670 insertions(+), 448 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index c2f7a3c62..6b8a32877 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -109,7 +109,10 @@ mod new { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<__S> { match field { - "id" => <_ as Field<__S, { fnv1a128("id") }>>::call(self, info, args, executor), + "id" => <_ as juniper::macros::helper::Field< + __S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(self, info, args, executor), _ => { return Err(::juniper::FieldError::from({ format!("Field `{}` not found on type `{}`", field, "CharacterValue",) @@ -165,12 +168,12 @@ mod new { executor: &'b ::juniper::Executor, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { match field { - "id" => Box::pin(::juniper::futures::future::ready(<_ as Field< - __S, - { fnv1a128("id") }, - >>::call( - self, info, args, executor, - ))), + "id" => Box::pin(::juniper::futures::future::ready( + <_ as juniper::macros::helper::Field< + __S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(self, info, args, executor), + )), _ => Box::pin(async move { return Err(::juniper::FieldError::from({ format!("Field `{}` not found on type `{}`", field, "CharacterValue",) @@ -220,132 +223,30 @@ mod new { } } - impl IsSubtype for String {} - impl IsSubtype<&'static str> for String {} - impl IsSubtype for &'static str {} - impl IsSubtype<&'static str> for &'static str {} - - impl IsSubtype> for Vec where T: IsSubtype {} - impl IsSubtype> for T where T: IsSubtype {} - - impl IsSubtype<()> for () {} - impl IsSubtype<(Option>,)> for () {} - impl - IsSubtype<(Option>, Option>)> for () - { - } - impl - IsSubtype<( - Option>, - Option>, - Option>, - )> for () - { + enum CharacterEnumValue { + Human(I1), + Droid(I2), } - impl IsSubtype<(Argument,)> for (Argument,) where - T1: IsSubtype - { - } - impl - IsSubtype<(Argument, Option>)> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<(Option>, Argument)> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Argument, - Option>, - Option>, - )> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Argument, - Option>, - )> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Option>, - Argument, - )> for (Argument,) - where - T1: IsSubtype, - { - } + type CharacterValue = CharacterEnumValue; - const _: () = assert!(is_subtype(::N, ::N)); - const _: () = assert!(is_subtype( - as WrappedType>::N, - ::N, - )); - const _: () = assert!(!is_subtype( - > as WrappedType>::N, - ::N, - )); - const _: () = assert!(is_subtype( - > as WrappedType>::N, - as WrappedType>::N, - )); - const _: () = assert!(is_subtype( - >>> as WrappedType>::N, - > as WrappedType>::N, - )); - - const fn fnv1a128(str: &str) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; + impl From for CharacterValue { + fn from(v: Human) -> Self { + Self::Human(v) } - hash } - const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; + impl From for CharacterValue { + fn from(v: Droid) -> Self { + Self::Droid(v) } - - true } const fn is_superset(superset: &[&str], set: &[&str]) -> bool { const fn find(set: &[&str], elem: &str) -> bool { let mut i = 0; while i < set.len() { - if str_eq(elem, set[i]) { + if juniper::macros::helper::str_eq(elem, set[i]) { return true; } i += 1; @@ -368,163 +269,7 @@ mod new { true } - const fn is_valid_field_args( - base_interface: &'static [(&'static str, &'static str, u128)], - implementation: &'static [(&'static str, &'static str, u128)], - ) -> bool { - const fn find( - base_interface: &'static [(&'static str, &'static str, u128)], - impl_name: &'static str, - impl_ty: &'static str, - impl_wrap_val: u128, - ) -> bool { - let mut i = 0; - while i < base_interface.len() { - let (base_name, base_ty, base_wrap_val) = base_interface[i]; - if str_eq(impl_name, base_name) { - return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; - } - i += 1; - } - false - } - - if base_interface.len() > implementation.len() { - return false; - } - - let mut i = 0; - let mut successfully_implemented_fields = 0; - while i < implementation.len() { - let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; - if find(base_interface, impl_name, impl_ty, impl_wrap_val) { - successfully_implemented_fields += 1; - } else if impl_wrap_val % 10 != 2 { - // Not an optional field. - return false; - } - i += 1; - } - - successfully_implemented_fields == base_interface.len() - } - - const fn is_subtype(ty: u128, subtype: u128) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; - - if ty_current == subtype_current { - if ty_current == 1 { - true - } else { - is_subtype(ty / 10, subtype / 10) - } - } else if ty_current == 2 { - is_subtype(ty / 10, subtype) - } else { - false - } - } - - trait WrappedType { - /// NonNull - 1 - /// Nullable - 2 - /// List - 3 - /// - /// `[[Int]!] - >>> as WrappedType>::N = 12332` - const N: u128; - } - - impl WrappedType for i32 { - const N: u128 = 1; - } - - impl WrappedType for Option { - const N: u128 = T::N * 10 + 2; - } - - impl WrappedType for Vec { - const N: u128 = T::N * 10 + 3; - } - - impl WrappedType for String { - const N: u128 = 1; - } - - impl<'a> WrappedType for &'a str { - const N: u128 = 1; - } - - trait Type { - const NAME: &'static str; - } - - impl Type for Option { - const NAME: &'static str = T::NAME; - } - - impl Type for Vec { - const NAME: &'static str = T::NAME; - } - - impl Type for String { - const NAME: &'static str = "String"; - } - - impl<'a> Type for &'a str { - const NAME: &'static str = "String"; - } - - impl Type for i32 { - const NAME: &'static str = "Int"; - } - - trait SubTypes { - const NAMES: &'static [&'static str]; - } - - impl SubTypes for Option { - const NAMES: &'static [&'static str] = T::NAMES; - } - - impl SubTypes for Vec { - const NAMES: &'static [&'static str] = T::NAMES; - } - - impl SubTypes for String { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl<'a> SubTypes for &'a str { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl<'a> SubTypes for i32 { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - trait IsSubtype { - fn mark() {} - } - - struct Argument(T); - - trait Field { - type Context; - type TypeInfo; - type Ret; - type ArgTypes; - const SUB_TYPES: &'static [&'static str]; - const WRAPPED_VALUE: u128; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)]; - - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult; - } + // -------------- // #[derive(GraphQLInterface)] // #[graphql(for(Human, Droid))] @@ -532,49 +277,45 @@ mod new { id: Option, } - impl Type for CharacterValue { - const NAME: &'static str = "Character"; + impl juniper::macros::helper::BaseType for CharacterValue + where + S: ScalarValue, + { + const NAME: juniper::macros::helper::Type = "Character"; } - impl SubTypes for CharacterValue { - const NAMES: &'static [&'static str] = &[ - ::NAME, - ::NAME, - ::NAME, + impl juniper::macros::helper::BaseSubTypes for CharacterValue + where + S: ScalarValue, + { + const NAMES: juniper::macros::helper::Types = &[ + >::NAME, + >::NAME, + >::NAME, ]; } - impl IsSubtype for Character {} - impl IsSubtype for Character {} - - enum CharacterEnumValue { - Human(I1), - Droid(I2), + impl juniper::macros::helper::WrappedType for CharacterValue { + const VALUE: u128 = 1; } - type CharacterValue = CharacterEnumValue; - - impl From for CharacterValue { - fn from(v: Human) -> Self { - Self::Human(v) - } - } - - impl From for CharacterValue { - fn from(v: Droid) -> Self { - Self::Droid(v) - } - } - - impl Field for CharacterValue { + impl + juniper::macros::helper::Field + for CharacterValue + { type Context = (); type TypeInfo = (); - type Ret = String; - type ArgTypes = (Argument,); - const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; - const WRAPPED_VALUE: u128 = as WrappedType>::N; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = - &[("required", "String", 1)]; + const TYPE: juniper::macros::helper::Type = + as juniper::macros::helper::BaseType>::NAME; + const SUB_TYPES: juniper::macros::helper::Types = + as juniper::macros::helper::BaseSubTypes>::NAMES; + const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = + as juniper::macros::helper::WrappedType>::VALUE; + const ARGUMENTS: &'static [( + juniper::macros::helper::Name, + juniper::macros::helper::Type, + juniper::macros::helper::WrappedValue, + )] = &[("required", "String", 1)]; fn call( &self, @@ -584,50 +325,62 @@ mod new { ) -> juniper::ExecutionResult { match self { CharacterValue::Human(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - const _: () = assert!(is_superset( - as SubTypes>::NAMES, - >::SUB_TYPES, - )); - const _: () = assert!(is_subtype( - as WrappedType>::N, - >::WRAPPED_VALUE, + const _: () = assert!(juniper::macros::helper::is_subtype( + as juniper::macros::helper::BaseSubTypes>::NAMES, + as juniper::macros::helper::WrappedType>::VALUE, + >::TYPE, + >::WRAPPED_VALUE, )); - const _: () = assert!(is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, + const _: () = assert!(juniper::macros::helper::is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, )); - <_ as Field>::call(v, info, args, executor) + <_ as juniper::macros::helper::Field< + S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(v, info, args, executor) } CharacterValue::Droid(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - const _: () = assert!(is_superset( - as SubTypes>::NAMES, - >::SUB_TYPES, + const _: () = assert!(juniper::macros::helper::is_subtype( + as juniper::macros::helper::BaseSubTypes>::NAMES, + as juniper::macros::helper::WrappedType>::VALUE, + >::TYPE, + >::WRAPPED_VALUE, )); - const _: () = assert!(is_subtype( - as WrappedType>::N, - >::WRAPPED_VALUE, - )); - const _: () = assert!(is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, + const _: () = assert!(juniper::macros::helper::is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, )); - <_ as Field>::call(v, info, args, executor) + <_ as juniper::macros::helper::Field< + S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(v, info, args, executor) } } } @@ -635,46 +388,19 @@ mod new { // --------- - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - impl Type for Human { - const NAME: &'static str = "Human"; - } - - impl SubTypes for Human { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl Field for Human { - type Context = (); - type TypeInfo = (); - type Ret = String; - type ArgTypes = ( - Argument<&'static str, { fnv1a128("required") }>, - Option>, - ); - const SUB_TYPES: &'static [&'static str] = ::NAMES; - const WRAPPED_VALUE: u128 = ::N; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = - &[("required", "String", 1), ("optional", "String", 12)]; - - fn call( - &self, - info: &Self::TypeInfo, - _: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - let res = &self.id; + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self, _required: String, _optional: Option) -> &str { + &self.id + } - ::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()), - }) + fn home_planet(&self) -> &str { + &self.home_planet } } @@ -685,7 +411,7 @@ mod new { #[graphql_object(impl = CharacterValue)] impl Droid { - fn id(&self) -> &str { + fn id(&self, _required: String) -> &str { &self.id } @@ -694,39 +420,6 @@ mod new { } } - impl Type for Droid { - const NAME: &'static str = "Droid"; - } - - impl SubTypes for Droid { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl Field for Droid { - type Context = (); - type TypeInfo = (); - type Ret = &'static str; - type ArgTypes = (Argument,); - const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; - const WRAPPED_VALUE: u128 = as WrappedType>::N; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = - &[("required", "String", 1), ("optional", "Int", 12)]; - - fn call( - &self, - info: &Self::TypeInfo, - _: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - 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(info, &r), - None => Ok(::juniper::Value::null()), - }) - } - } - // ----- #[derive(Clone, Copy)] @@ -760,7 +453,7 @@ mod new { const DOC: &str = r#"{ character { ... on Human { - humanId: id + humanId: id(required: "test") homePlanet } } @@ -782,7 +475,7 @@ mod new { const DOC: &str = r#"{ character { ... on Droid { - droidId: id + droidId: id(required: "test") primaryFunction } } diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 9d5cc0fc1..284f9c78a 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -6,7 +6,10 @@ use std::{fmt, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; -use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, FieldError, ScalarValue}; +use crate::{ + Arguments, DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ExecutionResult, + Executor, FieldError, Nullable, ScalarValue, +}; /// Conversion of a [`GraphQLValue`] to its [trait object][1]. /// @@ -84,11 +87,17 @@ where Box::pin(future::err(err_unnamed_type(name))) } +pub type Type = &'static str; +pub type Types = &'static [Type]; +pub type Name = &'static str; +pub type FieldName = u128; +pub type WrappedValue = u128; + /// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in /// const generics. See [spec] for more info. /// /// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -pub const fn fnv1a128(str: &str) -> u128 { +pub const fn fnv1a128(str: Name) -> u128 { const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; @@ -103,44 +112,273 @@ pub const fn fnv1a128(str: &str) -> u128 { hash } +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + +pub const fn is_valid_field_args( + base_interface: &[(Name, Type, WrappedValue)], + implementation: &[(Name, Type, WrappedValue)], +) -> bool { + const fn find( + base_interface: &[(Name, Type, WrappedValue)], + impl_name: Name, + impl_ty: Type, + impl_wrap_val: WrappedValue, + ) -> bool { + let mut i = 0; + while i < base_interface.len() { + let (base_name, base_ty, base_wrap_val) = base_interface[i]; + if str_eq(impl_name, base_name) { + return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; + } + i += 1; + } + false + } + + if base_interface.len() > implementation.len() { + return false; + } + + let mut i = 0; + let mut successfully_implemented_fields = 0; + while i < implementation.len() { + let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; + if find(base_interface, impl_name, impl_ty, impl_wrap_val) { + successfully_implemented_fields += 1; + } else if impl_wrap_val % 10 != 2 { + // Not an optional field. + return false; + } + i += 1; + } + + successfully_implemented_fields == base_interface.len() +} + +pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + can_be_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + can_be_subtype(ty / 10, subtype) + } else { + false + } +} + +pub const fn exists(val: Type, arr: Types) -> bool { + let mut i = 0; + while i < arr.len() { + if str_eq(val, arr[i]) { + return true; + } + i += 1; + } + false +} + +pub const fn is_subtype( + possible_subtypes: Types, + wrapped_type: WrappedValue, + subtype: Type, + wrapped_subtype: WrappedValue, +) -> bool { + exists(subtype, possible_subtypes) && can_be_subtype(wrapped_type, wrapped_subtype) +} + +/// TODO +pub trait BaseType { + const NAME: Type; +} + +impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { + const NAME: Type = T::NAME; +} + +// TODO: Reconsider +impl, Ctx> BaseType for (Ctx, T) { + const NAME: Type = T::NAME; +} + +impl> BaseType for Option { + const NAME: Type = T::NAME; +} + +impl> BaseType for Nullable { + const NAME: Type = T::NAME; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseType for Result { + const NAME: Type = T::NAME; +} + +impl> BaseType for Vec { + const NAME: Type = T::NAME; +} + +impl> BaseType for [T] { + const NAME: Type = T::NAME; +} + +impl, const N: usize> BaseType for [T; N] { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Box { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Arc { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Rc { + const NAME: Type = T::NAME; +} + /// TODO -pub trait Type { - const NAME: &'static str; +pub trait BaseSubTypes { + const NAMES: Types; +} + +impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { + const NAMES: Types = T::NAMES; +} + +// TODO: Reconsider +impl, Ctx> BaseSubTypes for (Ctx, T) { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Option { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Nullable { + const NAMES: Types = T::NAMES; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseSubTypes for Result { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Vec { + const NAMES: Types = T::NAMES; } -impl<'a, S, T: Type + ?Sized> Type for &'a T { - const NAME: &'static str = T::NAME; +impl> BaseSubTypes for [T] { + const NAMES: Types = T::NAMES; } -impl + ?Sized> Type for Box { - const NAME: &'static str = T::NAME; +impl, const N: usize> BaseSubTypes for [T; N] { + const NAMES: Types = T::NAMES; } -impl + ?Sized> Type for Arc { - const NAME: &'static str = T::NAME; +impl + ?Sized> BaseSubTypes for Box { + const NAMES: Types = T::NAMES; } -impl + ?Sized> Type for Rc { - const NAME: &'static str = T::NAME; +impl + ?Sized> BaseSubTypes for Arc { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Rc { + const NAMES: Types = T::NAMES; } /// TODO -pub trait SubTypes { - const NAMES: &'static [&'static str]; +pub trait WrappedType { + /// NonNull - 1 + /// Nullable - 2 + /// List - 3 + /// + /// `[[Int]!] - >>> as WrappedType>::N = 12332` + const VALUE: u128; +} + +// TODO: Reconsider +impl, Ctx> WrappedType for (Ctx, T) { + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Option { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +impl> WrappedType for Nullable { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +// TODO: Should Err be trait bounded somehow? +// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? +impl, E> WrappedType for Result { + const VALUE: u128 = T::VALUE; } -impl<'a, S, T: SubTypes + ?Sized> SubTypes for &'a T { - const NAMES: &'static [&'static str] = T::NAMES; +impl> WrappedType for Vec { + const VALUE: u128 = T::VALUE * 10 + 3; } -impl + ?Sized> SubTypes for Box { - const NAMES: &'static [&'static str] = T::NAMES; +impl> WrappedType for [T] { + const VALUE: u128 = T::VALUE * 10 + 3; } -impl + ?Sized> SubTypes for Arc { - const NAMES: &'static [&'static str] = T::NAMES; +impl, const N: usize> WrappedType for [T; N] { + const VALUE: u128 = T::VALUE * 10 + 3; } -impl + ?Sized> SubTypes for Rc { - const NAMES: &'static [&'static str] = T::NAMES; +impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Box { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Arc { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Rc { + const VALUE: u128 = T::VALUE; +} + +pub trait Field { + type Context; + type TypeInfo; + const TYPE: Type; + const SUB_TYPES: Types; + const WRAPPED_VALUE: WrappedValue; + const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &Arguments, + executor: &Executor, + ) -> ExecutionResult; } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 443240251..5d84bd6f8 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -202,12 +202,16 @@ where }) } -impl crate::macros::helper::Type for str { +impl crate::macros::helper::WrappedType for str { + const VALUE: u128 = 1; +} + +impl crate::macros::helper::BaseType for str { const NAME: &'static str = "String"; } -impl crate::macros::helper::SubTypes for str { - const NAMES: &'static [&'static str] = &[::NAME]; +impl crate::macros::helper::BaseSubTypes for str { + const NAMES: &'static [&'static str] = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 0437f97f3..72361cf72 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -441,6 +441,113 @@ impl Definition { }) } + /// TODO + #[must_use] + pub(crate) fn impl_field( + &self, + impl_ty: &syn::Type, + impl_generics: &TokenStream, + where_clause: Option<&syn::WhereClause>, + scalar: &scalar::Type, + trait_ty: Option<&syn::Type>, + context: &syn::Type, + ) -> Option { + if self.is_async { + return None; + } + + let (name, ty, mut res_ty, ident) = + (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); + + // if matches!( + // scalar, + // scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_), + // ) { + // impl_generics + // .params + // .push(parse_quote!( #scalar: ::juniper::ScalarValue )); + // } + // + // let (impl_gens, ty_gens, where_clause) = impl_generics.split_for_impl(); + + let res = if self.is_method() { + let args = self + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + + let rcv = self.has_receiver.then(|| { + quote! { self, } + }); + + if trait_ty.is_some() { + quote! { ::#ident(#rcv #( #args ),*) } + } else { + quote! { Self::#ident(#rcv #( #args ),*) } + } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + + let arguments = self + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + + Some(quote! {( + #name, + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )}) + } + MethodArgument::Executor | MethodArgument::Context(_) => None, + }) + .collect::>(); + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #[automatically_derived] + impl #impl_generics ::juniper::macros::helper::Field< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(#arguments,)*]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let res: #res_ty = #res; + #resolving_code + } + } + }) + } + /// Returns generated code for the /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves /// this [GraphQL field][1] asynchronously. diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 3ea24dcec..db8e3f3f6 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -225,13 +225,23 @@ fn impl_scalar_struct( where #scalar: ::juniper::ScalarValue, { } - impl#impl_generics ::juniper::macros::helper::Type<#scalar> for #ident { + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::SubTypes<#scalar> for #ident { + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { + const VALUE: u128 = 1; } ); diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0e4ff0665..89e24b75f 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -441,6 +441,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); } } @@ -713,6 +714,36 @@ impl Definition { } } } + + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let (impl_generics, where_clause) = self.ty.impl_generics(false); + let ty = self.ty.ty_tokens(); + + quote! { + impl #impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + impl #impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = + &[>::NAME]; + } + + impl #impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } } /// Representation of custom downcast into an [`Implementer`] from a @@ -902,6 +933,7 @@ impl Implementer { /// code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug)] struct EnumType { /// Name of this [`EnumType`] to generate it with. ident: syn::Ident, @@ -1405,6 +1437,7 @@ impl EnumType { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html +#[derive(Debug)] struct TraitObjectType { /// Name of this [`TraitObjectType`] to generate it with. ident: syn::Ident, @@ -1637,6 +1670,7 @@ impl ToTokens for TraitObjectType { /// type for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug)] enum Type { /// [GraphQL interface][1] type implementation as Rust enum. /// diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 171e00a3d..28bffa938 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -361,6 +361,42 @@ impl Definition { } } + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let (impl_generics, where_clause) = self.impl_generics(false); + let ty = &self.ty; + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -439,6 +475,7 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_as_dyn_graphql_value_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); } } @@ -494,6 +531,16 @@ impl Definition { .fields .iter() .filter_map(|f| f.method_resolve_field_tokens(scalar, None)); + let field_impls = self.fields.iter().filter_map(|f| { + f.impl_field( + ty, + &impl_generics, + where_clause.as_ref(), + scalar, + None, + context, + ) + }); let async_fields_err = { let names = self .fields @@ -543,6 +590,8 @@ impl Definition { #name.to_string() } } + + #(#field_impls)* } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 00ba528e5..9883aeed1 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -320,6 +320,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); } } @@ -609,6 +610,41 @@ impl Definition { } } } + + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let (impl_generics, ty, where_clause) = self.impl_generics(false); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } } /// Definition of [GraphQL union][1] variant for code generation. diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index d4676350c..7bdbf2d3e 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -326,13 +326,23 @@ pub fn build_scalar( } } - impl#generic_type_decl ::juniper::macros::helper::Type<#generic_type> for #impl_for_type { + impl#generic_type_decl ::juniper::macros::helper::BaseType<#generic_type> for #impl_for_type + #generic_type_bound + { const NAME: &'static str = #name; } - impl#generic_type_decl ::juniper::macros::helper::SubTypes<#generic_type> for #impl_for_type { + impl#generic_type_decl ::juniper::macros::helper::BaseSubTypes<#generic_type> for #impl_for_type + #generic_type_bound + { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; + } + + impl#generic_type_decl ::juniper::macros::helper::WrappedType<#generic_type> for #impl_for_type + #generic_type_bound + { + const VALUE: u128 = 1; } ); diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index d8002f512..df132e56a 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -881,6 +881,25 @@ impl GraphQLTypeDefiniton { } } } + + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + #where_clause + { + const NAME: &'static str = #name; + } + + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + #where_clause + { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + #where_clause + { + const VALUE: u128 = 1; + } ); if !self.no_async { @@ -1128,6 +1147,28 @@ impl GraphQLTypeDefiniton { ].into_iter().collect()) } } + + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const NAME: &'static str = #name; + } + + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const VALUE: u128 = 1; + } ); if !self.no_async { From 7a78f9eab4676fdb979881dec4f4aba552f63802 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 10:27:52 +0300 Subject: [PATCH 010/122] Use Field inside object's field resolver --- juniper_codegen/src/common/field/mod.rs | 11 ----------- juniper_codegen/src/graphql_object/mod.rs | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 72361cf72..e538417b3 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -459,17 +459,6 @@ impl Definition { let (name, ty, mut res_ty, ident) = (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); - // if matches!( - // scalar, - // scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_), - // ) { - // impl_generics - // .params - // .push(parse_quote!( #scalar: ::juniper::ScalarValue )); - // } - // - // let (impl_gens, ty_gens, where_clause) = impl_generics.split_for_impl(); - let res = if self.is_method() { let args = self .arguments diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 28bffa938..6fe408a8b 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -527,10 +527,20 @@ impl Definition { let name = &self.name; - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(scalar, None)); + let fields_resolvers = self.fields.iter().filter_map(|f| { + (!f.is_async).then(|| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::Field::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + }, + } + }) + }); + let field_impls = self.fields.iter().filter_map(|f| { f.impl_field( ty, From 5c2087c159ef8697a8ebba279f2f8326062b85bb Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 12:57:11 +0300 Subject: [PATCH 011/122] Add async object fields --- juniper/src/macros/helper/mod.rs | 16 +++++++ juniper_codegen/src/common/field/mod.rs | 55 +++++++++++++++++------ juniper_codegen/src/graphql_object/mod.rs | 34 +++++++++++--- juniper_codegen/src/graphql_union/mod.rs | 7 ++- 4 files changed, 91 insertions(+), 21 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 284f9c78a..8b05ffbc7 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -382,3 +382,19 @@ pub trait Field { executor: &Executor, ) -> ExecutionResult; } + +pub trait AsyncField { + type Context; + type TypeInfo; + const TYPE: Type; + const SUB_TYPES: Types; + const WRAPPED_VALUE: WrappedValue; + const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; + + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b Arguments, + executor: &'b Executor, + ) -> BoxFuture<'b, ExecutionResult>; +} diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index e538417b3..6dba69e32 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -451,21 +451,22 @@ impl Definition { scalar: &scalar::Type, trait_ty: Option<&syn::Type>, context: &syn::Type, + for_async: bool, ) -> Option { - if self.is_async { + if !for_async && self.is_async { return None; } let (name, ty, mut res_ty, ident) = (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); - let res = if self.is_method() { + let mut res = if self.is_method() { let args = self .arguments .as_ref() .unwrap() .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + .map(|arg| arg.method_resolve_field_tokens(scalar, for_async)); let rcv = self.has_receiver.then(|| { quote! { self, } @@ -480,6 +481,9 @@ impl Definition { res_ty = parse_quote! { _ }; quote! { &self.#ident } }; + if for_async && !self.is_async { + res = quote! { ::juniper::futures::future::ready(#res) }; + } let arguments = self .arguments @@ -500,11 +504,42 @@ impl Definition { }) .collect::>(); - let resolving_code = gen::sync_resolving_code(); + let (call, trait_name) = if for_async { + let resolving_code = gen::async_resolving_code(Some(&res_ty)); + let call = quote! { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let fut = #res; + #resolving_code + } + }; + + (call, quote! { AsyncField }) + } else { + let resolving_code = gen::sync_resolving_code(); + let call = quote! { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let res: #res_ty = #res; + #resolving_code + } + }; + + (call, quote! { Field }) + }; Some(quote! { + #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::helper::Field< + impl #impl_generics ::juniper::macros::helper::#trait_name< #scalar, { ::juniper::macros::helper::fnv1a128(#name) } > for #impl_ty @@ -524,15 +559,7 @@ impl Definition { ::juniper::macros::helper::WrappedValue, )] = &[#(#arguments,)*]; - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - let res: #res_ty = #res; - #resolving_code - } + #call } }) } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 6fe408a8b..86940f9f0 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -536,7 +536,7 @@ impl Definition { #scalar, { ::juniper::macros::helper::fnv1a128(#name) } >::call(self, info, args, executor) - }, + } } }) }); @@ -549,6 +549,7 @@ impl Definition { scalar, None, context, + false, ) }); let async_fields_err = { @@ -613,15 +614,36 @@ impl Definition { #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(true); let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); - let fields_resolvers = self - .fields - .iter() - .map(|f| f.method_resolve_field_async_tokens(scalar, None)); + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::AsyncField::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); + + let field_impls = self.fields.iter().filter_map(|f| { + f.impl_field( + ty, + &impl_generics, + where_clause.as_ref(), + scalar, + None, + context, + true, + ) + }); + let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -643,6 +665,8 @@ impl Definition { } } } + + #(#field_impls)* } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 9883aeed1..952889782 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -616,6 +616,7 @@ impl Definition { pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; + let variants = self.variants.iter().map(|var| &var.ty); let (impl_generics, ty, where_clause) = self.impl_generics(false); quote! { @@ -632,8 +633,10 @@ impl Definition { for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::helper::Types = &[ + >::NAME, + #(<#variants as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + ]; } #[automatically_derived] From e9c6046b4f724512ca179e64f15bd08248947843 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 15:58:15 +0300 Subject: [PATCH 012/122] Fix --- juniper_codegen/src/graphql_interface/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 89e24b75f..7ca048e69 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -722,6 +722,7 @@ impl Definition { let name = &self.name; let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); + let implementors = self.implementers.iter().map(|i| &i.ty); quote! { impl #impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty @@ -733,8 +734,10 @@ impl Definition { impl #impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::helper::Types = &[ + >::NAME, + #(<#implementors as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + ]; } impl #impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty From f2275a40d4f0c5ff1d42597d49709532c40e1f8e Mon Sep 17 00:00:00 2001 From: tyranron Date: Sat, 25 Dec 2021 07:56:23 +0200 Subject: [PATCH 013/122] Corrections --- juniper/Cargo.toml | 1 - juniper/src/integrations/chrono.rs | 218 ++++++++++++++--------------- 2 files changed, 103 insertions(+), 116 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 7cc9b57d6..cb180aec4 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -21,7 +21,6 @@ travis-ci = { repository = "graphql-rust/juniper" } [features] default = [ "bson", - "chrono", "schema-language", "url", "uuid", diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 2d4bd1b0d..3b293107b 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -2,25 +2,23 @@ //! //! # Supported types //! -//! | Rust type | Format | GraphQL scalar | -//! |-----------------------------------|-----------------------|-------------------| -//! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] | -//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | -//! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | -//! | [`DateTime`]`<`[`FixedOffset`]`>` | [RFC 3339] string | [`DateTime`][s4] | +//! | Rust type | Format | GraphQL scalar | +//! |-------------------|-----------------------|-------------------| +//! | [`NaiveDate`] | `yyyy-MM-dd` | [`Date`][s1] | +//! | [`NaiveTime`] | `HH:mm[:ss[.SSS]]` | [`LocalTime`][s2] | +//! | [`NaiveDateTime`] | `yyyy-MM-dd HH:mm:ss` | `LocalDateTime` | +//! | [`DateTime`] | [RFC 3339] string | [`DateTime`][s4] | //! //! [`DateTime`]: chrono::DateTime -//! [`FixedOffset`]: chrono::FixedOffset -//! [`NaiveDate`]: chrono::NaiveDate -//! [`NaiveDateTime`]: chrono::NaiveDateTime -//! [`NaiveTime`]: chrono::NaiveTime +//! [`NaiveDate`]: chrono::naive::NaiveDate +//! [`NaiveDateTime`]: chrono::naive::NaiveDateTime +//! [`NaiveTime`]: chrono::naive::NaiveTime //! [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 //! [s1]: https://graphql-scalars.dev/docs/scalars/date //! [s2]: https://graphql-scalars.dev/docs/scalars/local-time //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time -//! [s5]: https://graphql-scalars.dev/docs/scalars/utc-offset -use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use chrono::{SecondsFormat, Timelike as _}; use crate::{ graphql_scalar, @@ -29,13 +27,17 @@ use crate::{ Value, }; +pub use chrono::{ + DateTime, FixedOffset as UtcOffset, NaiveDate as Date, NaiveDateTime as LocalDateTime, + NaiveTime as LocalTime, Utc, +}; + /// Format of a [`Date` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date const DATE_FORMAT: &str = "%Y-%m-%d"; #[graphql_scalar( - description = "Date", description = "Date in the proleptic Gregorian calendar (without time \ zone).\ \n\n\ @@ -51,25 +53,22 @@ const DATE_FORMAT: &str = "%Y-%m-%d"; [2]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html", specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" )] -impl GraphQLScalar for NaiveDate -where - S: ScalarValue, -{ +impl GraphQLScalar for Date { fn resolve(&self) -> Value { Value::scalar(self.format(DATE_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse_from_str(s, "%Y-%m-%d").map_err(|e| format!("Invalid `Date`: {}", e)) + Self::parse_from_str(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)) }) } fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) + if let ScalarToken::String(s) = value { + Ok(S::from(s.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } @@ -92,7 +91,6 @@ const LOCAL_TIME_FORMAT_NO_MILLIS: &str = "%H:%M:%S"; const LOCAL_TIME_FORMAT_NO_SECS: &str = "%H:%M"; #[graphql_scalar( - description = "LocalTime", description = "Clock time within a given date (without time zone) in \ `HH:mm[:ss[.SSS]]` format.\ \n\n\ @@ -108,10 +106,7 @@ const LOCAL_TIME_FORMAT_NO_SECS: &str = "%H:%M"; [2]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveTime.html", specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" )] -impl GraphQLScalar for NaiveTime -where - S: ScalarValue, -{ +impl GraphQLScalar for LocalTime { fn resolve(&self) -> Value { Value::scalar( if self.nanosecond() == 0 { @@ -123,7 +118,7 @@ where ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -138,8 +133,8 @@ where } fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) + if let ScalarToken::String(s) = value { + Ok(S::from(s.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } @@ -150,7 +145,6 @@ where const LOCAL_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; #[graphql_scalar( - description = "LocalDateTime", description = "Combined date and time (without time zone) in `yyyy-MM-dd \ HH:mm:ss` format.\ \n\n\ @@ -158,15 +152,12 @@ const LOCAL_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; \n\n\ [1]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDateTime.html" )] -impl GraphQLScalar for NaiveDateTime -where - S: ScalarValue, -{ +impl GraphQLScalar for LocalDateTime { fn resolve(&self) -> Value { Value::scalar(self.format(LOCAL_DATE_TIME_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -184,13 +175,9 @@ where } } -/// Format of a [`DateTime` scalar][1]. -/// -/// [1]: https://graphql-scalars.dev/docs/scalars/date-time -const DATE_TIME_FORMAT: &str = "%FT%TZ"; - +// TODO: Make generic over `chrono::TimeZone` once `#[graphql_scalar]` macro +// supports generics. #[graphql_scalar( - name = "DateTime", description = "Combined date and time (with time zone) in [RFC 3339][0] \ format.\ \n\n\ @@ -200,8 +187,8 @@ const DATE_TIME_FORMAT: &str = "%FT%TZ"; \n\n\ [`DateTime` scalar][1] compliant.\ \n\n\ - See also [`chrono::DateTime`][2]`<`[`FixedOffset`][3]`>` \ - for details.\ + See also [`chrono::DateTime`][2]` and \ + [`chrono::FixedOffset`][3]` for details.\ \n\n\ [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ @@ -209,25 +196,25 @@ const DATE_TIME_FORMAT: &str = "%FT%TZ"; [3]: https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html", specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" )] -impl GraphQLScalar for DateTime -where - S: ScalarValue, -{ +impl GraphQLScalar for DateTime { fn resolve(&self) -> Value { - Value::scalar(self.naive_utc().format(DATE_TIME_FORMAT).to_string()) + Value::scalar( + self.with_timezone(&Utc) + .to_rfc3339_opts(SecondsFormat::AutoSi, true), + ) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - DateTime::parse_from_rfc3339(s).map_err(|e| format!("Invalid `DateTime`: {}", e)) + Self::parse_from_rfc3339(s).map_err(|e| format!("Invalid `DateTime`: {}", e)) }) } fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) + if let ScalarToken::String(s) = value { + Ok(S::from(s.to_owned())) } else { Err(ParseError::UnexpectedToken(Token::Scalar(value))) } @@ -238,16 +225,16 @@ where mod date_test { use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - use super::NaiveDate; + use super::Date; #[test] fn parses_correct_input() { for (raw, expected) in [ - ("1996-12-19", NaiveDate::from_ymd(1996, 12, 19)), - ("1564-01-30", NaiveDate::from_ymd(1564, 01, 30)), + ("1996-12-19", Date::from_ymd(1996, 12, 19)), + ("1564-01-30", Date::from_ymd(1564, 01, 30)), ] { let input: InputValue = graphql_input_value!((raw)); - let parsed = NaiveDate::from_input_value(&input); + let parsed = Date::from_input_value(&input); assert!( parsed.is_ok(), @@ -276,7 +263,7 @@ mod date_test { graphql_input_value!(false), ] { let input: InputValue = input; - let parsed = NaiveDate::from_input_value(&input); + let parsed = Date::from_input_value(&input); assert!(parsed.is_err(), "allows input: {:?}", input); } @@ -286,15 +273,15 @@ mod date_test { fn formats_correctly() { for (val, expected) in [ ( - NaiveDate::from_ymd(1996, 12, 19), + Date::from_ymd(1996, 12, 19), graphql_input_value!("1996-12-19"), ), ( - NaiveDate::from_ymd(1564, 01, 30), + Date::from_ymd(1564, 01, 30), graphql_input_value!("1564-01-30"), ), ( - NaiveDate::from_ymd(2020, 01, 01), + Date::from_ymd(2020, 01, 01), graphql_input_value!("2020-01-01"), ), ] { @@ -306,23 +293,23 @@ mod date_test { } #[cfg(test)] -mod naive_time_test { +mod local_time_test { use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - use super::NaiveTime; + use super::LocalTime; #[test] fn parses_correct_input() { for (raw, expected) in [ - ("14:23:43", NaiveTime::from_hms(14, 23, 43)), - ("14:00:00", NaiveTime::from_hms(14, 00, 00)), - ("14:00", NaiveTime::from_hms(14, 00, 00)), - ("14:32", NaiveTime::from_hms(14, 32, 00)), - ("14:00:00.000", NaiveTime::from_hms(14, 00, 00)), - ("14:23:43.345", NaiveTime::from_hms_milli(14, 23, 43, 345)), + ("14:23:43", LocalTime::from_hms(14, 23, 43)), + ("14:00:00", LocalTime::from_hms(14, 00, 00)), + ("14:00", LocalTime::from_hms(14, 00, 00)), + ("14:32", LocalTime::from_hms(14, 32, 00)), + ("14:00:00.000", LocalTime::from_hms(14, 00, 00)), + ("14:23:43.345", LocalTime::from_hms_milli(14, 23, 43, 345)), ] { let input: InputValue = graphql_input_value!((raw)); - let parsed = NaiveTime::from_input_value(&input); + let parsed = LocalTime::from_input_value(&input); assert!( parsed.is_ok(), @@ -355,7 +342,7 @@ mod naive_time_test { graphql_input_value!(false), ] { let input: InputValue = input; - let parsed = NaiveTime::from_input_value(&input); + let parsed = LocalTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {:?}", input); } @@ -365,19 +352,19 @@ mod naive_time_test { fn formats_correctly() { for (val, expected) in [ ( - NaiveTime::from_hms_micro(1, 2, 3, 4005), + LocalTime::from_hms_micro(1, 2, 3, 4005), graphql_input_value!("01:02:03.004"), ), ( - NaiveTime::from_hms(0, 0, 0), + LocalTime::from_hms(0, 0, 0), graphql_input_value!("00:00:00"), ), ( - NaiveTime::from_hms(12, 0, 0), + LocalTime::from_hms(12, 0, 0), graphql_input_value!("12:00:00"), ), ( - NaiveTime::from_hms(1, 2, 3), + LocalTime::from_hms(1, 2, 3), graphql_input_value!("01:02:03"), ), ] { @@ -389,31 +376,33 @@ mod naive_time_test { } #[cfg(test)] -mod naive_date_time_test { +mod local_date_time_test { + use chrono::naive::{NaiveDate, NaiveTime}; + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - use super::{NaiveDate, NaiveDateTime, NaiveTime}; + use super::LocalDateTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ( "1996-12-19 14:23:43", - NaiveDateTime::new( + LocalDateTime::new( NaiveDate::from_ymd(1996, 12, 19), NaiveTime::from_hms(14, 23, 43), ), ), ( "1564-01-30 14:00:00", - NaiveDateTime::new( + LocalDateTime::new( NaiveDate::from_ymd(1564, 1, 30), NaiveTime::from_hms(14, 00, 00), ), ), ] { let input: InputValue = graphql_input_value!((raw)); - let parsed = NaiveDateTime::from_input_value(&input); + let parsed = LocalDateTime::from_input_value(&input); assert!( parsed.is_ok(), @@ -448,7 +437,7 @@ mod naive_date_time_test { graphql_input_value!(false), ] { let input: InputValue = input; - let parsed = NaiveDateTime::from_input_value(&input); + let parsed = LocalDateTime::from_input_value(&input); assert!(parsed.is_err(), "allows input: {:?}", input); } @@ -458,14 +447,14 @@ mod naive_date_time_test { fn formats_correctly() { for (val, expected) in [ ( - NaiveDateTime::new( + LocalDateTime::new( NaiveDate::from_ymd(1996, 12, 19), NaiveTime::from_hms(0, 0, 0), ), graphql_input_value!("1996-12-19 00:00:00"), ), ( - NaiveDateTime::new( + LocalDateTime::new( NaiveDate::from_ymd(1564, 1, 30), NaiveTime::from_hms(14, 0, 0), ), @@ -481,56 +470,58 @@ mod naive_date_time_test { #[cfg(test)] mod date_time_test { + use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime}; + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - use super::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; + use super::{DateTime, UtcOffset}; #[test] fn parses_correct_input() { for (raw, expected) in [ ( "2014-11-28T21:00:09+09:00", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms(12, 0, 9), ), - FixedOffset::east(9 * 3600), + UtcOffset::east(9 * 3600), ), ), ( "2014-11-28T21:00:09Z", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms(21, 0, 9), ), - FixedOffset::east(0), + UtcOffset::east(0), ), ), ( "2014-11-28T21:00:09+00:00", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms(21, 0, 9), ), - FixedOffset::east(0), + UtcOffset::east(0), ), ), ( "2014-11-28T21:00:09.05+09:00", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms_milli(12, 0, 9, 50), ), - FixedOffset::east(0), + UtcOffset::east(0), ), ), ] { let input: InputValue = graphql_input_value!((raw)); - let parsed = DateTime::from_input_value(&input); + let parsed = DateTime::::from_input_value(&input); assert!( parsed.is_ok(), @@ -570,7 +561,7 @@ mod date_time_test { graphql_input_value!(false), ] { let input: InputValue = input; - let parsed = DateTime::::from_input_value(&input); + let parsed = DateTime::::from_input_value(&input); assert!(parsed.is_err(), "allows input: {:?}", input); } @@ -580,24 +571,24 @@ mod date_time_test { fn formats_correctly() { for (val, expected) in [ ( - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(1996, 12, 19), NaiveTime::from_hms(0, 0, 0), ), - FixedOffset::east(0), + UtcOffset::east(0), ), graphql_input_value!("1996-12-19T00:00:00Z"), ), ( - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(1564, 1, 30), - NaiveTime::from_hms(5, 0, 0), + NaiveTime::from_hms_milli(5, 0, 0, 123), ), - FixedOffset::east(9 * 3600), + UtcOffset::east(9 * 3600), ), - graphql_input_value!("1564-01-30T05:00:00Z"), + graphql_input_value!("1564-01-30T05:00:00.123Z"), ), ] { let actual: InputValue = val.to_input_value(); @@ -615,7 +606,7 @@ mod integration_test { types::scalars::{EmptyMutation, EmptySubscription}, }; - use super::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; + use super::{Date, DateTime, LocalDateTime, LocalTime, UtcOffset}; #[tokio::test] async fn serializes() { @@ -623,28 +614,25 @@ mod integration_test { #[graphql_object] impl Root { - fn date() -> NaiveDate { - NaiveDate::from_ymd(2015, 3, 14) + fn date() -> Date { + Date::from_ymd(2015, 3, 14) } - fn local_time() -> NaiveTime { - NaiveTime::from_hms(16, 7, 8) + fn local_time() -> LocalTime { + LocalTime::from_hms(16, 7, 8) } - fn local_date_time() -> NaiveDateTime { - NaiveDateTime::new( - NaiveDate::from_ymd(2016, 7, 8), - NaiveTime::from_hms(9, 10, 11), - ) + fn local_date_time() -> LocalDateTime { + LocalDateTime::new(Date::from_ymd(2016, 7, 8), LocalTime::from_hms(9, 10, 11)) } - fn date_time() -> DateTime { - DateTime::::from_utc( - NaiveDateTime::new( - NaiveDate::from_ymd(1996, 12, 20), - NaiveTime::from_hms(0, 39, 57), + fn date_time() -> DateTime { + DateTime::::from_utc( + LocalDateTime::new( + Date::from_ymd(1996, 12, 20), + LocalTime::from_hms(0, 39, 57), ), - FixedOffset::west(8 * 3600), + UtcOffset::west(8 * 3600), ) } } From 0739c3e654ba5a560f176792d2586a09a8a0df20 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Dec 2021 11:59:30 +0300 Subject: [PATCH 014/122] Fix Wrapped type for tuple with context --- juniper/src/macros/helper/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 8b05ffbc7..f5b4619aa 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -320,8 +320,11 @@ pub trait WrappedType { const VALUE: u128; } -// TODO: Reconsider -impl, Ctx> WrappedType for (Ctx, T) { +impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) +where + S: ScalarValue, + T: crate::GraphQLValue, +{ const VALUE: u128 = T::VALUE; } From 263d544db8bde9f5db43bc09cc92ad51fe9d5a9a Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Dec 2021 11:04:21 +0300 Subject: [PATCH 015/122] WIP (Make graphql_interface_new attribute work) --- .../src/codegen/interface_attr.rs | 29 +- .../juniper_tests/src/codegen/mod.rs | 1 + .../src/codegen/new_interface.rs | 139 +++ juniper/src/lib.rs | 5 +- juniper_codegen/src/graphql_interface/attr.rs | 220 +++- juniper_codegen/src/graphql_interface/mod.rs | 1 + juniper_codegen/src/graphql_interface/new.rs | 1028 +++++++++++++++++ juniper_codegen/src/lib.rs | 8 + 8 files changed, 1401 insertions(+), 30 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/new_interface.rs create mode 100644 juniper_codegen/src/graphql_interface/new.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 6b8a32877..bc8ea3081 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -34,6 +34,8 @@ where mod new { use super::*; + // ------- Implemented ------- + #[automatically_derived] impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue where @@ -242,33 +244,6 @@ mod new { } } - const fn is_superset(superset: &[&str], set: &[&str]) -> bool { - const fn find(set: &[&str], elem: &str) -> bool { - let mut i = 0; - while i < set.len() { - if juniper::macros::helper::str_eq(elem, set[i]) { - return true; - } - i += 1; - } - false - } - - if superset.len() < set.len() { - return false; - } - - let mut i = 0; - while i < set.len() { - if !find(superset, set[i]) { - return false; - } - i += 1; - } - - true - } - // -------------- // #[derive(GraphQLInterface)] diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 6348a66c8..1abc91e4d 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_with_raw_idents; mod derive_scalar; mod impl_scalar; mod interface_attr; +mod new_interface; mod object_attr; mod object_derive; mod scalar_value_transparent; diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs new file mode 100644 index 000000000..c1f7579a4 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -0,0 +1,139 @@ +use juniper::{ + execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, + DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, + GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, +}; + +#[graphql_interface_new(for = [Human, Droid])] +trait Character { + fn id(&self, required: String) -> String; +} + +struct Human { + id: String, + home_planet: String, +} + +#[graphql_object(impl = CharacterValue)] +impl Human { + fn id(&self, _required: String, _optional: Option) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } +} + +struct Droid { + id: String, + primary_function: String, +} + +#[graphql_object(impl = CharacterValue)] +impl Droid { + fn id(&self, _required: String) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } +} + +// ----- + +#[derive(Clone, Copy)] +enum QueryRoot { + Human, + Droid, +} + +#[graphql_object(scalar = S: ScalarValue + Send + Sync)] +impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } +} + +// -------------- + +fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> +where + Q: GraphQLType + 'q, +{ + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +fn schema_with_scalar<'q, S, C, Q>( + query_root: Q, +) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> +where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, +{ + RootNode::new_with_scalar_value( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +#[tokio::test] +async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id(required: "test") + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); +} + +#[tokio::test] +async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id(required: "test") + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); +} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 406879726..c4ce64163 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -114,8 +114,9 @@ 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_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, + graphql_interface, graphql_interface_new, graphql_object, graphql_scalar, graphql_subscription, + graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, + GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 2aedbb2ad..475ea3eaf 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::{ - inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, + inject_async_trait, new, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, TraitAttr, TraitObjectType, Type, }; @@ -45,6 +45,21 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result syn::Result { + if let Ok(mut ast) = syn::parse2::(body.clone()) { + let trait_attrs = parse::attr::unite(("graphql_interface_new", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_interface_new", ast.attrs); + return expand_on_trait_new(trait_attrs, ast); + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_interface_new] attribute is applicable to trait definitions and trait \ + implementations only", + )) +} + /// Expands `#[graphql_interface]` macro placed on trait definition. fn expand_on_trait( attrs: Vec, @@ -245,6 +260,209 @@ fn expand_on_trait( }) } +/// Expands `#[graphql_interface_new]` macro placed on trait definition. +fn expand_on_trait_new( + attrs: Vec, + mut ast: syn::ItemTrait, +) -> syn::Result { + let attr = new::TraitAttr::from_attrs("graphql_interface_new", &attrs)?; + + let trait_ident = &ast.ident; + let trait_span = ast.span(); + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| trait_ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| trait_ident.span()), + ); + } + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + // let mut implementers: Vec<_> = attr + // .implementers + // .iter() + // .map(|ty| Implementer { + // ty: ty.as_ref().clone(), + // downcast: None, + // context: None, + // scalar: scalar.clone(), + // }) + // .collect(); + // for (ty, downcast) in &attr.external_downcasts { + // match implementers.iter_mut().find(|i| &i.ty == ty) { + // Some(impler) => { + // impler.downcast = Some(ImplementerDowncast::External { + // path: downcast.inner().clone(), + // }); + // } + // None => err_only_implementer_downcast(&downcast.span_joined()), + // } + // } + + proc_macro_error::abort_if_dirty(); + + let renaming = attr + .rename_fields + .as_deref() + .copied() + .unwrap_or(RenameRule::CamelCase); + + let mut fields = vec![]; + for item in &mut ast.items { + if let syn::TraitItem::Method(m) = item { + match TraitMethod::parse(m, &renaming) { + Some(TraitMethod::Field(f)) => fields.push(f), + Some(TraitMethod::Downcast(_d)) => { + unimplemented!(); + // match implementers.iter_mut().find(|i| i.ty == d.ty) { + // Some(impler) => { + // if let Some(external) = &impler.downcast { + // err_duplicate_downcast(m, external, &impler.ty); + // } else { + // impler.downcast = d.downcast; + // impler.context = d.context; + // } + // } + // None => err_only_implementer_downcast(&m.sig), + // } + } + _ => {} + } + } + } + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(trait_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(trait_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + + let context = attr + .context + .as_deref() + .cloned() + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments.as_ref().and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) + }) + }) + // .or_else(|| { + // implementers + // .iter() + // .find_map(|impler| impler.context.as_ref()) + // .cloned() + // }) + .unwrap_or_else(|| parse_quote! { () }); + + // let is_trait_object = attr.r#dyn.is_some(); + + let is_async_trait = attr.asyncness.is_some() + || ast + .items + .iter() + .find_map(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness, + _ => None, + }) + .is_some(); + let has_default_async_methods = ast.items.iter().any(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), + _ => false, + }); + + // let ty = if is_trait_object { + // Type::TraitObject(Box::new(TraitObjectType::new( + // &ast, + // &attr, + // scalar.clone(), + // context.clone(), + // ))) + // } else { + // Type::Enum(Box::new(EnumType::new( + // &ast, + // &attr, + // &implementers, + // scalar.clone(), + // ))) + // }; + + let description = attr.description.as_ref().map(|c| c.inner().clone()); + let generated_code = new::Definition { + attrs: attr, + ident: trait_ident.clone(), + vis: ast.vis.clone(), + trait_generics: ast.generics.clone(), + name, + description, + + context, + scalar: scalar.clone(), + + fields, + }; + + // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. + // if is_trait_object { + // ast.attrs.push(parse_quote! { + // #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] + // }); + // + // let scalar_ty = scalar.generic_ty(); + // if !scalar.is_explicit_generic() { + // let default_ty = scalar.default_ty(); + // ast.generics + // .params + // .push(parse_quote! { #scalar_ty = #default_ty }); + // } + // ast.generics + // .make_where_clause() + // .predicates + // .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); + // ast.supertraits + // .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); + // } + + if is_async_trait { + if has_default_async_methods { + // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits + ast.supertraits.push(parse_quote! { Sync }); + } + inject_async_trait( + &mut ast.attrs, + ast.items.iter_mut().filter_map(|i| { + if let syn::TraitItem::Method(m) = i { + Some(&mut m.sig) + } else { + None + } + }), + &ast.generics, + ); + } + + Ok(quote! { + #ast + #generated_code + }) +} + /// Expands `#[graphql_interface]` macro placed on a trait implementation block. fn expand_on_impl(attrs: Vec, mut ast: syn::ItemImpl) -> syn::Result { let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 7ca048e69..2671b0ca7 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,6 +3,7 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; +pub mod new; use std::{ collections::{HashMap, HashSet}, diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs new file mode 100644 index 000000000..2e0b733dd --- /dev/null +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -0,0 +1,1028 @@ +//! Code generation for [GraphQL interface][1]. +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces + +use std::{ + any::TypeId, + collections::{HashMap, HashSet}, + convert::TryInto as _, +}; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, + token, +}; + +use crate::{ + common::{ + field, gen, + parse::{ + attr::{err, OptionExt as _}, + GenericsExt as _, ParseBufferExt as _, + }, + scalar, + }, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, +}; + +/// Available 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)] +pub(crate) struct TraitAttr { + /// Explicitly specified name of [GraphQL interface][1] type. + /// + /// If [`None`], then Rust trait name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) name: Option>, + + /// Explicitly specified [description][2] of [GraphQL interface][1] type. + /// + /// If [`None`], 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(crate) description: Option>, + + /// Explicitly specified identifier of the enum Rust type behind the trait, + /// being an actual implementation of a [GraphQL interface][1] type. + /// + /// If [`None`], then `{trait_name}Value` identifier will be used. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) r#enum: 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(crate) implementers: HashSet>, + + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL interface][1] type with. + /// + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) context: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to resolve this [GraphQL interface][1] type with. + /// + /// If [`None`], 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(crate) scalar: Option>, + + /// Explicitly specified marker indicating that the Rust trait should be + /// transformed into [`async_trait`]. + /// + /// If [`None`], then trait will be transformed into [`async_trait`] only if + /// it contains async methods. + pub(crate) asyncness: Option>, + + /// Explicitly specified [`RenameRule`] for all fields of this + /// [GraphQL interface][1] type. + /// + /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) rename_fields: Option>, + + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. + pub(crate) is_internal: bool, +} + +impl Parse for TraitAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ctx" | "context" | "Context" => { + input.parse::()?; + let ctx = input.parse::()?; + out.context + .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "for" | "implementers" => { + input.parse::()?; + for impler in input.parse_maybe_wrapped_and_punctuated::< + syn::TypePath, token::Bracket, token::Comma, + >()? { + let impler_span = impler.span(); + out + .implementers + .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) + .none_or_else(|_| err::dup_arg(impler_span))?; + } + } + "enum" => { + input.parse::()?; + let alias = input.parse::()?; + out.r#enum + .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "async" => { + let span = ident.span(); + out.asyncness + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))?; + } + "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + out.rename_fields + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } + "internal" => { + out.is_internal = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl TraitAttr { + /// Tries to merge two [`TraitAttr`]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), + r#enum: try_merge_opt!(r#enum: self, another), + asyncness: try_merge_opt!(asyncness: self, another), + rename_fields: try_merge_opt!(rename_fields: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`TraitAttr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a trait definition. + pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); + } + + Ok(attr) + } + + /// TODO + fn enum_alias_ident(&self, trait_name: &syn::Ident) -> SpanContainer { + self.r#enum.clone().unwrap_or_else(|| { + SpanContainer::new( + trait_name.span(), + Some(trait_name.span()), + format_ident!("{}Value", trait_name.to_string()), + ) + }) + } +} + +/// Definition of [GraphQL interface][1] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +pub(crate) struct Definition { + /// TODO + pub(crate) attrs: TraitAttr, + + pub(crate) ident: syn::Ident, + + pub(crate) vis: syn::Visibility, + + pub(crate) trait_generics: syn::Generics, + + // /// Rust type that this [GraphQL interface][1] is represented with. + // /// + // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + // ty: syn::Type, + /// Name of this [GraphQL interface][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) name: String, + + /// Description of this [GraphQL interface][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) description: Option, + + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL interface][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) context: syn::Type, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL interface][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) scalar: scalar::Type, + + /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) fields: Vec, + // /// Defined [`Implementer`]s of this [GraphQL interface][1]. + // /// + // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + // implementers: Vec, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.generate_enum().to_tokens(into); + self.impl_graphql_interface_tokens().to_tokens(into); + self.impl_output_type_tokens().to_tokens(into); + self.impl_graphql_type_tokens().to_tokens(into); + self.impl_graphql_value_tokens().to_tokens(into); + self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_fields(false).to_tokens(into); + self.impl_fields(true).to_tokens(into); + } +} + +impl Definition { + fn generate_enum(&self) -> TokenStream { + let vis = &self.vis; + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); + + let ty_params = self.trait_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>> + } + }); + let phantom_variant = + (!self.trait_generics.params.is_empty()).then(|| quote! { __Phantom(#(#ty_params,)*) }); + + let alias_ident = self.attrs.enum_alias_ident(&self.ident); + let enum_ident = self.attrs.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", self.ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + + let variants_generics = self + .attrs + .implementers + .iter() + .enumerate() + .map(|(id, ty)| format_ident!("I{}", id)); + + let variants_idents = self + .attrs + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); + + let enum_generics = self + .trait_generics + .params + .iter() + .map(ToTokens::to_token_stream) + .chain(variants_generics.clone().map(ToTokens::into_token_stream)); + let enum_to_alias_generics = self + .trait_generics + .params + .iter() + .map(ToTokens::to_token_stream) + .chain( + self.attrs + .implementers + .iter() + .map(ToTokens::to_token_stream), + ); + + let from_impls = self + .attrs + .implementers + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote! { + impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens + #trait_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) + } + } + } + }); + + quote! { + #[derive(Clone, Copy, Debug)] + #vis enum #enum_ident<#(#enum_generics,)*> { + #(#variants_idents(#variants_generics)),* + #phantom_variant + } + + #vis type #alias_ident#trait_gens = + #enum_ident<#(#enum_to_alias_generics,)*>; + + #(#from_impls)* + } + } + + /// Returns generated code implementing [`GraphQLInterface`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLInterface`]: juniper::GraphQLInterface + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_interface_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let gens = self.impl_generics(false); + let (impl_generics, _, where_clause) = gens.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + + let impler_tys = self.attrs.implementers.iter().collect::>(); + let all_implers_unique = (impler_tys.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } + }); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for + #ty#ty_generics + #where_clause + { + fn mark() { + #all_implers_unique + #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + } + } + } + } + + /// 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 + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + + let fields_marks = self + .fields + .iter() + .map(|f| f.method_mark_tokens(false, scalar)); + + let impler_tys = self.attrs.implementers.iter(); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for + #ty#ty_generics + #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } + } + } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + + 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 = self.attrs.implementers.iter().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(|f| f.method_meta_tokens(None)); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar> + for #ty#ty_generics + #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_tys>(info); )* + + let fields = [ + #( #fields_meta, )* + ]; + registry.build_interface_type::<#ty>(info, &fields) + #description + .into_meta() + } + } + } + } + + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + let context = &self.context; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + let trait_name = &self.name; + + let fields_resolvers = self.fields.iter().filter_map(|f| { + (!f.is_async).then(|| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::Field::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }) + }); + let async_fields_err = { + let names = self + .fields + .iter() + .filter_map(|f| f.is_async.then(|| f.name.as_str())) + .collect::>(); + (!names.is_empty()).then(|| { + field::Definition::method_resolve_field_err_async_field_tokens( + &names, scalar, trait_name, + ) + }) + }; + let no_field_err = + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); + + let downcast_check = self.method_concrete_type_name_tokens(); + + let downcast = self.method_resolve_into_type_tokens(); + + quote! { + #[allow(deprecated)] + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::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 )* + #async_fields_err + _ => #no_field_err, + } + } + + fn concrete_type_name( + &self, + context: &Self::Context, + info: &Self::TypeInfo, + ) -> String { + #downcast_check + } + + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<#scalar>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + #downcast + } + } + } + } + + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = &self.attrs.enum_alias_ident(&self.ident); + let trait_name = &self.name; + + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::AsyncField::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); + let no_field_err = + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); + + let downcast = self.method_resolve_into_type_async_tokens(); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics + #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 )* + _ => Box::pin(async move { #no_field_err }), + } + } + + 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>> { + #downcast + } + } + } + } + + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + let implementers = self.attrs.implementers.iter(); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty#ty_generics + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = &[ + >::NAME, + #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME,)* + ]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty#ty_generics + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } + + /// TODO + fn impl_fields(&self, for_async: bool) -> TokenStream { + let scalar = &self.scalar; + let const_scalar = match scalar { + scalar::Type::Concrete(ty) => ty.to_token_stream(), + scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { + quote! { ::juniper::DefaultScalarValue } + } + }; + + let ty = self.attrs.enum_alias_ident(&self.ident); + let context = &self.context; + let impl_tys = self.attrs.implementers.iter().collect::>(); + let impl_idents = self + .attrs + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + + let generics = self.impl_generics(for_async); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields.iter().filter_map(|field| { + if field.is_async && !for_async { + return None; + } + let (trait_name, call_sig) = if for_async { + ( + quote! { AsyncField }, + quote! { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> + }, + ) + } else { + ( + quote! { Field }, + quote! { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> + }, + ) + }; + + let name = &field.name; + let return_ty = &field.ty; + + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| { + match arg { + field::MethodArgument::Regular(arg) => { + Some((&arg.ty, &arg.name)) + } + _ => None, + } + }) + .unzip(); + + Some(quote! { + impl#impl_generics ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = + <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )),*]; + + #call_sig { + match self { + #(#ty::#impl_idents(v) => { + const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( + <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, + <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::TYPE, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::WRAPPED_VALUE, + )); + const _: () = ::std::assert!(::juniper::macros::helper::is_valid_field_args( + <#ty as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::ARGUMENTS, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::ARGUMENTS, + )); + + <_ as ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::call(v, info, args, executor) + })* + } + } + } + }) + }) + .collect() + } + + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`EnumType`]. + /// + /// [0]: juniper::GraphQLValue::concrete_type_name + #[must_use] + fn method_concrete_type_name_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let match_arms = self + .attrs + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) + .map(|(ident, ty)| { + quote! { + Self::#ident(v) => < + #ty as ::juniper::GraphQLValue<#scalar> + >::concrete_type_name(v, context, info), + } + }); + + let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() + || self.attrs.implementers.is_empty()) + .then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } + } + } + + /// Returns generated code for the + /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which + /// downcasts this [`EnumType`] into its underlying [`Implementer`] type + /// asynchronously. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + #[must_use] + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { + let resolving_code = gen::async_resolving_code(None); + + let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(v) => { + let fut = ::juniper::futures::future::ready(v); + #resolving_code + } + } + }) + }); + let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() + || self.attrs.implementers.is_empty()) + .then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } + } + } + + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which downcasts this [`EnumType`] into its underlying + /// [`Implementer`] type synchronously. + /// + /// [0]: juniper::GraphQLValue::resolve_into_type + #[must_use] + fn method_resolve_into_type_tokens(&self) -> TokenStream { + let resolving_code = gen::sync_resolving_code(); + + let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(res) => #resolving_code, + } + }) + }); + + let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() + || self.attrs.implementers.is_empty()) + .then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this [`EnumType`]. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.trait_generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.trait_generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.trait_generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ty = &self.ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ty#ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1e426af3d..0b30d10c0 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -673,6 +673,14 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +#[proc_macro_error] +#[proc_macro_attribute] +pub fn graphql_interface_new(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_interface::attr::expand_new(attr.into(), body.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// From f4c48a6f9a24ccb5663164886fa9d305924f62a3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Dec 2021 15:48:52 +0300 Subject: [PATCH 016/122] WIP (move to the new tests volume 1) --- .../src/codegen/interface_attr.rs | 1803 ++++------------- .../src/codegen/new_interface.rs | 95 +- juniper_codegen/src/graphql_interface/new.rs | 76 +- 3 files changed, 437 insertions(+), 1537 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index bc8ea3081..18115e77f 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, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, - GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, + DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, + GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -31,372 +31,94 @@ where ) } -mod new { +mod no_implers { use super::*; - // ------- Implemented ------- - - #[automatically_derived] - impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - fn mark() { - const _: fn() = || { - trait MutuallyExclusive {} - impl MutuallyExclusive for Droid {} - impl MutuallyExclusive for Human {} - }; - } - } - - #[automatically_derived] - impl<__S> ::juniper::marker::IsOutputType<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - fn mark() { - <<&str as ::juniper::IntoResolvable< - '_, - __S, - _, - >::Context, - >>::Type as ::juniper::marker::IsOutputType<__S>>::mark(); - >::mark(); - >::mark(); - } - } - - #[automatically_derived] - impl<__S> ::juniper::GraphQLType<__S> for CharacterValue - 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::(info); - let _ = registry.get_type::(info); - let fields = [registry.field_convert::<&str, _, Self::Context>("id", info)]; - registry - .build_interface_type::(info, &fields) - .into_meta() - } + #[graphql_interface_new] + trait Character { + fn id(&self) -> &str; } - #[allow(deprecated)] - #[automatically_derived] - impl<__S> ::juniper::GraphQLValue<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - type Context = (); - type TypeInfo = (); + struct QueryRoot; - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - fn resolve_field( - &self, - info: &Self::TypeInfo, - field: &str, - args: &::juniper::Arguments<__S>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<__S> { - match field { - "id" => <_ as juniper::macros::helper::Field< - __S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(self, info, args, executor), - _ => { - return Err(::juniper::FieldError::from({ - format!("Field `{}` not found on type `{}`", field, "CharacterValue",) - })) - } - } - } - fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - match self { - Self::Human(v) => { - >::concrete_type_name(v, context, info) - } - Self::Droid(v) => { - >::concrete_type_name(v, context, info) - } - } - } - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<__S>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<__S> { - match self { - Self::Human(res) => ::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()), - }), - Self::Droid(res) => ::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()), - }), - } - } - } - - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl<__S> ::juniper::GraphQLValueAsync<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - Self: Sync, - __S: Send + Sync, - { - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field: &'b str, - args: &'b ::juniper::Arguments<__S>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - match field { - "id" => Box::pin(::juniper::futures::future::ready( - <_ as juniper::macros::helper::Field< - __S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(self, info, args, executor), - )), - _ => Box::pin(async move { - return Err(::juniper::FieldError::from({ - format!("Field `{}` not found on type `{}`", field, "CharacterValue",) - })); - }), - } - } - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - _: Option<&'b [::juniper::Selection<'b, __S>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - match self { - Self::Droid(v) => { - let fut = ::juniper::futures::future::ready(v); - Box::pin(::juniper::futures::FutureExt::then( - fut, - move |res| 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()), - } - }, - )) - } - Self::Human(v) => { - let fut = ::juniper::futures::future::ready(v); - Box::pin(::juniper::futures::FutureExt::then( - fut, - move |res| 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()), - } - }, - )) - } - } + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() } } - enum CharacterEnumValue { - Human(I1), - Droid(I2), - } + #[tokio::test] + async fn is_graphql_interface() { + let schema = schema(QueryRoot); - type CharacterValue = CharacterEnumValue; + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; - impl From for CharacterValue { - fn from(v: Human) -> Self { - Self::Human(v) - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } - impl From for CharacterValue { - fn from(v: Droid) -> Self { - Self::Droid(v) - } - } + #[tokio::test] + async fn uses_trait_name() { + let schema = schema(QueryRoot); - // -------------- + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - // #[derive(GraphQLInterface)] - // #[graphql(for(Human, Droid))] - struct Character { - id: Option, + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } - impl juniper::macros::helper::BaseType for CharacterValue - where - S: ScalarValue, - { - const NAME: juniper::macros::helper::Type = "Character"; - } + #[tokio::test] + async fn has_no_description() { + let schema = schema(QueryRoot); - impl juniper::macros::helper::BaseSubTypes for CharacterValue - where - S: ScalarValue, - { - const NAMES: juniper::macros::helper::Types = &[ - >::NAME, - >::NAME, - >::NAME, - ]; - } + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - impl juniper::macros::helper::WrappedType for CharacterValue { - const VALUE: u128 = 1; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } +} - impl - juniper::macros::helper::Field - for CharacterValue - { - type Context = (); - type TypeInfo = (); - const TYPE: juniper::macros::helper::Type = - as juniper::macros::helper::BaseType>::NAME; - const SUB_TYPES: juniper::macros::helper::Types = - as juniper::macros::helper::BaseSubTypes>::NAMES; - const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = - as juniper::macros::helper::WrappedType>::VALUE; - const ARGUMENTS: &'static [( - juniper::macros::helper::Name, - juniper::macros::helper::Type, - juniper::macros::helper::WrappedValue, - )] = &[("required", "String", 1)]; +mod trivial { + use super::*; - fn call( - &self, - info: &Self::TypeInfo, - args: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - match self { - CharacterValue::Human(v) => { - const _: () = assert!(juniper::macros::helper::is_subtype( - as juniper::macros::helper::BaseSubTypes>::NAMES, - as juniper::macros::helper::WrappedType>::VALUE, - >::TYPE, - >::WRAPPED_VALUE, - )); - const _: () = assert!(juniper::macros::helper::is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, - )); - - <_ as juniper::macros::helper::Field< - S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(v, info, args, executor) - } - CharacterValue::Droid(v) => { - const _: () = assert!(juniper::macros::helper::is_subtype( - as juniper::macros::helper::BaseSubTypes>::NAMES, - as juniper::macros::helper::WrappedType>::VALUE, - >::TYPE, - >::WRAPPED_VALUE, - )); - const _: () = assert!(juniper::macros::helper::is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, - )); - - <_ as juniper::macros::helper::Field< - S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(v, info, args, executor) - } - } - } + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> &str; } - // --------- - + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_object(impl = CharacterValue)] - impl Human { - fn id(&self, _required: String, _optional: Option) -> &str { - &self.id - } - - fn home_planet(&self) -> &str { - &self.home_planet - } - } - + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[graphql_object(impl = CharacterValue)] - impl Droid { - fn id(&self, _required: String) -> &str { - &self.id - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - // ----- - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -421,14 +143,12 @@ mod new { } } - // -------------- - #[tokio::test] async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { - humanId: id(required: "test") + humanId: id homePlanet } } @@ -450,7 +170,7 @@ mod new { const DOC: &str = r#"{ character { ... on Droid { - droidId: id(required: "test") + droidId: id primaryFunction } } @@ -466,135 +186,155 @@ mod new { )), ); } -} -mod no_implers { - use super::*; + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; - #[graphql_interface] - trait Character { - fn id(&self) -> &str; + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } } - #[graphql_interface(dyn = DynHero)] - trait Hero { - fn info(&self) -> &str; + #[tokio::test] + async fn is_graphql_interface() { + let schema = schema(QueryRoot::Human); + + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } - struct QueryRoot; + #[tokio::test] + async fn registers_all_implementers() { + let schema = schema(QueryRoot::Human); - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - unimplemented!() - } + let doc = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; - fn hero(&self) -> Box> { - unimplemented!() - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] - async fn is_graphql_interface() { - let schema = schema(QueryRoot); + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { + for object in &["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ - kind - }} + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} }}"#, - interface, + object, ); assert_eq!( execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), ); } } #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot); + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot); + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } -mod trivial { +mod trivial_with_trait_imp { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(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 = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] impl Character for Human { fn id(&self) -> &str { &self.id } } - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, @@ -607,13 +347,6 @@ mod trivial { } } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -633,73 +366,15 @@ mod trivial { id: "droid-99".to_string(), primary_function: "run".to_string(), } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); + .into(), + } + } } #[tokio::test] - async fn dyn_resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ - hero { + character { ... on Human { humanId: id homePlanet @@ -712,16 +387,16 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), vec![], )), ); } #[tokio::test] - async fn dyn_resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ - hero { + character { ... on Droid { droidId: id primaryFunction @@ -734,7 +409,7 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), vec![], )), ); @@ -762,74 +437,45 @@ 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, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - #[tokio::test] async fn is_graphql_interface() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } #[tokio::test] async fn registers_all_implementers() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - possibleTypes {{ - kind - name - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] @@ -854,7 +500,6 @@ mod trivial { Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, - {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -866,50 +511,39 @@ mod trivial { async fn uses_trait_name() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } mod explicit_alias { use super::*; - #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] + #[graphql_interface_new(enum = CharacterEnum, for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -921,13 +555,6 @@ mod explicit_alias { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - #[derive(GraphQLObject)] #[graphql(impl = CharacterEnum)] struct Droid { @@ -935,13 +562,6 @@ mod explicit_alias { primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1084,58 +704,25 @@ mod explicit_alias { mod trivial_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1158,20 +745,6 @@ mod trivial_async { .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] @@ -1219,86 +792,23 @@ mod trivial_async { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { + async fn enum_resolves_id_field() { const DOC: &str = r#"{ - hero { - info + character { + id } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { let schema = schema(*root); - let expected_info: &str = *expected_info; + let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), ); } } @@ -1307,51 +817,41 @@ mod trivial_async { async fn is_graphql_interface() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } #[tokio::test] async fn registers_all_implementers() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - possibleTypes {{ - kind - name - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] @@ -1376,7 +876,6 @@ mod trivial_async { Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, - {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -1388,113 +887,69 @@ mod trivial_async { async fn uses_trait_name() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } mod explicit_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { fn id(&self) -> &str; - async fn info(&self) -> String { - "None available".to_owned() - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn id(&self) -> &str { - "Non-identified" - } - - fn info(&self) -> &str; + async fn info(&self) -> String; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, + info: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - async fn info(&self) -> String { - format!("Home planet is {}", &self.home_planet) - } - } - - #[graphql_interface(async, dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface(async)] - impl Character for Droid { + #[graphql_object(impl = CharacterValue)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id(&self) -> &str { - &self.id + async fn info(&self) -> String { + format!("Primary function is {}", &self.primary_function) } - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -1511,6 +966,7 @@ mod explicit_async { match self { Self::Human => Human { id: "human-32".to_string(), + info: "Home planet is earth".to_string(), home_planet: "earth".to_string(), } .into(), @@ -1521,20 +977,6 @@ mod explicit_async { .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] @@ -1581,50 +1023,6 @@ mod explicit_async { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_fields() { const DOC: &str = r#"{ @@ -1636,7 +1034,7 @@ mod explicit_async { for (root, expected_id, expected_info) in &[ (QueryRoot::Human, "human-32", "Home planet is earth"), - (QueryRoot::Droid, "droid-99", "None available"), + (QueryRoot::Droid, "droid-99", "Primary function is run"), ] { let schema = schema(*root); @@ -1654,36 +1052,6 @@ mod explicit_async { ); } } - - #[tokio::test] - async fn dyn_resolves_fields() { - const DOC: &str = r#"{ - hero { - id - info - } - }"#; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "Non-identified", "earth"), - (QueryRoot::Droid, "droid-99", "run"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } } mod fallible_field { @@ -1697,55 +1065,31 @@ mod fallible_field { } } - #[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 = [CharacterValue, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> Result<&str, CustomError> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> Result<&str, CustomError> { - Ok(&self.home_planet) - } + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> Result<&str, CustomError>; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> Result<&str, CustomError> { - Ok(&self.id) + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> String { + self.id.clone() } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> Result<&str, CustomError> { - Ok(&self.primary_function) + fn primary_function(&self) -> &str { + &self.primary_function } } @@ -1771,20 +1115,6 @@ mod fallible_field { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -1831,50 +1161,6 @@ mod fallible_field { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -1897,120 +1183,70 @@ mod fallible_field { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - #[tokio::test] async fn has_correct_graphql_type() { let schema = schema(QueryRoot::Human); - for (interface, field) in &[("Character", "id"), ("Hero", "info")] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name + let doc = r#"{ + __type(name: "Character") { + name + kind + fields { + name + type { kind - fields {{ + ofType { name - type {{ - kind - ofType {{ - name - }} - }} - }} - }} - }}"#, - interface, - ); + } + } + } + } + }"#; - let expected_name: &str = *interface; - let expected_field_name: &str = *field; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": { - "name": expected_name, - "kind": "INTERFACE", - "fields": [{ - "name": expected_field_name, - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, - }] - }}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Character", + "kind": "INTERFACE", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }] + }}), + vec![], + )), + ); } } mod generic { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(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 = [CharacterValue, DynHero])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -2037,20 +1273,6 @@ mod generic { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2097,50 +1319,6 @@ mod generic { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2158,104 +1336,55 @@ mod generic { let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), ); } } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } } mod generic_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id(&self) -> &str { + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { + fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -2282,20 +1411,6 @@ mod generic_async { .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] @@ -2342,50 +1457,6 @@ mod generic_async { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2409,98 +1480,49 @@ mod generic_async { } #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info + async fn uses_trait_name_without_type_params() { + let doc = r#"{ + __type(name: "Character") { + name } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn 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 schema = schema(QueryRoot::Human); - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } } mod generic_lifetime_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character<'me, A> { async fn id<'a>(&'a self) -> &'a str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero<'me, A> { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] + #[graphql(impl = CharacterValue<()>)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl<'me, A> Character<'me, A> for Human { - async fn id<'a>(&'a self) -> &'a str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl<'me, A> Hero<'me, A> for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl<'me, A> Character<'me, A> for Droid { - async fn id<'a>(&'a self) -> &'a str { + #[graphql_object(impl = CharacterValue<()>)] + impl Droid { + fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl<'me, A> Hero<'me, A> for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -2527,20 +1549,6 @@ mod generic_lifetime_async { .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] @@ -2587,50 +1595,6 @@ mod generic_lifetime_async { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2654,44 +1618,19 @@ mod generic_lifetime_async { } #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info + async fn uses_trait_name_without_type_params() { + let doc = r#"{ + __type(name: "Character") { + name } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn 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 schema = schema(QueryRoot::Human); - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } } diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index c1f7579a4..95c0d465d 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -4,35 +4,28 @@ use juniper::{ GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; -#[graphql_interface_new(for = [Human, Droid])] -trait Character { - fn id(&self, required: String) -> String; +// -------------------------- + +#[graphql_interface(for = [Human, Droid])] +trait Character<'me, A> { + async fn id<'a>(&'a self) -> &'a str; } +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<()>)] struct Human { id: String, home_planet: String, } -#[graphql_object(impl = CharacterValue)] -impl Human { - fn id(&self, _required: String, _optional: Option) -> &str { - &self.id - } - - fn home_planet(&self) -> &str { - &self.home_planet - } -} - struct Droid { id: String, primary_function: String, } -#[graphql_object(impl = CharacterValue)] +#[graphql_object(impl = CharacterValue<()>)] impl Droid { - fn id(&self, _required: String) -> &str { + fn id(&self) -> &str { &self.id } @@ -41,32 +34,6 @@ impl Droid { } } -// ----- - -#[derive(Clone, Copy)] -enum QueryRoot { - Human, - Droid, -} - -#[graphql_object(scalar = S: ScalarValue + Send + Sync)] -impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } -} - // -------------- fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -93,47 +60,3 @@ where EmptySubscription::::new(), ) } - -#[tokio::test] -async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id(required: "test") - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); -} - -#[tokio::test] -async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id(required: "test") - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); -} diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 2e0b733dd..3056b7175 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -14,6 +14,7 @@ use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, + punctuated::Punctuated, spanned::Spanned as _, token, }; @@ -343,12 +344,28 @@ impl Definition { |c| format_ident!("{}Enum", c.inner().to_string()), ); + let enum_alias_generics = { + let mut enum_alias_generics = trait_gens.clone(); + let enum_generics = std::mem::take(&mut enum_alias_generics.params); + enum_alias_generics.params = enum_generics + .into_iter() + .map(|gen| match gen { + syn::GenericParam::Type(mut ty) => { + ty.bounds = Punctuated::new(); + ty.into() + } + rest => rest, + }) + .collect(); + enum_alias_generics + }; + let variants_generics = self .attrs .implementers .iter() .enumerate() - .map(|(id, ty)| format_ident!("I{}", id)); + .map(|(id, _)| format_ident!("I{}", id)); let variants_idents = self .attrs @@ -356,23 +373,38 @@ impl Definition { .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - let enum_generics = self - .trait_generics - .params - .iter() - .map(ToTokens::to_token_stream) - .chain(variants_generics.clone().map(ToTokens::into_token_stream)); + let enum_generics = { + let mut enum_generics = self.trait_generics.clone(); + let enum_generic_params = std::mem::take(&mut enum_generics.params); + let (mut enum_generic_params_lifetimes, enum_generic_params_rest) = enum_generic_params + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let variants = variants_generics + .clone() + .map::(|var| parse_quote! { #var }) + .collect::>(); + enum_generic_params_lifetimes.extend(variants); + enum_generic_params_lifetimes.extend(enum_generic_params_rest); + // variants.extend(enum_generic_params_rest); + enum_generics.params = enum_generic_params_lifetimes; + enum_generics + }; + let enum_to_alias_generics = self - .trait_generics - .params + .attrs + .implementers .iter() .map(ToTokens::to_token_stream) - .chain( - self.attrs - .implementers - .iter() - .map(ToTokens::to_token_stream), - ); + .chain(self.trait_generics.params.iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })); let from_impls = self .attrs @@ -393,12 +425,12 @@ impl Definition { quote! { #[derive(Clone, Copy, Debug)] - #vis enum #enum_ident<#(#enum_generics,)*> { - #(#variants_idents(#variants_generics)),* + #vis enum #enum_ident#enum_generics { + #(#variants_idents(#variants_generics),)* #phantom_variant } - #vis type #alias_ident#trait_gens = + #vis type #alias_ident#enum_alias_generics = #enum_ident<#(#enum_to_alias_generics,)*>; #(#from_impls)* @@ -803,6 +835,10 @@ impl Definition { }) .unzip(); + let unreachable_arm = (self.attrs.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { + quote! { _ => unreachable!() } + }); + Some(quote! { impl#impl_generics ::juniper::macros::helper::#trait_name< #scalar, @@ -857,6 +893,7 @@ impl Definition { { ::juniper::macros::helper::fnv1a128(#name) }, >>::call(v, info, args, executor) })* + #unreachable_arm } } } @@ -1003,7 +1040,8 @@ impl Definition { } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.ident; + let ty = self.attrs.enum_alias_ident(&self.ident); + // let ty = &self.ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty#ty_generics } From a1df3873c948bd6953061e0f11c07506583082ce Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 14:31:06 +0300 Subject: [PATCH 017/122] WIP (move to the new tests volume 2) --- .../src/codegen/interface_attr.rs | 315 +++++++----------- .../src/codegen/new_interface.rs | 52 ++- juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/graphql_interface/new.rs | 79 ++++- 4 files changed, 224 insertions(+), 224 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 18115e77f..108d61cc7 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1637,29 +1637,30 @@ mod generic_lifetime_async { mod argument { use super::*; - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { fn id_wide(&self, is_number: bool) -> &str; async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } - #[graphql_interface(dyn = DynHero, for = Human)] - trait Hero { - fn info_wide(&self, is_planet: bool) -> &str; - - async fn info_wide2(&self, is_planet: bool, r#async: Option) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + // #[derive(GraphQLObject)] + // #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } + fn id_wide(&self, is_number: bool) -> &str { if is_number { &self.id @@ -1668,7 +1669,7 @@ mod argument { } } - async fn id_wide2(&self, is_number: bool, _: Option) -> &str { + async fn id_wide2(&self, is_number: bool, _async: Option) -> &str { if is_number { &self.id } else { @@ -1677,25 +1678,6 @@ mod argument { } } - #[graphql_interface(dyn)] - impl Hero for Human { - fn info_wide(&self, is_planet: bool) -> &str { - if is_planet { - &self.home_planet - } else { - &self.id - } - } - - async fn info_wide2(&self, is_planet: bool, _: Option) -> &str { - if is_planet { - &self.home_planet - } else { - &self.id - } - } - } - struct QueryRoot; #[graphql_object(scalar = S: ScalarValue + Send + Sync)] @@ -1707,13 +1689,6 @@ mod argument { } .into() } - - fn hero(&self) -> Box> { - Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }) - } } #[tokio::test] @@ -1745,141 +1720,90 @@ mod argument { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - let schema = schema(QueryRoot); - - for (input, expected) in &[ - ( - "{ hero { infoWide(isPlanet: true), infoWide2(isPlanet: true) } }", - "earth", - ), - ( - "{ hero { infoWide(isPlanet: false), infoWide2(isPlanet: false, async: 3) } }", - "human-32", - ), - ] { - let expected: &str = *expected; - - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": { - "infoWide": expected, - "infoWide2": expected, - }}), - vec![], - )), - ); - } - } - #[tokio::test] async fn camelcases_name() { let schema = schema(QueryRoot); - for (interface, field, arg) in &[ - ("Character", "idWide", "isNumber"), - ("Hero", "infoWide", "isPlanet"), - ] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - args {{ - name - }} - }} - }} - }}"#, - interface, - ); - - let expected_field_name: &str = *field; - let expected_field_name2: &str = &format!("{}2", field); - let expected_arg_name: &str = *arg; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{ - "name": expected_field_name, - "args": [ - {"name": expected_arg_name}, - ], - }, { - "name": expected_field_name2, - "args": [ - {"name": expected_arg_name}, - {"name": "async"}, - ], - }]}}), - vec![], - )), - ); - } + let doc = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{ + "name": "idWide", + "args": [ + {"name": "isNumber"}, + ], + },{ + "name": "idWide2", + "args": [ + {"name": "isNumber"}, + {"name": "async"} + ], + }]}}), + vec![], + )), + ); } #[tokio::test] async fn has_no_description() { let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - args {{ - description - }} - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + fields { + args { + description + } + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"args": [{"description": null}]}, - {"args": [{"description": null}, {"description": null}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"description": null}]}, + {"args": [{"description": null}, {"description": null}]}, + ]}}), + vec![], + )), + ); } #[tokio::test] async fn has_no_defaults() { let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - args {{ - defaultValue - }} - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + fields { + args { + defaultValue + } + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"args": [{"defaultValue": null}]}, - {"args": [{"defaultValue": null}, {"defaultValue": null}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"defaultValue": null}]}, + {"args": [{"defaultValue": null}, {"defaultValue": null}]}, + ]}}), + vec![], + )), + ); } } @@ -1891,7 +1815,7 @@ mod default_argument { x: i32, } - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { async fn id( &self, @@ -1905,15 +1829,17 @@ mod default_argument { } } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, info: i32, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn info(&self, coord: Point) -> i32 { + coord.x + } + async fn id(&self, first: String, second: String, third: String) -> String { format!("{}|{}&{}", first, second, third) } @@ -2030,7 +1956,7 @@ mod description_from_doc_comment { use super::*; /// Rust docs. - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { /// Rust `id` docs. /// Long. @@ -2044,13 +1970,6 @@ mod description_from_doc_comment { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - struct QueryRoot; #[graphql_object] @@ -2093,33 +2012,39 @@ mod description_from_doc_comment { mod deprecation_from_attr { use super::*; - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { fn id(&self) -> &str; #[deprecated] - fn a(&self) -> &str { - "a" - } + fn a(&self) -> &str; #[deprecated(note = "Use `id`.")] - fn b(&self) -> &str { - "b" - } + fn b(&self) -> &str; } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { fn id(&self) -> &str { &self.id } + + fn human_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> &'static str { + "a" + } + + fn b() -> String { + "b".to_owned() + } } struct QueryRoot; @@ -2225,7 +2150,7 @@ mod explicit_name_description_and_deprecation { use super::*; /// Rust docs. - #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] + #[graphql_interface_new(name = "MyChar", desc = "My character.", for = Human)] trait Character { /// Rust `id` docs. #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] @@ -2234,27 +2159,33 @@ mod explicit_name_description_and_deprecation { #[graphql(deprecated)] #[deprecated(note = "Should be omitted.")] - fn a(&self) -> &str { - "a" - } + fn a(&self) -> &str; - fn b(&self) -> &str { - "b" - } + fn b(&self) -> &str; } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self, _: Option) -> &str { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn my_id(&self, #[graphql(name = "myName")] _: Option) -> &str { &self.id } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> String { + "a".to_owned() + } + + fn b() -> &'static str { + "b" + } } struct QueryRoot; diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index 95c0d465d..24117e331 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -6,31 +6,51 @@ use juniper::{ // -------------------------- -#[graphql_interface(for = [Human, Droid])] -trait Character<'me, A> { - async fn id<'a>(&'a self) -> &'a str; +#[derive(GraphQLInputObject, Debug)] +struct Point { + x: i32, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<()>)] -struct Human { - id: String, - home_planet: String, +#[graphql_interface_new(for = Human)] +trait Character { + async fn id( + &self, + #[graphql(default)] first: String, + #[graphql(default = "second".to_string())] second: String, + #[graphql(default = "t")] third: String, + ) -> String; + + fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { + coord.x + } } -struct Droid { +struct Human { id: String, - primary_function: String, + info: i32, } -#[graphql_object(impl = CharacterValue<()>)] -impl Droid { - fn id(&self) -> &str { - &self.id +#[graphql_object(impl = CharacterValue)] +impl Human { + fn info(&self, _coord: Point) -> i32 { + self.info } - fn primary_function(&self) -> &str { - &self.primary_function + async fn id(&self, first: String, second: String, third: String) -> String { + format!("{}|{}&{}", first, second, third) + } +} + +struct QueryRoot; + +#[graphql_object] +impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + info: 0, + } + .into() } } diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index acd1a33ee..190c91129 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true proc-macro-error = "1.0.2" proc-macro2 = "1.0.1" quote = "1.0.3" -syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing"], default-features = false } +syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit"], default-features = false } url = "2.0" [dev-dependencies] diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 3056b7175..7f159fb21 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -8,7 +8,7 @@ use std::{ convert::TryInto as _, }; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; use syn::{ ext::IdentExt as _, @@ -17,6 +17,7 @@ use syn::{ punctuated::Punctuated, spanned::Spanned as _, token, + visit::Visit, }; use crate::{ @@ -393,18 +394,36 @@ impl Definition { enum_generics }; - let enum_to_alias_generics = self - .attrs - .implementers - .iter() - .map(ToTokens::to_token_stream) - .chain(self.trait_generics.params.iter().map(|par| match par { - syn::GenericParam::Type(ty) => { - let par_ident = &ty.ident; - quote! { #par_ident } - } - rest => quote! { #rest }, - })); + let enum_to_alias_generics = { + let (lifetimes, rest) = self + .trait_generics + .params + .iter() + .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); + + lifetimes + .into_iter() + .map(|par| match par { + syn::GenericParam::Lifetime(def) => { + let lifetime = &def.lifetime; + quote! { #lifetime } + } + rest => quote! { #rest }, + }) + .chain( + self.attrs + .implementers + .iter() + .map(ToTokens::to_token_stream), + ) + .chain(rest.into_iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })) + }; let from_impls = self .attrs @@ -556,7 +575,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_interface_type::<#ty>(info, &fields) + registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description .into_meta() } @@ -766,6 +785,30 @@ impl Definition { /// TODO fn impl_fields(&self, for_async: bool) -> TokenStream { + struct ReplaceGenericsForConst(syn::AngleBracketedGenericArguments); + + impl Visit<'_> for ReplaceGenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + match param { + syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + self.0.args.push(parse_quote!(())); + } + + // let ty = ty + // .default + // .as_ref() + // .map_or_else(|| parse_quote!(()), |def| parse_quote!( #def )); + // self.0.args.push(ty); + } + syn::GenericParam::Const(_) => { + unimplemented!() + } + } + } + } + let scalar = &self.scalar; let const_scalar = match scalar { scalar::Type::Concrete(ty) => ty.to_token_stream(), @@ -835,6 +878,12 @@ impl Definition { }) .unzip(); + let const_ty_generics = { + let mut visitor = ReplaceGenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + visitor.0 + }; + let unreachable_arm = (self.attrs.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { quote! { _ => unreachable!() } }); @@ -878,7 +927,7 @@ impl Definition { >>::WRAPPED_VALUE, )); const _: () = ::std::assert!(::juniper::macros::helper::is_valid_field_args( - <#ty as ::juniper::macros::helper::#trait_name< + <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< #const_scalar, { ::juniper::macros::helper::fnv1a128(#name) }, >>::ARGUMENTS, From 5040d688d417890b2ad8b8d7bc5cfa1118c898b1 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 15:28:50 +0300 Subject: [PATCH 018/122] WIP (move to the new tests volume 3) --- .../src/codegen/interface_attr.rs | 473 +----------------- .../src/codegen/new_interface.rs | 51 +- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/graphql_interface/new.rs | 49 +- 5 files changed, 66 insertions(+), 511 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 108d61cc7..3d1a3dc3a 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2336,7 +2336,7 @@ mod explicit_name_description_and_deprecation { mod renamed_all_fields_and_args { use super::*; - #[graphql_interface(rename_all = "none", for = Human)] + #[graphql_interface_new(rename_all = "none", for = Human)] trait Character { fn id(&self) -> &str { "human-32" @@ -2368,9 +2368,6 @@ mod renamed_all_fields_and_args { } } - #[graphql_interface] - impl Character for Human {} - struct QueryRoot; #[graphql_object] @@ -2437,60 +2434,26 @@ mod renamed_all_fields_and_args { mod explicit_scalar { use super::*; - #[graphql_interface(for = [Human, Droid])] - #[graphql_interface(scalar = DefaultScalarValue)] + #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface_new(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 = [CharacterValue, DynHero], scalar = DefaultScalarValue)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = DefaultScalarValue)] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = DefaultScalarValue)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = DefaultScalarValue)] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = DefaultScalarValue)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2513,20 +2476,6 @@ mod explicit_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2573,50 +2522,6 @@ mod explicit_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2638,25 +2543,6 @@ mod explicit_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod custom_scalar { @@ -2664,59 +2550,25 @@ mod custom_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] + #[graphql_interface_new(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 = [CharacterValue, DynHero], scalar = MyScalarValue)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = MyScalarValue)] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = MyScalarValue)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = MyScalarValue)] - impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = MyScalarValue)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2739,20 +2591,6 @@ mod custom_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2799,50 +2637,6 @@ mod custom_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema_with_scalar::(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema_with_scalar::(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2864,82 +2658,30 @@ mod custom_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema_with_scalar::(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod explicit_generic_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = S)] + #[graphql_interface_new(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 = [CharacterValue<__S>, DynHero<__S>])] + #[graphql(impl = CharacterValue<__S>)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S)] - impl Character for Human { - fn id(&self) -> FieldResult<&str, S> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Human { - async fn info(&self) -> FieldResult<&str, S> { - Ok(&self.home_planet) - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] + #[graphql(impl = CharacterValue<__S>)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = S)] - impl Character for Droid { - fn id(&self) -> FieldResult<&str, S> { - Ok(&self.id) - } - } - - #[graphql_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, @@ -2962,20 +2704,6 @@ mod explicit_generic_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3022,50 +2750,6 @@ mod explicit_generic_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -3087,82 +2771,30 @@ mod explicit_generic_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod bounded_generic_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + #[graphql_interface_new(for = [Human, Droid], scalar = S: ScalarValue + Clone)] trait Character { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S: ScalarValue + Clone)] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = S: ScalarValue + Clone)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S: ScalarValue + Clone)] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = S: ScalarValue + Clone)] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -3185,20 +2817,6 @@ mod bounded_generic_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3245,50 +2863,6 @@ mod bounded_generic_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -3310,25 +2884,6 @@ mod bounded_generic_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod explicit_custom_context { diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index 24117e331..a642822d2 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -6,52 +6,23 @@ use juniper::{ // -------------------------- -#[derive(GraphQLInputObject, Debug)] -struct Point { - x: i32, -} - -#[graphql_interface_new(for = Human)] -trait Character { - async fn id( - &self, - #[graphql(default)] first: String, - #[graphql(default = "second".to_string())] second: String, - #[graphql(default = "t")] third: String, - ) -> String; - - fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { - coord.x - } +#[graphql_interface_new(for = [Human, Droid], scalar = S)] +trait Character { + fn id(&self) -> FieldResult<&str, S>; } +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, - info: i32, -} - -#[graphql_object(impl = CharacterValue)] -impl Human { - fn info(&self, _coord: Point) -> i32 { - self.info - } - - async fn id(&self, first: String, second: String, third: String) -> String { - format!("{}|{}&{}", first, second, third) - } + home_planet: String, } -struct QueryRoot; - -#[graphql_object] -impl QueryRoot { - fn character(&self) -> CharacterValue { - Human { - id: "human-32".to_string(), - info: 0, - } - .into() - } +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<__S>)] +struct Droid { + id: String, + primary_function: String, } // -------------- diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 304ba34c5..76a1c8542 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'_, '_, CustomContext, S>, + executor: &'e Executor<'e, 'e, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 190c91129..746c54944 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true proc-macro-error = "1.0.2" proc-macro2 = "1.0.1" quote = "1.0.3" -syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit"], default-features = false } +syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit", "visit-mut"], default-features = false } url = "2.0" [dev-dependencies] diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 7f159fb21..8e5e386a8 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -18,6 +18,7 @@ use syn::{ spanned::Spanned as _, token, visit::Visit, + visit_mut::VisitMut, }; use crate::{ @@ -785,9 +786,9 @@ impl Definition { /// TODO fn impl_fields(&self, for_async: bool) -> TokenStream { - struct ReplaceGenericsForConst(syn::AngleBracketedGenericArguments); + struct GenericsForConst(syn::AngleBracketedGenericArguments); - impl Visit<'_> for ReplaceGenericsForConst { + impl Visit<'_> for GenericsForConst { fn visit_generic_param(&mut self, param: &syn::GenericParam) { match param { syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), @@ -795,12 +796,6 @@ impl Definition { if ty.default.is_none() { self.0.args.push(parse_quote!(())); } - - // let ty = ty - // .default - // .as_ref() - // .map_or_else(|| parse_quote!(()), |def| parse_quote!( #def )); - // self.0.args.push(ty); } syn::GenericParam::Const(_) => { unimplemented!() @@ -809,6 +804,39 @@ impl Definition { } } + struct ReplaceGenericsForConst<'a>(&'a syn::Generics); + + impl<'a> VisitMut for ReplaceGenericsForConst<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote!( 'static ); + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + println!("{} == {}", par, ty); + par == ty + }); + + if is_generic { + *ty = parse_quote!(()); + } + } + _ => {} + } + } + } + let scalar = &self.scalar; let const_scalar = match scalar { scalar::Type::Concrete(ty) => ty.to_token_stream(), @@ -862,7 +890,8 @@ impl Definition { }; let name = &field.name; - let return_ty = &field.ty; + let mut return_ty = field.ty.clone(); + ReplaceGenericsForConst(&generics).visit_type_mut(&mut return_ty); let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments @@ -879,7 +908,7 @@ impl Definition { .unzip(); let const_ty_generics = { - let mut visitor = ReplaceGenericsForConst(parse_quote!( <> )); + let mut visitor = GenericsForConst(parse_quote!( <> )); visitor.visit_generics(&self.trait_generics); visitor.0 }; From f5533e3ec5971cd16e956e5cb12e398e74e6f67a Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 16:48:04 +0300 Subject: [PATCH 019/122] WIP (move to the new tests volume 4) --- .../src/codegen/interface_attr.rs | 848 +++--------------- .../src/codegen/new_interface.rs | 50 +- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper_codegen/src/graphql_interface/new.rs | 7 +- 4 files changed, 193 insertions(+), 714 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 3d1a3dc3a..4fc7dfa49 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2893,7 +2893,7 @@ mod explicit_custom_context { impl juniper::Context for CustomContext {} - #[graphql_interface(for = [Human, Droid], context = CustomContext)] + #[graphql_interface_new(for = [Human, Droid], context = CustomContext)] trait Character { async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; @@ -2902,86 +2902,50 @@ mod explicit_custom_context { fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(context = CustomContext)] - trait Hero { - async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - - async fn info<'b>(&'b self, ctx: &()) -> &'b str; - - fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Human { + fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { &self.id } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn home_planet(&self) -> &str { &self.home_planet } - fn more(&self, _: &CustomContext) -> &'static str { - "human" - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { - &self.id - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self, _ctx: &()) -> &'b str { &self.home_planet } - fn more(&self, _: &CustomContext) -> &'static str { + fn more(&self, #[graphql(context)] _: &CustomContext) -> &'static str { "human" } } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Droid { + async fn id<'a>(&'a self) -> &'a str { &self.id } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn primary_function(&self) -> &str { &self.primary_function } - fn more(&self, _: &CustomContext) -> &'static str { - "droid" - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { - &self.id - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { + async fn info<'b>(&'b self) -> &'b str { &self.primary_function } - fn more(&self, _: &CustomContext) -> &'static str { + fn more(&self) -> &'static str { "droid" } } @@ -3008,20 +2972,6 @@ mod explicit_custom_context { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3069,86 +3019,35 @@ mod explicit_custom_context { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } + async fn resolves_fields() { + let doc = r#"{ + character { + id + info + more } }"#; - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } + 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); - #[tokio::test] - async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - more - }} - }}"#, - interface, + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + let expexted_more: &str = *expexted_more; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + "more": expexted_more, + }}), + vec![], + )), ); - - let expected_interface: &str = *interface; - - for (root, expected_id, expected_info, expexted_more) in &[ - (QueryRoot::Human, "human-32", "earth", "human"), - (QueryRoot::Droid, "droid-99", "run", "droid"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - let expexted_more: &str = *expexted_more; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({expected_interface: { - "id": expected_id, - "info": expected_info, - "more": expexted_more, - }}), - vec![], - )), - ); - } } } } @@ -3160,74 +3059,49 @@ mod inferred_custom_context_from_field { impl juniper::Context for CustomContext {} - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(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 = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Human { fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } - fn info<'b>(&'b self, _: &()) -> &'b str { + fn home_planet(&self) -> &str { &self.home_planet } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { - &ctx.0 - } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self, _context: &()) -> &'b str { &self.home_planet } } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Droid { fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } - fn info<'b>(&'b self, _: &()) -> &'b str { + fn primary_function(&self) -> &str { &self.primary_function } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { - &ctx.0 - } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self) -> &'b str { &self.primary_function } } @@ -3254,20 +3128,6 @@ mod inferred_custom_context_from_field { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3287,7 +3147,7 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "in-ctx", "homePlanet": "earth"}}), vec![], )), ); @@ -3310,189 +3170,120 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "in-droid", "primaryFunction": "run"}}), vec![], )), ); } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } + async fn resolves_fields() { + let doc = r#"{ + character { + id + info } }"#; - let schema = schema(QueryRoot::Human); - let ctx = CustomContext("in-ctx".into()); + 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()); - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + }}), + vec![], + )), + ); + } } +} - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; +mod executor { + use juniper::LookAheadMethods as _; - let schema = schema(QueryRoot::Droid); - let ctx = CustomContext("in-droid".into()); + use super::*; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); + #[graphql_interface_new(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, + arg: Option, + #[graphql(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; } - #[tokio::test] - async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - }} - }}"#, - interface, - ); + struct Human { + id: String, + home_planet: String, + } - let expected_interface: &str = *interface; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-ctx", "earth"), - (QueryRoot::Droid, "droid-ctx", "run"), - ] { - let schema = schema(*root); - let ctx = CustomContext(expected_id.to_string()); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({expected_interface: { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } + #[graphql_object(impl = CharacterValue<__S>)] + impl Human { + async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() } - } -} -mod inferred_custom_context_from_downcast { - use super::*; + fn home_planet(&self) -> &str { + &self.home_planet + } - struct Database { - droid: Option, + async fn info<'b>(&'b self, _arg: Option) -> &'b str { + &self.home_planet + } } - impl juniper::Context for Database {} - - #[graphql_interface(for = [Human, Droid])] - trait Character { - #[graphql(downcast)] - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human>; - - async fn id(&self) -> &str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - #[graphql(downcast)] - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid>; - - async fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { - Some(self) - } - - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn as_droid<'db>(&self, _: &'db Database) -> Option<&'db Droid> { - None - } - - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { - None - } - - async fn id(&self) -> &str { - &self.id + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { - db.droid.as_ref() + fn primary_function(&self) -> &str { + &self.primary_function } - async fn info(&self) -> &str { + async fn info<'b, S: ScalarValue>( + &'b self, + _arg: Option, + _executor: &Executor<'_, '_, (), S>, + ) -> &'b str { &self.primary_function } } - #[derive(Clone)] + #[derive(Clone, Copy)] enum QueryRoot { Human, Droid, } - #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn character(&self) -> CharacterValue { + fn character(&self) -> CharacterValue { match self { Self::Human => Human { id: "human-32".to_string(), @@ -3506,20 +3297,6 @@ mod inferred_custom_context_from_downcast { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3534,12 +3311,11 @@ mod inferred_custom_context_from_downcast { }"#; let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "humanId", "homePlanet": "earth"}}), vec![], )), ); @@ -3557,301 +3333,48 @@ mod inferred_custom_context_from_downcast { }"#; let schema = schema(QueryRoot::Droid); - let db = Database { - droid: Some(Droid { - id: "droid-88".to_string(), - primary_function: "sit".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - droid: Some(Droid { - id: "droid-88".to_string(), - primary_function: "sit".to_string(), - }), - }; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-88", "primaryFunction": "sit"}}), + graphql_value!({"character": {"droidId": "droidId", "primaryFunction": "run"}}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ + async fn resolves_fields() { + let doc = r#"{ character { id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - let db = Database { droid: None }; - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { info } }"#; for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - let db = Database { droid: None }; + let schema = schema(*root); let expected_info: &str = *expected_info; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"id": "id", "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, - arg: Option, - #[graphql(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, - arg: Option, - #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface(scalar = S)] - impl Character for Human { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.home_planet - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Human { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface(scalar = S)] - impl Character for Droid { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.primary_function - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Droid { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = DefaultScalarValue)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet + async fn not_arg() { + let doc = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } } } }"#; @@ -3859,97 +3382,16 @@ mod executor { let schema = schema(QueryRoot::Human); assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "info", "args": [{"name": "arg"}]}, + ]}}), 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, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({expected_interface: {"id": "id", "info": expected_info}}), - vec![], - )), - ); - } - } - } - - #[tokio::test] - async fn not_arg() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - args {{ - name - }} - }} - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"name": "id", "args": []}, - {"name": "info", "args": [{"name": "arg"}]}, - ]}}), - vec![], - )), - ); - } - } } mod ignored_method { diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index a642822d2..ca3a92922 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -1,30 +1,66 @@ use juniper::{ execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, LookAheadMethods, RootNode, + ScalarValue, }; // -------------------------- #[graphql_interface_new(for = [Human, Droid], scalar = S)] -trait Character { - fn id(&self) -> FieldResult<&str, 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, + arg: Option, + #[graphql(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, home_planet: String, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] +#[graphql_object(impl = CharacterValue<__S>)] +impl Human { + async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() + } + + async fn info<'b>(&'b self, _arg: Option) -> &'b str { + &self.home_planet + } +} + struct Droid { id: String, primary_function: String, } +#[graphql_object(impl = CharacterValue<__S>)] +impl Droid { + fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() + } + + async fn info<'b, S: ScalarValue>( + &'b self, + _arg: Option, + _executor: &Executor<'_, '_, (), S>, + ) -> &'b str { + &self.primary_function + } +} + // -------------- fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 76a1c8542..304ba34c5 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'e, 'e, CustomContext, S>, + executor: &'e Executor<'_, '_, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 8e5e386a8..5321477a7 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -794,7 +794,9 @@ impl Definition { syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), syn::GenericParam::Type(ty) => { if ty.default.is_none() { - self.0.args.push(parse_quote!(())); + self.0 + .args + .push(parse_quote!(::juniper::DefaultScalarValue)); } } syn::GenericParam::Const(_) => { @@ -824,12 +826,11 @@ impl Definition { .any(|par| { let par = quote! { #par }.to_string(); let ty = quote! { #ty }.to_string(); - println!("{} == {}", par, ty); par == ty }); if is_generic { - *ty = parse_quote!(()); + *ty = parse_quote!(::juniper::DefaultScalarValue); } } _ => {} From 527325c85a65130a4a489daed0845124d121e9dc Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 16:52:07 +0300 Subject: [PATCH 020/122] WIP (move to the new tests volume 5) --- .../src/codegen/interface_attr.rs | 553 +----------------- .../juniper_tests/src/codegen/object_attr.rs | 2 +- 2 files changed, 2 insertions(+), 553 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 4fc7dfa49..0a76fd315 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3397,7 +3397,7 @@ mod executor { mod ignored_method { use super::*; - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { fn id(&self) -> &str; @@ -3417,13 +3417,6 @@ mod ignored_method { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - struct QueryRoot; #[graphql_object] @@ -3496,547 +3489,3 @@ mod ignored_method { ); } } - -mod downcast_method { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - - #[graphql(downcast)] - fn as_human(&self) -> Option<&Human> { - None - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - - #[graphql(downcast)] - fn as_droid(&self) -> Option<&Droid> { - None - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn as_human(&self) -> Option<&Human> { - Some(self) - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - 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)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_not_field() { - let schema = schema(QueryRoot::Human); - - for (doc, field) in &[ - (r#"{__type(name: "Character") { fields { name } } }"#, "id"), - (r#"{__type(name: "Hero") { fields { name } } }"#, "info"), - ] { - let expected_field: &str = *field; - - assert_eq!( - execute(*doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{"name": expected_field}]}}), - vec![], - )), - ); - } - } -} - -mod external_downcast { - use super::*; - - struct Database { - human: Option, - droid: Option, - } - - impl juniper::Context for Database {} - - #[graphql_interface(for = [Human, Droid])] - #[graphql_interface(context = Database, on Human = CharacterValue::as_human)] - trait Character { - fn id(&self) -> &str; - } - - impl CharacterValue { - fn as_human<'db>(&self, db: &'db Database) -> Option<&'db Human> { - db.human.as_ref() - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(context = Database)] - #[graphql_interface(on Droid = DynHero::as_droid)] - trait Hero { - fn info(&self) -> &str; - } - - impl<'a, S: ScalarValue> DynHero<'a, S> { - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { - db.droid.as_ref() - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { - human: Some(Human { - id: "human-64".to_string(), - home_planet: "mars".to_string(), - }), - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"humanId": "human-64", "homePlanet": "mars"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - human: None, - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { - human: None, - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - human: None, - droid: Some(Droid { - id: "droid-01".to_string(), - primary_function: "swim".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-01", "primaryFunction": "swim"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - let db = Database { - human: Some(Human { - id: "human-64".to_string(), - home_planet: "mars".to_string(), - }), - droid: None, - }; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - let db = Database { - human: None, - droid: Some(Droid { - id: "droid-01".to_string(), - primary_function: "swim".to_string(), - }), - }; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } -} diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 304ba34c5..76a1c8542 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'_, '_, CustomContext, S>, + executor: &'e Executor<'e, 'e, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } From 704f07937766901a973f7ee7543eb353e7a0d1c5 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 17:04:54 +0300 Subject: [PATCH 021/122] WIP (move to the new tests volume 5) --- juniper_codegen/src/graphql_interface/new.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 5321477a7..e61c10ffb 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -2,14 +2,10 @@ //! //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces -use std::{ - any::TypeId, - collections::{HashMap, HashSet}, - convert::TryInto as _, -}; +use std::{collections::HashSet, convert::TryInto as _}; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, @@ -26,7 +22,7 @@ use crate::{ field, gen, parse::{ attr::{err, OptionExt as _}, - GenericsExt as _, ParseBufferExt as _, + ParseBufferExt as _, }, scalar, }, From b84009e627ae6fa65a7449cf2a3ff2fa05365f0a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 12:59:11 +0300 Subject: [PATCH 022/122] WIP (refactor) --- juniper_codegen/src/graphql_interface/attr.rs | 25 +- juniper_codegen/src/graphql_interface/new.rs | 444 +++++++++--------- 2 files changed, 244 insertions(+), 225 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 475ea3eaf..33dce4f74 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::{ @@ -403,19 +403,32 @@ fn expand_on_trait_new( // ))) // }; + let enum_alias_ident = attr + .r#enum + .as_deref() + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + let enum_ident = attr.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", trait_ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + let description = attr.description.as_ref().map(|c| c.inner().clone()); let generated_code = new::Definition { - attrs: attr, - ident: trait_ident.clone(), - vis: ast.vis.clone(), trait_generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, name, description, - context, scalar: scalar.clone(), - fields, + implementers: attr + .implementers + .iter() + .map(|c| c.inner().clone()) + .collect(), }; // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index e61c10ffb..cb929b704 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -22,7 +22,7 @@ use crate::{ field, gen, parse::{ attr::{err, OptionExt as _}, - ParseBufferExt as _, + GenericsExt as _, ParseBufferExt as _, }, scalar, }, @@ -229,36 +229,35 @@ impl TraitAttr { Ok(attr) } - - /// TODO - fn enum_alias_ident(&self, trait_name: &syn::Ident) -> SpanContainer { - self.r#enum.clone().unwrap_or_else(|| { - SpanContainer::new( - trait_name.span(), - Some(trait_name.span()), - format_ident!("{}Value", trait_name.to_string()), - ) - }) - } } /// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub(crate) struct Definition { - /// TODO - pub(crate) attrs: TraitAttr, - - pub(crate) ident: syn::Ident, + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) trait_generics: syn::Generics, + /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub(crate) vis: syn::Visibility, - pub(crate) trait_generics: syn::Generics, + /// Name of the generic enum describing all [`implementers`]. It's generic + /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. + /// + /// [`implementers`]: Self::implementers + /// [`Debug`]: std::fmt::Debug + pub(crate) enum_ident: syn::Ident, + + /// Name of the type alias for [`enum_ident`] with [`implementers`]. + /// + /// [`enum_ident`]: Self::enum_ident + /// [`implementers`]: Self::implementers + pub(crate) enum_alias_ident: syn::Ident, - // /// Rust type that this [GraphQL interface][1] is represented with. - // /// - // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - // ty: syn::Type, /// Name of this [GraphQL interface][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -290,10 +289,11 @@ pub(crate) struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) fields: Vec, - // /// Defined [`Implementer`]s of this [GraphQL interface][1]. - // /// - // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - // implementers: Vec, + + /// Defined [`Implementer`]s of this [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) implementers: Vec, } impl ToTokens for Definition { @@ -304,101 +304,64 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection().to_tokens(into); self.impl_fields(false).to_tokens(into); self.impl_fields(true).to_tokens(into); } } impl Definition { + /// Generates enum describing all [`implementers`]. + /// + /// [`implementers`]: Self::implementers + #[must_use] fn generate_enum(&self) -> TokenStream { let vis = &self.vis; - let trait_gens = &self.trait_generics; - let (trait_impl_gens, trait_ty_gens, trait_where_clause) = - self.trait_generics.split_for_impl(); + let enum_ident = &self.enum_ident; + let alias_ident = &self.enum_alias_ident; - let ty_params = self.trait_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>> - } - }); - let phantom_variant = - (!self.trait_generics.params.is_empty()).then(|| quote! { __Phantom(#(#ty_params,)*) }); - - let alias_ident = self.attrs.enum_alias_ident(&self.ident); - let enum_ident = self.attrs.r#enum.as_ref().map_or_else( - || format_ident!("{}ValueEnum", self.ident.to_string()), - |c| format_ident!("{}Enum", c.inner().to_string()), - ); - - let enum_alias_generics = { - let mut enum_alias_generics = trait_gens.clone(); - let enum_generics = std::mem::take(&mut enum_alias_generics.params); - enum_alias_generics.params = enum_generics - .into_iter() - .map(|gen| match gen { - syn::GenericParam::Type(mut ty) => { - ty.bounds = Punctuated::new(); - ty.into() - } - rest => rest, - }) - .collect(); - enum_alias_generics - }; - - let variants_generics = self - .attrs + let variant_gens_pars = self .implementers .iter() .enumerate() - .map(|(id, _)| format_ident!("I{}", id)); + .map::(|(id, _)| { + let par = format_ident!("__I{}", id); + parse_quote!( #par ) + }); let variants_idents = self - .attrs .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - let enum_generics = { - let mut enum_generics = self.trait_generics.clone(); - let enum_generic_params = std::mem::take(&mut enum_generics.params); - let (mut enum_generic_params_lifetimes, enum_generic_params_rest) = enum_generic_params - .into_iter() - .partition::, _>(|par| { - matches!(par, syn::GenericParam::Lifetime(_)) - }); + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); - let variants = variants_generics - .clone() - .map::(|var| parse_quote! { #var }) - .collect::>(); - enum_generic_params_lifetimes.extend(variants); - enum_generic_params_lifetimes.extend(enum_generic_params_rest); - // variants.extend(enum_generic_params_rest); - enum_generics.params = enum_generic_params_lifetimes; - enum_generics + let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + .params + .clone() + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let enum_gens = { + let mut enum_gens = trait_gens.clone(); + enum_gens.params = trait_gens_lifetimes.clone(); + enum_gens.params.extend(variant_gens_pars.clone()); + enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens }; - let enum_to_alias_generics = { - let (lifetimes, rest) = self - .trait_generics - .params - .iter() - .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); + let enum_alias_gens = { + let mut enum_alias_gens = trait_gens.clone(); + enum_alias_gens.move_bounds_to_where_clause(); + enum_alias_gens + }; - lifetimes + let enum_to_alias_gens = { + trait_gens_lifetimes .into_iter() .map(|par| match par { syn::GenericParam::Lifetime(def) => { @@ -407,13 +370,8 @@ impl Definition { } rest => quote! { #rest }, }) - .chain( - self.attrs - .implementers - .iter() - .map(ToTokens::to_token_stream), - ) - .chain(rest.into_iter().map(|par| match par { + .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(trait_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; quote! { #par_ident } @@ -422,12 +380,28 @@ impl Definition { })) }; - let from_impls = self - .attrs - .implementers - .iter() - .zip(variants_idents.clone()) - .map(|(ty, ident)| { + let phantom_variant = self.has_phantom_variant().then(|| { + let phantom_params = trait_gens.params.iter().filter_map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => return None, + }; + Some(quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + }) + }); + quote! { __Phantom(#(#phantom_params),*) } + }); + + let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( + |(ty, ident)| { quote! { impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens #trait_where_clause @@ -437,17 +411,18 @@ impl Definition { } } } - }); + }, + ); quote! { #[derive(Clone, Copy, Debug)] - #vis enum #enum_ident#enum_generics { - #(#variants_idents(#variants_generics),)* + #vis enum #enum_ident#enum_gens { + #(#variants_idents(#variant_gens_pars),)* #phantom_variant } - #vis type #alias_ident#enum_alias_generics = - #enum_ident<#(#enum_to_alias_generics,)*>; + #vis type #alias_ident#enum_alias_gens = + #enum_ident<#(#enum_to_alias_gens),*>; #(#from_impls)* } @@ -460,14 +435,14 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; let gens = self.impl_generics(false); let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); - let impler_tys = self.attrs.implementers.iter().collect::>(); + let impler_tys = &self.implementers; let all_implers_unique = (impler_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); @@ -493,19 +468,19 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.attrs.implementers.iter(); + let impler_tys = self.implementers.iter(); quote! { #[automatically_derived] @@ -528,12 +503,12 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); let name = &self.name; let description = self @@ -542,7 +517,7 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.attrs.implementers.iter().collect::>(); + let mut impler_tys = self.implementers.clone(); impler_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) @@ -587,14 +562,14 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let context = &self.context; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); - let trait_name = &self.name; let fields_resolvers = self.fields.iter().filter_map(|f| { (!f.is_async).then(|| { @@ -683,13 +658,13 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = &self.attrs.enum_alias_ident(&self.ident); - let trait_name = &self.name; let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; @@ -739,16 +714,23 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::helper::BaseSubTypes + /// [`BaseType`]: juniper::macros::helper::BaseType + /// [`WrappedType`]: juniper::macros::helper::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let implementers = &self.implementers; let scalar = &self.scalar; let name = &self.name; + let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); - let implementers = self.attrs.implementers.iter(); quote! { #[automatically_derived] @@ -780,73 +762,25 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`Field`] or [`AsyncField`] trait + /// for this [GraphQL interface][1]. + /// + /// [`AsyncField`]: juniper::macros::helper::AsyncField + /// [`Field`]: juniper::macros::helper::Field + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { - struct GenericsForConst(syn::AngleBracketedGenericArguments); - - impl Visit<'_> for GenericsForConst { - fn visit_generic_param(&mut self, param: &syn::GenericParam) { - match param { - syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), - syn::GenericParam::Type(ty) => { - if ty.default.is_none() { - self.0 - .args - .push(parse_quote!(::juniper::DefaultScalarValue)); - } - } - syn::GenericParam::Const(_) => { - unimplemented!() - } - } - } - } - - struct ReplaceGenericsForConst<'a>(&'a syn::Generics); - - impl<'a> VisitMut for ReplaceGenericsForConst<'a> { - fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { - match arg { - syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote!( 'static ); - } - syn::GenericArgument::Type(ty) => { - let is_generic = self - .0 - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .any(|par| { - let par = quote! { #par }.to_string(); - let ty = quote! { #ty }.to_string(); - par == ty - }); - - if is_generic { - *ty = parse_quote!(::juniper::DefaultScalarValue); - } - } - _ => {} - } - } - } - + let ty = &self.enum_alias_ident; + let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { - scalar::Type::Concrete(ty) => ty.to_token_stream(), + scalar::Type::Concrete(ty) => ty.clone(), scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { - quote! { ::juniper::DefaultScalarValue } + parse_quote! { ::juniper::DefaultScalarValue } } }; - let ty = self.attrs.enum_alias_ident(&self.ident); - let context = &self.context; - let impl_tys = self.attrs.implementers.iter().collect::>(); + let impl_tys = self.implementers.iter().collect::>(); let impl_idents = self - .attrs .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) @@ -860,6 +794,11 @@ impl Definition { if field.is_async && !for_async { return None; } + + let name = &field.name; + let mut return_ty = field.ty.clone(); + Self::replace_generics_for_const(&mut return_ty, &generics); + let (trait_name, call_sig) = if for_async { ( quote! { AsyncField }, @@ -886,10 +825,6 @@ impl Definition { ) }; - let name = &field.name; - let mut return_ty = field.ty.clone(); - ReplaceGenericsForConst(&generics).visit_type_mut(&mut return_ty); - let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments .iter() @@ -904,17 +839,14 @@ impl Definition { }) .unzip(); - let const_ty_generics = { - let mut visitor = GenericsForConst(parse_quote!( <> )); - visitor.visit_generics(&self.trait_generics); - visitor.0 - }; + let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.attrs.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { + let unreachable_arm = (self.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { quote! { _ => unreachable!() } }); Some(quote! { + #[allow(non_snake_case)] impl#impl_generics ::juniper::macros::helper::#trait_name< #scalar, { ::juniper::macros::helper::fnv1a128(#name) } @@ -987,7 +919,6 @@ impl Definition { let scalar = &self.scalar; let match_arms = self - .attrs .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) @@ -999,11 +930,10 @@ impl Definition { } }); - let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() - || self.attrs.implementers.is_empty()) - .then(|| { - quote! { _ => unreachable!(), } - }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1023,7 +953,7 @@ impl Definition { fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + let match_arms = self.implementers.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(v) => { @@ -1033,11 +963,10 @@ impl Definition { } }) }); - let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() - || self.attrs.implementers.is_empty()) - .then(|| { - quote! { _ => unreachable!(), } - }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1056,7 +985,7 @@ impl Definition { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + let match_arms = self.implementers.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(res) => #resolving_code, @@ -1064,11 +993,10 @@ impl Definition { }) }); - let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() - || self.attrs.implementers.is_empty()) - .then(|| { - quote! { _ => unreachable!(), } - }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1078,6 +1006,78 @@ impl Definition { } } + /// Returns trait generics replaced with default values for usage in `const` + /// context. + #[must_use] + fn const_trait_generics(&self) -> syn::PathArguments { + struct GenericsForConst(syn::AngleBracketedGenericArguments); + + impl Visit<'_> for GenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + let arg = match param { + syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + parse_quote!(::juniper::DefaultScalarValue) + } else { + return; + } + } + syn::GenericParam::Const(_) => { + // This hack works because only `min_const_generics` are + // enabled for now. + // TODO: replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } + }; + self.0.args.push(arg) + } + } + + let mut visitor = GenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + syn::PathArguments::AngleBracketed(visitor.0) + } + + /// Replaces `generics` in `ty` for usage in `const` context. + fn replace_generics_for_const(ty: &mut syn::Type, generics: &syn::Generics) { + struct ReplaceGenericsForConst<'a>(&'a syn::Generics); + + impl<'a> VisitMut for ReplaceGenericsForConst<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote!( 'static ); + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + *ty = parse_quote!(()); + } + } + _ => {} + } + } + } + + ReplaceGenericsForConst(&generics).visit_type_mut(ty) + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this [`EnumType`]. /// @@ -1115,8 +1115,7 @@ impl Definition { } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = self.attrs.enum_alias_ident(&self.ident); - // let ty = &self.ident; + let ty = &self.enum_alias_ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty#ty_generics } @@ -1138,4 +1137,11 @@ impl Definition { generics } + + /// 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() + } } From 11345f7b9178b339507b1f0b70bbbf947c227b39 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 13:01:29 +0300 Subject: [PATCH 023/122] WIP (refactor) --- juniper_codegen/src/graphql_interface/attr.rs | 77 ------------------- 1 file changed, 77 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 33dce4f74..511a2d175 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -286,27 +286,6 @@ fn expand_on_trait_new( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - // let mut implementers: Vec<_> = attr - // .implementers - // .iter() - // .map(|ty| Implementer { - // ty: ty.as_ref().clone(), - // downcast: None, - // context: None, - // scalar: scalar.clone(), - // }) - // .collect(); - // for (ty, downcast) in &attr.external_downcasts { - // match implementers.iter_mut().find(|i| &i.ty == ty) { - // Some(impler) => { - // impler.downcast = Some(ImplementerDowncast::External { - // path: downcast.inner().clone(), - // }); - // } - // None => err_only_implementer_downcast(&downcast.span_joined()), - // } - // } - proc_macro_error::abort_if_dirty(); let renaming = attr @@ -322,17 +301,6 @@ fn expand_on_trait_new( Some(TraitMethod::Field(f)) => fields.push(f), Some(TraitMethod::Downcast(_d)) => { unimplemented!(); - // match implementers.iter_mut().find(|i| i.ty == d.ty) { - // Some(impler) => { - // if let Some(external) = &impler.downcast { - // err_duplicate_downcast(m, external, &impler.ty); - // } else { - // impler.downcast = d.downcast; - // impler.context = d.context; - // } - // } - // None => err_only_implementer_downcast(&m.sig), - // } } _ => {} } @@ -363,16 +331,8 @@ fn expand_on_trait_new( }) }) }) - // .or_else(|| { - // implementers - // .iter() - // .find_map(|impler| impler.context.as_ref()) - // .cloned() - // }) .unwrap_or_else(|| parse_quote! { () }); - // let is_trait_object = attr.r#dyn.is_some(); - let is_async_trait = attr.asyncness.is_some() || ast .items @@ -387,22 +347,6 @@ fn expand_on_trait_new( _ => false, }); - // let ty = if is_trait_object { - // Type::TraitObject(Box::new(TraitObjectType::new( - // &ast, - // &attr, - // scalar.clone(), - // context.clone(), - // ))) - // } else { - // Type::Enum(Box::new(EnumType::new( - // &ast, - // &attr, - // &implementers, - // scalar.clone(), - // ))) - // }; - let enum_alias_ident = attr .r#enum .as_deref() @@ -431,27 +375,6 @@ fn expand_on_trait_new( .collect(), }; - // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. - // if is_trait_object { - // ast.attrs.push(parse_quote! { - // #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - // }); - // - // let scalar_ty = scalar.generic_ty(); - // if !scalar.is_explicit_generic() { - // let default_ty = scalar.default_ty(); - // ast.generics - // .params - // .push(parse_quote! { #scalar_ty = #default_ty }); - // } - // ast.generics - // .make_where_clause() - // .predicates - // .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); - // ast.supertraits - // .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); - // } - if is_async_trait { if has_default_async_methods { // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits From 5e7230f95ede63acde71005f7b2959ad1cb13421 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 16:02:14 +0300 Subject: [PATCH 024/122] WIP (moving to readable const asserts) --- juniper/src/macros/helper/mod.rs | 175 ++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index f5b4619aa..15bead5d1 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,7 +2,7 @@ pub mod subscription; -use std::{fmt, rc::Rc, sync::Arc}; +use std::{fmt, mem, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; @@ -130,6 +130,179 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +pub const fn number_of_digits(n: u128) -> usize { + if n == 0 { + return 1; + } + + let mut len = 0; + let mut current = n; + while current % 10 != 0 { + len += 1; + current /= 10; + } + len +} + +pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { + let mut len = ty.len() + 1; // Type! + + let mut current_wrap_val = v; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => len -= 1, // remove ! + 3 => len += 3, // [Type]! + _ => {} + } + + current_wrap_val /= 10; + } + + len +} + +const _: () = check_valid_field_args(); + +pub const fn check_valid_field_args() { + const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 1)]; + const IMPL: &[(Name, Type, WrappedValue)] = &[("test", "String", 12323)]; + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn find_len() -> usize { + let mut base_i = 0; + while base_i < BASE.len() { + let (base_name, base_type, base_wrap_val) = BASE[base_i]; + + let mut impl_i = 0; + while impl_i < IMPL.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; + + if str_eq(base_name, impl_name) { + if !(str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val) { + return str_len_from_wrapped_val(impl_type, impl_wrap_val); + } + } + impl_i += 1; + } + base_i += 1; + } + 0 + } + + let mut base_i = 0; + while base_i < BASE.len() { + let (base_name, base_type, base_wrap_val) = BASE[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; + + if str_eq(base_name, impl_name) { + if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { + was_found = true; + } else { + const IMPL_TYPE_LEN: usize = find_len(); + let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; + + let mut current_start = 0; + let mut current_end = IMPL_TYPE_LEN - 1; + let mut current_wrap_val = impl_wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = + OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end + - CLOSING_BRACKET.as_bytes().len() + + i + + 1] = CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = + OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < BANG.as_bytes().len() { + impl_type_arr + [current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end + - CLOSING_BRACKET.as_bytes().len() + + i + + 1] = CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + is_null = false; + } + _ => {} + } + + current_wrap_val /= 10; + } + + let mut i = 0; + while i < impl_type.as_bytes().len() { + impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + } + + let impl_type_str: &str = unsafe { mem::transmute(impl_type_arr.as_slice()) }; + + // TODO: format incorrect field type error. + panic!(impl_type_str); + } + } else if impl_wrap_val % 10 != 2 { + // TODO: format field must be nullable error. + panic!(impl_name); + } + + impl_i += 1; + } + + if !was_found { + // TODO: format field not found error. + panic!(base_name); + } + + base_i += 1; + } +} + pub const fn is_valid_field_args( base_interface: &[(Name, Type, WrappedValue)], implementation: &[(Name, Type, WrappedValue)], From d17ba4fa03917d2d59f6cb42d25591ccf0a76a0f Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 17:47:25 +0300 Subject: [PATCH 025/122] WIP (finally good const assert message) --- juniper/src/macros/helper/mod.rs | 366 +++++++++++++++++++++++-------- 1 file changed, 272 insertions(+), 94 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 15bead5d1..32b6a9e56 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -161,145 +161,323 @@ pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { len } +macro_rules! const_concat { + ($($s:expr),* $(,)?) => {{ + const LEN: usize = 0 $(+ $s.len())*; + const CNT: usize = [$($s),*].len(); + const fn concat(input: [&str; CNT]) -> [u8; LEN] { + let mut bytes = [0; LEN]; + let (mut i, mut byte) = (0, 0); + while i < CNT { + let mut b = 0; + while b < input[i].len() { + bytes[byte] = input[i].as_bytes()[b]; + byte += 1; + b += 1; + } + i += 1; + } + bytes + } + const CON: [u8; LEN] = concat([$($s),*]); + unsafe { std::str::from_utf8_unchecked(&CON) } + }}; +} + const _: () = check_valid_field_args(); pub const fn check_valid_field_args() { - const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 1)]; - const IMPL: &[(Name, Type, WrappedValue)] = &[("test", "String", 12323)]; + const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 13)]; + const IMPL: &[(Name, Type, WrappedValue)] = + &[("test", "String", 123), ("additional", "String", 12)]; const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; const BANG: &str = "!"; - const fn find_len() -> usize { + struct Error { + base: (Name, Type, WrappedValue), + implementation: (Name, Type, WrappedValue), + cause: Cause, + } + + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, + } + + const fn unwrap_error(v: Result<(), Error>) -> Error { + match v { + Ok(()) => Error { + base: ("empty", "empty", 1), + implementation: ("empty", "empty", 1), + cause: Cause::TypeMismatch, + }, + Err(err) => err, + } + } + + const fn check() -> Result<(), Error> { let mut base_i = 0; while base_i < BASE.len() { let (base_name, base_type, base_wrap_val) = BASE[base_i]; let mut impl_i = 0; + let mut was_found = false; while impl_i < IMPL.len() { let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; if str_eq(base_name, impl_name) { - if !(str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val) { - return str_len_from_wrapped_val(impl_type, impl_wrap_val); + if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { + was_found = true; + } else { + return Err(Error { + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + cause: Cause::TypeMismatch, + }); } } + impl_i += 1; } + + if !was_found { + return Err(Error { + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + cause: Cause::RequiredField, + }); + } + base_i += 1; } - 0 - } - - let mut base_i = 0; - while base_i < BASE.len() { - let (base_name, base_type, base_wrap_val) = BASE[base_i]; let mut impl_i = 0; - let mut was_found = false; while impl_i < IMPL.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; + let (impl_name, impl_type, impl_wrapped_val) = IMPL[impl_i]; + impl_i += 1; - if str_eq(base_name, impl_name) { - if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { + if impl_wrapped_val % 10 == 2 { + continue; + } + + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE.len() { + let (base_name, _, _) = BASE[base_i]; + if str_eq(base_name, impl_name) { was_found = true; - } else { - const IMPL_TYPE_LEN: usize = find_len(); - let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; - - let mut current_start = 0; - let mut current_end = IMPL_TYPE_LEN - 1; - let mut current_wrap_val = impl_wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = - OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - - CLOSING_BRACKET.as_bytes().len() - + i - + 1] = CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = - OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < BANG.as_bytes().len() { - impl_type_arr - [current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - - CLOSING_BRACKET.as_bytes().len() - + i - + 1] = CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } - is_null = false; - } - _ => {} - } + } + base_i += 1; + } + if !was_found { + return Err(Error { + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + cause: Cause::AdditionalNonNullableField, + }); + } + } - current_wrap_val /= 10; - } + Ok(()) + } - let mut i = 0; - while i < impl_type.as_bytes().len() { - impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { + const RES: Result<(), Error> = check(); + if RES.is_ok() { + return; + } + + const ERROR: Error = unwrap_error(RES); + + const IMPL_NAME: &str = ERROR.implementation.0; + const IMPL_TYPE_LEN: usize = + str_len_from_wrapped_val(ERROR.implementation.1, ERROR.implementation.2); + + const BASE_NAME: &str = ERROR.base.0; + const BASE_TYPE_LEN: usize = str_len_from_wrapped_val(ERROR.base.1, ERROR.base.2); + + const fn format_impl_type() -> [u8; IMPL_TYPE_LEN] { + let (_, impl_type, impl_wrap_val) = ERROR.implementation; + let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; + + let mut current_start = 0; + let mut current_end = IMPL_TYPE_LEN - 1; + let mut current_wrap_val = impl_wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; while i < BANG.as_bytes().len() { impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; } + is_null = false; + } + _ => {} + } - let impl_type_str: &str = unsafe { mem::transmute(impl_type_arr.as_slice()) }; + current_wrap_val /= 10; + } - // TODO: format incorrect field type error. - panic!(impl_type_str); + let mut i = 0; + while i < impl_type.as_bytes().len() { + impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } + } + + impl_type_arr + } + + const IMPL_TYPE_ARR: [u8; IMPL_TYPE_LEN] = format_impl_type(); + const IMPL_TYPE_FORMATTED: &str = unsafe { mem::transmute(IMPL_TYPE_ARR.as_slice()) }; + + const fn format_base_type() -> [u8; BASE_TYPE_LEN] { + let (_, base_type, base_wrap_val) = ERROR.base; + let mut base_type_arr: [u8; BASE_TYPE_LEN] = [0; BASE_TYPE_LEN]; + + let mut current_start = 0; + let mut current_end = BASE_TYPE_LEN - 1; + let mut current_wrap_val = base_wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < BANG.as_bytes().len() { + base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + is_null = false; } - } else if impl_wrap_val % 10 != 2 { - // TODO: format field must be nullable error. - panic!(impl_name); + _ => {} } - impl_i += 1; + current_wrap_val /= 10; } - if !was_found { - // TODO: format field not found error. - panic!(base_name); + let mut i = 0; + while i < base_type.as_bytes().len() { + base_type_arr[current_start + i] = base_type.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } } - base_i += 1; + base_type_arr + } + + const BASE_TYPE_ARR: [u8; BASE_TYPE_LEN] = format_base_type(); + const BASE_TYPE_FORMATTED: &str = unsafe { mem::transmute(BASE_TYPE_ARR.as_slice()) }; + + match ERROR.cause { + Cause::TypeMismatch => { + panic!(const_concat!( + "Field `", + BASE_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + )); + } + Cause::RequiredField => { + panic!(const_concat!( + "Field `", + BASE_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found." + )); + } + Cause::AdditionalNonNullableField => { + panic!(const_concat!( + "Field `", + IMPL_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` not present on the interface and so has to be nullable." + )); + } } } From 8cb9b8127bdb3e9cc3e7eef9f41995a4da38f30a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 18:09:10 +0300 Subject: [PATCH 026/122] WIP (corrections) --- juniper/src/macros/helper/mod.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 32b6a9e56..309cd7892 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -145,13 +145,13 @@ pub const fn number_of_digits(n: u128) -> usize { } pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { - let mut len = ty.len() + 1; // Type! + let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! let mut current_wrap_val = v; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { - 2 => len -= 1, // remove ! - 3 => len += 3, // [Type]! + 2 => len -= "!".as_bytes().len(), // remove ! + 3 => len += "[]!".as_bytes().len(), // [Type]! _ => {} } @@ -231,6 +231,7 @@ pub const fn check_valid_field_args() { if str_eq(base_name, impl_name) { if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { was_found = true; + break; } else { return Err(Error { base: (base_name, base_type, base_wrap_val), @@ -269,6 +270,7 @@ pub const fn check_valid_field_args() { let (base_name, _, _) = BASE[base_i]; if str_eq(base_name, impl_name) { was_found = true; + break; } base_i += 1; } @@ -450,7 +452,7 @@ pub const fn check_valid_field_args() { match ERROR.cause { Cause::TypeMismatch => { - panic!(const_concat!( + const MSG: &str = const_concat!( "Field `", BASE_NAME, "`: expected type `", @@ -458,25 +460,28 @@ pub const fn check_valid_field_args() { "`, found: `", IMPL_TYPE_FORMATTED, "`.", - )); + ); + panic!("{}", MSG); } Cause::RequiredField => { - panic!(const_concat!( + const MSG: &str = const_concat!( "Field `", BASE_NAME, "` of type `", BASE_TYPE_FORMATTED, "` was expected, but not found." - )); + ); + panic!("{}", MSG); } Cause::AdditionalNonNullableField => { - panic!(const_concat!( + const MSG: &str = const_concat!( "Field `", IMPL_NAME, "` of type `", IMPL_TYPE_FORMATTED, "` not present on the interface and so has to be nullable." - )); + ); + panic!("{}", MSG); } } } From 116cdd9b324e7e2c1e9db823f6b9706bae1be692 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 09:43:08 +0300 Subject: [PATCH 027/122] WIP (pretty field arguments check) --- .../src/codegen/interface_attr.rs | 1 - .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper/src/macros/helper/mod.rs | 554 ++++++++---------- juniper/src/macros/mod.rs | 1 + juniper_codegen/src/graphql_interface/new.rs | 38 +- 5 files changed, 266 insertions(+), 330 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 0a76fd315..56b6fa3d2 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -340,7 +340,6 @@ mod trivial_with_trait_imp { primary_function: String, } - #[graphql_interface] impl Character for Droid { fn id(&self) -> &str { &self.id diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 76a1c8542..304ba34c5 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'e, 'e, CustomContext, S>, + executor: &'e Executor<'_, '_, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 309cd7892..1d54c349f 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,7 +2,7 @@ pub mod subscription; -use std::{fmt, mem, rc::Rc, sync::Arc}; +use std::{fmt, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; @@ -161,6 +161,7 @@ pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { len } +#[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ const LEN: usize = 0 $(+ $s.len())*; @@ -184,347 +185,274 @@ macro_rules! const_concat { }}; } -const _: () = check_valid_field_args(); - -pub const fn check_valid_field_args() { - const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 13)]; - const IMPL: &[(Name, Type, WrappedValue)] = - &[("test", "String", 123), ("additional", "String", 12)]; - - const OPENING_BRACKET: &str = "["; - const CLOSING_BRACKET: &str = "]"; - const BANG: &str = "!"; - - struct Error { - base: (Name, Type, WrappedValue), - implementation: (Name, Type, WrappedValue), - cause: Cause, - } - - enum Cause { - RequiredField, - AdditionalNonNullableField, - TypeMismatch, - } - - const fn unwrap_error(v: Result<(), Error>) -> Error { - match v { - Ok(()) => Error { - base: ("empty", "empty", 1), - implementation: ("empty", "empty", 1), - cause: Cause::TypeMismatch, - }, - Err(err) => err, - } - } - - const fn check() -> Result<(), Error> { - let mut base_i = 0; - while base_i < BASE.len() { - let (base_name, base_type, base_wrap_val) = BASE[base_i]; - - let mut impl_i = 0; - let mut was_found = false; - while impl_i < IMPL.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; - - if str_eq(base_name, impl_name) { - if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { - was_found = true; - break; - } else { - return Err(Error { - base: (base_name, base_type, base_wrap_val), - implementation: (impl_name, impl_type, impl_wrap_val), - cause: Cause::TypeMismatch, - }); +#[macro_export] +macro_rules! format_type { + ($ty: expr) => {{ + const TYPE: ( + $crate::macros::helper::Name, + $crate::macros::helper::Type, + $crate::macros::helper::WrappedValue, + ) = $ty; + const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.1, TYPE.2); + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn format_type_arr() -> [u8; RES_LEN] { + let (_, ty, wrap_val) = TYPE; + let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; + + let mut current_start = 0; + let mut current_end = RES_LEN - 1; + let mut current_wrap_val = wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + is_null = false; } + _ => {} } - impl_i += 1; + current_wrap_val /= 10; } - if !was_found { - return Err(Error { - base: (base_name, base_type, base_wrap_val), - implementation: (base_name, base_type, base_wrap_val), - cause: Cause::RequiredField, - }); + let mut i = 0; + while i < ty.as_bytes().len() { + type_arr[current_start + i] = ty.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } } - base_i += 1; + type_arr } - let mut impl_i = 0; - while impl_i < IMPL.len() { - let (impl_name, impl_type, impl_wrapped_val) = IMPL[impl_i]; - impl_i += 1; + const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); + // SAFETY: This is safe, as `TYPE_ARR` was formatted from `TYPE.Name`, + // `[`, `]` and `!`. + const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + TYPE_FORMATTED + }}; +} + +#[macro_export] +macro_rules! check_field_args { + ( + $field_name: expr, + ( + $base_name: expr, + $base_args: expr $(,)? + ), ( + $impl_name: expr, + $impl_args: expr $(,)? + ) $(,)?) => { + const _: () = { + type FullArg = ( + $crate::macros::helper::Name, + $crate::macros::helper::Type, + $crate::macros::helper::WrappedValue, + ); - if impl_wrapped_val % 10 == 2 { - continue; + const FIELD_NAME: &str = $field_name; + const BASE_NAME: &str = $base_name; + const IMPL_NAME: &str = $impl_name; + const BASE_ARGS: &[FullArg] = $base_args; + const IMPL_ARGS: &[FullArg] = $impl_args; + + struct Error { + cause: Cause, + base: FullArg, + implementation: FullArg, } - let mut base_i = 0; - let mut was_found = false; - while base_i < BASE.len() { - let (base_name, _, _) = BASE[base_i]; - if str_eq(base_name, impl_name) { - was_found = true; - break; - } - base_i += 1; + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, } - if !was_found { - return Err(Error { - base: (impl_name, impl_type, impl_wrapped_val), - implementation: (impl_name, impl_type, impl_wrapped_val), - cause: Cause::AdditionalNonNullableField, - }); + + const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { + match v { + Ok(()) => Error { + cause: Cause::RequiredField, + base: ("unreachable", "unreachable", 1), + implementation: ("unreachable", "unreachable", 1), + }, + Err(err) => err, + } } - } - Ok(()) - } + const fn check() -> Result<(), Error> { + let mut base_i = 0; + while base_i < BASE_ARGS.len() { + let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; + + if $crate::macros::helper::str_eq(base_name, impl_name) { + if $crate::macros::helper::str_eq(base_type, impl_type) + && base_wrap_val == impl_wrap_val + { + was_found = true; + break; + } else { + return Err(Error { + cause: Cause::TypeMismatch, + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + }); + } + } - const RES: Result<(), Error> = check(); - if RES.is_ok() { - return; - } + impl_i += 1; + } - const ERROR: Error = unwrap_error(RES); - - const IMPL_NAME: &str = ERROR.implementation.0; - const IMPL_TYPE_LEN: usize = - str_len_from_wrapped_val(ERROR.implementation.1, ERROR.implementation.2); - - const BASE_NAME: &str = ERROR.base.0; - const BASE_TYPE_LEN: usize = str_len_from_wrapped_val(ERROR.base.1, ERROR.base.2); - - const fn format_impl_type() -> [u8; IMPL_TYPE_LEN] { - let (_, impl_type, impl_wrap_val) = ERROR.implementation; - let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; - - let mut current_start = 0; - let mut current_end = IMPL_TYPE_LEN - 1; - let mut current_wrap_val = impl_wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < BANG.as_bytes().len() { - impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; + if !was_found { + return Err(Error { + cause: Cause::RequiredField, + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + }); } - is_null = false; + + base_i += 1; } - _ => {} - } - current_wrap_val /= 10; - } + let mut impl_i = 0; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; + impl_i += 1; - let mut i = 0; - while i < impl_type.as_bytes().len() { - impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - while i < BANG.as_bytes().len() { - impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; - } - } - - impl_type_arr - } + if impl_wrapped_val % 10 == 2 { + continue; + } - const IMPL_TYPE_ARR: [u8; IMPL_TYPE_LEN] = format_impl_type(); - const IMPL_TYPE_FORMATTED: &str = unsafe { mem::transmute(IMPL_TYPE_ARR.as_slice()) }; - - const fn format_base_type() -> [u8; BASE_TYPE_LEN] { - let (_, base_type, base_wrap_val) = ERROR.base; - let mut base_type_arr: [u8; BASE_TYPE_LEN] = [0; BASE_TYPE_LEN]; - - let mut current_start = 0; - let mut current_end = BASE_TYPE_LEN - 1; - let mut current_wrap_val = base_wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < BANG.as_bytes().len() { - base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE_ARGS.len() { + let (base_name, _, _) = BASE_ARGS[base_i]; + if $crate::macros::helper::str_eq(base_name, impl_name) { + was_found = true; + break; } - current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; + base_i += 1; + } + if !was_found { + return Err(Error { + cause: Cause::AdditionalNonNullableField, + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + }); } - is_null = false; } - _ => {} - } - current_wrap_val /= 10; - } - - let mut i = 0; - while i < base_type.as_bytes().len() { - base_type_arr[current_start + i] = base_type.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - while i < BANG.as_bytes().len() { - base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; + Ok(()) } - } - - base_type_arr - } - - const BASE_TYPE_ARR: [u8; BASE_TYPE_LEN] = format_base_type(); - const BASE_TYPE_FORMATTED: &str = unsafe { mem::transmute(BASE_TYPE_ARR.as_slice()) }; - - match ERROR.cause { - Cause::TypeMismatch => { - const MSG: &str = const_concat!( - "Field `", - BASE_NAME, - "`: expected type `", - BASE_TYPE_FORMATTED, - "`, found: `", - IMPL_TYPE_FORMATTED, - "`.", - ); - panic!("{}", MSG); - } - Cause::RequiredField => { - const MSG: &str = const_concat!( - "Field `", - BASE_NAME, - "` of type `", - BASE_TYPE_FORMATTED, - "` was expected, but not found." - ); - panic!("{}", MSG); - } - Cause::AdditionalNonNullableField => { - const MSG: &str = const_concat!( - "Field `", - IMPL_NAME, - "` of type `", - IMPL_TYPE_FORMATTED, - "` not present on the interface and so has to be nullable." - ); - panic!("{}", MSG); - } - } -} -pub const fn is_valid_field_args( - base_interface: &[(Name, Type, WrappedValue)], - implementation: &[(Name, Type, WrappedValue)], -) -> bool { - const fn find( - base_interface: &[(Name, Type, WrappedValue)], - impl_name: Name, - impl_ty: Type, - impl_wrap_val: WrappedValue, - ) -> bool { - let mut i = 0; - while i < base_interface.len() { - let (base_name, base_ty, base_wrap_val) = base_interface[i]; - if str_eq(impl_name, base_name) { - return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; + const RES: ::std::result::Result<(), Error> = check(); + if RES.is_err() { + const ERROR: Error = unwrap_error(RES); + + const BASE_ARG_NAME: &str = ERROR.base.0; + const IMPL_ARG_NAME: &str = ERROR.implementation.0; + + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base); + const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation); + + const PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_NAME, + "` on object `", + IMPL_NAME, + "`: Field `", + FIELD_NAME, + "`: ", + ); + const MSG: &str = match ERROR.cause { + Cause::TypeMismatch => { + $crate::const_concat!( + PREFIX, + "Argument `", + BASE_ARG_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + ) + } + Cause::RequiredField => { + $crate::const_concat!( + PREFIX, + "Argument `", + BASE_ARG_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found." + ) + } + Cause::AdditionalNonNullableField => { + $crate::const_concat!( + PREFIX, + "Argument `", + IMPL_ARG_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` not present on the interface and so has to be nullable." + ) + } + }; + ::std::panic!("{}", MSG); } - i += 1; - } - false - } - - if base_interface.len() > implementation.len() { - return false; - } - - let mut i = 0; - let mut successfully_implemented_fields = 0; - while i < implementation.len() { - let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; - if find(base_interface, impl_name, impl_ty, impl_wrap_val) { - successfully_implemented_fields += 1; - } else if impl_wrap_val % 10 != 2 { - // Not an optional field. - return false; - } - i += 1; - } - - successfully_implemented_fields == base_interface.len() + }; + }; } pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index cecfee388..591c4c411 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,6 +1,7 @@ //! Declarative macros and helper definitions for procedural macros. #[doc(hidden)] +#[macro_use] pub mod helper; #[macro_use] diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index cb929b704..a74ffd838 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -770,6 +770,7 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { let ty = &self.enum_alias_ident; + let name = &self.name; let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { @@ -795,7 +796,7 @@ impl Definition { return None; } - let name = &field.name; + let field_name = &field.name; let mut return_ty = field.ty.clone(); Self::replace_generics_for_const(&mut return_ty, &generics); @@ -849,7 +850,7 @@ impl Definition { #[allow(non_snake_case)] impl#impl_generics ::juniper::macros::helper::#trait_name< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::helper::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { type Context = #context; type TypeInfo = (); @@ -877,27 +878,34 @@ impl Definition { <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, <#impl_tys as ::juniper::macros::helper::#trait_name< #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, + { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::TYPE, <#impl_tys as ::juniper::macros::helper::#trait_name< #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, + { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::WRAPPED_VALUE, )); - const _: () = ::std::assert!(::juniper::macros::helper::is_valid_field_args( - <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, - >>::ARGUMENTS, - <#impl_tys as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, - >>::ARGUMENTS, - )); + ::juniper::check_field_args!( + #field_name, + ( + #name, + <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) }, + >>::ARGUMENTS, + ), + ( + <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) }, + >>::ARGUMENTS, + ), + ); <_ as ::juniper::macros::helper::#trait_name< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, + { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm From 7bd8c734d504d498c0b998071027c9ae2f5f5b26 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 10:38:42 +0300 Subject: [PATCH 028/122] WIP (pretty subtype check) --- .../src/codegen/interface_attr.rs | 19 +------ juniper/src/macros/helper/mod.rs | 54 +++++++++++++++---- juniper_codegen/src/graphql_interface/new.rs | 24 +++++++-- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 56b6fa3d2..2ea00b554 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1828,10 +1828,7 @@ mod default_argument { } } - struct Human { - id: String, - info: i32, - } + struct Human; #[graphql_object(impl = CharacterValue)] impl Human { @@ -1849,11 +1846,7 @@ mod default_argument { #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { - Human { - id: "human-32".to_string(), - info: 0, - } - .into() + Human.into() } } @@ -3066,7 +3059,6 @@ mod inferred_custom_context_from_field { } struct Human { - id: String, home_planet: String, } @@ -3086,7 +3078,6 @@ mod inferred_custom_context_from_field { } struct Droid { - id: String, primary_function: String, } @@ -3116,12 +3107,10 @@ mod inferred_custom_context_from_field { 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(), @@ -3231,7 +3220,6 @@ mod executor { } struct Human { - id: String, home_planet: String, } @@ -3251,7 +3239,6 @@ mod executor { } struct Droid { - id: String, primary_function: String, } @@ -3285,12 +3272,10 @@ mod executor { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), home_planet: "earth".to_string(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), primary_function: "run".to_string(), } .into(), diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 1d54c349f..62d6e1377 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -187,20 +187,19 @@ macro_rules! const_concat { #[macro_export] macro_rules! format_type { - ($ty: expr) => {{ + ($ty: expr, $wrapped_value: expr $(,)?) => {{ const TYPE: ( - $crate::macros::helper::Name, $crate::macros::helper::Type, $crate::macros::helper::WrappedValue, - ) = $ty; - const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.1, TYPE.2); + ) = ($ty, $wrapped_value); + const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.0, TYPE.1); const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; const BANG: &str = "!"; const fn format_type_arr() -> [u8; RES_LEN] { - let (_, ty, wrap_val) = TYPE; + let (ty, wrap_val) = TYPE; let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; let mut current_start = 0; @@ -272,8 +271,7 @@ macro_rules! format_type { } const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: This is safe, as `TYPE_ARR` was formatted from `TYPE.Name`, - // `[`, `]` and `!`. + // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; TYPE_FORMATTED }}; @@ -403,13 +401,14 @@ macro_rules! check_field_args { const BASE_ARG_NAME: &str = ERROR.base.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0; - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base); - const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation); + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2,); + const IMPL_TYPE_FORMATTED: &str = + $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2,); const PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", BASE_NAME, - "` on object `", + "` on `", IMPL_NAME, "`: Field `", FIELD_NAME, @@ -492,6 +491,41 @@ pub const fn is_subtype( exists(subtype, possible_subtypes) && can_be_subtype(wrapped_type, wrapped_subtype) } +#[macro_export] +macro_rules! check_subtype { + ( + $field_name: expr, + $base_name: expr, + $base_ty: expr, + $base_wrapped_val: expr, + $possible_subtypes: expr, + $impl_name: expr, + $impl_ty: expr, + $impl_wrapped_val: expr $(,)? + ) => { + const _: () = { + let is_subtype = $crate::macros::helper::exists($impl_ty, $possible_subtypes) + && $crate::macros::helper::can_be_subtype($base_wrapped_val, $impl_wrapped_val); + if !is_subtype { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + $base_name, + "` on `", + $impl_name, + "`: Field `", + $field_name, + "` return object is `", + $crate::format_type!($base_ty, $base_wrapped_val), + "`, which is not a subtype of `", + $crate::format_type!($impl_ty, $impl_wrapped_val), + "`.", + ); + ::std::panic!("{}", MSG); + } + }; + }; +} + /// TODO pub trait BaseType { const NAME: Type; diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index a74ffd838..8e6c2290e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -873,9 +873,27 @@ impl Definition { #call_sig { match self { #(#ty::#impl_idents(v) => { - const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( + // const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( + // <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, + // <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, + // <#impl_tys as ::juniper::macros::helper::#trait_name< + // #const_scalar, + // { ::juniper::macros::helper::fnv1a128(#field_name) }, + // >>::TYPE, + // <#impl_tys as ::juniper::macros::helper::#trait_name< + // #const_scalar, + // { ::juniper::macros::helper::fnv1a128(#field_name) }, + // >>::WRAPPED_VALUE, + // )); + ::juniper::check_subtype!( + #field_name, + + #name, + <#return_ty as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, + <#return_ty as ::juniper::macros::helper::WrappedType<#const_scalar>>::VALUE, <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, - <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, + + <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, <#impl_tys as ::juniper::macros::helper::#trait_name< #const_scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, @@ -884,7 +902,7 @@ impl Definition { #const_scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::WRAPPED_VALUE, - )); + ); ::juniper::check_field_args!( #field_name, ( From 3ebc8fd332f93cddbff9a4a890408afed1f2eaf4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 12:29:04 +0300 Subject: [PATCH 029/122] WIP (corrections) --- juniper/src/macros/helper/mod.rs | 334 ++++++++++--------- juniper_codegen/src/graphql_interface/new.rs | 222 +++++------- 2 files changed, 269 insertions(+), 287 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 62d6e1377..2e01f8e66 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -93,74 +93,6 @@ pub type Name = &'static str; pub type FieldName = u128; pub type WrappedValue = u128; -/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in -/// const generics. See [spec] for more info. -/// -/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -pub const fn fnv1a128(str: Name) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash -} - -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - -pub const fn number_of_digits(n: u128) -> usize { - if n == 0 { - return 1; - } - - let mut len = 0; - let mut current = n; - while current % 10 != 0 { - len += 1; - current /= 10; - } - len -} - -pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { - let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - - let mut current_wrap_val = v; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => len -= "!".as_bytes().len(), // remove ! - 3 => len += "[]!".as_bytes().len(), // [Type]! - _ => {} - } - - current_wrap_val /= 10; - } - - len -} - #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ @@ -278,16 +210,92 @@ macro_rules! format_type { } #[macro_export] -macro_rules! check_field_args { +macro_rules! assert_field { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); + $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); + }; +} + +#[macro_export] +macro_rules! assert_subtype { ( - $field_name: expr, - ( - $base_name: expr, - $base_args: expr $(,)? - ), ( - $impl_name: expr, - $impl_args: expr $(,)? - ) $(,)?) => { + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + const FIELD_NAME: $crate::macros::helper::Name = + $field_name; + const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = + <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::WRAPPED_VALUE; + const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = + <$impl_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::WRAPPED_VALUE; + + const BASE_TY: $crate::macros::helper::Type = + <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::helper::Type = + <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + + const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = + <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::SUB_TYPES; + + const BASE_RETURN_TY: $crate::macros::helper::Type = + <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::TYPE; + const IMPL_RETURN_TY: $crate::macros::helper::Type = + <$impl_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::TYPE; + + let is_subtype = $crate::macros::helper::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + && $crate::macros::helper::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); + if !is_subtype { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_TY, + "` on `", + IMPL_TY, + "`: Field `", + FIELD_NAME, + "`: implementor is expected to return a subtype of interface's return object: `", + $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), + "` is not a subtype of `", + $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), + "`.", + ); + ::std::panic!("{}", MSG); + } + }; + }; +} + +#[macro_export] +macro_rules! assert_field_args { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { const _: () = { type FullArg = ( $crate::macros::helper::Name, @@ -296,10 +304,16 @@ macro_rules! check_field_args { ); const FIELD_NAME: &str = $field_name; - const BASE_NAME: &str = $base_name; - const IMPL_NAME: &str = $impl_name; - const BASE_ARGS: &[FullArg] = $base_args; - const IMPL_ARGS: &[FullArg] = $impl_args; + const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::ARGUMENTS; + const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::ARGUMENTS; struct Error { cause: Cause, @@ -401,9 +415,9 @@ macro_rules! check_field_args { const BASE_ARG_NAME: &str = ERROR.base.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0; - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2,); + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); const IMPL_TYPE_FORMATTED: &str = - $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2,); + $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); const PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", @@ -454,78 +468,6 @@ macro_rules! check_field_args { }; } -pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; - - if ty_current == subtype_current { - if ty_current == 1 { - true - } else { - can_be_subtype(ty / 10, subtype / 10) - } - } else if ty_current == 2 { - can_be_subtype(ty / 10, subtype) - } else { - false - } -} - -pub const fn exists(val: Type, arr: Types) -> bool { - let mut i = 0; - while i < arr.len() { - if str_eq(val, arr[i]) { - return true; - } - i += 1; - } - false -} - -pub const fn is_subtype( - possible_subtypes: Types, - wrapped_type: WrappedValue, - subtype: Type, - wrapped_subtype: WrappedValue, -) -> bool { - exists(subtype, possible_subtypes) && can_be_subtype(wrapped_type, wrapped_subtype) -} - -#[macro_export] -macro_rules! check_subtype { - ( - $field_name: expr, - $base_name: expr, - $base_ty: expr, - $base_wrapped_val: expr, - $possible_subtypes: expr, - $impl_name: expr, - $impl_ty: expr, - $impl_wrapped_val: expr $(,)? - ) => { - const _: () = { - let is_subtype = $crate::macros::helper::exists($impl_ty, $possible_subtypes) - && $crate::macros::helper::can_be_subtype($base_wrapped_val, $impl_wrapped_val); - if !is_subtype { - const MSG: &str = $crate::const_concat!( - "Failed to implement interface `", - $base_name, - "` on `", - $impl_name, - "`: Field `", - $field_name, - "` return object is `", - $crate::format_type!($base_ty, $base_wrapped_val), - "`, which is not a subtype of `", - $crate::format_type!($impl_ty, $impl_wrapped_val), - "`.", - ); - ::std::panic!("{}", MSG); - } - }; - }; -} - /// TODO pub trait BaseType { const NAME: Type; @@ -719,3 +661,85 @@ pub trait AsyncField { executor: &'b Executor, ) -> BoxFuture<'b, ExecutionResult>; } + +/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in +/// const generics. See [spec] for more info. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +pub const fn fnv1a128(str: Name) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash +} + +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + +pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { + let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! + + let mut current_wrap_val = v; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => len -= "!".as_bytes().len(), // remove ! + 3 => len += "[]!".as_bytes().len(), // [Type]! + _ => {} + } + + current_wrap_val /= 10; + } + + len +} + +pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + can_be_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + can_be_subtype(ty / 10, subtype) + } else { + false + } +} + +pub const fn exists(val: Type, arr: Types) -> bool { + let mut i = 0; + while i < arr.len() { + if str_eq(val, arr[i]) { + return true; + } + i += 1; + } + false +} diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 8e6c2290e..64a8ee22e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -770,7 +770,6 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { let ty = &self.enum_alias_ident; - let name = &self.name; let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { @@ -791,148 +790,107 @@ impl Definition { let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - self.fields.iter().filter_map(|field| { - if field.is_async && !for_async { - return None; - } + self.fields + .iter() + .filter_map(|field| { + if field.is_async && !for_async { + return None; + } - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - Self::replace_generics_for_const(&mut return_ty, &generics); - - let (trait_name, call_sig) = if for_async { - ( - quote! { AsyncField }, - quote! { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> - }, - ) - } else { - ( - quote! { Field }, - quote! { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> - }, - ) - }; + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + Self::replace_generics_for_const(&mut return_ty, &generics); + + let (trait_name, call_sig) = if for_async { + ( + quote! { AsyncField }, + quote! { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> + }, + ) + } else { + ( + quote! { Field }, + quote! { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> + }, + ) + }; - let (args_tys, args_names): (Vec<_>, Vec<_>) = field - .arguments - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| { - match arg { - field::MethodArgument::Regular(arg) => { - Some((&arg.ty, &arg.name)) - } + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), _ => None, - } - }) - .unzip(); + }) + .unzip(); - let const_ty_generics = self.const_trait_generics(); + let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); - Some(quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::#trait_name< - #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = - <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, - )] = &[#(( - #args_names, - <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, - )),*]; - - #call_sig { - match self { - #(#ty::#impl_idents(v) => { - // const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( - // <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, - // <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, - // <#impl_tys as ::juniper::macros::helper::#trait_name< - // #const_scalar, - // { ::juniper::macros::helper::fnv1a128(#field_name) }, - // >>::TYPE, - // <#impl_tys as ::juniper::macros::helper::#trait_name< - // #const_scalar, - // { ::juniper::macros::helper::fnv1a128(#field_name) }, - // >>::WRAPPED_VALUE, - // )); - ::juniper::check_subtype!( - #field_name, - - #name, - <#return_ty as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, - <#return_ty as ::juniper::macros::helper::WrappedType<#const_scalar>>::VALUE, - <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, - - <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, - <#impl_tys as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::TYPE, - <#impl_tys as ::juniper::macros::helper::#trait_name< + Some(quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = + <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )),*]; + + #call_sig { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::helper::#trait_name< + #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::WRAPPED_VALUE, - ); - ::juniper::check_field_args!( - #field_name, - ( - #name, - <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::ARGUMENTS, - ), - ( - <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, - <#impl_tys as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::ARGUMENTS, - ), - ); - - <_ as ::juniper::macros::helper::#trait_name< - #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) - })* - #unreachable_arm + >>::call(v, info, args, executor) + })* + #unreachable_arm + } } } - } + }) }) - }) - .collect() + .collect() } /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] From ce249b03e82488ca29ed1324802199d31b8de9ff Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 13:55:24 +0300 Subject: [PATCH 030/122] WIP (FieldMeta trait) --- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper/src/macros/helper/mod.rs | 27 +++--- juniper_codegen/src/common/field/mod.rs | 94 +++++++++++------- juniper_codegen/src/graphql_interface/new.rs | 95 +++++++++++++------ juniper_codegen/src/graphql_object/mod.rs | 9 +- 5 files changed, 140 insertions(+), 87 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 304ba34c5..76a1c8542 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'_, '_, CustomContext, S>, + executor: &'e Executor<'e, 'e, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 2e01f8e66..b65c69680 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -234,12 +234,12 @@ macro_rules! assert_subtype { const FIELD_NAME: $crate::macros::helper::Name = $field_name; const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$base_ty as $crate::macros::helper::AsyncField< + <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$impl_ty as $crate::macros::helper::AsyncField< + <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::WRAPPED_VALUE; @@ -250,18 +250,18 @@ macro_rules! assert_subtype { <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = - <$base_ty as $crate::macros::helper::AsyncField< + <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::SUB_TYPES; const BASE_RETURN_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::AsyncField< + <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::AsyncField< + <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::TYPE; @@ -306,11 +306,11 @@ macro_rules! assert_field_args { const FIELD_NAME: &str = $field_name; const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::AsyncField< + const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::ARGUMENTS; - const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::AsyncField< + const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::ARGUMENTS; @@ -630,14 +630,16 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -pub trait Field { +pub trait FieldMeta { type Context; type TypeInfo; const TYPE: Type; const SUB_TYPES: Types; const WRAPPED_VALUE: WrappedValue; const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; +} +pub trait Field: FieldMeta { fn call( &self, info: &Self::TypeInfo, @@ -646,14 +648,7 @@ pub trait Field { ) -> ExecutionResult; } -pub trait AsyncField { - type Context; - type TypeInfo; - const TYPE: Type; - const SUB_TYPES: Types; - const WRAPPED_VALUE: WrappedValue; - const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; - +pub trait AsyncField: FieldMeta { fn call<'b>( &'b self, info: &'b Self::TypeInfo, diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 6dba69e32..385ed22d4 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -441,6 +441,63 @@ impl Definition { }) } + /// TODO + #[must_use] + pub(crate) fn impl_field_meta( + &self, + impl_ty: &syn::Type, + impl_generics: &TokenStream, + where_clause: Option<&syn::WhereClause>, + scalar: &scalar::Type, + context: &syn::Type, + ) -> TokenStream { + let (name, ty) = (&self.name, self.ty.clone()); + + let arguments = self + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + + Some(quote! {( + #name, + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )}) + } + MethodArgument::Executor | MethodArgument::Context(_) => None, + }) + .collect::>(); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::helper::FieldMeta< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(#arguments,)*]; + } + } + } + /// TODO #[must_use] pub(crate) fn impl_field( @@ -450,15 +507,13 @@ impl Definition { where_clause: Option<&syn::WhereClause>, scalar: &scalar::Type, trait_ty: Option<&syn::Type>, - context: &syn::Type, for_async: bool, ) -> Option { if !for_async && self.is_async { return None; } - let (name, ty, mut res_ty, ident) = - (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); + let (name, mut res_ty, ident) = (&self.name, self.ty.clone(), &self.ident); let mut res = if self.is_method() { let args = self @@ -485,25 +540,6 @@ impl Definition { res = quote! { ::juniper::futures::future::ready(#res) }; } - let arguments = self - .arguments - .as_ref() - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - MethodArgument::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - - Some(quote! {( - #name, - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, - )}) - } - MethodArgument::Executor | MethodArgument::Context(_) => None, - }) - .collect::>(); - let (call, trait_name) = if for_async { let resolving_code = gen::async_resolving_code(Some(&res_ty)); let call = quote! { @@ -545,20 +581,6 @@ impl Definition { > for #impl_ty #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, - )] = &[#(#arguments,)*]; - #call } }) diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 64a8ee22e..f92e8729e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -50,8 +50,9 @@ pub(crate) struct TraitAttr { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions pub(crate) description: Option>, - /// Explicitly specified identifier of the enum Rust type behind the trait, - /// being an actual implementation of a [GraphQL interface][1] type. + /// Explicitly specified identifier of the type alias of Rust enum type + /// behind the trait, being an actual implementation of a + /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// @@ -305,6 +306,7 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_traits_for_reflection().to_tokens(into); + self.impl_fields_meta().to_tokens(into); self.impl_fields(false).to_tokens(into); self.impl_fields(true).to_tokens(into); } @@ -762,6 +764,66 @@ impl Definition { } } + /// Returns generated code implementing [`FieldMeta`] for this + /// [GraphQL interface][1]. + /// + /// [`FieldMeta`]: juniper::macros::helper::FieldMeta + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_fields_meta(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let context = &self.context; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + Self::replace_generics_for_const(&mut return_ty, &generics); + + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), + _ => None, + }) + .unzip(); + + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::helper::FieldMeta< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = + <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )),*]; + } + } + }) + .collect() + } + /// Returns generated code implementing [`Field`] or [`AsyncField`] trait /// for this [GraphQL interface][1]. /// @@ -770,7 +832,6 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { let ty = &self.enum_alias_ident; - let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { scalar::Type::Concrete(ty) => ty.clone(), @@ -827,16 +888,6 @@ impl Definition { ) }; - let (args_tys, args_names): (Vec<_>, Vec<_>) = field - .arguments - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), - _ => None, - }) - .unzip(); - let const_ty_generics = self.const_trait_generics(); let unreachable_arm = (self.implementers.is_empty() @@ -851,24 +902,6 @@ impl Definition { #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = - <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, - )] = &[#(( - #args_names, - <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, - )),*]; - #call_sig { match self { #(#ty::#impl_idents(v) => { diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 86940f9f0..e99c24614 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -541,6 +541,10 @@ impl Definition { }) }); + let field_meta_impls = self + .fields + .iter() + .map(|f| f.impl_field_meta(ty, &impl_generics, where_clause.as_ref(), scalar, context)); let field_impls = self.fields.iter().filter_map(|f| { f.impl_field( ty, @@ -548,7 +552,6 @@ impl Definition { where_clause.as_ref(), scalar, None, - context, false, ) }); @@ -602,6 +605,8 @@ impl Definition { } } + #(#field_meta_impls)* + #(#field_impls)* } } @@ -614,7 +619,6 @@ impl Definition { #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(true); let ty = &self.ty; @@ -639,7 +643,6 @@ impl Definition { where_clause.as_ref(), scalar, None, - context, true, ) }); From d9971a05e2d6f4f7dfc6b0aad369670cd4a005f4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 15:19:35 +0300 Subject: [PATCH 031/122] WIP (Refactor graphql_interface_new a bit) --- juniper_codegen/src/common/parse/mod.rs | 41 ++++ juniper_codegen/src/graphql_interface/new.rs | 196 +++++++++++-------- 2 files changed, 154 insertions(+), 83 deletions(-) diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index cae19a015..f74e75b12 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -11,12 +11,14 @@ use std::{ }; use proc_macro2::Span; +use quote::quote; use syn::{ ext::IdentExt as _, parse::{Parse, ParseBuffer}, parse_quote, punctuated::Punctuated, token::{self, Token}, + visit_mut::VisitMut, }; /// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing. @@ -250,6 +252,9 @@ pub(crate) trait GenericsExt { /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`]. fn move_bounds_to_where_clause(&mut self); + + /// Replaces generic parameters in `ty` found in `self` with default ones. + fn replace_type_with_defaults(&self, ty: &mut syn::Type); } impl GenericsExt for syn::Generics { @@ -299,4 +304,40 @@ impl GenericsExt for syn::Generics { } } } + + fn replace_type_with_defaults(&self, ty: &mut syn::Type) { + struct Replace<'a>(&'a syn::Generics); + + impl<'a> VisitMut for Replace<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote!( 'static ); + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + *ty = parse_quote!(()); + } + } + _ => {} + } + } + } + + Replace(self).visit_type_mut(ty) + } } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index f92e8729e..491b8b705 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -306,9 +306,9 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_traits_for_reflection().to_tokens(into); - self.impl_fields_meta().to_tokens(into); - self.impl_fields(false).to_tokens(into); - self.impl_fields(true).to_tokens(into); + self.impl_field_meta().to_tokens(into); + self.impl_field().to_tokens(into); + self.impl_async_field().to_tokens(into); } } @@ -750,7 +750,7 @@ impl Definition { { const NAMES: ::juniper::macros::helper::Types = &[ >::NAME, - #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME,)* + #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* ]; } @@ -769,7 +769,7 @@ impl Definition { /// /// [`FieldMeta`]: juniper::macros::helper::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_fields_meta(&self) -> TokenStream { + fn impl_field_meta(&self) -> TokenStream { let ty = &self.enum_alias_ident; let context = &self.context; let scalar = &self.scalar; @@ -783,7 +783,7 @@ impl Definition { .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); - Self::replace_generics_for_const(&mut return_ty, &generics); + generics.replace_type_with_defaults(&mut return_ty); let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments @@ -830,15 +830,10 @@ impl Definition { /// [`AsyncField`]: juniper::macros::helper::AsyncField /// [`Field`]: juniper::macros::helper::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_fields(&self, for_async: bool) -> TokenStream { + fn impl_field(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let const_scalar = match scalar { - scalar::Type::Concrete(ty) => ty.clone(), - scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { - parse_quote! { ::juniper::DefaultScalarValue } - } - }; + let const_scalar = self.const_scalar(); let impl_tys = self.implementers.iter().collect::>(); let impl_idents = self @@ -854,39 +849,13 @@ impl Definition { self.fields .iter() .filter_map(|field| { - if field.is_async && !for_async { + if field.is_async { return None; } let field_name = &field.name; let mut return_ty = field.ty.clone(); - Self::replace_generics_for_const(&mut return_ty, &generics); - - let (trait_name, call_sig) = if for_async { - ( - quote! { AsyncField }, - quote! { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> - }, - ) - } else { - ( - quote! { Field }, - quote! { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> - }, - ) - }; + generics.replace_type_with_defaults(&mut return_ty); let const_ty_generics = self.const_trait_generics(); @@ -898,11 +867,16 @@ impl Definition { Some(quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::#trait_name< + impl#impl_generics ::juniper::macros::helper::Field< #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { - #call_sig { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { match self { #(#ty::#impl_idents(v) => { ::juniper::assert_field!( @@ -926,6 +900,77 @@ impl Definition { .collect() } + /// Returns generated code implementing [`AsyncField`] trait for this + /// [GraphQL interface][1]. + /// + /// [`AsyncField`]: juniper::macros::helper::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_async_field(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let scalar = &self.scalar; + let const_scalar = self.const_scalar(); + + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + + let generics = self.impl_generics(for_async); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::helper::AsyncField< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + } + }) + .collect() + } + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] /// method, which returns name of the underlying [`Implementer`] GraphQL /// type contained in this [`EnumType`]. @@ -1040,13 +1085,17 @@ impl Definition { return; } } - syn::GenericParam::Const(_) => { - // This hack works because only `min_const_generics` are - // enabled for now. - // TODO: replace this once full `const_generics` are - // available. - // Maybe with `<_ as Default>::default()`? - parse_quote!({ 0_u8 as _ }) + syn::GenericParam::Const(c) => { + if c.default.is_none() { + // This hack works because only `min_const_generics` + // are enabled for now. + // TODO: replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } else { + return; + } } }; self.0.args.push(arg) @@ -1058,41 +1107,22 @@ impl Definition { syn::PathArguments::AngleBracketed(visitor.0) } - /// Replaces `generics` in `ty` for usage in `const` context. - fn replace_generics_for_const(ty: &mut syn::Type, generics: &syn::Generics) { - struct ReplaceGenericsForConst<'a>(&'a syn::Generics); - - impl<'a> VisitMut for ReplaceGenericsForConst<'a> { - fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { - match arg { - syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote!( 'static ); - } - syn::GenericArgument::Type(ty) => { - let is_generic = self - .0 - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .any(|par| { - let par = quote! { #par }.to_string(); - let ty = quote! { #ty }.to_string(); - par == ty - }); - - if is_generic { - *ty = parse_quote!(()); - } - } - _ => {} - } + /// Returns [`scalar`] replaced with [`DefaultScalarValue`] in case it's + /// [`ExplicitGeneric`] or [`ImplicitGeneric`] for using [`scalar`] in + /// `const` context. + /// + /// [`scalar`]: Self::scalar + /// [`DefaultScalarValue`]: juniper::DefaultScalarValue + /// [`ExplicitGeneric`]: scalar::Type::ExplicitGeneric + /// [`ImplicitGeneric`]: scalar::Type::ImplicitGeneric + #[must_use] + fn const_scalar(&self) -> syn::Type { + match &self.scalar { + scalar::Type::Concrete(ty) => ty.clone(), + scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { + parse_quote! { ::juniper::DefaultScalarValue } } } - - ReplaceGenericsForConst(&generics).visit_type_mut(ty) } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and From 8a966fbe1f7cc218076c38b483656cfcca99f3ed Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 15:58:48 +0300 Subject: [PATCH 032/122] WIP (Corrections) --- juniper_codegen/src/graphql_interface/new.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 491b8b705..2ab8c6e51 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -14,7 +14,6 @@ use syn::{ spanned::Spanned as _, token, visit::Visit, - visit_mut::VisitMut, }; use crate::{ @@ -842,7 +841,7 @@ impl Definition { .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); - let generics = self.impl_generics(for_async); + let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); @@ -886,7 +885,7 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::#trait_name< + <_ as ::juniper::macros::helper::Field< #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::call(v, info, args, executor) @@ -917,7 +916,7 @@ impl Definition { .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); - let generics = self.impl_generics(for_async); + let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); @@ -957,7 +956,7 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::#trait_name< + <_ as ::juniper::macros::helper::AsyncField< #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::call(v, info, args, executor) From 21f4940d6c861325694d963aad9142ad40648421 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 09:18:17 +0300 Subject: [PATCH 033/122] WIP (Prettify non-existent Field assertions) --- juniper/src/macros/helper/mod.rs | 136 +++++++++++-------- juniper_codegen/src/graphql_interface/new.rs | 9 ++ juniper_codegen/src/graphql_object/mod.rs | 9 ++ 3 files changed, 95 insertions(+), 59 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index b65c69680..684043e72 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -90,6 +90,7 @@ where pub type Type = &'static str; pub type Types = &'static [Type]; pub type Name = &'static str; +pub type Names = &'static [Name]; pub type FieldName = u128; pub type WrappedValue = u128; @@ -117,6 +118,31 @@ macro_rules! const_concat { }}; } +#[macro_export] +macro_rules! hash { + ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ + let exists = $crate::macros::helper::exists( + $field_name, + <$impl_ty as $crate::macros::helper::Fields<$scalar>>::NAMES, + ); + if exists { + $crate::macros::helper::fnv1a128(FIELD_NAME) + } else { + ::std::panic!( + "{}", + $crate::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME, + "`." + ) + ) + } + }}; +} + #[macro_export] macro_rules! format_type { ($ty: expr, $wrapped_value: expr $(,)?) => {{ @@ -142,27 +168,13 @@ macro_rules! format_type { match current_wrap_val % 10 { 2 => is_null = true, 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + if !is_null { i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = @@ -170,14 +182,14 @@ macro_rules! format_type { i += 1; } current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; } + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; is_null = false; } _ => {} @@ -231,50 +243,54 @@ macro_rules! assert_subtype { $field_name: expr $(,)? ) => { const _: () = { + const BASE_TY: $crate::macros::helper::Type = + <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::helper::Type = + <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_TY, + "` on `", + IMPL_TY, + "`: ", + ); + const FIELD_NAME: $crate::macros::helper::Name = $field_name; const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; - const BASE_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const IMPL_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::SUB_TYPES; const BASE_RETURN_TY: $crate::macros::helper::Type = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::helper::Type = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; let is_subtype = $crate::macros::helper::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::helper::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { const MSG: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_TY, - "` on `", - IMPL_TY, - "`: Field `", + ERR_PREFIX, + "Field `", FIELD_NAME, "`: implementor is expected to return a subtype of interface's return object: `", $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), @@ -303,16 +319,24 @@ macro_rules! assert_field_args { $crate::macros::helper::WrappedValue, ); - const FIELD_NAME: &str = $field_name; const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_NAME, + "` on `", + IMPL_NAME, + "`: ", + ); + + const FIELD_NAME: &str = $field_name; const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; struct Error { @@ -419,19 +443,9 @@ macro_rules! assert_field_args { const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); - const PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_NAME, - "` on `", - IMPL_NAME, - "`: Field `", - FIELD_NAME, - "`: ", - ); const MSG: &str = match ERROR.cause { Cause::TypeMismatch => { $crate::const_concat!( - PREFIX, "Argument `", BASE_ARG_NAME, "`: expected type `", @@ -443,7 +457,6 @@ macro_rules! assert_field_args { } Cause::RequiredField => { $crate::const_concat!( - PREFIX, "Argument `", BASE_ARG_NAME, "` of type `", @@ -453,7 +466,6 @@ macro_rules! assert_field_args { } Cause::AdditionalNonNullableField => { $crate::const_concat!( - PREFIX, "Argument `", IMPL_ARG_NAME, "` of type `", @@ -462,7 +474,9 @@ macro_rules! assert_field_args { ) } }; - ::std::panic!("{}", MSG); + const ERROR_MSG: &str = + $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); + ::std::panic!("{}", ERROR_MSG); } }; }; @@ -630,6 +644,10 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } +pub trait Fields { + const NAMES: Names; +} + pub trait FieldMeta { type Context; type TypeInfo; diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 2ab8c6e51..d58917b5a 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -728,6 +728,7 @@ impl Definition { let implementers = &self.implementers; let scalar = &self.scalar; let name = &self.name; + let fields = self.fields.iter().map(|f| &f.name); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); @@ -760,6 +761,14 @@ impl Definition { { const VALUE: ::juniper::macros::helper::WrappedValue = 1; } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + } } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index e99c24614..ac838bc06 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -368,6 +368,7 @@ impl Definition { let name = &self.name; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; + let fields = self.fields.iter().map(|f| &f.name); quote! { #[automatically_derived] @@ -394,6 +395,14 @@ impl Definition { { const VALUE: ::juniper::macros::helper::WrappedValue = 1; } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + } } } From e03901b12bab3a87a1d8a43084de03a1200f1a31 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 09:39:36 +0300 Subject: [PATCH 034/122] WIP (Move to macros::reflection module) --- juniper/src/macros/helper/mod.rs | 677 +----------------- juniper/src/macros/mod.rs | 3 + juniper/src/macros/reflection.rs | 679 +++++++++++++++++++ juniper/src/types/scalars.rs | 9 +- juniper_codegen/src/common/field/mod.rs | 30 +- juniper_codegen/src/derive_scalar_value.rs | 8 +- juniper_codegen/src/graphql_interface/mod.rs | 16 +- juniper_codegen/src/graphql_interface/new.rs | 84 +-- juniper_codegen/src/graphql_object/mod.rs | 26 +- juniper_codegen/src/graphql_union/mod.rs | 16 +- juniper_codegen/src/impl_scalar.rs | 8 +- juniper_codegen/src/util/mod.rs | 16 +- 12 files changed, 791 insertions(+), 781 deletions(-) create mode 100644 juniper/src/macros/reflection.rs diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 684043e72..0f81018da 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,14 +2,11 @@ pub mod subscription; -use std::{fmt, rc::Rc, sync::Arc}; +use std::fmt; use futures::future::{self, BoxFuture}; -use crate::{ - Arguments, DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ExecutionResult, - Executor, FieldError, Nullable, ScalarValue, -}; +use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, FieldError, ScalarValue}; /// Conversion of a [`GraphQLValue`] to its [trait object][1]. /// @@ -86,673 +83,3 @@ where { Box::pin(future::err(err_unnamed_type(name))) } - -pub type Type = &'static str; -pub type Types = &'static [Type]; -pub type Name = &'static str; -pub type Names = &'static [Name]; -pub type FieldName = u128; -pub type WrappedValue = u128; - -#[macro_export] -macro_rules! const_concat { - ($($s:expr),* $(,)?) => {{ - const LEN: usize = 0 $(+ $s.len())*; - const CNT: usize = [$($s),*].len(); - const fn concat(input: [&str; CNT]) -> [u8; LEN] { - let mut bytes = [0; LEN]; - let (mut i, mut byte) = (0, 0); - while i < CNT { - let mut b = 0; - while b < input[i].len() { - bytes[byte] = input[i].as_bytes()[b]; - byte += 1; - b += 1; - } - i += 1; - } - bytes - } - const CON: [u8; LEN] = concat([$($s),*]); - unsafe { std::str::from_utf8_unchecked(&CON) } - }}; -} - -#[macro_export] -macro_rules! hash { - ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ - let exists = $crate::macros::helper::exists( - $field_name, - <$impl_ty as $crate::macros::helper::Fields<$scalar>>::NAMES, - ); - if exists { - $crate::macros::helper::fnv1a128(FIELD_NAME) - } else { - ::std::panic!( - "{}", - $crate::const_concat!( - $($prefix,)? - "Field `", - $field_name, - "` isn't implemented on `", - <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME, - "`." - ) - ) - } - }}; -} - -#[macro_export] -macro_rules! format_type { - ($ty: expr, $wrapped_value: expr $(,)?) => {{ - const TYPE: ( - $crate::macros::helper::Type, - $crate::macros::helper::WrappedValue, - ) = ($ty, $wrapped_value); - const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.0, TYPE.1); - - const OPENING_BRACKET: &str = "["; - const CLOSING_BRACKET: &str = "]"; - const BANG: &str = "!"; - - const fn format_type_arr() -> [u8; RES_LEN] { - let (ty, wrap_val) = TYPE; - let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; - - let mut current_start = 0; - let mut current_end = RES_LEN - 1; - let mut current_wrap_val = wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - if !is_null { - i = 0; - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - } - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - is_null = false; - } - _ => {} - } - - current_wrap_val /= 10; - } - - let mut i = 0; - while i < ty.as_bytes().len() { - type_arr[current_start + i] = ty.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; - } - } - - type_arr - } - - const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. - const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; - TYPE_FORMATTED - }}; -} - -#[macro_export] -macro_rules! assert_field { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); - $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); - }; -} - -#[macro_export] -macro_rules! assert_subtype { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - const _: () = { - const BASE_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const IMPL_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_TY, - "` on `", - IMPL_TY, - "`: ", - ); - - const FIELD_NAME: $crate::macros::helper::Name = - $field_name; - const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::WRAPPED_VALUE; - const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$impl_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::WRAPPED_VALUE; - - const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = - <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::SUB_TYPES; - - const BASE_RETURN_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::TYPE; - const IMPL_RETURN_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::TYPE; - - let is_subtype = $crate::macros::helper::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) - && $crate::macros::helper::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); - if !is_subtype { - const MSG: &str = $crate::const_concat!( - ERR_PREFIX, - "Field `", - FIELD_NAME, - "`: implementor is expected to return a subtype of interface's return object: `", - $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), - "` is not a subtype of `", - $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), - "`.", - ); - ::std::panic!("{}", MSG); - } - }; - }; -} - -#[macro_export] -macro_rules! assert_field_args { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - const _: () = { - type FullArg = ( - $crate::macros::helper::Name, - $crate::macros::helper::Type, - $crate::macros::helper::WrappedValue, - ); - - const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_NAME, - "` on `", - IMPL_NAME, - "`: ", - ); - - const FIELD_NAME: &str = $field_name; - const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - - struct Error { - cause: Cause, - base: FullArg, - implementation: FullArg, - } - - enum Cause { - RequiredField, - AdditionalNonNullableField, - TypeMismatch, - } - - const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { - match v { - Ok(()) => Error { - cause: Cause::RequiredField, - base: ("unreachable", "unreachable", 1), - implementation: ("unreachable", "unreachable", 1), - }, - Err(err) => err, - } - } - - const fn check() -> Result<(), Error> { - let mut base_i = 0; - while base_i < BASE_ARGS.len() { - let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; - - let mut impl_i = 0; - let mut was_found = false; - while impl_i < IMPL_ARGS.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; - - if $crate::macros::helper::str_eq(base_name, impl_name) { - if $crate::macros::helper::str_eq(base_type, impl_type) - && base_wrap_val == impl_wrap_val - { - was_found = true; - break; - } else { - return Err(Error { - cause: Cause::TypeMismatch, - base: (base_name, base_type, base_wrap_val), - implementation: (impl_name, impl_type, impl_wrap_val), - }); - } - } - - impl_i += 1; - } - - if !was_found { - return Err(Error { - cause: Cause::RequiredField, - base: (base_name, base_type, base_wrap_val), - implementation: (base_name, base_type, base_wrap_val), - }); - } - - base_i += 1; - } - - let mut impl_i = 0; - while impl_i < IMPL_ARGS.len() { - let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; - impl_i += 1; - - if impl_wrapped_val % 10 == 2 { - continue; - } - - let mut base_i = 0; - let mut was_found = false; - while base_i < BASE_ARGS.len() { - let (base_name, _, _) = BASE_ARGS[base_i]; - if $crate::macros::helper::str_eq(base_name, impl_name) { - was_found = true; - break; - } - base_i += 1; - } - if !was_found { - return Err(Error { - cause: Cause::AdditionalNonNullableField, - base: (impl_name, impl_type, impl_wrapped_val), - implementation: (impl_name, impl_type, impl_wrapped_val), - }); - } - } - - Ok(()) - } - - const RES: ::std::result::Result<(), Error> = check(); - if RES.is_err() { - const ERROR: Error = unwrap_error(RES); - - const BASE_ARG_NAME: &str = ERROR.base.0; - const IMPL_ARG_NAME: &str = ERROR.implementation.0; - - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); - const IMPL_TYPE_FORMATTED: &str = - $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); - - const MSG: &str = match ERROR.cause { - Cause::TypeMismatch => { - $crate::const_concat!( - "Argument `", - BASE_ARG_NAME, - "`: expected type `", - BASE_TYPE_FORMATTED, - "`, found: `", - IMPL_TYPE_FORMATTED, - "`.", - ) - } - Cause::RequiredField => { - $crate::const_concat!( - "Argument `", - BASE_ARG_NAME, - "` of type `", - BASE_TYPE_FORMATTED, - "` was expected, but not found." - ) - } - Cause::AdditionalNonNullableField => { - $crate::const_concat!( - "Argument `", - IMPL_ARG_NAME, - "` of type `", - IMPL_TYPE_FORMATTED, - "` not present on the interface and so has to be nullable." - ) - } - }; - const ERROR_MSG: &str = - $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); - ::std::panic!("{}", ERROR_MSG); - } - }; - }; -} - -/// TODO -pub trait BaseType { - const NAME: Type; -} - -impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { - const NAME: Type = T::NAME; -} - -// TODO: Reconsider -impl, Ctx> BaseType for (Ctx, T) { - const NAME: Type = T::NAME; -} - -impl> BaseType for Option { - const NAME: Type = T::NAME; -} - -impl> BaseType for Nullable { - const NAME: Type = T::NAME; -} - -// TODO: Should Err be trait bounded somehow? -impl, E> BaseType for Result { - const NAME: Type = T::NAME; -} - -impl> BaseType for Vec { - const NAME: Type = T::NAME; -} - -impl> BaseType for [T] { - const NAME: Type = T::NAME; -} - -impl, const N: usize> BaseType for [T; N] { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Box { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Arc { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Rc { - const NAME: Type = T::NAME; -} - -/// TODO -pub trait BaseSubTypes { - const NAMES: Types; -} - -impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { - const NAMES: Types = T::NAMES; -} - -// TODO: Reconsider -impl, Ctx> BaseSubTypes for (Ctx, T) { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Option { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Nullable { - const NAMES: Types = T::NAMES; -} - -// TODO: Should Err be trait bounded somehow? -impl, E> BaseSubTypes for Result { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Vec { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for [T] { - const NAMES: Types = T::NAMES; -} - -impl, const N: usize> BaseSubTypes for [T; N] { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Box { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Arc { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Rc { - const NAMES: Types = T::NAMES; -} - -/// TODO -pub trait WrappedType { - /// NonNull - 1 - /// Nullable - 2 - /// List - 3 - /// - /// `[[Int]!] - >>> as WrappedType>::N = 12332` - const VALUE: u128; -} - -impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) -where - S: ScalarValue, - T: crate::GraphQLValue, -{ - const VALUE: u128 = T::VALUE; -} - -impl> WrappedType for Option { - const VALUE: u128 = T::VALUE * 10 + 2; -} - -impl> WrappedType for Nullable { - const VALUE: u128 = T::VALUE * 10 + 2; -} - -// TODO: Should Err be trait bounded somehow? -// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? -impl, E> WrappedType for Result { - const VALUE: u128 = T::VALUE; -} - -impl> WrappedType for Vec { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl> WrappedType for [T] { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl, const N: usize> WrappedType for [T; N] { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Box { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Arc { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Rc { - const VALUE: u128 = T::VALUE; -} - -pub trait Fields { - const NAMES: Names; -} - -pub trait FieldMeta { - type Context; - type TypeInfo; - const TYPE: Type; - const SUB_TYPES: Types; - const WRAPPED_VALUE: WrappedValue; - const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; -} - -pub trait Field: FieldMeta { - fn call( - &self, - info: &Self::TypeInfo, - args: &Arguments, - executor: &Executor, - ) -> ExecutionResult; -} - -pub trait AsyncField: FieldMeta { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b Arguments, - executor: &'b Executor, - ) -> BoxFuture<'b, ExecutionResult>; -} - -/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in -/// const generics. See [spec] for more info. -/// -/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -pub const fn fnv1a128(str: Name) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash -} - -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - -pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { - let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - - let mut current_wrap_val = v; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => len -= "!".as_bytes().len(), // remove ! - 3 => len += "[]!".as_bytes().len(), // [Type]! - _ => {} - } - - current_wrap_val /= 10; - } - - len -} - -pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; - - if ty_current == subtype_current { - if ty_current == 1 { - true - } else { - can_be_subtype(ty / 10, subtype / 10) - } - } else if ty_current == 2 { - can_be_subtype(ty / 10, subtype) - } else { - false - } -} - -pub const fn exists(val: Type, arr: Types) -> bool { - let mut i = 0; - while i < arr.len() { - if str_eq(val, arr[i]) { - return true; - } - i += 1; - } - false -} diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index 591c4c411..182bd2055 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -3,6 +3,9 @@ #[doc(hidden)] #[macro_use] pub mod helper; +#[doc(hidden)] +#[macro_use] +pub mod reflection; #[macro_use] mod graphql_input_value; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs new file mode 100644 index 000000000..a9a12109c --- /dev/null +++ b/juniper/src/macros/reflection.rs @@ -0,0 +1,679 @@ +//! Helper traits and macros for compile-time reflection. + +use std::{rc::Rc, sync::Arc}; + +use futures::future::BoxFuture; + +use crate::{Arguments, DefaultScalarValue, ExecutionResult, Executor, Nullable, ScalarValue}; + +pub type Type = &'static str; +pub type Types = &'static [Type]; +pub type Name = &'static str; +pub type Names = &'static [Name]; +pub type FieldName = u128; +pub type WrappedValue = u128; + +/// TODO +pub trait BaseType { + const NAME: Type; +} + +impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { + const NAME: Type = T::NAME; +} + +// TODO: Reconsider +impl, Ctx> BaseType for (Ctx, T) { + const NAME: Type = T::NAME; +} + +impl> BaseType for Option { + const NAME: Type = T::NAME; +} + +impl> BaseType for Nullable { + const NAME: Type = T::NAME; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseType for Result { + const NAME: Type = T::NAME; +} + +impl> BaseType for Vec { + const NAME: Type = T::NAME; +} + +impl> BaseType for [T] { + const NAME: Type = T::NAME; +} + +impl, const N: usize> BaseType for [T; N] { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Box { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Arc { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Rc { + const NAME: Type = T::NAME; +} + +/// TODO +pub trait BaseSubTypes { + const NAMES: Types; +} + +impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { + const NAMES: Types = T::NAMES; +} + +// TODO: Reconsider +impl, Ctx> BaseSubTypes for (Ctx, T) { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Option { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Nullable { + const NAMES: Types = T::NAMES; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseSubTypes for Result { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Vec { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for [T] { + const NAMES: Types = T::NAMES; +} + +impl, const N: usize> BaseSubTypes for [T; N] { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Box { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Arc { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Rc { + const NAMES: Types = T::NAMES; +} + +/// TODO +pub trait WrappedType { + /// NonNull - 1 + /// Nullable - 2 + /// List - 3 + /// + /// `[[Int]!] - >>> as WrappedType>::N = 12332` + const VALUE: u128; +} + +impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) +where + S: ScalarValue, + T: crate::GraphQLValue, +{ + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Option { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +impl> WrappedType for Nullable { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +// TODO: Should Err be trait bounded somehow? +// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? +impl, E> WrappedType for Result { + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Vec { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl> WrappedType for [T] { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl, const N: usize> WrappedType for [T; N] { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Box { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Arc { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Rc { + const VALUE: u128 = T::VALUE; +} + +pub trait Fields { + const NAMES: Names; +} + +pub trait FieldMeta { + type Context; + type TypeInfo; + const TYPE: Type; + const SUB_TYPES: Types; + const WRAPPED_VALUE: WrappedValue; + const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; +} + +pub trait Field: FieldMeta { + fn call( + &self, + info: &Self::TypeInfo, + args: &Arguments, + executor: &Executor, + ) -> ExecutionResult; +} + +pub trait AsyncField: FieldMeta { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b Arguments, + executor: &'b Executor, + ) -> BoxFuture<'b, ExecutionResult>; +} + +/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in +/// const generics. See [spec] for more info. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +pub const fn fnv1a128(str: Name) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash +} + +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + +pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { + let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! + + let mut current_wrap_val = v; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => len -= "!".as_bytes().len(), // remove ! + 3 => len += "[]!".as_bytes().len(), // [Type]! + _ => {} + } + + current_wrap_val /= 10; + } + + len +} + +pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + can_be_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + can_be_subtype(ty / 10, subtype) + } else { + false + } +} + +pub const fn exists(val: Type, arr: Types) -> bool { + let mut i = 0; + while i < arr.len() { + if str_eq(val, arr[i]) { + return true; + } + i += 1; + } + false +} + +#[macro_export] +macro_rules! assert_field { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); + $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); + }; +} + +#[macro_export] +macro_rules! assert_subtype { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + const BASE_TY: $crate::macros::reflection::Type = + <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::reflection::Type = + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_TY, + "` on `", + IMPL_TY, + "`: ", + ); + + const FIELD_NAME: $crate::macros::reflection::Name = + $field_name; + const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::WRAPPED_VALUE; + const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = + <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::WRAPPED_VALUE; + + const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::SUB_TYPES; + + const BASE_RETURN_TY: $crate::macros::reflection::Type = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::TYPE; + const IMPL_RETURN_TY: $crate::macros::reflection::Type = + <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::TYPE; + + let is_subtype = $crate::macros::reflection::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); + if !is_subtype { + const MSG: &str = $crate::const_concat!( + ERR_PREFIX, + "Field `", + FIELD_NAME, + "`: implementor is expected to return a subtype of interface's return object: `", + $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), + "` is not a subtype of `", + $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), + "`.", + ); + ::std::panic!("{}", MSG); + } + }; + }; +} + +#[macro_export] +macro_rules! assert_field_args { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + type FullArg = ( + $crate::macros::reflection::Name, + $crate::macros::reflection::Type, + $crate::macros::reflection::WrappedValue, + ); + + const BASE_NAME: &str = + <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const IMPL_NAME: &str = + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_NAME, + "` on `", + IMPL_NAME, + "`: ", + ); + + const FIELD_NAME: &str = $field_name; + const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; + const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; + + struct Error { + cause: Cause, + base: FullArg, + implementation: FullArg, + } + + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, + } + + const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { + match v { + Ok(()) => Error { + cause: Cause::RequiredField, + base: ("unreachable", "unreachable", 1), + implementation: ("unreachable", "unreachable", 1), + }, + Err(err) => err, + } + } + + const fn check() -> Result<(), Error> { + let mut base_i = 0; + while base_i < BASE_ARGS.len() { + let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; + + if $crate::macros::reflection::str_eq(base_name, impl_name) { + if $crate::macros::reflection::str_eq(base_type, impl_type) + && base_wrap_val == impl_wrap_val + { + was_found = true; + break; + } else { + return Err(Error { + cause: Cause::TypeMismatch, + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + }); + } + } + + impl_i += 1; + } + + if !was_found { + return Err(Error { + cause: Cause::RequiredField, + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + }); + } + + base_i += 1; + } + + let mut impl_i = 0; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; + impl_i += 1; + + if impl_wrapped_val % 10 == 2 { + continue; + } + + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE_ARGS.len() { + let (base_name, _, _) = BASE_ARGS[base_i]; + if $crate::macros::reflection::str_eq(base_name, impl_name) { + was_found = true; + break; + } + base_i += 1; + } + if !was_found { + return Err(Error { + cause: Cause::AdditionalNonNullableField, + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + }); + } + } + + Ok(()) + } + + const RES: ::std::result::Result<(), Error> = check(); + if RES.is_err() { + const ERROR: Error = unwrap_error(RES); + + const BASE_ARG_NAME: &str = ERROR.base.0; + const IMPL_ARG_NAME: &str = ERROR.implementation.0; + + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); + const IMPL_TYPE_FORMATTED: &str = + $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); + + const MSG: &str = match ERROR.cause { + Cause::TypeMismatch => { + $crate::const_concat!( + "Argument `", + BASE_ARG_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + ) + } + Cause::RequiredField => { + $crate::const_concat!( + "Argument `", + BASE_ARG_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found." + ) + } + Cause::AdditionalNonNullableField => { + $crate::const_concat!( + "Argument `", + IMPL_ARG_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` not present on the interface and so has to be nullable." + ) + } + }; + const ERROR_MSG: &str = + $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); + ::std::panic!("{}", ERROR_MSG); + } + }; + }; +} + +#[macro_export] +macro_rules! const_concat { + ($($s:expr),* $(,)?) => {{ + const LEN: usize = 0 $(+ $s.len())*; + const CNT: usize = [$($s),*].len(); + const fn concat(input: [&str; CNT]) -> [u8; LEN] { + let mut bytes = [0; LEN]; + let (mut i, mut byte) = (0, 0); + while i < CNT { + let mut b = 0; + while b < input[i].len() { + bytes[byte] = input[i].as_bytes()[b]; + byte += 1; + b += 1; + } + i += 1; + } + bytes + } + const CON: [u8; LEN] = concat([$($s),*]); + unsafe { std::str::from_utf8_unchecked(&CON) } + }}; +} + +#[macro_export] +macro_rules! hash { + ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ + let exists = $crate::macros::reflection::exists( + $field_name, + <$impl_ty as $crate::macros::reflection::Fields<$scalar>>::NAMES, + ); + if exists { + $crate::macros::reflection::fnv1a128(FIELD_NAME) + } else { + ::std::panic!( + "{}", + $crate::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`." + ) + ) + } + }}; +} + +#[macro_export] +macro_rules! format_type { + ($ty: expr, $wrapped_value: expr $(,)?) => {{ + const TYPE: ( + $crate::macros::reflection::Type, + $crate::macros::reflection::WrappedValue, + ) = ($ty, $wrapped_value); + const RES_LEN: usize = $crate::macros::reflection::str_len_from_wrapped_val(TYPE.0, TYPE.1); + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn format_type_arr() -> [u8; RES_LEN] { + let (ty, wrap_val) = TYPE; + let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; + + let mut current_start = 0; + let mut current_end = RES_LEN - 1; + let mut current_wrap_val = wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + if !is_null { + i = 0; + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + is_null = false; + } + _ => {} + } + + current_wrap_val /= 10; + } + + let mut i = 0; + while i < ty.as_bytes().len() { + type_arr[current_start + i] = ty.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } + } + + type_arr + } + + const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); + // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. + const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + TYPE_FORMATTED + }}; +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 5d84bd6f8..3fd3fcf18 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -202,16 +202,17 @@ where }) } -impl crate::macros::helper::WrappedType for str { +impl crate::macros::reflection::WrappedType for str { const VALUE: u128 = 1; } -impl crate::macros::helper::BaseType for str { +impl crate::macros::reflection::BaseType for str { const NAME: &'static str = "String"; } -impl crate::macros::helper::BaseSubTypes for str { - const NAMES: &'static [&'static str] = &[>::NAME]; +impl crate::macros::reflection::BaseSubTypes for str { + const NAMES: &'static [&'static str] = + &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 385ed22d4..1f89dd296 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -464,8 +464,8 @@ impl Definition { Some(quote! {( #name, - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, )}) } MethodArgument::Executor | MethodArgument::Context(_) => None, @@ -475,24 +475,24 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::helper::FieldMeta< + impl #impl_generics ::juniper::macros::reflection::FieldMeta< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } > for #impl_ty #where_clause { type Context = #context; type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const TYPE: ::juniper::macros::reflection::Type = + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, )] = &[#(#arguments,)*]; } } @@ -575,9 +575,9 @@ impl Definition { Some(quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::helper::#trait_name< + impl #impl_generics ::juniper::macros::reflection::#trait_name< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } > for #impl_ty #where_clause { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 724a616d8..9e2698d74 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -238,20 +238,20 @@ fn impl_scalar_struct( where #scalar: ::juniper::ScalarValue, { } - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { const VALUE: u128 = 1; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 2671b0ca7..c5c7aaca5 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -726,25 +726,25 @@ impl Definition { let implementors = self.implementers.iter().map(|i| &i.ty); quote! { - impl #impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + impl #impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } - impl #impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + impl #impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = &[ - >::NAME, - #(<#implementors as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflection::Types = &[ + >::NAME, + #(<#implementors as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } - impl #impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + impl #impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } } } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index d58917b5a..0b21cec40 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -577,9 +577,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::Field::< + ::juniper::macros::reflection::Field::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -671,9 +671,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::AsyncField::< + ::juniper::macros::reflection::AsyncField::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -718,9 +718,9 @@ impl Definition { /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL interface][1]. /// - /// [`BaseSubTypes`]: juniper::macros::helper::BaseSubTypes - /// [`BaseType`]: juniper::macros::helper::BaseType - /// [`WrappedType`]: juniper::macros::helper::WrappedType + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] pub(crate) fn impl_traits_for_reflection(&self) -> TokenStream { @@ -736,38 +736,38 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty#ty_generics #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty#ty_generics #where_clause { - const NAMES: ::juniper::macros::helper::Types = &[ - >::NAME, - #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflection::Types = &[ + >::NAME, + #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty#ty_generics #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> for #ty#ty_generics #where_clause { - const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; } } } @@ -775,7 +775,7 @@ impl Definition { /// Returns generated code implementing [`FieldMeta`] for this /// [GraphQL interface][1]. /// - /// [`FieldMeta`]: juniper::macros::helper::FieldMeta + /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field_meta(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -805,26 +805,26 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::FieldMeta< + impl#impl_generics ::juniper::macros::reflection::FieldMeta< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } + { ::juniper::macros::reflection::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { type Context = #context; type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = - <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const TYPE: ::juniper::macros::reflection::Type = + <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = + <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, )] = &[#(( #args_names, - <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, )),*]; } } @@ -835,8 +835,8 @@ impl Definition { /// Returns generated code implementing [`Field`] or [`AsyncField`] trait /// for this [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::helper::AsyncField - /// [`Field`]: juniper::macros::helper::Field + /// [`AsyncField`]: juniper::macros::reflection::AsyncField + /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -875,9 +875,9 @@ impl Definition { Some(quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::Field< + impl#impl_generics ::juniper::macros::reflection::Field< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } + { ::juniper::macros::reflection::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { fn call( &self, @@ -894,9 +894,9 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::Field< + <_ as ::juniper::macros::reflection::Field< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm @@ -911,7 +911,7 @@ impl Definition { /// Returns generated code implementing [`AsyncField`] trait for this /// [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::helper::AsyncField + /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_async_field(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -946,9 +946,9 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::AsyncField< + impl#impl_generics ::juniper::macros::reflection::AsyncField< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } + { ::juniper::macros::reflection::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { fn call<'b>( &'b self, @@ -965,9 +965,9 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::AsyncField< + <_ as ::juniper::macros::reflection::AsyncField< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index ac838bc06..c1caa2e17 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -372,36 +372,36 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflection::Types = + &[>::NAME]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; } } } @@ -541,9 +541,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::Field::< + ::juniper::macros::reflection::Field::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -637,9 +637,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::AsyncField::< + ::juniper::macros::reflection::AsyncField::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 952889782..14048cd0c 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -621,30 +621,30 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = &[ - >::NAME, - #(<#variants as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflection::Types = &[ + >::NAME, + #(<#variants as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } } } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 681620eae..b28aabedc 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -328,20 +328,20 @@ pub fn build_scalar( } } - impl#generic_type_decl ::juniper::macros::helper::BaseType<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type #generic_type_bound { const NAME: &'static str = #name; } - impl#generic_type_decl ::juniper::macros::helper::BaseSubTypes<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type #generic_type_bound { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#generic_type_decl ::juniper::macros::helper::WrappedType<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflection::WrappedType<#generic_type> for #impl_for_type #generic_type_bound { const VALUE: u128 = 1; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index d88eeee3d..1a301717c 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -907,20 +907,20 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { const VALUE: u128 = 1; @@ -1173,22 +1173,22 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #type_generics_tokens #where_clause { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #type_generics_tokens #where_clause { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #type_generics_tokens #where_clause { From 3ad83990adc1e0448ac37a455d98e4f9204dba1f Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 11:39:35 +0300 Subject: [PATCH 035/122] WIP (Docs vol. 1) --- .../src/codegen/interface_attr.rs | 2 +- juniper/src/macros/reflection.rs | 260 +++++++++++++++--- 2 files changed, 220 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 2ea00b554..8dbc45920 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,7 +1,7 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, + execute, graphql_interface_new, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index a9a12109c..673f01430 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -4,17 +4,66 @@ use std::{rc::Rc, sync::Arc}; use futures::future::BoxFuture; -use crate::{Arguments, DefaultScalarValue, ExecutionResult, Executor, Nullable, ScalarValue}; +use crate::{ + Arguments as FieldArguments, DefaultScalarValue, ExecutionResult, Executor, GraphQLValue, + Nullable, ScalarValue, +}; +/// Type alias for GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] type +/// name. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Scalars +/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces pub type Type = &'static str; + +/// Type alias for slice of [`Type`]s. See [`BaseType`] for more info. pub type Types = &'static [Type]; + +/// Type alias for GraphQL [`object`][1] or [`interface`][2] +/// [`field argument`][3] name. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Name = &'static str; + +/// Type alias for slice of [`Name`]s. pub type Names = &'static [Name]; -pub type FieldName = u128; + +/// Type alias for value of [`WrappedType`]. pub type WrappedValue = u128; -/// TODO -pub trait BaseType { +/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and +/// [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments +pub type Argument = (Name, Type, WrappedValue); + +/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and +/// [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments +pub type Arguments = &'static [(Name, Type, WrappedValue)]; + +/// Type alias for constantly hashed [`Name`] for usage in const generics. +pub type FieldName = u128; + +/// GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] [`Type`] name. +/// This trait is transparent to the [`Option`], [`Vec`] and other containers, +/// so to fully represent GraphQL [`object`][1] we additionally use +/// [`WrappedType`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Scalars +/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces +pub trait BaseType { + /// [`Type`] of the GraphQL [`object`][1], [`scalar`][2] or + /// [`interface`][3]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [2]: https://spec.graphql.org/October2021/#sec-Scalars + /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces const NAME: Type; } @@ -22,8 +71,11 @@ impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { const NAME: Type = T::NAME; } -// TODO: Reconsider -impl, Ctx> BaseType for (Ctx, T) { +impl<'ctx, S, T> BaseType for (&'ctx T::Context, T) +where + S: ScalarValue, + T: BaseType + GraphQLValue, +{ const NAME: Type = T::NAME; } @@ -35,7 +87,6 @@ impl> BaseType for Nullable { const NAME: Type = T::NAME; } -// TODO: Should Err be trait bounded somehow? impl, E> BaseType for Result { const NAME: Type = T::NAME; } @@ -64,8 +115,16 @@ impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } -/// TODO -pub trait BaseSubTypes { +/// GraphQL [`object`][1] [`sub-types`][2]. This trait is transparent to the +/// [`Option`], [`Vec`] and other containers. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC +pub trait BaseSubTypes { + /// [`Types`] for the GraphQL [`object`][1]s [`sub-types`][2]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC const NAMES: Types; } @@ -73,8 +132,11 @@ impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { const NAMES: Types = T::NAMES; } -// TODO: Reconsider -impl, Ctx> BaseSubTypes for (Ctx, T) { +impl<'ctx, S, T> BaseSubTypes for (&'ctx T::Context, T) +where + S: ScalarValue, + T: BaseSubTypes + GraphQLValue, +{ const NAMES: Types = T::NAMES; } @@ -86,7 +148,6 @@ impl> BaseSubTypes for Nullable { const NAMES: Types = T::NAMES; } -// TODO: Should Err be trait bounded somehow? impl, E> BaseSubTypes for Result { const NAMES: Types = T::NAMES; } @@ -115,20 +176,52 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// TODO -pub trait WrappedType { - /// NonNull - 1 - /// Nullable - 2 - /// List - 3 - /// - /// `[[Int]!] - >>> as WrappedType>::N = 12332` - const VALUE: u128; +/// To fully represent GraphQL [`object`][1] it's not enough to use [`Type`], +/// because of the [`wrapping types`][2]. To work around this we use +/// [`WrappedValue`] which is represented with [`u128`]. +/// +/// - In base case of non-nullable [`object`] [`VALUE`] is `1`. +/// - To represent nullability we "append" `2` to the [`VALUE`], so +/// [`Option`]`<`[`object`][1]`>` has [`VALUE`] of `12`. +/// - To represent list we "append" `3` to the [`VALUE`], so +/// [`Vec`]`<`[`object`][1]`>` has [`VALUE`] of `13`. +/// +/// This approach allows us to uniquely represent any GraphQL [`object`] with +/// combination of [`Type`] and [`WrappedValue`] and even constantly format it +/// with [`format_type`] macro. +/// +/// # Examples +/// +/// ```rust +/// # use juniper::{macros::reflection::{WrappedType, BaseType, WrappedValue, Type}, DefaultScalarValue, format_type}; +/// # +/// assert_eq!( as WrappedType>::VALUE, 12); +/// assert_eq!( as WrappedType>::VALUE, 13); +/// assert_eq!(> as WrappedType>::VALUE, 123); +/// assert_eq!(> as WrappedType>::VALUE, 132); +/// assert_eq!(>> as WrappedType>::VALUE, 1232); +/// +/// const TYPE_STRING: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STRING: WrappedValue = >> as WrappedType>::VALUE; +/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// +/// const TYPE_STR: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::VALUE; +/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// ``` +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Wrapping-Types +/// [`VALUE`]: Self::VALUE +pub trait WrappedType { + /// [`WrappedValue`] of this type. + const VALUE: WrappedValue; } -impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) +impl<'ctx, S, T: WrappedType> WrappedType for (&'ctx T::Context, T) where S: ScalarValue, - T: crate::GraphQLValue, + T: GraphQLValue, { const VALUE: u128 = T::VALUE; } @@ -141,8 +234,6 @@ impl> WrappedType for Nullable { const VALUE: u128 = T::VALUE * 10 + 2; } -// TODO: Should Err be trait bounded somehow? -// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? impl, E> WrappedType for Result { const VALUE: u128 = T::VALUE; } @@ -175,33 +266,103 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -pub trait Fields { +/// GraphQL [`object`][1] or [`interface`][2] [`Field arguments`][3] [`Names`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments +pub trait Fields { + /// [`Names`] of the GraphQL [`object`][1] or [`interface`][2] + /// [`Field arguments`][3]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces + /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments const NAMES: Names; } +/// Stores meta information of GraphQL [`Fields`][1]: +/// - [`Context`] and [`TypeInfo`]. +/// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. +/// - [`ARGUMENTS`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields +/// [`Context`]: Self::Context +/// [`TypeInfo`]: Self::TypeInfo +/// [`TYPE`]: Self::TYPE +/// [`SUB_TYPES`]: Self::SUB_TYPES +/// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE +/// [`ARGUMENTS`]: Self::ARGUMENTS pub trait FieldMeta { + /// [`GraphQLValue::Context`] of this [`Field`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type Context; + + /// [`GraphQLValue::TypeInfo`] of this [`Field`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type TypeInfo; + + /// [`Types`] of [`Field`][1]'s return type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const TYPE: Type; + + /// Sub-[`Types`] of [`Field`][1]'s return type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const SUB_TYPES: Types; + + /// [`WrappedValue`] of [`Field`][1]'s return type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const WRAPPED_VALUE: WrappedValue; - const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; + + /// [`Field`][1]'s [`Arguments`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + const ARGUMENTS: Arguments; } +/// Synchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait Field: FieldMeta { + /// Resolves the [`Value`] of this synchronous [`Field`]. + /// + /// The `arguments` object contains all the specified arguments, with + /// default values being substituted for the ones not provided by the query. + /// + /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call( &self, info: &Self::TypeInfo, - args: &Arguments, + args: &FieldArguments, executor: &Executor, ) -> ExecutionResult; } +/// Asynchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait AsyncField: FieldMeta { + /// Resolves the [`Value`] of this asynchronous [`Field`]. + /// + /// The `arguments` object contains all the specified arguments, with + /// default values being substituted for the ones not provided by the query. + /// + /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call<'b>( &'b self, info: &'b Self::TypeInfo, - args: &'b Arguments, + args: &'b FieldArguments, executor: &'b Executor, ) -> BoxFuture<'b, ExecutionResult>; } @@ -225,6 +386,13 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } +/// Compares strings in `const` context. +/// +/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to +/// write custom comparison function. +/// +/// [`Eq`]: std::cmp::Eq +// TODO: Remove once `Eq` trait is allowed in `const` context. pub const fn str_eq(l: &str, r: &str) -> bool { let (l, r) = (l.as_bytes(), r.as_bytes()); @@ -243,7 +411,8 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } -pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { +/// Length of the [`format_type`] macro result __in bytes__. +pub const fn type_len_with_wrapped_val(ty: Type, v: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! let mut current_wrap_val = v; @@ -260,6 +429,13 @@ pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { len } +/// Based on the [`WrappedValue`] checks whether GraphQL [`objects`][1] can be +/// subtypes. +/// +/// To fully determine sub-typing relation [`Type`] should be one of the +/// [`BaseSubTypes::NAMES`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { let ty_current = ty % 10; let subtype_current = subtype % 10; @@ -277,7 +453,8 @@ pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { } } -pub const fn exists(val: Type, arr: Types) -> bool { +/// Checks whether `val` exists in `arr`. +pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { let mut i = 0; while i < arr.len() { if str_eq(val, arr[i]) { @@ -327,32 +504,32 @@ macro_rules! assert_subtype { const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = <$impl_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::SUB_TYPES; const BASE_RETURN_TY: $crate::macros::reflection::Type = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::reflection::Type = <$impl_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; - let is_subtype = $crate::macros::reflection::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + let is_subtype = $crate::macros::reflection::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { const MSG: &str = $crate::const_concat!( @@ -401,11 +578,11 @@ macro_rules! assert_field_args { const FIELD_NAME: &str = $field_name; const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; struct Error { @@ -576,9 +753,9 @@ macro_rules! const_concat { } #[macro_export] -macro_rules! hash { +macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ - let exists = $crate::macros::reflection::exists( + let exists = $crate::macros::reflection::str_exists_in_arr( $field_name, <$impl_ty as $crate::macros::reflection::Fields<$scalar>>::NAMES, ); @@ -607,7 +784,8 @@ macro_rules! format_type { $crate::macros::reflection::Type, $crate::macros::reflection::WrappedValue, ) = ($ty, $wrapped_value); - const RES_LEN: usize = $crate::macros::reflection::str_len_from_wrapped_val(TYPE.0, TYPE.1); + const RES_LEN: usize = + $crate::macros::reflection::type_len_with_wrapped_val(TYPE.0, TYPE.1); const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; From 463bbb8b6f2c9cec4c53bbed9b5b3ab9adc01f37 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 12:23:56 +0300 Subject: [PATCH 036/122] WIP (Docs vol. 2) --- juniper/src/macros/reflection.rs | 113 ++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 673f01430..61a92418c 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -5,8 +5,7 @@ use std::{rc::Rc, sync::Arc}; use futures::future::BoxFuture; use crate::{ - Arguments as FieldArguments, DefaultScalarValue, ExecutionResult, Executor, GraphQLValue, - Nullable, ScalarValue, + Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; /// Type alias for GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] type @@ -465,6 +464,11 @@ pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { false } +/// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This +/// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. +/// See [spec][1] for more info. +/// +/// [1]: https://spec.graphql.org/October2021/#IsValidImplementation() #[macro_export] macro_rules! assert_field { ( @@ -478,6 +482,10 @@ macro_rules! assert_field { }; } +/// Asserts validness of the [`Field`]s return type. See [spec][1] for more +/// info. +/// +/// [1]: https://spec.graphql.org/October2021/#IsValidImplementationFieldType() #[macro_export] macro_rules! assert_subtype { ( @@ -501,6 +509,7 @@ macro_rules! assert_subtype { const FIELD_NAME: $crate::macros::reflection::Name = $field_name; + const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, @@ -512,12 +521,6 @@ macro_rules! assert_subtype { { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; - const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = - <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::SUB_TYPES; - const BASE_RETURN_TY: $crate::macros::reflection::Type = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, @@ -529,6 +532,12 @@ macro_rules! assert_subtype { { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; + const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::SUB_TYPES; + let is_subtype = $crate::macros::reflection::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { @@ -548,6 +557,10 @@ macro_rules! assert_subtype { }; } +/// Asserts validness of the [`Field`]s arguments. See [spec][1] for more +/// info. +/// +/// [1]: https://spec.graphql.org/October2021/#sel-IAHZhCHCDEEFAAADHD8Cxob #[macro_export] macro_rules! assert_field_args { ( @@ -557,12 +570,6 @@ macro_rules! assert_field_args { $field_name: expr $(,)? ) => { const _: () = { - type FullArg = ( - $crate::macros::reflection::Name, - $crate::macros::reflection::Type, - $crate::macros::reflection::WrappedValue, - ); - const BASE_NAME: &str = <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = @@ -576,19 +583,22 @@ macro_rules! assert_field_args { ); const FIELD_NAME: &str = $field_name; - const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; + + const BASE_ARGS: ::juniper::macros::reflection::Arguments = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; + const IMPL_ARGS: ::juniper::macros::reflection::Arguments = + <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; struct Error { cause: Cause, - base: FullArg, - implementation: FullArg, + base: ::juniper::macros::reflection::Argument, + implementation: ::juniper::macros::reflection::Argument, } enum Cause { @@ -599,6 +609,8 @@ macro_rules! assert_field_args { const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { match v { + // Unfortunately we can't use `unreachable!()` here, as this + // branch will be executed either way. Ok(()) => Error { cause: Cause::RequiredField, base: ("unreachable", "unreachable", 1), @@ -636,6 +648,7 @@ macro_rules! assert_field_args { impl_i += 1; } + // TODO: Maybe that's ok? if !was_found { return Err(Error { cause: Cause::RequiredField, @@ -728,10 +741,11 @@ macro_rules! assert_field_args { }; } +/// Concatenates const [`str`](prim@str)s in const context. #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ - const LEN: usize = 0 $(+ $s.len())*; + const LEN: usize = 0 $(+ $s.as_bytes().len())*; const CNT: usize = [$($s),*].len(); const fn concat(input: [&str; CNT]) -> [u8; LEN] { let mut bytes = [0; LEN]; @@ -748,10 +762,16 @@ macro_rules! const_concat { bytes } const CON: [u8; LEN] = concat([$($s),*]); + + // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one + // after the other byte by byte. + #[allow(unsafe_code)] unsafe { std::str::from_utf8_unchecked(&CON) } }}; } +/// Before executing [`fnv1a128`] checks whether `impl_ty` has corresponding +/// [`Field`] impl and panics with understandable message. #[macro_export] macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ @@ -762,21 +782,29 @@ macro_rules! checked_hash { if exists { $crate::macros::reflection::fnv1a128(FIELD_NAME) } else { - ::std::panic!( - "{}", - $crate::const_concat!( - $($prefix,)? - "Field `", - $field_name, - "` isn't implemented on `", - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "`." - ) - ) + const MSG: &str = $crate::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`." + ); + ::std::panic!("{}", MSG) } }}; } +/// Formats [`Type`] and [`WrappedValue`] into GraphQL type. +/// +/// # Examples +/// +/// ``` +/// # use juniper::format_type; +/// # +/// assert_eq!(format_type!("String", 123), "[String]!"); +/// assert_eq!(format_type!("🦀", 123), "[🦀]!"); +/// ``` #[macro_export] macro_rules! format_type { ($ty: expr, $wrapped_value: expr $(,)?) => {{ @@ -801,8 +829,9 @@ macro_rules! format_type { let mut is_null = false; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { - 2 => is_null = true, + 2 => is_null = true, // Skips writing BANG later. 3 => { + // Write OPENING_BRACKET at current_start. let mut i = 0; while i < OPENING_BRACKET.as_bytes().len() { type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; @@ -810,6 +839,7 @@ macro_rules! format_type { } current_start += i; if !is_null { + // Write BANG at current_end. i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = @@ -818,6 +848,7 @@ macro_rules! format_type { } current_end -= i; } + // Write CLOSING_BRACKET at current_end. i = 0; while i < CLOSING_BRACKET.as_bytes().len() { type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = @@ -833,6 +864,7 @@ macro_rules! format_type { current_wrap_val /= 10; } + // Writes Type at current_start. let mut i = 0; while i < ty.as_bytes().len() { type_arr[current_start + i] = ty.as_bytes()[i]; @@ -840,6 +872,7 @@ macro_rules! format_type { } i = 0; if !is_null { + // Writes BANG at current_end. while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; @@ -850,8 +883,12 @@ macro_rules! format_type { } const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. + + // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one + // after the other byte by byte. + #[allow(unsafe_code)] const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + TYPE_FORMATTED }}; } From 26ab27e3d0103a333ed054521105e97ed379e465 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 14:09:10 +0300 Subject: [PATCH 037/122] WIP (Refactoring) --- juniper_codegen/src/common/field/mod.rs | 145 ------------ juniper_codegen/src/graphql_interface/new.rs | 28 +-- juniper_codegen/src/graphql_object/mod.rs | 237 ++++++++++++++++--- 3 files changed, 216 insertions(+), 194 deletions(-) diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 1f89dd296..0437f97f3 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -441,151 +441,6 @@ impl Definition { }) } - /// TODO - #[must_use] - pub(crate) fn impl_field_meta( - &self, - impl_ty: &syn::Type, - impl_generics: &TokenStream, - where_clause: Option<&syn::WhereClause>, - scalar: &scalar::Type, - context: &syn::Type, - ) -> TokenStream { - let (name, ty) = (&self.name, self.ty.clone()); - - let arguments = self - .arguments - .as_ref() - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - MethodArgument::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - - Some(quote! {( - #name, - <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, - )}) - } - MethodArgument::Executor | MethodArgument::Context(_) => None, - }) - .collect::>(); - - quote! { - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::FieldMeta< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - > for #impl_ty - #where_clause - { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::reflection::Type = - <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::reflection::Types = - <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = - <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::reflection::Name, - ::juniper::macros::reflection::Type, - ::juniper::macros::reflection::WrappedValue, - )] = &[#(#arguments,)*]; - } - } - } - - /// TODO - #[must_use] - pub(crate) fn impl_field( - &self, - impl_ty: &syn::Type, - impl_generics: &TokenStream, - where_clause: Option<&syn::WhereClause>, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - for_async: bool, - ) -> Option { - if !for_async && self.is_async { - return None; - } - - let (name, mut res_ty, ident) = (&self.name, self.ty.clone(), &self.ident); - - let mut res = if self.is_method() { - let args = self - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, for_async)); - - let rcv = self.has_receiver.then(|| { - quote! { self, } - }); - - if trait_ty.is_some() { - quote! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - res_ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - if for_async && !self.is_async { - res = quote! { ::juniper::futures::future::ready(#res) }; - } - - let (call, trait_name) = if for_async { - let resolving_code = gen::async_resolving_code(Some(&res_ty)); - let call = quote! { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - let fut = #res; - #resolving_code - } - }; - - (call, quote! { AsyncField }) - } else { - let resolving_code = gen::sync_resolving_code(); - let call = quote! { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - let res: #res_ty = #res; - #resolving_code - } - }; - - (call, quote! { Field }) - }; - - Some(quote! { - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::#trait_name< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - > for #impl_ty - #where_clause - { - #call - } - }) - } - /// Returns generated code for the /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves /// this [GraphQL field][1] asynchronously. diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 0b21cec40..f6d10c81e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -298,16 +298,16 @@ pub(crate) struct Definition { impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { - self.generate_enum().to_tokens(into); + self.generate_enum_tokens().to_tokens(into); self.impl_graphql_interface_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_reflection().to_tokens(into); - self.impl_field_meta().to_tokens(into); - self.impl_field().to_tokens(into); - self.impl_async_field().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } @@ -316,7 +316,7 @@ impl Definition { /// /// [`implementers`]: Self::implementers #[must_use] - fn generate_enum(&self) -> TokenStream { + fn generate_enum_tokens(&self) -> TokenStream { let vis = &self.vis; let enum_ident = &self.enum_ident; let alias_ident = &self.enum_alias_ident; @@ -723,7 +723,7 @@ impl Definition { /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_reflection(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let implementers = &self.implementers; let scalar = &self.scalar; @@ -777,7 +777,7 @@ impl Definition { /// /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field_meta(&self) -> TokenStream { + fn impl_field_meta_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let context = &self.context; let scalar = &self.scalar; @@ -832,13 +832,13 @@ impl Definition { .collect() } - /// Returns generated code implementing [`Field`] or [`AsyncField`] trait - /// for this [GraphQL interface][1]. + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field(&self) -> TokenStream { + fn impl_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = self.const_scalar(); @@ -908,12 +908,12 @@ impl Definition { .collect() } - /// Returns generated code implementing [`AsyncField`] trait for this - /// [GraphQL interface][1]. + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_async_field(&self) -> TokenStream { + fn impl_async_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = self.const_scalar(); diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index c1caa2e17..48d4a9f30 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -18,7 +18,7 @@ use syn::{ use crate::{ common::{ - field, + field, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, TypeExt, @@ -361,9 +361,15 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL object][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (impl_generics, where_clause) = self.impl_generics(false); @@ -484,7 +490,10 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_as_dyn_graphql_value_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } @@ -520,6 +529,195 @@ impl Definition { } } + /// Returns generated code implementing [`FieldMeta`] traits for each field + /// of this [GraphQL object][1]. + /// + /// [`FieldMeta`]: juniper::FieldMeta + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_field_meta_tokens(&self) -> TokenStream { + let impl_ty = &self.ty; + let scalar = &self.scalar; + let context = &self.context; + let (impl_generics, where_clause) = self.impl_generics(false); + + self.fields + .iter() + .map(|field| { + let (name, ty) = (&field.name, field.ty.clone()); + + let arguments = field + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + + Some(quote! {( + #name, + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, + )}) + } + field::MethodArgument::Executor | field::MethodArgument::Context(_) => None, + }) + .collect::>(); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflection::FieldMeta< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::reflection::Type = + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, + )] = &[#(#arguments,)*]; + } + } + }) + .collect() + } + + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL object][1]. + /// + /// [`Field`]: juniper::Field + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_field_tokens(&self) -> TokenStream { + let (impl_ty, scalar) = (&self.ty, &self.scalar); + let (impl_generics, where_clause) = self.impl_generics(false); + + self.fields + .iter() + .filter_map(|field| { + if field.is_async { + return None; + } + + let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); + + let res = if field.is_method() { + let args = field + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflection::Field< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let res: #res_ty = #res; + #resolving_code + } + } + }) + }) + .collect() + } + + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL object][1]. + /// + /// [`AsyncField`]: juniper::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_async_field_tokens(&self) -> TokenStream { + let (impl_ty, scalar) = (&self.ty, &self.scalar); + let (impl_generics, where_clause) = self.impl_generics(true); + + self.fields + .iter() + .map(|field| { + let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); + + let mut res = if field.is_method() { + let args = field + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, true)); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + if !field.is_async { + res = quote! { ::juniper::futures::future::ready(#res) }; + } + + let resolving_code = gen::async_resolving_code(Some(&res_ty)); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflection::AsyncField< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let fut = #res; + #resolving_code + } + } + } + }) + .collect() + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL object][1]. /// @@ -550,20 +748,6 @@ impl Definition { }) }); - let field_meta_impls = self - .fields - .iter() - .map(|f| f.impl_field_meta(ty, &impl_generics, where_clause.as_ref(), scalar, context)); - let field_impls = self.fields.iter().filter_map(|f| { - f.impl_field( - ty, - &impl_generics, - where_clause.as_ref(), - scalar, - None, - false, - ) - }); let async_fields_err = { let names = self .fields @@ -613,10 +797,6 @@ impl Definition { #name.to_string() } } - - #(#field_meta_impls)* - - #(#field_impls)* } } @@ -645,17 +825,6 @@ impl Definition { } }); - let field_impls = self.fields.iter().filter_map(|f| { - f.impl_field( - ty, - &impl_generics, - where_clause.as_ref(), - scalar, - None, - true, - ) - }); - let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -677,8 +846,6 @@ impl Definition { } } } - - #(#field_impls)* } } From 997774656e3c916da93317e9714b3abca73f9289 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 14:48:09 +0300 Subject: [PATCH 038/122] WIP (Refactoring) --- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper/src/macros/reflection.rs | 138 +++++++++--------- juniper/src/types/scalars.rs | 10 +- juniper_codegen/src/graphql_interface/mod.rs | 13 +- juniper_codegen/src/graphql_interface/new.rs | 28 ++-- juniper_codegen/src/graphql_object/mod.rs | 25 ++-- juniper_codegen/src/graphql_union/mod.rs | 8 +- 7 files changed, 119 insertions(+), 105 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 76a1c8542..9f2f7d528 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -1901,7 +1901,7 @@ mod executor { #[graphql_object(scalar = S: ScalarValue)] impl Human { - async fn id<'e, 'r, 'a, S>(&self, executor: &'e Executor<'r, 'a, (), S>) -> &'e str + async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> &'e str where S: ScalarValue, { diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 61a92418c..d0b304d8d 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -8,57 +8,58 @@ use crate::{ Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; -/// Type alias for GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] type -/// name. +/// Type alias for [GraphQL object][1], [scalar][2] or [interface][3] type name. +/// See [`BaseType`] for more info. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Scalars /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces pub type Type = &'static str; -/// Type alias for slice of [`Type`]s. See [`BaseType`] for more info. +/// Type alias for slice of [`Type`]s. See [`BaseSubTypes`] for more info. pub type Types = &'static [Type]; -/// Type alias for GraphQL [`object`][1] or [`interface`][2] -/// [`field argument`][3] name. +/// Type alias for [GraphQL object][1] or [interface][2] [field argument][3] +/// name. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Name = &'static str; -/// Type alias for slice of [`Name`]s. +/// Type alias for slice of [`Name`]s. See [`Fields`] for more info. pub type Names = &'static [Name]; /// Type alias for value of [`WrappedType`]. pub type WrappedValue = u128; -/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and -/// [`WrappedValue`]. +/// Type alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Argument = (Name, Type, WrappedValue); -/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and +/// Type alias for [field argument][1]s [`Name`], [`Type`] and /// [`WrappedValue`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Arguments = &'static [(Name, Type, WrappedValue)]; -/// Type alias for constantly hashed [`Name`] for usage in const generics. +/// Type alias for constantly hashed [`Name`] for usage in const context. pub type FieldName = u128; -/// GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] [`Type`] name. -/// This trait is transparent to the [`Option`], [`Vec`] and other containers, -/// so to fully represent GraphQL [`object`][1] we additionally use -/// [`WrappedType`]. +/// [GraphQL object][1], [scalar][2] or [interface][3] [`Type`] name. This trait +/// is transparent to the [`Option`], [`Vec`] and other containers, so to fully +/// represent [GraphQL object][1] we additionally use [`WrappedType`]. +/// +/// Different Rust type may have the same [`NAME`]. For example [`String`] and +/// &[`str`](prim@str). /// +/// [`NAME`]: Self::NAME /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Scalars /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait BaseType { - /// [`Type`] of the GraphQL [`object`][1], [`scalar`][2] or - /// [`interface`][3]. + /// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Scalars @@ -114,13 +115,13 @@ impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } -/// GraphQL [`object`][1] [`sub-types`][2]. This trait is transparent to the +/// [GraphQL object][1] [sub-types][2]. This trait is transparent to the /// [`Option`], [`Vec`] and other containers. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC pub trait BaseSubTypes { - /// [`Types`] for the GraphQL [`object`][1]s [`sub-types`][2]. + /// [`Types`] for the [GraphQL object][1]s [sub-types][2]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC @@ -175,17 +176,17 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// To fully represent GraphQL [`object`][1] it's not enough to use [`Type`], -/// because of the [`wrapping types`][2]. To work around this we use +/// To fully represent [GraphQL object][1] it's not enough to use [`Type`], +/// because of the [wrapping types][2]. To work around this we use /// [`WrappedValue`] which is represented with [`u128`]. /// -/// - In base case of non-nullable [`object`] [`VALUE`] is `1`. +/// - In base case of non-nullable [object] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so -/// [`Option`]`<`[`object`][1]`>` has [`VALUE`] of `12`. +/// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so -/// [`Vec`]`<`[`object`][1]`>` has [`VALUE`] of `13`. +/// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. /// -/// This approach allows us to uniquely represent any GraphQL [`object`] with +/// This approach allows us to uniquely represent any [GraphQL object][1] with /// combination of [`Type`] and [`WrappedValue`] and even constantly format it /// with [`format_type`] macro. /// @@ -265,14 +266,14 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -/// GraphQL [`object`][1] or [`interface`][2] [`Field arguments`][3] [`Names`]. +/// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub trait Fields { - /// [`Names`] of the GraphQL [`object`][1] or [`interface`][2] - /// [`Field arguments`][3]. + /// [`Names`] of the [GraphQL object][1] or [interface][2] + /// [field arguments][3]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces @@ -280,51 +281,52 @@ pub trait Fields { const NAMES: Names; } -/// Stores meta information of GraphQL [`Fields`][1]: +/// Stores meta information of [GraphQL fields][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields +/// [`ARGUMENTS`]: Self::ARGUMENTS /// [`Context`]: Self::Context -/// [`TypeInfo`]: Self::TypeInfo -/// [`TYPE`]: Self::TYPE /// [`SUB_TYPES`]: Self::SUB_TYPES +/// [`TYPE`]: Self::TYPE +/// [`TypeInfo`]: Self::TypeInfo /// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE -/// [`ARGUMENTS`]: Self::ARGUMENTS +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields pub trait FieldMeta { /// [`GraphQLValue::Context`] of this [`Field`][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type Context; - /// [`GraphQLValue::TypeInfo`] of this [`Field`][1]. + /// [`GraphQLValue::TypeInfo`] of this [GraphQL field][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type TypeInfo; - /// [`Types`] of [`Field`][1]'s return type. + /// [`Types`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const TYPE: Type; - /// Sub-[`Types`] of [`Field`][1]'s return type. + /// [Sub-Types][1] of [GraphQL field's][2] return type. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: BaseSubTypes + /// [2]: https://spec.graphql.org/October2021/#sec-Language.Fields const SUB_TYPES: Types; - /// [`WrappedValue`] of [`Field`][1]'s return type. + /// [`WrappedValue`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const WRAPPED_VALUE: WrappedValue; - /// [`Field`][1]'s [`Arguments`]. + /// [GraphQL field's][1] [`Arguments`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const ARGUMENTS: Arguments; } -/// Synchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// Synchronous field of a [GraphQL object][1] or [interface][2]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces @@ -334,8 +336,9 @@ pub trait Field: FieldMeta { /// The `arguments` object contains all the specified arguments, with /// default values being substituted for the ones not provided by the query. /// - /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// The `executor` can be used to drive selections into sub-[objects][1]. /// + /// [`Value`]: crate::Value /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call( &self, @@ -350,12 +353,12 @@ pub trait Field: FieldMeta { /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait AsyncField: FieldMeta { - /// Resolves the [`Value`] of this asynchronous [`Field`]. + /// Resolves the [`Value`] of this asynchronous [`AsyncField`]. /// /// The `arguments` object contains all the specified arguments, with /// default values being substituted for the ones not provided by the query. /// - /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// The `executor` can be used to drive selections into sub-[objects][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call<'b>( @@ -385,31 +388,6 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } -/// Compares strings in `const` context. -/// -/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to -/// write custom comparison function. -/// -/// [`Eq`]: std::cmp::Eq -// TODO: Remove once `Eq` trait is allowed in `const` context. -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - /// Length of the [`format_type`] macro result __in bytes__. pub const fn type_len_with_wrapped_val(ty: Type, v: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! @@ -464,6 +442,31 @@ pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { false } +/// Compares strings in `const` context. +/// +/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to +/// write custom comparison function. +/// +/// [`Eq`]: std::cmp::Eq +// TODO: Remove once `Eq` trait is allowed in `const` context. +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + /// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This /// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. /// See [spec][1] for more info. @@ -648,7 +651,6 @@ macro_rules! assert_field_args { impl_i += 1; } - // TODO: Maybe that's ok? if !was_found { return Err(Error { cause: Cause::RequiredField, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 3fd3fcf18..87c293d7b 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, + macros::reflection, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, types::{ @@ -202,17 +203,16 @@ where }) } -impl crate::macros::reflection::WrappedType for str { +impl reflection::WrappedType for str { const VALUE: u128 = 1; } -impl crate::macros::reflection::BaseType for str { +impl reflection::BaseType for str { const NAME: &'static str = "String"; } -impl crate::macros::reflection::BaseSubTypes for str { - const NAMES: &'static [&'static str] = - &[>::NAME]; +impl reflection::BaseSubTypes for str { + const NAMES: &'static [&'static str] = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index c5c7aaca5..8b3deefaa 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -442,7 +442,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); } } @@ -716,9 +716,13 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (impl_generics, where_clause) = self.ty.impl_generics(false); @@ -937,7 +941,6 @@ impl Implementer { /// code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug)] struct EnumType { /// Name of this [`EnumType`] to generate it with. ident: syn::Ident, @@ -1441,7 +1444,6 @@ impl EnumType { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html -#[derive(Debug)] struct TraitObjectType { /// Name of this [`TraitObjectType`] to generate it with. ident: syn::Ident, @@ -1674,7 +1676,6 @@ impl ToTokens for TraitObjectType { /// type for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug)] enum Type { /// [GraphQL interface][1] type implementation as Rust enum. /// diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index f6d10c81e..a7621f7ab 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -573,15 +573,17 @@ impl Definition { let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let fields_resolvers = self.fields.iter().filter_map(|f| { - (!f.is_async).then(|| { - let name = &f.name; - quote! { - #name => { - ::juniper::macros::reflection::Field::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } + if f.is_async { + return None; + } + + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) } }) }); @@ -715,11 +717,12 @@ impl Definition { } } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`Fields`]: juniper::macros::reflection::Fields /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] @@ -772,7 +775,7 @@ impl Definition { } } - /// Returns generated code implementing [`FieldMeta`] for this + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta @@ -835,7 +838,6 @@ impl Definition { /// Returns generated code implementing [`Field`] trait for each field of /// this [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field_tokens(&self) -> TokenStream { diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 48d4a9f30..507e8a363 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -361,11 +361,12 @@ impl Definition { } } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL object][1]. + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL object][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`Fields`]: juniper::macros::reflection::Fields /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] @@ -735,15 +736,17 @@ impl Definition { let name = &self.name; let fields_resolvers = self.fields.iter().filter_map(|f| { - (!f.is_async).then(|| { - let name = &f.name; - quote! { - #name => { - ::juniper::macros::reflection::Field::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } + if f.is_async { + return None; + } + + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) } }) }); diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 14048cd0c..42eb6123b 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -611,7 +611,13 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL union][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[must_use] pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { let scalar = &self.scalar; From f5340a72f05516273e28c652a903154fa5401ca8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 15:35:16 +0300 Subject: [PATCH 039/122] WIP (Tests corrections) --- .../src/codegen/interface_attr.rs | 306 ++++-------------- .../juniper_tests/src/codegen/mod.rs | 1 - .../src/codegen/new_interface.rs | 89 ----- 3 files changed, 70 insertions(+), 326 deletions(-) delete mode 100644 integration_tests/juniper_tests/src/codegen/new_interface.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8dbc45920..75d4a024f 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -112,237 +112,19 @@ mod trivial { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - kind - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } - - #[tokio::test] - async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - possibleTypes { - kind - name - } - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn registers_itself_in_implementers() { - let schema = schema(QueryRoot::Human); - - for object in &["Human", "Droid"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - interfaces {{ - kind - name - }} - }} - }}"#, - object, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"interfaces": [ - {"kind": "INTERFACE", "name": "Character"}, - ]}}), - vec![], - )), - ); - } - } - - #[tokio::test] - async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - name - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - description - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod trivial_with_trait_imp { - use super::*; - - #[graphql_interface_new(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - home_planet: String, - } - - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Droid { fn id(&self) -> &str { &self.id } - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Droid { - id: String, - primary_function: String, - } - impl Character for Droid { - fn id(&self) -> &str { - &self.id + fn primary_function(&self) -> &str { + &self.primary_function } } @@ -554,13 +336,22 @@ mod explicit_alias { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterEnum)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterEnum)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -715,13 +506,22 @@ mod trivial_async { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1643,8 +1443,6 @@ mod argument { async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } - // #[derive(GraphQLObject)] - // #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -2439,13 +2237,22 @@ mod explicit_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2554,13 +2361,22 @@ mod custom_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2667,13 +2483,22 @@ mod explicit_generic_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue<__S>)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2780,13 +2605,22 @@ mod bounded_generic_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 1abc91e4d..6348a66c8 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_with_raw_idents; mod derive_scalar; mod impl_scalar; mod interface_attr; -mod new_interface; mod object_attr; mod object_derive; mod scalar_value_transparent; diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs deleted file mode 100644 index ca3a92922..000000000 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ /dev/null @@ -1,89 +0,0 @@ -use juniper::{ - execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, - DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, LookAheadMethods, RootNode, - ScalarValue, -}; - -// -------------------------- - -#[graphql_interface_new(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, - arg: Option, - #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; -} - -struct Human { - id: String, - home_planet: String, -} - -#[graphql_object(impl = CharacterValue<__S>)] -impl Human { - async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { - executor.look_ahead().field_name() - } - - async fn info<'b>(&'b self, _arg: Option) -> &'b str { - &self.home_planet - } -} - -struct Droid { - id: String, - primary_function: String, -} - -#[graphql_object(impl = CharacterValue<__S>)] -impl Droid { - fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { - executor.look_ahead().field_name() - } - - async fn info<'b, S: ScalarValue>( - &'b self, - _arg: Option, - _executor: &Executor<'_, '_, (), S>, - ) -> &'b str { - &self.primary_function - } -} - -// -------------- - -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} From 4c31c13cd462b841c12359f342457056799d3836 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 16:00:37 +0300 Subject: [PATCH 040/122] WIP (More tests vol. 1) --- .../src/codegen/interface_attr.rs | 294 +++++++++++++++++- .../juniper_tests/src/codegen/object_attr.rs | 2 +- 2 files changed, 294 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 75d4a024f..7afcfe047 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3,7 +3,8 @@ use juniper::{ execute, graphql_interface_new, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + GraphQLInputObject, GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, + ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -3307,3 +3308,294 @@ mod ignored_method { ); } } + +mod field_return_subtyping { + use super::*; + + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod field_return_union_subtyping { + use super::*; + + #[derive(GraphQLObject)] + struct Strength { + value: i32, + } + + #[derive(GraphQLObject)] + struct Knowledge { + value: i32, + } + + #[allow(dead_code)] + #[derive(GraphQLUnion)] + enum KeyFeature { + Strength(Strength), + Knowledge(Knowledge), + } + + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + + fn key_feature(&self) -> KeyFeature; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + key_feature: Knowledge, + } + + struct Droid { + id: String, + primary_function: String, + strength: i32, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + + fn key_feature(&self) -> Strength { + Strength { + value: self.strength, + } + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + key_feature: Knowledge { value: 10 }, + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + strength: 42, + } + .into(), + } + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth", "keyFeature": {"value": 10}}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run", "keyFeature": {"value": 42}}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } + } + }"#; + + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_val = *expected_val; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"id": expected_id, "keyFeature": {"value": expected_val}}}), + vec![] + )), + ); + } + } +} diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 9f2f7d528..9161cc55d 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'e, 'e, CustomContext, S>, + executor: &'e Executor<'_, '_, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } From 7bd09d05bf6c481da69307dbed4ed7cd192b02af Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 14:47:58 +0300 Subject: [PATCH 041/122] WIP (Move everything to the new graphql_interface macro) --- .../interface/argument_double_underscored.rs | 11 +- .../argument_double_underscored.stderr | 22 +- .../fail/interface/argument_non_input_type.rs | 8 - .../interface/argument_non_input_type.stderr | 26 +- .../interface/argument_wrong_default_array.rs | 17 +- .../argument_wrong_default_array.stderr | 20 +- ...hod_conflicts_with_external_downcast_fn.rs | 29 - ...conflicts_with_external_downcast_fn.stderr | 20 - .../downcast_method_wrong_input_args.rs | 24 - .../downcast_method_wrong_input_args.stderr | 19 - .../downcast_method_wrong_return_type.rs | 24 - .../downcast_method_wrong_return_type.stderr | 19 - .../interface/field_double_underscored.rs | 11 +- .../interface/field_double_underscored.stderr | 22 +- .../interface/field_non_output_return_type.rs | 15 +- .../field_non_output_return_type.stderr | 12 +- .../fail/interface/fields_duplicate.rs | 19 +- .../fail/interface/fields_duplicate.stderr | 33 +- .../interface/implementer_non_object_type.rs | 20 - .../implementer_non_object_type.stderr | 15 - .../implementers_duplicate_pretty.rs | 9 +- .../implementers_duplicate_pretty.stderr | 16 +- .../interface/implementers_duplicate_ugly.rs | 9 +- .../implementers_duplicate_ugly.stderr | 14 +- .../fail/interface/name_double_underscored.rs | 4 +- .../codegen_fail/fail/interface/no_fields.rs | 13 +- .../fail/interface/no_fields.stderr | 22 +- .../{wrong_item.rs => wrong_item_enum.rs} | 0 ...ong_item.stderr => wrong_item_enum.stderr} | 4 +- .../fail/interface/wrong_item_impl_block.rs | 11 + .../interface/wrong_item_impl_block.stderr | 7 + .../src/codegen/interface_attr.rs | 59 +- .../juniper_tests/src/issue_407.rs | 14 - .../juniper_tests/src/issue_798.rs | 14 - .../juniper_tests/src/issue_922.rs | 22 - .../src/executor_tests/interfaces_unions.rs | 54 +- .../src/executor_tests/introspection/mod.rs | 7 +- juniper/src/lib.rs | 5 +- juniper/src/tests/fixtures/starwars/schema.rs | 2 - juniper_codegen/src/common/field/mod.rs | 139 +- juniper_codegen/src/common/scalar.rs | 16 - juniper_codegen/src/graphql_interface/attr.rs | 571 +----- juniper_codegen/src/graphql_interface/mod.rs | 1673 ++++++----------- juniper_codegen/src/graphql_interface/new.rs | 1204 ------------ juniper_codegen/src/lib.rs | 8 - 45 files changed, 758 insertions(+), 3525 deletions(-) delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr rename integration_tests/codegen_fail/fail/interface/{wrong_item.rs => wrong_item_enum.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{wrong_item.stderr => wrong_item_enum.stderr} (80%) create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr delete mode 100644 juniper_codegen/src/graphql_interface/new.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs index d7227c381..b842c7ce4 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { fn id(&self, __num: i32) -> &str { "funA" 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 0c47e6158..a75859459 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -1,19 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/argument_double_underscored.rs:14:18 - | -14 | fn id(&self, __num: i32) -> &str { - | ^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Schema - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/argument_double_underscored.rs:4:18 + --> fail/interface/argument_double_underscored.rs:5:18 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/argument_double_underscored.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +5 | fn id(&self, __num: i32) -> &str { + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs index db6ad3002..abca82751 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs @@ -1,19 +1,11 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] pub struct ObjA { test: String, } #[graphql_interface] -impl Character for ObjA { - fn id(&self, obj: Self) -> &str { - "funA" - } -} - -#[graphql_interface(for = ObjA)] trait Character { fn id(&self, obj: ObjA) -> &str; } diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr index 8b87b4023..e20698e5b 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr @@ -1,16 +1,16 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/argument_non_input_type.rs:8:1 + | +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 + --> fail/interface/argument_non_input_type.rs:8:1 | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` | note: required by a bound in `Registry::<'r, S>::arg` --> $WORKSPACE/juniper/src/executor/mod.rs @@ -18,11 +18,3 @@ note: required by a bound in `Registry::<'r, S>::arg` | T: GraphQLType + FromInputValue, | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg` = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs index 85156a3eb..48ce93738 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs @@ -1,21 +1,8 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn wrong( - &self, - #[graphql(default = [true, false, false])] - input: [bool; 2], - ) -> bool { + fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { input[0] } } diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr index 0b149ee54..b745a82d0 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr @@ -1,11 +1,11 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:12:1 - | -12 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` - | - = help: the following implementations were found: - <[T; LANES] as From>> - <[bool; LANES] as From>> - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/argument_wrong_default_array.rs:3:1 + | +3 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | + = help: the following implementations were found: + <[T; LANES] as From>> + <[bool; LANES] as From>> + = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs deleted file mode 100644 index fdc9dfb08..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs +++ /dev/null @@ -1,29 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self, _: i32) -> &str { - "funA" - } - - fn as_obja(&self) -> Option<&ObjA> { - Some(self) - } -} - -#[graphql_interface(for = ObjA)] -#[graphql_interface(on ObjA = downcast_obja)] -trait Character { - fn id(&self, num: i32) -> &str; - - #[graphql(downcast)] - fn as_obja(&self) -> Option<&ObjA>; -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr deleted file mode 100644 index 1aede59e0..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA` - --> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:26:5 - | -26 | fn as_obja(&self) -> Option<&ObjA>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - = note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:4:18 - | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:10:6 - | -10 | impl Character for ObjA { - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs deleted file mode 100644 index 29cfc225a..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs +++ /dev/null @@ -1,24 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[graphql_interface(for = Human)] -trait Character { - fn id(&self) -> i32 { - 0 - } - - #[graphql(downcast)] - fn a(&self, ctx: &(), rand: u8) -> Option<&Human> { - None - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct Human { - id: String, -} - -#[graphql_interface] -impl Character for Human {} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr deleted file mode 100644 index ee9e48343..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context` - --> fail/interface/downcast_method_wrong_input_args.rs:10:10 - | -10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> { - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/downcast_method_wrong_input_args.rs:16:18 - | -16 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/downcast_method_wrong_input_args.rs:22:6 - | -22 | impl Character for Human {} - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs deleted file mode 100644 index 2fd26790e..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs +++ /dev/null @@ -1,24 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[graphql_interface(for = Human)] -trait Character { - fn id(&self) -> i32 { - 0 - } - - #[graphql(downcast)] - fn a(&self, ctx: &(), rand: u8) -> &Human { - unimplemented!() - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct Human { - id: String, -} - -#[graphql_interface] -impl Character for Human {} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr deleted file mode 100644 index 22aa7aba2..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only - --> fail/interface/downcast_method_wrong_return_type.rs:10:40 - | -10 | fn a(&self, ctx: &(), rand: u8) -> &Human { - | ^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/downcast_method_wrong_return_type.rs:16:18 - | -16 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/downcast_method_wrong_return_type.rs:22:6 - | -22 | impl Character for Human {} - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs index 43115d327..3096cd73a 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { fn __id(&self) -> &str { "funA" 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 2cdfafa6d..ef662d93d 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -1,19 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/field_double_underscored.rs:14:8 - | -14 | fn __id(&self) -> &str { - | ^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Schema - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/field_double_underscored.rs:4:18 + --> fail/interface/field_double_underscored.rs:5:8 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/field_double_underscored.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +5 | fn __id(&self) -> &str { + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs index a860ace35..ad802cf59 100644 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs @@ -1,24 +1,13 @@ -use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject}; +use juniper::{graphql_interface, GraphQLInputObject}; #[derive(GraphQLInputObject)] pub struct ObjB { id: i32, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} - #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self) -> ObjB { - ObjB { id: 34 } - } + fn id(&self) -> ObjB; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr index 1491540eb..5bd7c8c9c 100644 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr @@ -1,7 +1,7 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/field_non_output_return_type.rs:17:1 - | -17 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/field_non_output_return_type.rs:8:1 + | +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs index aba568b36..b69704d27 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs @@ -1,24 +1,11 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self) -> &str { - "funA" - } + fn id(&self) -> &str; #[graphql(name = "id")] - fn id2(&self) -> &str { - "funB" - } + fn id2(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr index 284a3e1d5..143f85dab 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr @@ -1,25 +1,12 @@ error: GraphQL interface must have a different name for each field - --> fail/interface/fields_duplicate.rs:13:1 - | -13 | / trait Character { -14 | | fn id(&self) -> &str { -15 | | "funA" -16 | | } -... | -21 | | } -22 | | } - | |_^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/fields_duplicate.rs:4:18 + --> fail/interface/fields_duplicate.rs:4:1 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/fields_duplicate.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +4 | / trait Character { +5 | | fn id(&self) -> &str; +6 | | +7 | | #[graphql(name = "id")] +8 | | fn id2(&self) -> &str; +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs deleted file mode 100644 index 0aba2508d..000000000 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs +++ /dev/null @@ -1,20 +0,0 @@ -use juniper::{graphql_interface, GraphQLInputObject}; - -#[derive(GraphQLInputObject)] -pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } -} - -#[graphql_interface(for = ObjA)] -trait Character { - fn id(&self) -> &str; -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr deleted file mode 100644 index 35c2dd0ca..000000000 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0277]: the trait bound `ObjA: GraphQLObject<__S>` is not satisfied - --> fail/interface/implementer_non_object_type.rs:15:1 - | -15 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied - --> fail/interface/implementer_non_object_type.rs:15:1 - | -15 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs index fbdc8f12e..889fbd016 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs @@ -3,14 +3,7 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } + id: String, } #[graphql_interface(for = [ObjA, ObjA])] diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr index 40db273e9..ce72fa2d7 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr @@ -1,17 +1,11 @@ error: duplicated attribute argument found - --> $DIR/implementers_duplicate_pretty.rs:16:34 - | -16 | #[graphql_interface(for = [ObjA, ObjA])] - | ^^^^ + --> fail/interface/implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ error[E0412]: cannot find type `CharacterValue` in this scope - --> $DIR/implementers_duplicate_pretty.rs:4:18 + --> fail/interface/implementers_duplicate_pretty.rs:4:18 | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> $DIR/implementers_duplicate_pretty.rs:10:6 - | -10 | impl Character for ObjA { - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs index 1acb56c01..a0b7c7ed0 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs @@ -3,18 +3,11 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] pub struct ObjA { - test: String, + id: String, } type ObjAlias = ObjA; -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } -} - #[graphql_interface(for = [ObjA, ObjAlias])] trait Character { fn id(&self) -> &str; diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr index d06dda364..61340f732 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr @@ -1,7 +1,7 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` - --> $DIR/implementers_duplicate_ugly.rs:18:1 +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/implementers_duplicate_ugly.rs:11:1 | -18 | #[graphql_interface(for = [ObjA, ObjAlias])] +11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here @@ -9,13 +9,13 @@ error[E0119]: conflicting implementations of trait `` for type `CharacterValue` - --> $DIR/implementers_duplicate_ugly.rs:18:1 +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/implementers_duplicate_ugly.rs:11:1 | -18 | #[graphql_interface(for = [ObjA, ObjAlias])] +11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here - | conflicting implementation for `CharacterValue` + | conflicting implementation for `CharacterValueEnum` | = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs index b233119f1..77343a973 100644 --- a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait __Character { - fn id(&self) -> &str { - "funA" - } + fn id(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/no_fields.rs index 115960359..1308cdde8 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.rs +++ b/integration_tests/codegen_fail/fail/interface/no_fields.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character {} -fn main() {} \ No newline at end of file +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/no_fields.stderr index ddab0d4cc..70ab57452 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/no_fields.stderr @@ -1,19 +1,7 @@ error: GraphQL interface must have at least one field - --> fail/interface/no_fields.rs:13:1 - | -13 | trait Character {} - | ^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/no_fields.rs:4:18 + --> fail/interface/no_fields.rs:4:1 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/no_fields.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +4 | trait Character {} + | ^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.rs b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_item.rs rename to integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/interface/wrong_item.stderr rename to integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr index a86b15c20..9c2607dd4 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr @@ -1,5 +1,5 @@ -error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only - --> $DIR/wrong_item.rs:8:1 +error: #[graphql_interface] attribute is applicable to trait definitions only + --> fail/interface/wrong_item_enum.rs:8:1 | 8 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs new file mode 100644 index 000000000..61e142410 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs @@ -0,0 +1,11 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +impl ObjA {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr new file mode 100644 index 000000000..712206c0b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr @@ -0,0 +1,7 @@ +error: #[graphql_interface] attribute is applicable to trait definitions only + --> fail/interface/wrong_item_impl_block.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 7afcfe047..c3b453804 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,10 +1,9 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface_new, graphql_object, graphql_value, graphql_vars, - DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, - ScalarValue, + execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, + EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, + GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -35,7 +34,7 @@ where mod no_implers { use super::*; - #[graphql_interface_new] + #[graphql_interface] trait Character { fn id(&self) -> &str; } @@ -101,7 +100,7 @@ mod no_implers { mod trivial { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -325,7 +324,7 @@ mod trivial { mod explicit_alias { use super::*; - #[graphql_interface_new(enum = CharacterEnum, for = [Human, Droid])] + #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -495,7 +494,7 @@ mod explicit_alias { mod trivial_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } @@ -719,7 +718,7 @@ mod trivial_async { mod explicit_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; @@ -865,7 +864,7 @@ mod fallible_field { } } - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Result<&str, CustomError>; } @@ -1023,7 +1022,7 @@ mod fallible_field { mod generic { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -1161,7 +1160,7 @@ mod generic { mod generic_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } @@ -1299,7 +1298,7 @@ mod generic_async { mod generic_lifetime_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character<'me, A> { async fn id<'a>(&'a self) -> &'a str; } @@ -1437,7 +1436,7 @@ mod generic_lifetime_async { mod argument { use super::*; - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { fn id_wide(&self, is_number: bool) -> &str; @@ -1613,7 +1612,7 @@ mod default_argument { x: i32, } - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { async fn id( &self, @@ -1747,7 +1746,7 @@ mod description_from_doc_comment { use super::*; /// Rust docs. - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { /// Rust `id` docs. /// Long. @@ -1803,7 +1802,7 @@ mod description_from_doc_comment { mod deprecation_from_attr { use super::*; - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { fn id(&self) -> &str; @@ -1941,7 +1940,7 @@ mod explicit_name_description_and_deprecation { use super::*; /// Rust docs. - #[graphql_interface_new(name = "MyChar", desc = "My character.", for = Human)] + #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] trait Character { /// Rust `id` docs. #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] @@ -2127,7 +2126,7 @@ mod explicit_name_description_and_deprecation { mod renamed_all_fields_and_args { use super::*; - #[graphql_interface_new(rename_all = "none", for = Human)] + #[graphql_interface(rename_all = "none", for = Human)] trait Character { fn id(&self) -> &str { "human-32" @@ -2225,8 +2224,8 @@ mod renamed_all_fields_and_args { mod explicit_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid])] - #[graphql_interface_new(scalar = DefaultScalarValue)] + #[graphql_interface(for = [Human, Droid])] + #[graphql_interface(scalar = DefaultScalarValue)] trait Character { fn id(&self) -> &str; } @@ -2350,7 +2349,7 @@ mod custom_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = MyScalarValue)] + #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] trait Character { async fn id(&self) -> &str; } @@ -2472,7 +2471,7 @@ mod custom_scalar { mod explicit_generic_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = S)] + #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { fn id(&self) -> FieldResult<&str, S>; } @@ -2594,7 +2593,7 @@ mod explicit_generic_scalar { mod bounded_generic_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] trait Character { fn id(&self) -> &str; } @@ -2720,7 +2719,7 @@ mod explicit_custom_context { impl juniper::Context for CustomContext {} - #[graphql_interface_new(for = [Human, Droid], context = CustomContext)] + #[graphql_interface(for = [Human, Droid], context = CustomContext)] trait Character { async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; @@ -2886,7 +2885,7 @@ mod inferred_custom_context_from_field { impl juniper::Context for CustomContext {} - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id<'a>(&self, context: &'a CustomContext) -> &'a str; @@ -3036,7 +3035,7 @@ mod executor { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = S)] + #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where @@ -3216,7 +3215,7 @@ mod executor { mod ignored_method { use super::*; - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { fn id(&self) -> &str; @@ -3312,7 +3311,7 @@ mod ignored_method { mod field_return_subtyping { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Option; } @@ -3451,7 +3450,7 @@ mod field_return_union_subtyping { Knowledge(Knowledge), } - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Option; diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/integration_tests/juniper_tests/src/issue_407.rs index 51bf4211a..245398c36 100644 --- a/integration_tests/juniper_tests/src/issue_407.rs +++ b/integration_tests/juniper_tests/src/issue_407.rs @@ -20,13 +20,6 @@ struct Human { name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -34,13 +27,6 @@ struct Droid { serial_number: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - #[graphql_object] impl Query { fn characters() -> Vec { diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/integration_tests/juniper_tests/src/issue_798.rs index fc23bbd6e..fed322fc3 100644 --- a/integration_tests/juniper_tests/src/issue_798.rs +++ b/integration_tests/juniper_tests/src/issue_798.rs @@ -18,13 +18,6 @@ struct Human { home_planet: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -32,13 +25,6 @@ struct Droid { primary_function: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLUnion)] enum FieldResult { Human(Human), diff --git a/integration_tests/juniper_tests/src/issue_922.rs b/integration_tests/juniper_tests/src/issue_922.rs index 60418de4a..fd28b45f9 100644 --- a/integration_tests/juniper_tests/src/issue_922.rs +++ b/integration_tests/juniper_tests/src/issue_922.rs @@ -38,17 +38,6 @@ struct Human { pub name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> i32 { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -56,17 +45,6 @@ struct Droid { pub name: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> i32 { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } -} - type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 02787ce58..1044baafd 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -3,72 +3,28 @@ mod interface { graphql_interface, graphql_object, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, + GraphQLObject, }; #[graphql_interface(for = [Cat, Dog])] trait Pet { fn name(&self) -> &str; - - #[graphql(downcast)] - fn as_dog(&self) -> Option<&Dog> { - None - } - #[graphql(downcast)] - fn as_cat(&self) -> Option<&Cat> { - None - } } + #[derive(GraphQLObject)] + #[graphql(impl = PetValue)] struct Dog { name: String, woofs: bool, } - #[graphql_interface] - impl Pet for Dog { - fn name(&self) -> &str { - &self.name - } - fn as_dog(&self) -> Option<&Dog> { - Some(self) - } - } - - #[graphql_object(impl = PetValue)] - impl Dog { - fn name(&self) -> &str { - &self.name - } - fn woofs(&self) -> bool { - self.woofs - } - } - + #[derive(GraphQLObject)] + #[graphql(impl = PetValue)] struct Cat { name: String, meows: bool, } - #[graphql_interface] - impl Pet for Cat { - fn name(&self) -> &str { - &self.name - } - fn as_cat(&self) -> Option<&Cat> { - Some(self) - } - } - - #[graphql_object(impl = PetValue)] - impl Cat { - fn name(&self) -> &str { - &self.name - } - fn meows(&self) -> bool { - self.meows - } - } - struct Schema { pets: Vec, } diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index a14a0f294..a5ec22cbd 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -43,9 +43,7 @@ impl GraphQLScalar for Scalar { #[graphql_interface(name = "SampleInterface", for = Root)] trait Interface { /// A sample field in the interface - fn sample_enum(&self) -> Sample { - Sample::One - } + fn sample_enum(&self) -> Sample; } struct Root; @@ -66,9 +64,6 @@ impl Root { } } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Interface for Root {} - #[tokio::test] async fn test_execution() { let doc = r#" diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index c4ce64163..406879726 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -114,9 +114,8 @@ 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_interface, graphql_interface_new, graphql_object, graphql_scalar, graphql_subscription, - graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, - GraphQLUnion, + graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, + GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index d5eebbe5c..237dc6f84 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -136,7 +136,6 @@ impl Human { } } -#[graphql_interface] impl Character for Human { fn id(&self) -> &str { &self.id @@ -223,7 +222,6 @@ impl Droid { } } -#[graphql_interface] impl Character for Droid { fn id(&self) -> &str { &self.id diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 0437f97f3..4272bffa0 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -16,7 +16,6 @@ use syn::{ use crate::{ common::{ - gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -65,20 +64,6 @@ pub(crate) struct Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) ignore: Option>, - - /// Explicitly specified marker indicating that this trait method doesn't - /// represent a [GraphQL field][1], but is a downcasting function into the - /// [GraphQL object][2] implementer type returned by this trait method. - /// - /// Once this marker is specified, the [GraphQL object][2] implementer type - /// cannot be downcast via another trait method or external downcasting - /// function. - /// - /// Omit using this field if you're generating code for [GraphQL object][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - pub(crate) downcast: Option>, } impl Parse for Attr { @@ -119,10 +104,6 @@ impl Parse for Attr { .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, - "downcast" => out - .downcast - .replace(SpanContainer::new(ident.span(), None, ident.clone())) - .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } @@ -142,7 +123,6 @@ impl Attr { description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), - downcast: try_merge_opt!(downcast: self, another), }) } @@ -156,11 +136,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if let Some(ignore) = &attr.ignore { - if attr.name.is_some() - || attr.description.is_some() - || attr.deprecated.is_some() - || attr.downcast.is_some() - { + if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() { return Err(syn::Error::new( ignore.span(), "`ignore` attribute argument is not composable with any other arguments", @@ -168,19 +144,6 @@ impl Attr { } } - if let Some(downcast) = &attr.downcast { - if attr.name.is_some() - || attr.description.is_some() - || attr.deprecated.is_some() - || attr.ignore.is_some() - { - return Err(syn::Error::new( - downcast.span(), - "`downcast` attribute argument is not composable with any other arguments", - )); - } - } - if attr.description.is_none() { attr.description = get_doc_comment(attrs).map(|sc| { let span = sc.span_ident(); @@ -390,106 +353,6 @@ impl Definition { } } - /// Returns generated code for the [`GraphQLValue::resolve_field`][0] - /// method, which resolves this [GraphQL field][1] synchronously. - /// - /// Returns [`None`] if this [`Definition::is_async`]. - /// - /// [0]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_tokens( - &self, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - ) -> Option { - if self.is_async { - return None; - } - - let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); - - let res = if self.is_method() { - let args = self - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, false)); - - let rcv = self.has_receiver.then(|| { - quote! { self, } - }); - - if trait_ty.is_some() { - quote! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - - let resolving_code = gen::sync_resolving_code(); - - Some(quote! { - #name => { - let res: #ty = #res; - #resolving_code - } - }) - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves - /// this [GraphQL field][1] asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_field_async - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_async_tokens( - &self, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - ) -> TokenStream { - let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); - - let mut fut = if self.is_method() { - let args = self - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, true)); - - let rcv = self.has_receiver.then(|| { - quote! { self, } - }); - - if trait_ty.is_some() { - quote! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - if !self.is_async { - fut = quote! { ::juniper::futures::future::ready(#fut) }; - } - - let resolving_code = gen::async_resolving_code(Some(&ty)); - - quote! { - #name => { - let fut = #fut; - #resolving_code - } - } - } - /// Returns generated code for the /// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which /// resolves this [GraphQL field][1] as [subscription][2]. diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs index 1569a5391..8f11833de 100644 --- a/juniper_codegen/src/common/scalar.rs +++ b/juniper_codegen/src/common/scalar.rs @@ -90,12 +90,6 @@ impl Type { matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_)) } - /// Indicates whether this [`Type`] is [`Type::ExplicitGeneric`]. - #[must_use] - pub(crate) fn is_explicit_generic(&self) -> bool { - matches!(self, Self::ExplicitGeneric(_)) - } - /// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`]. #[must_use] pub(crate) fn is_implicit_generic(&self) -> bool { @@ -123,16 +117,6 @@ impl Type { } } - /// Returns a type parameter identifier that suits this [`Type`]. - #[must_use] - pub(crate) fn generic_ty(&self) -> syn::Type { - match self { - Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, - Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(), - Self::ImplicitGeneric(None) | Self::Concrete(_) => parse_quote! { __S }, - } - } - /// Returns a default [`ScalarValue`] type that is compatible with this [`Type`]. /// /// [`ScalarValue`]: juniper::ScalarValue diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 511a2d175..47a3fc3a4 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::{format_ident, quote}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -16,10 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{ - inject_async_trait, new, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, - TraitAttr, TraitObjectType, Type, -}; +use super::{inject_async_trait, Definition, TraitAttr}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -30,33 +27,11 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result(body) { - if ast.trait_.is_some() { - let impl_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); - return expand_on_impl(impl_attrs, ast); - } } Err(syn::Error::new( Span::call_site(), - "#[graphql_interface] attribute is applicable to trait definitions and trait \ - implementations only", - )) -} - -/// Expands `#[graphql_interface_new]` macro into generated code. -pub fn expand_new(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body.clone()) { - let trait_attrs = parse::attr::unite(("graphql_interface_new", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_interface_new", ast.attrs); - return expand_on_trait_new(trait_attrs, ast); - } - - Err(syn::Error::new( - Span::call_site(), - "#[graphql_interface_new] attribute is applicable to trait definitions and trait \ - implementations only", + "#[graphql_interface] attribute is applicable to trait definitions only", )) } @@ -86,27 +61,6 @@ fn expand_on_trait( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - let mut implementers: Vec<_> = attr - .implementers - .iter() - .map(|ty| Implementer { - ty: ty.as_ref().clone(), - downcast: None, - context: None, - scalar: scalar.clone(), - }) - .collect(); - for (ty, downcast) in &attr.external_downcasts { - match implementers.iter_mut().find(|i| &i.ty == ty) { - Some(impler) => { - impler.downcast = Some(ImplementerDowncast::External { - path: downcast.inner().clone(), - }); - } - None => err_only_implementer_downcast(&downcast.span_joined()), - } - } - proc_macro_error::abort_if_dirty(); let renaming = attr @@ -118,191 +72,8 @@ fn expand_on_trait( let mut fields = vec![]; for item in &mut ast.items { if let syn::TraitItem::Method(m) = item { - match TraitMethod::parse(m, &renaming) { - Some(TraitMethod::Field(f)) => fields.push(f), - Some(TraitMethod::Downcast(d)) => { - match implementers.iter_mut().find(|i| i.ty == d.ty) { - Some(impler) => { - if let Some(external) = &impler.downcast { - err_duplicate_downcast(m, external, &impler.ty); - } else { - impler.downcast = d.downcast; - impler.context = d.context; - } - } - None => err_only_implementer_downcast(&m.sig), - } - } - _ => {} - } - } - } - - proc_macro_error::abort_if_dirty(); - - if fields.is_empty() { - ERR.emit_custom(trait_span, "must have at least one field"); - } - if !field::all_different(&fields) { - ERR.emit_custom(trait_span, "must have a different name for each field"); - } - - proc_macro_error::abort_if_dirty(); - - let context = attr - .context - .as_deref() - .cloned() - .or_else(|| { - fields.iter().find_map(|f| { - f.arguments.as_ref().and_then(|f| { - f.iter() - .find_map(field::MethodArgument::context_ty) - .cloned() - }) - }) - }) - .or_else(|| { - implementers - .iter() - .find_map(|impler| impler.context.as_ref()) - .cloned() - }) - .unwrap_or_else(|| parse_quote! { () }); - - let is_trait_object = attr.r#dyn.is_some(); - - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - let has_default_async_methods = ast.items.iter().any(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), - _ => false, - }); - - let ty = if is_trait_object { - Type::TraitObject(Box::new(TraitObjectType::new( - &ast, - &attr, - scalar.clone(), - context.clone(), - ))) - } else { - Type::Enum(Box::new(EnumType::new( - &ast, - &attr, - &implementers, - scalar.clone(), - ))) - }; - - let generated_code = Definition { - ty, - - name, - description: attr.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.generic_ty(); - if !scalar.is_explicit_generic() { - let default_ty = scalar.default_ty(); - ast.generics - .params - .push(parse_quote! { #scalar_ty = #default_ty }); - } - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); - ast.supertraits - .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); - } - - if is_async_trait { - if has_default_async_methods { - // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits - ast.supertraits.push(parse_quote! { Sync }); - } - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - } - - Ok(quote! { - #ast - #generated_code - }) -} - -/// Expands `#[graphql_interface_new]` macro placed on trait definition. -fn expand_on_trait_new( - attrs: Vec, - mut ast: syn::ItemTrait, -) -> syn::Result { - let attr = new::TraitAttr::from_attrs("graphql_interface_new", &attrs)?; - - let trait_ident = &ast.ident; - let trait_span = ast.span(); - - let name = attr - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| trait_ident.unraw().to_string()); - if !attr.is_internal && name.starts_with("__") { - ERR.no_double_underscore( - attr.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| trait_ident.span()), - ); - } - - let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - - proc_macro_error::abort_if_dirty(); - - let renaming = attr - .rename_fields - .as_deref() - .copied() - .unwrap_or(RenameRule::CamelCase); - - let mut fields = vec![]; - for item in &mut ast.items { - if let syn::TraitItem::Method(m) = item { - match TraitMethod::parse(m, &renaming) { - Some(TraitMethod::Field(f)) => fields.push(f), - Some(TraitMethod::Downcast(_d)) => { - unimplemented!(); - } - _ => {} + if let Some(f) = parse_field(m, &renaming) { + fields.push(f) } } } @@ -358,7 +129,7 @@ fn expand_on_trait_new( ); let description = attr.description.as_ref().map(|c| c.inner().clone()); - let generated_code = new::Definition { + let generated_code = Definition { trait_generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, @@ -399,233 +170,96 @@ fn expand_on_trait_new( }) } -/// Expands `#[graphql_interface]` macro placed on a trait implementation block. -fn expand_on_impl(attrs: Vec, mut ast: syn::ItemImpl) -> syn::Result { - let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?; - - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::ImplItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - - let is_trait_object = attr.r#dyn.is_some(); - - if is_trait_object { - let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - - ast.attrs.push(parse_quote! { - #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - }); +/// Parses [`field::Definition`] from the given trait method definition. +/// +/// Returns [`None`] if parsing fails, or the method field is ignored. +#[must_use] +fn parse_field( + method: &mut syn::TraitItemMethod, + renaming: &RenameRule, +) -> Option { + let method_ident = &method.sig.ident; + let method_attrs = method.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + method.attrs = mem::take(&mut method.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); - if scalar.is_implicit_generic() { - ast.generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); - } + let attr = field::Attr::from_attrs("graphql", &method_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; - if !scalar.is_explicit_generic() { - let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); - let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); - } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { #scalar }); - } - } + if attr.ignore.is_some() { + return None; } - if is_async_trait { - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::ImplItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| method_ident.span()), ); + return None; } - Ok(quote! { #ast }) -} - -/// Representation of parsed Rust trait method for `#[graphql_interface]` macro code generation. -enum TraitMethod { - /// Method represents a [`Field`] of [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Field(field::Definition), - - /// Method represents a custom downcasting function into the [`Implementer`] of - /// [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Downcast(Box), -} - -impl TraitMethod { - /// Parses this [`TraitMethod`] from the given trait method definition. - /// - /// Returns [`None`] if the trait method marked with `#[graphql(ignore)]` attribute, - /// or parsing fails. - #[must_use] - fn parse(method: &mut syn::TraitItemMethod, renaming: &RenameRule) -> Option { - let method_attrs = method.attrs.clone(); - - // Remove repeated attributes from the method, to omit incorrect expansion. - method.attrs = mem::take(&mut method.attrs) - .into_iter() - .filter(|attr| !path_eq_single(&attr.path, "graphql")) - .collect(); - - let attr = field::Attr::from_attrs("graphql", &method_attrs) - .map_err(|e| proc_macro_error::emit_error!(e)) - .ok()?; - - if attr.ignore.is_some() { - return None; - } - - if attr.downcast.is_some() { - return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?))); - } - - Some(Self::Field(Self::parse_field(method, attr, renaming)?)) - } - - /// Parses [`TraitMethod::Downcast`] from the given trait method definition. - /// - /// Returns [`None`] if parsing fails. - #[must_use] - fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { - let method_ident = &method.sig.ident; - - let ty = parse::downcaster::output_type(&method.sig.output) - .map_err(|span| { - ERR.emit_custom( - span, - "expects trait method return type to be `Option<&ImplementerType>` only", - ) - }) - .ok()?; - let context_ty = parse::downcaster::context_ty(&method.sig) - .map_err(|span| { - ERR.emit_custom( - span, - "expects trait method to accept `&self` only and, optionally, `&Context`", - ) - }) - .ok()?; - if let Some(is_async) = &method.sig.asyncness { - ERR.emit_custom( - is_async.span(), - "async downcast to interface implementer is not supported", - ); - return None; - } - - let downcast = ImplementerDowncast::Method { - name: method_ident.clone(), - with_context: context_ty.is_some(), - }; - - Some(Implementer { - ty, - downcast: Some(downcast), - context: context_ty, - scalar: scalar::Type::ImplicitGeneric(None), - }) - } - - /// Parses [`TraitMethod::Field`] from the given trait method definition. - /// - /// Returns [`None`] if parsing fails. - #[must_use] - fn parse_field( - method: &mut syn::TraitItemMethod, - attr: field::Attr, - renaming: &RenameRule, - ) -> Option { - let method_ident = &method.sig.ident; - - let name = attr - .name - .as_ref() - .map(|m| m.as_ref().value()) - .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); - if name.starts_with("__") { - ERR.no_double_underscore( - attr.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| method_ident.span()), - ); - return None; + let arguments = { + if method.sig.inputs.is_empty() { + return err_no_method_receiver(&method.sig.inputs); } - - let arguments = { - if method.sig.inputs.is_empty() { - return err_no_method_receiver(&method.sig.inputs); - } - let mut args_iter = method.sig.inputs.iter_mut(); - match args_iter.next().unwrap() { - syn::FnArg::Receiver(rcv) => { - if rcv.reference.is_none() || rcv.mutability.is_some() { - return err_invalid_method_receiver(rcv); - } + let mut args_iter = method.sig.inputs.iter_mut(); + match args_iter.next().unwrap() { + syn::FnArg::Receiver(rcv) => { + if rcv.reference.is_none() || rcv.mutability.is_some() { + return err_invalid_method_receiver(rcv); } - syn::FnArg::Typed(arg) => { - if let syn::Pat::Ident(a) = &*arg.pat { - if a.ident.to_string().as_str() != "self" { - return err_invalid_method_receiver(arg); - } + } + syn::FnArg::Typed(arg) => { + if let syn::Pat::Ident(a) = &*arg.pat { + if a.ident.to_string().as_str() != "self" { + return err_invalid_method_receiver(arg); } - return err_no_method_receiver(arg); } - }; - args_iter - .filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), - }) - .collect() + return err_no_method_receiver(arg); + } }; + args_iter + .filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), + }) + .collect() + }; - let mut ty = match &method.sig.output { - syn::ReturnType::Default => parse_quote! { () }, - syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), - }; - ty.lifetimes_anonymized(); - - let description = attr.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = attr - .deprecated - .as_deref() - .map(|d| d.as_ref().map(syn::LitStr::value)); - - Some(field::Definition { - name, - ty, - description, - deprecated, - ident: method_ident.clone(), - arguments: Some(arguments), - has_receiver: method.sig.receiver().is_some(), - is_async: method.sig.asyncness.is_some(), - }) - } + let mut ty = match &method.sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), + }; + ty.lifetimes_anonymized(); + + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr + .deprecated + .as_deref() + .map(|d| d.as_ref().map(syn::LitStr::value)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: method_ident.clone(), + arguments: Some(arguments), + has_receiver: method.sig.receiver().is_some(), + is_async: method.sig.asyncness.is_some(), + }) } /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given @@ -649,42 +283,3 @@ fn err_no_method_receiver(span: &S) -> Option { ); None } - -/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given -/// `span`. -fn err_only_implementer_downcast(span: &S) { - ERR.emit_custom( - span.span(), - "downcasting is possible only to interface implementers", - ); -} - -/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and -/// `external` [`ImplementerDowncast`] function. -fn err_duplicate_downcast( - method: &syn::TraitItemMethod, - external: &ImplementerDowncast, - impler_ty: &syn::Type, -) { - let external = match external { - ImplementerDowncast::External { path } => path, - _ => unreachable!(), - }; - - ERR.custom( - method.span(), - format!( - "trait method `{}` conflicts with the external downcast function \ - `{}` declared on the trait to downcast into the implementer type \ - `{}`", - method.sig.ident, - external.to_token_stream(), - impler_ty.to_token_stream(), - ), - ) - .note(String::from( - "use `#[graphql(ignore)]` attribute argument to ignore this trait \ - method for interface implementers downcasting", - )) - .emit() -} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 8b3deefaa..6c67f465d 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,21 +3,19 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; -pub mod new; -use std::{ - collections::{HashMap, HashSet}, - convert::TryInto as _, -}; +use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, + punctuated::Punctuated, spanned::Spanned as _, token, + visit::Visit, }; use crate::{ @@ -53,32 +51,21 @@ struct TraitAttr { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions description: Option>, - /// Explicitly specified identifier of the enum Rust type behind the trait, - /// being an actual implementation of a [GraphQL interface][1] type. + /// Explicitly specified identifier of the type alias of Rust enum type + /// behind the trait, being an actual implementation of a + /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces r#enum: Option>, - /// Explicitly specified identifier of the Rust type alias of the - /// [trait object][2], being an actual implementation of a - /// [GraphQL interface][1] type. - /// - /// Effectively makes code generation to use a [trait object][2] as a - /// [GraphQL interface][1] type rather than an enum. If [`None`], then enum - /// is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - r#dyn: Option>, - /// Explicitly specified Rust types of [GraphQL objects][2] implementing /// this [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - implementers: HashSet>, + implementers: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -110,22 +97,6 @@ struct TraitAttr { /// it contains async methods. asyncness: Option>, - /// Explicitly specified external downcasting functions for - /// [GraphQL interface][1] implementers. - /// - /// If [`None`], then macro will downcast to the implementers via enum - /// dispatch or dynamic dispatch (if the one is chosen). That's why - /// specifying an external resolver function has sense, when some custom - /// [interface][1] implementer resolving logic is involved. - /// - /// Once the downcasting function is specified for some [GraphQL object][2] - /// implementer type, it cannot be downcast another such function or trait - /// method marked with a [`MethodMeta::downcast`] marker. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - external_downcasts: HashMap>, - /// Explicitly specified [`RenameRule`] for all fields of this /// [GraphQL interface][1] type. /// @@ -184,7 +155,7 @@ impl Parse for TraitAttr { "for" | "implementers" => { input.parse::()?; for impler in input.parse_maybe_wrapped_and_punctuated::< - syn::Type, token::Bracket, token::Comma, + syn::TypePath, token::Bracket, token::Comma, >()? { let impler_span = impler.span(); out @@ -193,13 +164,6 @@ impl Parse for TraitAttr { .none_or_else(|_| err::dup_arg(impler_span))?; } } - "dyn" => { - input.parse::()?; - let alias = input.parse::()?; - out.r#dyn - .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) - .none_or_else(|_| err::dup_arg(&ident))? - } "enum" => { input.parse::()?; let alias = input.parse::()?; @@ -213,16 +177,6 @@ impl Parse for TraitAttr { .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } - "on" => { - let ty = input.parse::()?; - input.parse::()?; - let dwncst = input.parse::()?; - let dwncst_spanned = SpanContainer::new(ident.span(), Some(ty.span()), dwncst); - let dwncst_span = dwncst_spanned.span_joined(); - out.external_downcasts - .insert(ty, dwncst_spanned) - .none_or_else(|_| err::dup_arg(dwncst_span))? - } "rename_all" => { input.parse::()?; let val = input.parse::()?; @@ -257,12 +211,8 @@ impl TraitAttr { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), - r#dyn: try_merge_opt!(r#dyn: self, another), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), - external_downcasts: try_merge_hashmap!( - external_downcasts: self, another => span_joined - ), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -275,15 +225,6 @@ impl TraitAttr { .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - if let Some(as_dyn) = &attr.r#dyn { - if attr.r#enum.is_some() { - return Err(syn::Error::new( - as_dyn.span(), - "`dyn` attribute argument is not composable with `enum` attribute argument", - )); - } - } - if attr.description.is_none() { attr.description = get_doc_comment(attrs); } @@ -292,109 +233,32 @@ impl TraitAttr { } } -/// Available arguments behind `#[graphql_interface]` attribute placed on a -/// trait implementation block, when generating code for [GraphQL interface][1] -/// type. +/// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug, Default)] -struct ImplAttr { - /// Explicitly specified type (or type parameter with its bounds) of - /// [`ScalarValue`] to implementing the [GraphQL interface][1] type with. - /// - /// If absent, then generated code will be generic over any [`ScalarValue`] - /// type, which, in turn, requires all [interface][1] implementers to be - /// generic over any [`ScalarValue`] type too. That's why this type should - /// be specified only if the implementer itself implements [`GraphQLType`] - /// in a non-generic way over [`ScalarValue`] type. +struct Definition { + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - scalar: Option>, + trait_generics: syn::Generics, - /// 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. + /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. /// - /// This marker is especially useful when Rust trait contains async default - /// methods, while the implementation block doesn't. - asyncness: Option>, + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + vis: syn::Visibility, - /// 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). + /// Name of the generic enum describing all [`implementers`]. It's generic + /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. /// - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - r#dyn: Option>, -} - -impl Parse for ImplAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "dyn" => { - let span = ident.span(); - out.r#dyn - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - "async" => { - let span = ident.span(); - out.asyncness - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - Ok(out) - } -} - -impl ImplAttr { - /// Tries to merge two [`ImplAttr`]s into a single one, reporting about - /// duplicates, if any. - fn try_merge(self, mut another: Self) -> syn::Result { - Ok(Self { - scalar: try_merge_opt!(scalar: self, another), - r#dyn: try_merge_opt!(r#dyn: self, another), - asyncness: try_merge_opt!(asyncness: self, another), - }) - } - - /// Parses [`ImplAttr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a trait implementation block. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) - } -} + /// [`implementers`]: Self::implementers + /// [`Debug`]: std::fmt::Debug + enum_ident: syn::Ident, -/// Definition of [GraphQL interface][1] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct Definition { - /// Rust type that this [GraphQL interface][1] is represented with. + /// Name of the type alias for [`enum_ident`] with [`implementers`]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ty: Type, + /// [`enum_ident`]: Self::enum_ident + /// [`implementers`]: Self::implementers + enum_alias_ident: syn::Ident, /// Name of this [GraphQL interface][1] in GraphQL schema. /// @@ -431,22 +295,142 @@ struct Definition { /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + implementers: Vec, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { - self.ty.to_token_stream().to_tokens(into); + self.generate_enum_tokens().to_tokens(into); self.impl_graphql_interface_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } impl Definition { + /// Generates enum describing all [`implementers`]. + /// + /// [`implementers`]: Self::implementers + #[must_use] + fn generate_enum_tokens(&self) -> TokenStream { + let vis = &self.vis; + let enum_ident = &self.enum_ident; + let alias_ident = &self.enum_alias_ident; + + let variant_gens_pars = self + .implementers + .iter() + .enumerate() + .map::(|(id, _)| { + let par = format_ident!("__I{}", id); + parse_quote!( #par ) + }); + + let variants_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); + + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); + + let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + .params + .clone() + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let enum_gens = { + let mut enum_gens = trait_gens.clone(); + enum_gens.params = trait_gens_lifetimes.clone(); + enum_gens.params.extend(variant_gens_pars.clone()); + enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens + }; + + let enum_alias_gens = { + let mut enum_alias_gens = trait_gens.clone(); + enum_alias_gens.move_bounds_to_where_clause(); + enum_alias_gens + }; + + let enum_to_alias_gens = { + trait_gens_lifetimes + .into_iter() + .map(|par| match par { + syn::GenericParam::Lifetime(def) => { + let lifetime = &def.lifetime; + quote! { #lifetime } + } + rest => quote! { #rest }, + }) + .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(trait_gens_tys.into_iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })) + }; + + let phantom_variant = self.has_phantom_variant().then(|| { + let phantom_params = trait_gens.params.iter().filter_map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => return None, + }; + Some(quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + }) + }); + quote! { __Phantom(#(#phantom_params),*) } + }); + + let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( + |(ty, ident)| { + quote! { + impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens + #trait_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) + } + } + } + }, + ); + + quote! { + #[derive(Clone, Copy, Debug)] + #vis enum #enum_ident#enum_gens { + #(#variants_idents(#variant_gens_pars),)* + #phantom_variant + } + + #vis type #alias_ident#enum_alias_gens = + #enum_ident<#(#enum_to_alias_gens),*>; + + #(#from_impls)* + } + } + /// Returns generated code implementing [`GraphQLInterface`] trait for this /// [GraphQL interface][1]. /// @@ -454,19 +438,23 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let gens = self.impl_generics(false); + let (impl_generics, _, where_clause) = gens.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let impler_tys = &self.implementers; let all_implers_unique = (impler_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for + #ty#ty_generics + #where_clause { fn mark() { #all_implers_unique @@ -483,21 +471,25 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter().map(|impler| &impler.ty); + let impler_tys = self.implementers.iter(); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for + #ty#ty_generics + #where_clause { fn mark() { #( #fields_marks )* @@ -514,10 +506,12 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let name = &self.name; let description = self @@ -526,7 +520,7 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let mut impler_tys = self.implementers.clone(); impler_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) @@ -536,7 +530,9 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLType<#scalar> + for #ty#ty_generics + #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -554,7 +550,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_interface_type::<#ty>(info, &fields) + registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description .into_meta() } @@ -569,18 +565,30 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let context = &self.context; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); - let ty_name = ty.to_string(); - let trait_ty = self.ty.trait_ty(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(scalar, Some(&trait_ty))); + let fields_resolvers = self.fields.iter().filter_map(|f| { + if f.is_async { + return None; + } + + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) + } + }) + }); let async_fields_err = { let names = self .fields @@ -589,29 +597,22 @@ impl Definition { .collect::>(); (!names.is_empty()).then(|| { field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, &ty_name, + &names, scalar, trait_name, ) }) }; let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - let custom_downcast_checks = self - .implementers - .iter() - .filter_map(|i| i.method_concrete_type_name_tokens(&trait_ty)); - let regular_downcast_check = self.ty.method_concrete_type_name_tokens(); + let downcast_check = self.method_concrete_type_name_tokens(); - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.method_resolve_into_type_tokens(&trait_ty)); - let regular_downcast = self.ty.method_resolve_into_type_tokens(); + let downcast = self.method_resolve_into_type_tokens(); quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics + #where_clause { type Context = #context; type TypeInfo = (); @@ -639,8 +640,7 @@ impl Definition { context: &Self::Context, info: &Self::TypeInfo, ) -> String { - #( #custom_downcast_checks )* - #regular_downcast_check + #downcast_check } fn resolve_into_type( @@ -650,8 +650,7 @@ impl Definition { _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - #( #custom_downcasts )* - #regular_downcast + #downcast } } } @@ -664,30 +663,35 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(true); - let ty = self.ty.ty_tokens(); - let ty_name = ty.to_string(); - let trait_ty = self.ty.trait_ty(); + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self - .fields - .iter() - .map(|f| f.method_resolve_field_async_tokens(scalar, Some(&trait_ty))); + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::reflection::AsyncField::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.method_resolve_into_type_async_tokens(&trait_ty)); - let regular_downcast = self.ty.method_resolve_into_type_async_tokens(); + let downcast = self.method_resolve_into_type_async_tokens(); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics + #where_clause { fn resolve_field_async<'b>( &'b self, @@ -709,670 +713,334 @@ impl Definition { _: Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - #( #custom_downcasts )* - #regular_downcast + #downcast } } } } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. /// - /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`Fields`]: juniper::macros::reflection::Fields + /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let implementers = &self.implementers; let scalar = &self.scalar; let name = &self.name; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); - let implementors = self.implementers.iter().map(|i| &i.ty); + let fields = self.fields.iter().map(|f| &f.name); + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); quote! { - impl #impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> + for #ty#ty_generics #where_clause { const NAME: ::juniper::macros::reflection::Type = #name; } - impl #impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> + for #ty#ty_generics #where_clause { const NAMES: ::juniper::macros::reflection::Types = &[ >::NAME, - #(<#implementors as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* + #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } - impl #impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> + for #ty#ty_generics #where_clause { const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; + } } } -} -/// Representation of custom downcast into an [`Implementer`] from a -/// [GraphQL interface][1] type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Clone, Debug)] -enum ImplementerDowncast { - /// Downcast is performed via a method of trait describing a + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// + /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Method { - /// Name of trait method which performs this [`ImplementerDowncast`]. - name: syn::Ident, - - /// Indicator whether the trait method accepts a [`Context`] as its - /// second argument. - /// - /// [`Context`]: juniper::Context - with_context: bool, - }, + fn impl_field_meta_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let context = &self.context; + let scalar = &self.scalar; - /// Downcast is performed via some external function. - External { - /// Path of the external function to be called with. - path: syn::ExprPath, - }, -} + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); -/// 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, + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), + _ => None, + }) + .unzip(); - /// Custom [`ImplementerDowncast`] for this [`Implementer`]. - /// - /// If absent, then [`Implementer`] is downcast from an enum variant or a - /// trait object. - downcast: Option, + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::reflection::FieldMeta< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::reflection::Type = + <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = + <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, + )),*]; + } + } + }) + .collect() + } - /// 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. + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL interface][1]. /// - /// [`Context`]: juniper::Context + /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - context: Option, + fn impl_field_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); - /// [`ScalarValue`] parametrization of this [`Implementer`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, -} + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); -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; + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .filter_map(|field| { + if field.is_async { + return None; } - quote! { ::#name } - } - ImplementerDowncast::External { path } => { - quote! { #path } - } - }; - Some(quote! { - #fn_path(self #ctx_arg) - }) + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + Some(quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::reflection::Field< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::reflection::Field< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + }) + }) + .collect() } - /// 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`]. + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL interface][1]. /// - /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; + /// [`AsyncField`]: juniper::macros::reflection::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_async_field_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); + + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); - let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context })); + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - // 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(); - } - }) + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::reflection::AsyncField< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::reflection::AsyncField< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + } + }) + .collect() } - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts the [GraphQL interface][1] type into this - /// [`Implementer`] synchronously. - /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`EnumType`]. /// - /// [0]: juniper::GraphQLValue::resolve_into_type - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: juniper::GraphQLValue::concrete_type_name #[must_use] - fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; - let ty_name = ty.to_token_stream().to_string(); + fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(trait_ty, None); + let match_arms = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) + .map(|(ident, ty)| { + quote! { + Self::#ident(v) => < + #ty as ::juniper::GraphQLValue<#scalar> + >::concrete_type_name(v, context, info), + } + }); - let resolving_code = gen::sync_resolving_code(); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); - Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info) - .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))? - { - let res = #downcast; - return #resolving_code; + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm } - }) + } } /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts the [GraphQL interface][1] type into this [`Implementer`] + /// downcasts this [`EnumType`] into its underlying [`Implementer`] type /// asynchronously. /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn method_resolve_into_type_async_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; - let ty_name = ty.to_token_stream().to_string(); - let scalar = &self.scalar; - - let downcast = self.downcast_call_tokens(trait_ty, None); - + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - Some(quote! { - match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) { - Some(name) => { - if type_name == name { - let fut = ::juniper::futures::future::ready(#downcast); - return #resolving_code; - } - } - None => return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name), - } - }) - } -} - -/// Representation of Rust enum implementing [GraphQL interface][1] type for -/// code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct EnumType { - /// Name of this [`EnumType`] to generate it with. - ident: syn::Ident, - - /// [`syn::Visibility`] of this [`EnumType`] to generate it with. - visibility: syn::Visibility, - - /// Rust types of all [GraphQL interface][1] implements to represent - /// variants of this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - variants: Vec, - - /// Name of the trait describing the [GraphQL interface][1] represented by - /// this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_ident: syn::Ident, - - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, - - /// Associated types of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - trait_types: Vec<(syn::Ident, syn::Generics)>, - - /// Associated constants of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_consts: Vec<(syn::Ident, syn::Type)>, - - /// Methods of the trait describing the [GraphQL interface][1] represented - /// by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_methods: Vec, - - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] - /// implementation with for this [`EnumType`]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, -} - -impl ToTokens for EnumType { - fn to_tokens(&self, into: &mut TokenStream) { - self.type_definition_tokens().to_tokens(into); - into.append_all(self.impl_from_tokens()); - self.impl_trait_tokens().to_tokens(into); - } -} - -impl EnumType { - /// Constructs a new [`EnumType`] out of the given parameters. - #[must_use] - fn new( - r#trait: &syn::ItemTrait, - meta: &TraitAttr, - implers: &[Implementer], - scalar: scalar::Type, - ) -> Self { - Self { - ident: meta - .r#enum - .as_ref() - .map(SpanContainer::as_ref) - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", r#trait.ident)), - visibility: r#trait.vis.clone(), - variants: implers.iter().map(|impler| impler.ty.clone()).collect(), - trait_ident: r#trait.ident.clone(), - trait_generics: r#trait.generics.clone(), - trait_types: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Type(ty) = i { - Some((ty.ident.clone(), ty.generics.clone())) - } else { - None - } - }) - .collect(), - trait_consts: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Const(cnst) = i { - Some((cnst.ident.clone(), cnst.ty.clone())) - } else { - None - } - }) - .collect(), - trait_methods: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(m.sig.clone()) - } else { - None - } - }) - .collect(), - scalar, - } - } - - /// Returns name of a single variant of this [`EnumType`] by the given - /// underlying [`syn::Type`] of the variant. - #[must_use] - fn variant_ident(ty: &syn::Type) -> &syn::Ident { - if let syn::Type::Path(p) = ty { - &p.path.segments.last().unwrap().ident - } else { - unreachable!("GraphQL object has unexpected type `{}`", quote! { #ty }) - } - } - - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant - /// to hold type parameters. - #[must_use] - fn has_phantom_variant(&self) -> bool { - !self.trait_generics.params.is_empty() - } - - /// Returns generate code for dispatching non-exhaustive phantom variant of - /// this [`EnumType`] in `match` expressions. - /// - /// Returns [`None`] if this [`EnumType`] is exhaustive. - #[must_use] - fn non_exhaustive_match_arm_tokens(&self) -> Option { - if self.has_phantom_variant() || self.variants.is_empty() { - Some(quote! { _ => unreachable!(), }) - } else { - None - } - } - - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); - - let scalar = &self.scalar; - if scalar.is_implicit_generic() { - generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - if let Some(bound) = scalar.bounds() { - generics.make_where_clause().predicates.push(bound); - } - - if for_async { - let self_ty = if self.trait_generics.lifetimes().next().is_some() { - // Modify lifetime names to omit "lifetime name `'a` shadows a - // lifetime name that is already in scope" error. - let mut generics = self.trait_generics.clone(); - for lt in generics.lifetimes_mut() { - let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); - } - - let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.ident; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } - } else { - quote! { Self } - }; - generics - .make_where_clause() - .predicates - .push(parse_quote! { #self_ty: Sync }); - - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - } - - generics - } - - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`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(); - - parse_quote! { #ty#generics } - } - - /// Returns generated code of the full type signature of this [`EnumType`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - let ty = &self.ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); - - quote! { #ty#generics } - } - - /// Returns generate code of the Rust type definitions of this [`EnumType`]. - /// - /// If the [`EnumType::trait_generics`] are not empty, then they are - /// contained in the generated enum too. - #[must_use] - fn type_definition_tokens(&self) -> TokenStream { - let enum_ty = &self.ident; - let generics = &self.trait_generics; - let vis = &self.visibility; - - let doc = format!( - "Type implementing [GraphQL interface][1] represented by `{}` trait.\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces", - self.trait_ident, - ); - - let variants = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - let doc = format!( - "`{}` implementer of this GraphQL interface.", - quote! { #ty }, - ); - - quote! { - #[doc = #doc] - #variant(#ty), - } - }); - - let phantom_variant = if self.has_phantom_variant() { - let ty_params = generics.params.iter().map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => unimplemented!(), - }; + let match_arms = self.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - } - }); - - Some(quote! { - #[doc(hidden)] - __Phantom(#( #ty_params ),*), - }) - } else { - None - }; - - quote! { - #[automatically_derived] - #[doc = #doc] - #vis enum #enum_ty#generics { - #( #variants )* - #phantom_variant - } - } - } - - /// Returns generated code implementing [`From`] trait for this [`EnumType`] - /// from its [`EnumType::variants`]. - fn impl_from_tokens(&self) -> impl Iterator + '_ { - let enum_ty = &self.ident; - let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - - self.variants.iter().map(move |ty| { - let variant = Self::variant_ident(ty); - - quote! { - #[automatically_derived] - impl#impl_generics From<#ty> for #enum_ty#generics #where_clause { - fn from(v: #ty) -> Self { - Self::#variant(v) + Self::#ident(v) => { + let fut = ::juniper::futures::future::ready(v); + #resolving_code } } - } - }) - } - - /// Returns generated code implementing the original trait describing the - /// [GraphQL interface][1] for this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_trait_tokens(&self) -> TokenStream { - let enum_ty = &self.ident; - - let trait_ident = &self.trait_ident; - let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - - let var_ty = self.variants.first(); - - let assoc_types = self.trait_types.iter().map(|(ty, ty_gen)| { - quote! { - type #ty#ty_gen = <#var_ty as #trait_ident#generics>::#ty#ty_gen; - } - }); - - let assoc_consts = self.trait_consts.iter().map(|(ident, ty)| { - quote! { - const #ident: #ty = <#var_ty as #trait_ident#generics>::#ident; - } + }) }); - - let methods = self.trait_methods.iter().map(|sig| { - let method = &sig.ident; - - let mut sig = sig.clone(); - let mut args = vec![]; - for (n, arg) in sig.inputs.iter_mut().enumerate() { - match arg { - syn::FnArg::Receiver(_) => {} - syn::FnArg::Typed(a) => { - if !matches!(&*a.pat, syn::Pat::Ident(_)) { - let ident = format_ident!("__arg{}", n); - a.pat = parse_quote! { #ident }; - } - args.push(a.pat.clone()); - } - } - } - - let and_await = if sig.asyncness.is_some() { - Some(quote! { .await }) - } else { - None - }; - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - let args = args.clone(); - - quote! { - Self::#variant(v) => - <#ty as #trait_ident#generics>::#method(v #( , #args )* )#and_await, - } + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - #sig { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - }); - - let mut impl_tokens = quote! { - #[allow(deprecated)] - #[automatically_derived] - impl#impl_generics #trait_ident#generics for #enum_ty#generics #where_clause { - #( #assoc_types )* - - #( #assoc_consts )* - - #( #methods )* - } - }; - - if self.trait_methods.iter().any(|sig| sig.asyncness.is_some()) { - let mut ast: syn::ItemImpl = parse_quote! { #impl_tokens }; - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::ImplItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - impl_tokens = quote! { #ast }; - } - - impl_tokens - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(v) => < - #ty as ::juniper::GraphQLValue<#scalar> - >::concrete_type_name(v, context, info), - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); quote! { match self { @@ -1391,14 +1059,18 @@ impl EnumType { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(res) => #resolving_code, - } + let match_arms = self.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(res) => #resolving_code, + } + }) }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); + + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1408,94 +1080,47 @@ 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 + /// Returns trait generics replaced with default values for usage in `const` + /// context. #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); + fn const_trait_generics(&self) -> syn::PathArguments { + struct GenericsForConst(syn::AngleBracketedGenericArguments); - quote! { - Self::#variant(v) => { - let fut = ::juniper::futures::future::ready(v); - #resolving_code - } - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm + impl Visit<'_> for GenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + let arg = match param { + syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + parse_quote!(::juniper::DefaultScalarValue) + } else { + return; + } + } + syn::GenericParam::Const(c) => { + if c.default.is_none() { + // This hack works because only `min_const_generics` + // are enabled for now. + // TODO: replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } else { + return; + } + } + }; + self.0.args.push(arg) } } - } -} - -/// Representation of Rust [trait object][2] implementing [GraphQL interface][1] -/// type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://doc.rust-lang.org/reference/types/trait-object.html -struct TraitObjectType { - /// Name of this [`TraitObjectType`] to generate it with. - ident: syn::Ident, - - /// [`syn::Visibility`] of this [`TraitObjectType`] to generate it with. - visibility: syn::Visibility, - - /// Name of the trait describing the [GraphQL interface][1] represented by - /// this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_ident: syn::Ident, - - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] - /// represented by this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, - - /// [`ScalarValue`] parametrization of this [`TraitObjectType`] to generate - /// it with. - /// - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, - /// Rust type of [`Context`] to generate this [`TraitObjectType`] with. - /// - /// [`Context`]: juniper::Context - context: syn::Type, -} - -impl TraitObjectType { - /// Constructs a new [`TraitObjectType`] out of the given parameters. - #[must_use] - fn new( - r#trait: &syn::ItemTrait, - meta: &TraitAttr, - scalar: scalar::Type, - context: syn::Type, - ) -> Self { - Self { - ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(), - visibility: r#trait.vis.clone(), - trait_ident: r#trait.ident.clone(), - trait_generics: r#trait.generics.clone(), - scalar, - context, - } + let mut visitor = GenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + syn::PathArguments::AngleBracketed(visitor.0) } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`TraitObjectType`]. + /// similar) implementation of this [`EnumType`]. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. @@ -1506,8 +1131,6 @@ impl TraitObjectType { fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.trait_generics.clone(); - generics.params.push(parse_quote! { '__obj }); - let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); @@ -1523,10 +1146,28 @@ impl TraitObjectType { } if for_async { + let self_ty = if self.trait_generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.trait_generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ty = &self.enum_alias_ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ty#ty_generics } + } else { + quote! { Self } + }; generics .make_where_clause() .predicates - .push(parse_quote! { Self: Sync }); + .push(parse_quote! { #self_ty: Sync }); + if scalar.is_generic() { generics .make_where_clause() @@ -1538,243 +1179,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; - - let mut generics = self.trait_generics.clone(); - if !self.scalar.is_explicit_generic() { - let scalar = &self.scalar; - generics.params.push(parse_quote! { #scalar }); - } - let (_, generics, _) = generics.split_for_impl(); - - parse_quote! { #ty#generics } - } - - /// Returns generated code of the full type signature of this - /// [`TraitObjectType`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - let ty = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - generics.remove_defaults(); - generics.move_bounds_to_where_clause(); - if !self.scalar.is_explicit_generic() { - let scalar = &self.scalar; - generics.params.push(parse_quote! { #scalar }); - } - let ty_params = &generics.params; - - let context = &self.context; - - quote! { - dyn #ty<#ty_params, Context = #context, TypeInfo = ()> + '__obj + Send + Sync - } - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`TraitObjectType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - quote! { - self.as_dyn_graphql_value().concrete_type_name(context, info) - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`TraitObjectType`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - let resolving_code = gen::sync_resolving_code(); - - quote! { - let res = self.as_dyn_graphql_value(); - #resolving_code - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`TraitObjectType`] into its underlying [`Implementer`] - /// type asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - quote! { - let fut = ::juniper::futures::future::ready(self.as_dyn_graphql_value_async()); - #resolving_code - } - } -} - -impl ToTokens for TraitObjectType { - fn to_tokens(&self, into: &mut TokenStream) { - let dyn_ty = &self.ident; - let vis = &self.visibility; - - let doc = format!( - "Helper alias for the `{}` [trait object][2] implementing [GraphQL interface][1].\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces\n\ - [2]: https://doc.rust-lang.org/reference/types/trait-object.html", - self.trait_ident, - ); - - let trait_ident = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - if !self.scalar.is_explicit_generic() { - let scalar_ty = self.scalar.generic_ty(); - let default_ty = self.scalar.default_ty(); - generics - .params - .push(parse_quote! { #scalar_ty = #default_ty }); - } - - let (mut ty_params_left, mut ty_params_right) = (None, None); - if !generics.params.is_empty() { - // We should preserve defaults for left side. - generics.move_bounds_to_where_clause(); - let params = &generics.params; - ty_params_left = Some(quote! { , #params }); - - generics.remove_defaults(); - let params = &generics.params; - ty_params_right = Some(quote! { #params, }); - }; - - let context = &self.context; - - let dyn_alias = quote! { - #[automatically_derived] - #[doc = #doc] - #vis type #dyn_ty<'a #ty_params_left> = - dyn #trait_ident<#ty_params_right Context = #context, TypeInfo = ()> + - 'a + Send + Sync; - }; - - into.append_all(&[dyn_alias]); - } -} - -/// Representation of possible Rust types implementing [GraphQL interface][1] -/// type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -enum Type { - /// [GraphQL interface][1] type implementation as Rust enum. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Enum(Box), - - /// [GraphQL interface][1] type implementation as Rust [trait object][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - TraitObject(Box), -} - -impl ToTokens for Type { - fn to_tokens(&self, into: &mut TokenStream) { - match self { - Self::Enum(e) => e.to_tokens(into), - Self::TraitObject(o) => o.to_tokens(into), - } - } -} - -impl Type { - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`Type`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> (TokenStream, Option) { - let generics = match self { - Self::Enum(e) => e.impl_generics(for_async), - Self::TraitObject(o) => o.impl_generics(for_async), - }; - let (impl_generics, _, where_clause) = generics.split_for_impl(); - (quote! { #impl_generics }, where_clause.cloned()) - } - - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`Type`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn trait_ty(&self) -> syn::Type { - match self { - Self::Enum(e) => e.trait_ty(), - Self::TraitObject(o) => o.trait_ty(), - } - } - - /// Returns generated code of the full type signature of this [`Type`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.ty_tokens(), - Self::TraitObject(o) => o.ty_tokens(), - } - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`Type`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_concrete_type_name_tokens(), - Self::TraitObject(o) => o.method_concrete_type_name_tokens(), - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`Type`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type + /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant + /// to hold type parameters. #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_resolve_into_type_tokens(), - Self::TraitObject(o) => o.method_resolve_into_type_tokens(), - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`Type`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_resolve_into_type_async_tokens(), - Self::TraitObject(o) => o.method_resolve_into_type_async_tokens(), - } + fn has_phantom_variant(&self) -> bool { + !self.trait_generics.params.is_empty() } } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs deleted file mode 100644 index a7621f7ab..000000000 --- a/juniper_codegen/src/graphql_interface/new.rs +++ /dev/null @@ -1,1204 +0,0 @@ -//! Code generation for [GraphQL interface][1]. -//! -//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces - -use std::{collections::HashSet, convert::TryInto as _}; - -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::{ - ext::IdentExt as _, - parse::{Parse, ParseStream}, - parse_quote, - punctuated::Punctuated, - spanned::Spanned as _, - token, - visit::Visit, -}; - -use crate::{ - common::{ - field, gen, - parse::{ - attr::{err, OptionExt as _}, - GenericsExt as _, ParseBufferExt as _, - }, - scalar, - }, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, -}; - -/// Available 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)] -pub(crate) struct TraitAttr { - /// Explicitly specified name of [GraphQL interface][1] type. - /// - /// If [`None`], then Rust trait name is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) name: Option>, - - /// Explicitly specified [description][2] of [GraphQL interface][1] type. - /// - /// If [`None`], 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(crate) description: Option>, - - /// Explicitly specified identifier of the type alias of Rust enum type - /// behind the trait, being an actual implementation of a - /// [GraphQL interface][1] type. - /// - /// If [`None`], then `{trait_name}Value` identifier will be used. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) r#enum: 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(crate) implementers: HashSet>, - - /// Explicitly specified type of [`Context`] to use for resolving this - /// [GraphQL interface][1] type with. - /// - /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. - /// - /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) context: Option>, - - /// Explicitly specified type (or type parameter with its bounds) of - /// [`ScalarValue`] to resolve this [GraphQL interface][1] type with. - /// - /// If [`None`], 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(crate) scalar: Option>, - - /// Explicitly specified marker indicating that the Rust trait should be - /// transformed into [`async_trait`]. - /// - /// If [`None`], then trait will be transformed into [`async_trait`] only if - /// it contains async methods. - pub(crate) asyncness: Option>, - - /// Explicitly specified [`RenameRule`] for all fields of this - /// [GraphQL interface][1] type. - /// - /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) rename_fields: Option>, - - /// Indicator whether the generated code is intended to be used only inside - /// the [`juniper`] library. - pub(crate) is_internal: bool, -} - -impl Parse for TraitAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let name = input.parse::()?; - out.name - .replace(SpanContainer::new( - ident.span(), - Some(name.span()), - name.value(), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "desc" | "description" => { - input.parse::()?; - let desc = input.parse::()?; - out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "ctx" | "context" | "Context" => { - input.parse::()?; - let ctx = input.parse::()?; - out.context - .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "for" | "implementers" => { - input.parse::()?; - for impler in input.parse_maybe_wrapped_and_punctuated::< - syn::TypePath, token::Bracket, token::Comma, - >()? { - let impler_span = impler.span(); - out - .implementers - .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) - .none_or_else(|_| err::dup_arg(impler_span))?; - } - } - "enum" => { - input.parse::()?; - let alias = input.parse::()?; - out.r#enum - .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "async" => { - let span = ident.span(); - out.asyncness - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - "rename_all" => { - input.parse::()?; - let val = input.parse::()?; - out.rename_fields - .replace(SpanContainer::new( - ident.span(), - Some(val.span()), - val.try_into()?, - )) - .none_or_else(|_| err::dup_arg(&ident))?; - } - "internal" => { - out.is_internal = true; - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - Ok(out) - } -} - -impl TraitAttr { - /// Tries to merge two [`TraitAttr`]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), - r#enum: try_merge_opt!(r#enum: self, another), - asyncness: try_merge_opt!(asyncness: self, another), - rename_fields: try_merge_opt!(rename_fields: self, another), - is_internal: self.is_internal || another.is_internal, - }) - } - - /// Parses [`TraitAttr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a trait definition. - pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let mut attr = filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - - if attr.description.is_none() { - attr.description = get_doc_comment(attrs); - } - - Ok(attr) - } -} - -/// Definition of [GraphQL interface][1] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -pub(crate) struct Definition { - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) trait_generics: syn::Generics, - - /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) vis: syn::Visibility, - - /// Name of the generic enum describing all [`implementers`]. It's generic - /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. - /// - /// [`implementers`]: Self::implementers - /// [`Debug`]: std::fmt::Debug - pub(crate) enum_ident: syn::Ident, - - /// Name of the type alias for [`enum_ident`] with [`implementers`]. - /// - /// [`enum_ident`]: Self::enum_ident - /// [`implementers`]: Self::implementers - pub(crate) enum_alias_ident: syn::Ident, - - /// Name of this [GraphQL interface][1] in GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) name: String, - - /// Description of this [GraphQL interface][1] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) description: Option, - - /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with - /// for this [GraphQL interface][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) context: syn::Type, - - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] - /// implementation with for this [GraphQL interface][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) scalar: scalar::Type, - - /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - pub(crate) fields: Vec, - - /// Defined [`Implementer`]s of this [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) implementers: Vec, -} - -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - self.generate_enum_tokens().to_tokens(into); - self.impl_graphql_interface_tokens().to_tokens(into); - self.impl_output_type_tokens().to_tokens(into); - self.impl_graphql_type_tokens().to_tokens(into); - self.impl_graphql_value_tokens().to_tokens(into); - self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); - self.impl_field_meta_tokens().to_tokens(into); - self.impl_field_tokens().to_tokens(into); - self.impl_async_field_tokens().to_tokens(into); - } -} - -impl Definition { - /// Generates enum describing all [`implementers`]. - /// - /// [`implementers`]: Self::implementers - #[must_use] - fn generate_enum_tokens(&self) -> TokenStream { - let vis = &self.vis; - let enum_ident = &self.enum_ident; - let alias_ident = &self.enum_alias_ident; - - let variant_gens_pars = self - .implementers - .iter() - .enumerate() - .map::(|(id, _)| { - let par = format_ident!("__I{}", id); - parse_quote!( #par ) - }); - - let variants_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - - let trait_gens = &self.trait_generics; - let (trait_impl_gens, trait_ty_gens, trait_where_clause) = - self.trait_generics.split_for_impl(); - - let (trait_gens_lifetimes, trait_gens_tys) = trait_gens - .params - .clone() - .into_iter() - .partition::, _>(|par| { - matches!(par, syn::GenericParam::Lifetime(_)) - }); - - let enum_gens = { - let mut enum_gens = trait_gens.clone(); - enum_gens.params = trait_gens_lifetimes.clone(); - enum_gens.params.extend(variant_gens_pars.clone()); - enum_gens.params.extend(trait_gens_tys.clone()); - enum_gens - }; - - let enum_alias_gens = { - let mut enum_alias_gens = trait_gens.clone(); - enum_alias_gens.move_bounds_to_where_clause(); - enum_alias_gens - }; - - let enum_to_alias_gens = { - trait_gens_lifetimes - .into_iter() - .map(|par| match par { - syn::GenericParam::Lifetime(def) => { - let lifetime = &def.lifetime; - quote! { #lifetime } - } - rest => quote! { #rest }, - }) - .chain(self.implementers.iter().map(ToTokens::to_token_stream)) - .chain(trait_gens_tys.into_iter().map(|par| match par { - syn::GenericParam::Type(ty) => { - let par_ident = &ty.ident; - quote! { #par_ident } - } - rest => quote! { #rest }, - })) - }; - - let phantom_variant = self.has_phantom_variant().then(|| { - let phantom_params = trait_gens.params.iter().filter_map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => return None, - }; - Some(quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - }) - }); - quote! { __Phantom(#(#phantom_params),*) } - }); - - let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( - |(ty, ident)| { - quote! { - impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens - #trait_where_clause - { - fn from(v: #ty) -> Self { - Self::#ident(v) - } - } - } - }, - ); - - quote! { - #[derive(Clone, Copy, Debug)] - #vis enum #enum_ident#enum_gens { - #(#variants_idents(#variant_gens_pars),)* - #phantom_variant - } - - #vis type #alias_ident#enum_alias_gens = - #enum_ident<#(#enum_to_alias_gens),*>; - - #(#from_impls)* - } - } - - /// Returns generated code implementing [`GraphQLInterface`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLInterface`]: juniper::GraphQLInterface - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_interface_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - - let gens = self.impl_generics(false); - let (impl_generics, _, where_clause) = gens.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let impler_tys = &self.implementers; - let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } - }); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for - #ty#ty_generics - #where_clause - { - fn mark() { - #all_implers_unique - #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* - } - } - } - } - - /// 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 - #[must_use] - fn impl_output_type_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let fields_marks = self - .fields - .iter() - .map(|f| f.method_mark_tokens(false, scalar)); - - let impler_tys = self.implementers.iter(); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for - #ty#ty_generics - #where_clause - { - fn mark() { - #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* - } - } - } - } - - /// Returns generated code implementing [`GraphQLType`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_type_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let name = &self.name; - let description = self - .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 = self.implementers.clone(); - 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(|f| f.method_meta_tokens(None)); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> - for #ty#ty_generics - #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_tys>(info); )* - - let fields = [ - #( #fields_meta, )* - ]; - registry.build_interface_type::<#ty#ty_generics>(info, &fields) - #description - .into_meta() - } - } - } - } - - /// Returns generated code implementing [`GraphQLValue`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_value_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let trait_name = &self.name; - let scalar = &self.scalar; - let context = &self.context; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let fields_resolvers = self.fields.iter().filter_map(|f| { - if f.is_async { - return None; - } - - let name = &f.name; - Some(quote! { - #name => { - ::juniper::macros::reflection::Field::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } - }) - }); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, trait_name, - ) - }) - }; - let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - - let downcast_check = self.method_concrete_type_name_tokens(); - - let downcast = self.method_resolve_into_type_tokens(); - - quote! { - #[allow(deprecated)] - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics - #where_clause - { - type Context = #context; - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::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 )* - #async_fields_err - _ => #no_field_err, - } - } - - fn concrete_type_name( - &self, - context: &Self::Context, - info: &Self::TypeInfo, - ) -> String { - #downcast_check - } - - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - #downcast - } - } - } - } - - /// Returns generated code implementing [`GraphQLValueAsync`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_value_async_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let trait_name = &self.name; - let scalar = &self.scalar; - - let generics = self.impl_generics(true); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let fields_resolvers = self.fields.iter().map(|f| { - let name = &f.name; - quote! { - #name => { - ::juniper::macros::reflection::AsyncField::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } - } - }); - let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - - let downcast = self.method_resolve_into_type_async_tokens(); - - quote! { - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics - #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 )* - _ => Box::pin(async move { #no_field_err }), - } - } - - 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>> { - #downcast - } - } - } - } - - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], - /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. - /// - /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflection::BaseType - /// [`Fields`]: juniper::macros::reflection::Fields - /// [`WrappedType`]: juniper::macros::reflection::WrappedType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let implementers = &self.implementers; - let scalar = &self.scalar; - let name = &self.name; - let fields = self.fields.iter().map(|f| &f.name); - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> - for #ty#ty_generics - #where_clause - { - const NAME: ::juniper::macros::reflection::Type = #name; - } - - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> - for #ty#ty_generics - #where_clause - { - const NAMES: ::juniper::macros::reflection::Types = &[ - >::NAME, - #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* - ]; - } - - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> - for #ty#ty_generics - #where_clause - { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; - } - - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> - for #ty#ty_generics - #where_clause - { - const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; - } - } - } - - /// Returns generated code implementing [`FieldMeta`] for each field of this - /// [GraphQL interface][1]. - /// - /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field_meta_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let context = &self.context; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - self.fields - .iter() - .map(|field| { - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - generics.replace_type_with_defaults(&mut return_ty); - - let (args_tys, args_names): (Vec<_>, Vec<_>) = field - .arguments - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), - _ => None, - }) - .unzip(); - - quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::FieldMeta< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::reflection::Type = - <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::reflection::Types = - <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = - <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::reflection::Name, - ::juniper::macros::reflection::Type, - ::juniper::macros::reflection::WrappedValue, - )] = &[#(( - #args_names, - <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, - )),*]; - } - } - }) - .collect() - } - - /// Returns generated code implementing [`Field`] trait for each field of - /// this [GraphQL interface][1]. - /// - /// [`Field`]: juniper::macros::reflection::Field - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - let const_scalar = self.const_scalar(); - - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) - .collect::>(); - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - self.fields - .iter() - .filter_map(|field| { - if field.is_async { - return None; - } - - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - generics.replace_type_with_defaults(&mut return_ty); - - let const_ty_generics = self.const_trait_generics(); - - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) - .then(|| { - quote! { _ => unreachable!() } - }); - - Some(quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::Field< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - match self { - #(#ty::#impl_idents(v) => { - ::juniper::assert_field!( - #ty#const_ty_generics, - #impl_tys, - #const_scalar, - #field_name, - ); - - <_ as ::juniper::macros::reflection::Field< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) - })* - #unreachable_arm - } - } - } - }) - }) - .collect() - } - - /// Returns generated code implementing [`AsyncField`] trait for each field - /// of this [GraphQL interface][1]. - /// - /// [`AsyncField`]: juniper::macros::reflection::AsyncField - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_async_field_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - let const_scalar = self.const_scalar(); - - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) - .collect::>(); - - let generics = self.impl_generics(true); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - self.fields - .iter() - .map(|field| { - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - generics.replace_type_with_defaults(&mut return_ty); - - let const_ty_generics = self.const_trait_generics(); - - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) - .then(|| { - quote! { _ => unreachable!() } - }); - - quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::AsyncField< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - match self { - #(#ty::#impl_idents(v) => { - ::juniper::assert_field!( - #ty#const_ty_generics, - #impl_tys, - #const_scalar, - #field_name, - ); - - <_ as ::juniper::macros::reflection::AsyncField< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) - })* - #unreachable_arm - } - } - } - } - }) - .collect() - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let match_arms = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) - .map(|(ident, ty)| { - quote! { - Self::#ident(v) => < - #ty as ::juniper::GraphQLValue<#scalar> - >::concrete_type_name(v, context, info), - } - }); - - let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { - quote! { _ => unreachable!(), } - }); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`EnumType`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - let match_arms = self.implementers.iter().filter_map(|ty| { - ty.path.segments.last().map(|ident| { - quote! { - Self::#ident(v) => { - let fut = ::juniper::futures::future::ready(v); - #resolving_code - } - } - }) - }); - let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { - quote! { _ => unreachable!(), } - }); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`EnumType`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - let resolving_code = gen::sync_resolving_code(); - - let match_arms = self.implementers.iter().filter_map(|ty| { - ty.path.segments.last().map(|ident| { - quote! { - Self::#ident(res) => #resolving_code, - } - }) - }); - - let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { - quote! { _ => unreachable!(), } - }); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns trait generics replaced with default values for usage in `const` - /// context. - #[must_use] - fn const_trait_generics(&self) -> syn::PathArguments { - struct GenericsForConst(syn::AngleBracketedGenericArguments); - - impl Visit<'_> for GenericsForConst { - fn visit_generic_param(&mut self, param: &syn::GenericParam) { - let arg = match param { - syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), - syn::GenericParam::Type(ty) => { - if ty.default.is_none() { - parse_quote!(::juniper::DefaultScalarValue) - } else { - return; - } - } - syn::GenericParam::Const(c) => { - if c.default.is_none() { - // This hack works because only `min_const_generics` - // are enabled for now. - // TODO: replace this once full `const_generics` are - // available. - // Maybe with `<_ as Default>::default()`? - parse_quote!({ 0_u8 as _ }) - } else { - return; - } - } - }; - self.0.args.push(arg) - } - } - - let mut visitor = GenericsForConst(parse_quote!( <> )); - visitor.visit_generics(&self.trait_generics); - syn::PathArguments::AngleBracketed(visitor.0) - } - - /// Returns [`scalar`] replaced with [`DefaultScalarValue`] in case it's - /// [`ExplicitGeneric`] or [`ImplicitGeneric`] for using [`scalar`] in - /// `const` context. - /// - /// [`scalar`]: Self::scalar - /// [`DefaultScalarValue`]: juniper::DefaultScalarValue - /// [`ExplicitGeneric`]: scalar::Type::ExplicitGeneric - /// [`ImplicitGeneric`]: scalar::Type::ImplicitGeneric - #[must_use] - fn const_scalar(&self) -> syn::Type { - match &self.scalar { - scalar::Type::Concrete(ty) => ty.clone(), - scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { - parse_quote! { ::juniper::DefaultScalarValue } - } - } - } - - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); - - let scalar = &self.scalar; - if scalar.is_implicit_generic() { - generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - if let Some(bound) = scalar.bounds() { - generics.make_where_clause().predicates.push(bound); - } - - if for_async { - let self_ty = if self.trait_generics.lifetimes().next().is_some() { - // Modify lifetime names to omit "lifetime name `'a` shadows a - // lifetime name that is already in scope" error. - let mut generics = self.trait_generics.clone(); - for lt in generics.lifetimes_mut() { - let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); - } - - let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.enum_alias_ident; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } - } else { - quote! { Self } - }; - generics - .make_where_clause() - .predicates - .push(parse_quote! { #self_ty: Sync }); - - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - } - - generics - } - - /// 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() - } -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 0b30d10c0..1e426af3d 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -673,14 +673,6 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } -#[proc_macro_error] -#[proc_macro_attribute] -pub fn graphql_interface_new(attr: TokenStream, body: TokenStream) -> TokenStream { - self::graphql_interface::attr::expand_new(attr.into(), body.into()) - .unwrap_or_abort() - .into() -} - /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// From b5f552a7ce68ec62c52b1616c2c2b6e2bd0b6321 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 15:39:42 +0300 Subject: [PATCH 042/122] WIP (More codegen_fail tests) --- .../additional_non_nullable_argument.rs | 19 +++++++++++++++++++ .../additional_non_nullable_argument.stderr | 7 +++++++ .../fail/interface/missing_field.rs | 14 ++++++++++++++ .../fail/interface/missing_field.stderr | 7 +++++++ .../fail/interface/missing_field_argument.rs | 19 +++++++++++++++++++ .../interface/missing_field_argument.stderr | 7 +++++++ .../fail/interface/non_subtype_return.rs | 14 ++++++++++++++ .../fail/interface/non_subtype_return.stderr | 7 +++++++ .../fail/interface/wrong_argument_type.rs | 19 +++++++++++++++++++ .../fail/interface/wrong_argument_type.stderr | 7 +++++++ 10 files changed, 120 insertions(+) create mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs new file mode 100644 index 000000000..c69f0f664 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, is_present: bool) -> &str { + is_present.then(|| self.id.as_str()).unwrap_or("missing") + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr new file mode 100644 index 000000000..0805ac203 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/additional_non_nullable_argument.rs:14:1 + | +14 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` not present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.rs b/integration_tests/codegen_fail/fail/interface/missing_field.rs new file mode 100644 index 000000000..641556939 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/missing_field.stderr new file mode 100644 index 000000000..d0efa3518 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_field.rs:9:1 + | +9 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/missing_field.rs:9:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs b/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs new file mode 100644 index 000000000..b71a38d6b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self) -> &String { + &self.id + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self, is_present: bool) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr new file mode 100644 index 000000000..719cfd1a8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_field_argument.rs:14:1 + | +14 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/missing_field_argument.rs:14:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs new file mode 100644 index 000000000..8eab508d3 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr new file mode 100644 index 000000000..ca48f8464 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/non_subtype_return.rs:9:1 + | +9 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/non_subtype_return.rs:9:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs new file mode 100644 index 000000000..388aea1ed --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, _is_present: i32) -> &str { + &self.id + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self, is_present: bool) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr new file mode 100644 index 000000000..204bd1234 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/wrong_argument_type.rs:14:1 + | +14 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/wrong_argument_type.rs:14:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) From 66819e932bfacd9881bc136084ae91426c78d27c Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 17:05:59 +0300 Subject: [PATCH 043/122] WIP (prettify missing references in `impl` and `for` attributes) --- .../fail/interface/missing_for_attr.rs | 14 +++++ .../fail/interface/missing_for_attr.stderr | 7 +++ .../fail/interface/missing_impl_attr.rs | 13 +++++ .../fail/interface/missing_impl_attr.stderr | 7 +++ juniper/src/macros/reflection.rs | 52 +++++++++++++++++++ juniper_codegen/src/graphql_interface/mod.rs | 6 ++- juniper_codegen/src/graphql_object/mod.rs | 16 +++++- 7 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/missing_for_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs new file mode 100644 index 000000000..77a3043af --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[graphql_interface] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr new file mode 100644 index 000000000..84072d87d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_for_attr.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/missing_for_attr.rs:3:10 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs new file mode 100644 index 000000000..c35b23b33 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs @@ -0,0 +1,13 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr new file mode 100644 index 000000000..767a53203 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_impl_attr.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/missing_impl_attr.rs:8:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index d0b304d8d..bd14b8586 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -281,6 +281,10 @@ pub trait Fields { const NAMES: Names; } +pub trait Implements { + const NAMES: Types; +} + /// Stores meta information of [GraphQL fields][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. @@ -467,6 +471,54 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +#[macro_export] +macro_rules! assert_implemented_for { + ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { + const _: () = { + $({ + let is_present = $crate::macros::reflection::str_exists_in_arr( + <$implementor as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, + <$interfaces as ::juniper::macros::reflection::BaseSubTypes<$scalar>>::NAMES, + ); + if !is_present { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + <$interfaces as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "` on `", + <$implementor as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`: missing implementer reference in interface's `for` attribute.", + ); + ::std::panic!("{}", MSG); + } + })* + }; + }; +} + +#[macro_export] +macro_rules! assert_interfaces_impls { + ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { + const _: () = { + $({ + let is_present = $crate::macros::reflection::str_exists_in_arr( + <$interface as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, + <$implementers as ::juniper::macros::reflection::Implements<$scalar>>::NAMES, + ); + if !is_present { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + <$interface as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "` on `", + <$implementers as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`: missing interface reference in implementer's `impl` attribute.", + ); + ::std::panic!("{}", MSG); + } + })* + }; + }; +} + /// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This /// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. /// See [spec][1] for more info. diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 6c67f465d..499e90fcc 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -473,6 +473,7 @@ impl Definition { fn impl_output_type_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; + let const_scalar = &self.scalar.default_ty(); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); @@ -483,7 +484,7 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter(); + let impler_tys = self.implementers.iter().collect::>(); quote! { #[automatically_derived] @@ -494,6 +495,9 @@ impl Definition { fn mark() { #( #fields_marks )* #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + ::juniper::assert_interfaces_impls!( + #const_scalar, #ty, #(#impler_tys),* + ); } } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 507e8a363..901e81d12 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -376,6 +376,7 @@ impl Definition { let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let fields = self.fields.iter().map(|f| &f.name); + let interfaces = self.interfaces.iter(); quote! { #[automatically_derived] @@ -395,6 +396,15 @@ impl Definition { &[>::NAME]; } + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::Implements<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::reflection::Types = + &[#(<#interfaces as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),*]; + } + #[automatically_derived] impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty @@ -507,11 +517,12 @@ impl Definition { #[must_use] fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let interface_tys = self.interfaces.iter(); + let interface_tys = self.interfaces.iter().collect::>(); // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, // but considering generics. //let interface_tys: Vec<_> = self.interfaces.iter().collect(); @@ -525,6 +536,9 @@ impl Definition { { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + ::juniper::assert_implemented_for!( + #const_scalar, #ty, #(#interface_tys),* + ); } } } From 3d53dc10d56e69a622e8bdd4ee9f52d88a477401 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 17:33:28 +0300 Subject: [PATCH 044/122] WIP (more tests) --- .../src/codegen/interface_attr.rs | 125 ++++++++++++++++++ juniper_codegen/src/common/parse/mod.rs | 2 +- juniper_codegen/src/graphql_interface/mod.rs | 3 +- juniper_codegen/src/graphql_object/mod.rs | 19 ++- 4 files changed, 144 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 c3b453804..5646a5d9b 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3598,3 +3598,128 @@ mod field_return_union_subtyping { } } } + +mod additional_nullable_argument { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id(isPresent: true) + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "missing"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index f74e75b12..7f5cda6d5 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -330,7 +330,7 @@ impl GenericsExt for syn::Generics { }); if is_generic { - *ty = parse_quote!(()); + *ty = parse_quote!(::juniper::DefaultScalarValue); } } _ => {} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 499e90fcc..a26645d54 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -478,6 +478,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty_const_generics = self.const_trait_generics(); let fields_marks = self .fields @@ -496,7 +497,7 @@ impl Definition { #( #fields_marks )* #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* ::juniper::assert_interfaces_impls!( - #const_scalar, #ty, #(#impler_tys),* + #const_scalar, #ty#ty_const_generics, #(#impler_tys),* ); } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 901e81d12..d306d37a4 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -21,7 +21,7 @@ use crate::{ field, gen, parse::{ attr::{err, OptionExt as _}, - ParseBufferExt as _, TypeExt, + GenericsExt as _, ParseBufferExt as _, TypeExt, }, scalar, }, @@ -522,7 +522,20 @@ impl Definition { let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let interface_tys = self.interfaces.iter().collect::>(); + let interface_tys = self.interfaces.iter(); + + let generics = { + let mut generics = self.generics.clone(); + if scalar.is_implicit_generic() { + generics.params.push(parse_quote!(#scalar)) + } + generics + }; + let const_interface_tys = interface_tys.clone().cloned().map(|mut ty| { + generics.replace_type_with_defaults(&mut ty); + ty + }); + // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, // but considering generics. //let interface_tys: Vec<_> = self.interfaces.iter().collect(); @@ -537,7 +550,7 @@ impl Definition { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* ::juniper::assert_implemented_for!( - #const_scalar, #ty, #(#interface_tys),* + #const_scalar, #ty, #(#const_interface_tys),* ); } } From 732bc131ea1b5550b93338caa0f9a6b75a7698c8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 08:33:17 +0300 Subject: [PATCH 045/122] WIP (Correct docs) --- juniper_codegen/src/lib.rs | 211 +++++++++---------------------------- 1 file changed, 47 insertions(+), 164 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1e426af3d..409d29757 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -271,70 +271,33 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// Specifying multiple `#[graphql_interface]` attributes on the same definition /// is totally okay. They all will be treated as a single attribute. /// -/// The main difference between [GraphQL interface][1] type and Rust trait is +/// [GraphQL interfaces][1] are more like Go's structural interfaces, while +/// Rust's traits are more like typeclasses. So using `impl Trait` isn't an +/// option. +/// +/// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to /// concrete implementers_, while in Rust, a trait is an _abstraction only_ and /// you need a separate type to downcast into a concrete implementer, like enum /// or [trait object][3], because trait doesn't represent a type itself. -/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by -/// default, however [trait object][3] may be used too (use `dyn` attribute -/// argument for that). -/// -/// A __trait has to be [object safe][2]__ if its values are represented by -/// [trait object][3], because schema resolvers will need to return that -/// [trait object][3]. The [trait object][3] has to be [`Send`] and [`Sync`], -/// and the macro automatically generate a convenien type alias for such -/// [trait object][3]. +/// Macro uses Rust enum to represent a value type of [GraphQL interface][1]. /// /// ``` /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this /// // GraphQL interface. -/// #[graphql_interface(for = [Human, Droid])] // enumerating all implementers is mandatory +/// #[graphql_interface(for = Human)] // enumerating all implementers is mandatory /// trait Character { /// fn id(&self) -> &str; /// } /// -/// // NOTICE: `dyn` attribute argument enables trait object usage to represent values of this -/// // GraphQL interface. Also, for trait objects a trait is slightly modified -/// // under-the-hood by adding a `ScalarValue` type parameter. -/// #[graphql_interface(dyn = DynSerial, for = Droid)] -/// trait Serial { -/// fn number(&self) -> i32; -/// } -/// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name /// struct Human { -/// id: String, +/// id: String, // this field is used to resolve Character::id /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = [CharacterValue, DynSerial<__S>])] // notice the trait object referred by alias -/// struct Droid { // and its parametrization by generic -/// id: String, // `ScalarValue` -/// primary_function: String, -/// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// #[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too -/// impl Serial for Droid { -/// fn number(&self) -> i32 { -/// 78953 -/// } -/// } /// ``` /// /// # Custom name, description, deprecation and argument defaults @@ -390,7 +353,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// `none` (disables any renaming). /// /// ``` -/// # use juniper::{graphql_interface, GraphQLObject}; +/// # use juniper::{graphql_interface, graphql_object}; /// # /// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming /// trait Character { @@ -399,19 +362,26 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// fn detailed_info(&self, info_kind: String) -> String; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn detailed_info(&self, info_kind: String) -> String { +/// +/// #[graphql_object(impl = CharacterValue, rename_all = "none")] +/// impl Human { +/// fn id(&self) -> &str { +/// &self.id +/// } +/// +/// fn home_planet(&self) -> &str { +/// &self.home_planet +/// } +/// +/// // You can return `&str` even if trait definition returns `String`. +/// fn detailed_info(&self, info_kind: String) -> &str { /// (info_kind == "planet") /// .then(|| &self.home_planet) /// .unwrap_or(&self.id) -/// .clone() /// } /// } /// ``` @@ -447,7 +417,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// /// ``` /// # use std::collections::HashMap; -/// # use juniper::{graphql_interface, GraphQLObject}; +/// # use juniper::{graphql_interface, graphql_object}; /// # /// struct Database { /// humans: HashMap, @@ -461,36 +431,38 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str>; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> { -/// db.humans.get(&self.id).map(|h| h.id.as_str()) +/// #[graphql_object(impl = CharacterValue, Context = Database)] +/// impl Human { +/// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> { +/// context.humans.get(&self.id).map(|h| h.id.as_str()) /// } -/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> { +/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.humans.get(&self.id).map(|h| h.home_planet.as_str()) /// } +/// fn home_planet(&self) -> &str { +/// &self.home_planet +/// } /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] /// struct Droid { /// id: String, /// primary_function: String, /// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> { -/// db.droids.get(&self.id).map(|h| h.id.as_str()) +/// #[graphql_object(impl = CharacterValue, Context = Database)] +/// impl Droid { +/// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str> { +/// ctx.droids.get(&self.id).map(|h| h.id.as_str()) /// } -/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> { +/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.droids.get(&self.id).map(|h| h.primary_function.as_str()) /// } +/// fn primary_function(&self) -> &str { +/// &self.primary_function +/// } /// } /// ``` /// @@ -503,7 +475,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// /// ``` -/// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +/// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; /// # /// // NOTICE: Specifying `ScalarValue` as existing type parameter. /// #[graphql_interface(for = Human, scalar = S)] @@ -520,24 +492,22 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// 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 +/// #[graphql_object(impl = CharacterValue<__S>)] +/// impl Human { +/// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where -/// S: Send + Sync, +/// S: ScalarValue + Send + Sync, /// { /// executor.look_ahead().field_name() /// } /// -/// async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str +/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str /// where -/// S: Send + Sync, +/// S: ScalarValue + Send + Sync, /// { /// &self.name /// } @@ -557,7 +527,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// // NOTICE: Removing `Scalar` argument will fail compilation. -/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)] +/// #[graphql_interface(for = Human, scalar = DefaultScalarValue)] /// trait Character { /// fn id(&self) -> &str; /// } @@ -568,93 +538,6 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface(scalar = DefaultScalarValue)] -/// impl Character for Human { -/// fn id(&self) -> &str{ -/// &self.id -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] -/// struct Droid { -/// id: String, -/// primary_function: String, -/// } -/// #[graphql_interface(scalar = DefaultScalarValue)] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// ``` -/// -/// # Downcasting -/// -/// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via -/// matching the enum variant or downcasting the trait object (if `dyn` attribute's argument is -/// used). -/// -/// To use a custom logic for downcasting a [GraphQL interface][1] into its implementer, there may -/// be specified: -/// - either a `downcast` attribute's argument directly on a trait method; -/// - or an `on` attribute's argument on aa trait definition referring an exteranl function. -/// -/// ``` -/// # use std::collections::HashMap; -/// # use juniper::{graphql_interface, GraphQLObject}; -/// # -/// struct Database { -/// humans: HashMap, -/// droids: HashMap, -/// } -/// impl juniper::Context for Database {} -/// -/// #[graphql_interface(for = [Human, Droid], Context = Database)] -/// #[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` -/// trait Character { -/// fn id(&self) -> &str; -/// -/// #[graphql(downcast)] // makes method a downcast to `Human`, not a field -/// // NOTICE: The method signature may optionally contain `&Database` context argument. -/// fn as_human(&self) -> Option<&Human> { -/// None -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] -/// struct Human { -/// id: String, -/// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// -/// fn as_human(&self) -> Option<&Self> { -/// Some(self) -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] -/// struct Droid { -/// id: String, -/// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// -/// // External downcast function doesn't have to be a method of a type. -/// // It's only a matter of the function signature to match the requirements. -/// fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> { -/// db.droids.get(ch.id()) -/// } /// ``` /// /// [`Context`]: juniper::Context From 81d96cefcea5aa43a57831095b69ee7ebfc760ca Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 08:52:34 +0300 Subject: [PATCH 046/122] WIP (Forbid default trait method impls) --- .../fail/interface/argument_double_underscored.rs | 4 +--- .../fail/interface/argument_double_underscored.stderr | 2 +- .../fail/interface/argument_wrong_default_array.rs | 10 ---------- .../interface/argument_wrong_default_array.stderr | 11 ----------- .../fail/interface/field_double_underscored.rs | 4 +--- .../fail/interface/field_double_underscored.stderr | 2 +- .../fail/interface/method_default_impl.rs | 10 ++++++++++ .../fail/interface/method_default_impl.stderr | 10 ++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 11 +++++++++++ 9 files changed, 35 insertions(+), 29 deletions(-) delete mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/method_default_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/method_default_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs index b842c7ce4..449d67485 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait Character { - fn id(&self, __num: i32) -> &str { - "funA" - } + fn id(&self, __num: i32) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr index a75859459..0a6e79eba 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -1,7 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. --> fail/interface/argument_double_underscored.rs:5:18 | -5 | fn id(&self, __num: i32) -> &str { +5 | fn id(&self, __num: i32) -> &str; | ^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs deleted file mode 100644 index 48ce93738..000000000 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs +++ /dev/null @@ -1,10 +0,0 @@ -use juniper::graphql_interface; - -#[graphql_interface] -trait Character { - fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { - input[0] - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr deleted file mode 100644 index b745a82d0..000000000 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:3:1 - | -3 | #[graphql_interface] - | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` - | - = help: the following implementations were found: - <[T; LANES] as From>> - <[bool; LANES] as From>> - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs index 3096cd73a..10d001198 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait Character { - fn __id(&self) -> &str { - "funA" - } + fn __id(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr index ef662d93d..6c683ed51 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -1,7 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. --> fail/interface/field_double_underscored.rs:5:8 | -5 | fn __id(&self) -> &str { +5 | fn __id(&self) -> &str; | ^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.rs b/integration_tests/codegen_fail/fail/interface/method_default_impl.rs new file mode 100644 index 000000000..e7ac7832e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.rs @@ -0,0 +1,10 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn id(&self) -> &str { + "default" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr new file mode 100644 index 000000000..a5a728b90 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr @@ -0,0 +1,10 @@ +error: GraphQL interface trait method can't have default impl block + --> fail/interface/method_default_impl.rs:5:26 + | +5 | fn id(&self) -> &str { + | __________________________^ +6 | | "default" +7 | | } + | |_____^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 47a3fc3a4..a5659274e 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -178,6 +178,10 @@ fn parse_field( method: &mut syn::TraitItemMethod, renaming: &RenameRule, ) -> Option { + if method.default.is_some() { + return err_default_impl_block(&method.default); + } + let method_ident = &method.sig.ident; let method_attrs = method.attrs.clone(); @@ -262,6 +266,13 @@ fn parse_field( }) } +/// Emits "trait method can't have default impl block" [`syn::Error`] pointing +/// to the given `span`. +fn err_default_impl_block(span: &S) -> Option { + ERR.emit_custom(span.span(), "trait method can't have default impl block"); + None +} + /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given /// `span`. #[must_use] From 94164eb0b442f5577390b5512f745fbf124c8cd8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 09:50:50 +0300 Subject: [PATCH 047/122] WIP (Corrections) --- docs/book/content/types/interfaces.md | 208 ++---------------- .../src/codegen/interface_attr.rs | 21 +- juniper/src/ast.rs | 1 + juniper/src/executor/mod.rs | 3 +- juniper/src/executor/owned_executor.rs | 2 + juniper/src/macros/reflection.rs | 5 +- juniper/src/parser/value.rs | 4 +- juniper/src/schema/meta.rs | 15 ++ juniper/src/tests/fixtures/starwars/schema.rs | 12 +- juniper/src/types/nullable.rs | 3 + juniper_codegen/src/graphql_interface/attr.rs | 12 +- juniper_codegen/src/lib.rs | 6 +- juniper_graphql_ws/src/lib.rs | 2 + juniper_hyper/src/lib.rs | 12 +- juniper_iron/src/lib.rs | 16 +- juniper_subscriptions/src/lib.rs | 4 +- 16 files changed, 77 insertions(+), 249 deletions(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 414d2f299..a7d723ae1 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -45,32 +45,14 @@ trait Character { struct Human { id: String, } -#[graphql_interface] // implementing requires macro attribute too, (°ï½Â°)! -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { id: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - -# fn main() { -let human = Human { id: "human-32".to_owned() }; -// Values type for interface has `From` implementations for all its implementers, -// so we don't need to bother with enum variant names. -let character: CharacterValue = human.into(); -assert_eq!(character.id(), "human-32"); -# } +# +# fn main() {} ``` Also, enum name can be specified explicitly, if desired. @@ -90,71 +72,11 @@ struct Human { id: String, home_planet: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` -### Trait object values - -If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, then it should be specified explicitly in the trait definition. - -Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood. - -> __NOTICE__: -> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it. - -```rust -# extern crate juniper; -# extern crate tokio; -use juniper::{graphql_interface, GraphQLObject}; - -// `dyn` argument accepts the name of type alias for the required trait object, -// and macro generates this alias automatically. -#[graphql_interface(dyn = DynCharacter, for = Human)] -trait Character { - async fn id(&self) -> &str; // async fields are supported natively -} - -#[derive(GraphQLObject)] -#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait, -struct Human { // so it may be specified explicitly when required - id: String, -} -#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too -impl Character for Human { - async fn id(&self) -> &str { - &self.id - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = DynCharacter<__S>)] -struct Droid { - id: String, -} -#[graphql_interface] -impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } -} - -# #[tokio::main] -# async fn main() { -let human = Human { id: "human-32".to_owned() }; -let character: Box = Box::new(human); -assert_eq!(character.id().await, "human-32"); -# } -``` - - ### Ignoring trait methods We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. @@ -176,12 +98,6 @@ trait Character { struct Human { id: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` @@ -278,24 +194,6 @@ struct Human { id: String, name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self, db: &Database) -> Option<&str> { - if db.humans.contains_key(&self.id) { - Some(&self.id) - } else { - None - } - } - - fn name(&self, db: &Database) -> Option<&str> { - if db.humans.contains_key(&self.id) { - Some(&self.name) - } else { - None - } - } -} # # fn main() {} ``` @@ -309,7 +207,7 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`] ```rust # extern crate juniper; -use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; #[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter trait Character { @@ -326,102 +224,40 @@ trait Character { ) -> &'b str where S: Send + Sync; + + fn home_planet(&self) -> &str; } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, name: String, + home_planet: String, } -#[graphql_interface(scalar = S)] -impl Character for Human { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str +#[graphql_object(impl = CharacterValue<__S>)] +impl Human { + async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: Send + Sync, + S: ScalarValue + Send + Sync, { executor.look_ahead().field_name() } - async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str where - S: Send + Sync, + S: ScalarValue + Send + Sync, { &self.name } -} -# -# fn main() {} -``` - - -### Downcasting - -By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` macro argument is used). - -However, if some custom logic is needed to downcast a [GraphQL interface][1] implementer, you may specify either an external function or a trait method to do so. - -```rust -# extern crate juniper; -# use std::collections::HashMap; -use juniper::{graphql_interface, GraphQLObject}; - -struct Database { - droids: HashMap, -} -impl juniper::Context for Database {} - -#[graphql_interface(for = [Human, Droid], context = Database)] -#[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` function -trait Character { - fn id(&self) -> &str; - - #[graphql(downcast)] // makes method a downcast to `Human`, not a field - // NOTICE: The method signature may optionally contain `&Database` context argument. - fn as_human(&self) -> Option<&Human> { - None + + fn home_planet<'c, S>(&'c self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'c str { + // Executor may not be present on the trait method ^^^^^^^^^^^^^^^^^^^^^^^^ + &self.home_planet } } - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue, Context = Database)] -struct Human { - id: String, -} -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn as_human(&self) -> Option<&Self> { - Some(self) - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue, Context = Database)] -struct Droid { - id: String, -} -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - -// External downcast function doesn't have to be a method of a type. -// It's only a matter of the function signature to match the requirements. -fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> { - db.droids.get(ch.id()) -} # # fn main() {} ``` -The attribute syntax `#[graphql_interface(on ImplementerType = resolver_fn)]` follows the [GraphQL syntax for downcasting interface implementer](https://spec.graphql.org/June2018/#example-5cc55). - @@ -445,12 +281,6 @@ struct Human { id: String, home_planet: String, } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} #[derive(GraphQLObject)] #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)] @@ -458,12 +288,6 @@ struct Droid { id: String, primary_function: String, } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 5646a5d9b..9666f430c 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1621,9 +1621,7 @@ mod default_argument { #[graphql(default = "t")] third: String, ) -> String; - fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { - coord.x - } + fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32; } struct Human; @@ -2128,17 +2126,11 @@ mod renamed_all_fields_and_args { #[graphql_interface(rename_all = "none", for = Human)] trait Character { - fn id(&self) -> &str { - "human-32" - } + fn id(&self) -> &str; - async fn home_planet(&self, planet_name: String) -> String { - planet_name - } + async fn home_planet(&self, planet_name: String) -> String; - async fn r#async_info(&self, r#my_num: i32) -> i32 { - r#my_num - } + async fn r#async_info(&self, r#my_num: i32) -> i32; } struct Human; @@ -3039,10 +3031,7 @@ mod executor { trait Character { async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: Send + Sync, - { - executor.look_ahead().field_name() - } + S: Send + Sync; async fn info<'b>( &'b self, diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index f7805eeb3..ea2c0fec2 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -296,6 +296,7 @@ impl InputValue { } /// Resolve all variables to their values. + #[must_use] pub fn into_const(self, vars: &Variables) -> Self where S: Clone, diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 553af48ea..9b9de9497 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -701,7 +701,7 @@ where FieldPath::Root(_) => unreachable!(), }; self.parent_selection_set - .map(|p| { + .and_then(|p| { // Search the parent's fields to find this field within the set let found_field = p.iter().find(|&x| { match *x { @@ -721,7 +721,6 @@ where None } }) - .flatten() .unwrap_or_else(|| { // We didn't find a field in the parent's selection matching // this field, which means we're inside a FragmentSpread diff --git a/juniper/src/executor/owned_executor.rs b/juniper/src/executor/owned_executor.rs index 28b03c3a7..cce1a208e 100644 --- a/juniper/src/executor/owned_executor.rs +++ b/juniper/src/executor/owned_executor.rs @@ -49,6 +49,7 @@ where S: Clone, { #[doc(hidden)] + #[must_use] pub fn type_sub_executor( &self, type_name: Option<&str>, @@ -76,6 +77,7 @@ where } #[doc(hidden)] + #[must_use] pub fn field_sub_executor( &self, field_alias: &'a str, diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index bd14b8586..de324e674 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -820,7 +820,7 @@ macro_rules! const_concat { // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one // after the other byte by byte. #[allow(unsafe_code)] - unsafe { std::str::from_utf8_unchecked(&CON) } + unsafe { ::std::str::from_utf8_unchecked(&CON) } }}; } @@ -941,7 +941,8 @@ macro_rules! format_type { // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one // after the other byte by byte. #[allow(unsafe_code)] - const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + const TYPE_FORMATTED: &str = + unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) }; TYPE_FORMATTED }}; diff --git a/juniper/src/parser/value.rs b/juniper/src/parser/value.rs index 8d66661c9..3ff6d5efc 100644 --- a/juniper/src/parser/value.rs +++ b/juniper/src/parser/value.rs @@ -112,9 +112,7 @@ where .. }, _, - ) => Ok(parser - .next_token()? - .map(|_| InputValue::enum_value(name.to_owned()))), + ) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))), _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), } } diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index d4f350842..ccd43a99a 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -447,6 +447,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Sets the `description` of this [`ScalarMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -457,6 +458,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Overwrites any previously set [specification URL][0]. /// /// [0]: https://spec.graphql.org/October2021#sec--specifiedBy + #[must_use] pub fn specified_by_url(mut self, url: impl Into>) -> Self { self.specified_by_url = Some(url.into()); self @@ -515,6 +517,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Sets the `description` of this [`ObjectMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -523,6 +526,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Set the `interfaces` this [`ObjectMeta`] type implements. /// /// Overwrites any previously set list of interfaces. + #[must_use] pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { self.interface_names = interfaces .iter() @@ -556,6 +560,7 @@ impl<'a, S> EnumMeta<'a, S> { /// Sets the `description` of this [`EnumMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -584,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> { /// Sets the `description` of this [`InterfaceMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -612,6 +618,7 @@ impl<'a> UnionMeta<'a> { /// Sets the `description` of this [`UnionMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -643,6 +650,7 @@ impl<'a, S> InputObjectMeta<'a, S> { /// Set the `description` of this [`InputObjectMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -658,6 +666,7 @@ impl<'a, S> Field<'a, S> { /// Set the `description` of this [`Field`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -666,6 +675,7 @@ impl<'a, S> Field<'a, S> { /// Adds an `argument` to this [`Field`]. /// /// Arguments are unordered and can't contain duplicates by name. + #[must_use] pub fn argument(mut self, argument: Argument<'a, S>) -> Self { match self.arguments { None => { @@ -681,6 +691,7 @@ impl<'a, S> Field<'a, S> { /// Sets this [`Field`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. + #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self @@ -701,6 +712,7 @@ impl<'a, S> Argument<'a, S> { /// Sets the `description` of this [`Argument`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -709,6 +721,7 @@ impl<'a, S> Argument<'a, S> { /// Set the default value of this [`Argument`]. /// /// Overwrites any previously set default value. + #[must_use] pub fn default_value(mut self, val: InputValue) -> Self { self.default_value = Some(val); self @@ -728,6 +741,7 @@ impl EnumValue { /// Sets the `description` of this [`EnumValue`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -736,6 +750,7 @@ impl EnumValue { /// Sets this [`EnumValue`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. + #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 237dc6f84..233e2f347 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -95,11 +95,7 @@ impl Human { Self { id: id.to_owned(), name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(ToOwned::to_owned) - .collect(), + friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(ToOwned::to_owned), home_planet: home_planet.map(|p| p.to_owned()), @@ -181,11 +177,7 @@ impl Droid { Self { id: id.to_owned(), name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(ToOwned::to_owned) - .collect(), + friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(ToOwned::to_owned), primary_function: primary_function.map(ToOwned::to_owned), diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 81341c597..7e8d01cc1 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -141,6 +141,7 @@ impl Nullable { /// Returns the nullable if it contains a value, otherwise returns `b`. #[inline] + #[must_use] pub fn or(self, b: Self) -> Self { match self { Self::Some(_) => self, @@ -151,6 +152,7 @@ impl Nullable { /// Returns the nullable if it contains a value, otherwise calls `f` and /// returns the result. #[inline] + #[must_use] pub fn or_else Nullable>(self, f: F) -> Nullable { match self { Self::Some(_) => self, @@ -161,6 +163,7 @@ impl Nullable { /// Replaces the actual value in the nullable by the value given in parameter, returning the /// old value if present, leaving a `Some` in its place without deinitializing either one. #[inline] + #[must_use] pub fn replace(&mut self, value: T) -> Self { std::mem::replace(self, Self::Some(value)) } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a5659274e..c2a504db4 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -23,7 +23,7 @@ const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body.clone()) { + if let Ok(mut ast) = syn::parse2::(body) { let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); return expand_on_trait(trait_attrs, ast); @@ -137,7 +137,7 @@ fn expand_on_trait( name, description, context, - scalar: scalar.clone(), + scalar, fields, implementers: attr .implementers @@ -178,10 +178,6 @@ fn parse_field( method: &mut syn::TraitItemMethod, renaming: &RenameRule, ) -> Option { - if method.default.is_some() { - return err_default_impl_block(&method.default); - } - let method_ident = &method.sig.ident; let method_attrs = method.attrs.clone(); @@ -199,6 +195,10 @@ fn parse_field( return None; } + if method.default.is_some() { + return err_default_impl_block(&method.default); + } + let name = attr .name .as_ref() diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 409d29757..1c27cf356 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -272,8 +272,10 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// is totally okay. They all will be treated as a single attribute. /// /// [GraphQL interfaces][1] are more like Go's structural interfaces, while -/// Rust's traits are more like typeclasses. So using `impl Trait` isn't an -/// option. +/// Rust's traits are more like typeclasses. Using `impl Trait` isn't an +/// option, so you have to cover all trait's methods with type's fields or +/// impl block. But no one is stopping you from additionally implementing trait +/// manually. /// /// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs index f1fe9bd3b..51c2e914f 100644 --- a/juniper_graphql_ws/src/lib.rs +++ b/juniper_graphql_ws/src/lib.rs @@ -68,6 +68,7 @@ impl ConnectionConfig { /// Specifies the maximum number of in-flight operations that a connection can have. If this /// number is exceeded, attempting to start more will result in an error. By default, there is /// no limit to in-flight operations. + #[must_use] pub fn with_max_in_flight_operations(mut self, max: usize) -> Self { self.max_in_flight_operations = max; self @@ -75,6 +76,7 @@ impl ConnectionConfig { /// Specifies the interval at which to send keep-alives. Specifying a zero duration will /// disable keep-alives. By default, keep-alives are sent every 15 seconds. + #[must_use] pub fn with_keep_alive_interval(mut self, interval: Duration) -> Self { self.keep_alive_interval = interval; self diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 52e67a929..50ccff197 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -286,13 +286,13 @@ enum GraphQLRequestError { } impl fmt::Display for GraphQLRequestError { - fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, &mut f), + GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, f), } } } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 5a6651878..40d1f8039 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -311,15 +311,15 @@ where Subscription: GraphQLType + Send + Sync + 'static, 'a: 'static, { - fn handle(&self, mut req: &mut Request) -> IronResult { + fn handle(&self, req: &mut Request) -> IronResult { let context = (self.context_factory)(req)?; let graphql_request = match req.method { - method::Get => self.handle_get(&mut req)?, + method::Get => self.handle_get(req)?, method::Post => match req.headers.get::().map(ContentType::deref) { Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() { - "json" => self.handle_post_json(&mut req)?, - "graphql" => self.handle_post_graphql(&mut req)?, + "json" => self.handle_post_json(req)?, + "graphql" => self.handle_post_graphql(req)?, _ => return Ok(Response::with(status::BadRequest)), }, _ => return Ok(Response::with(status::BadRequest)), @@ -369,11 +369,11 @@ enum GraphQLIronError { } impl fmt::Display for GraphQLIronError { - fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, &mut f), + GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, f), + GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, f), + GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f), } } } diff --git a/juniper_subscriptions/src/lib.rs b/juniper_subscriptions/src/lib.rs index 5e890b630..e09e11279 100644 --- a/juniper_subscriptions/src/lib.rs +++ b/juniper_subscriptions/src/lib.rs @@ -183,7 +183,7 @@ where ready_vec.push(None); } - let stream = stream::poll_fn(move |mut ctx| -> Poll>> { + let stream = stream::poll_fn(move |ctx| -> Poll>> { let mut obj_iterator = object.iter_mut(); // Due to having to modify `ready_vec` contents (by-move pattern) @@ -204,7 +204,7 @@ where match val { Value::Scalar(stream) => { - match Pin::new(stream).poll_next(&mut ctx) { + match Pin::new(stream).poll_next(ctx) { Poll::Ready(None) => return Poll::Ready(None), Poll::Ready(Some(value)) => { *ready = Some((field_name.clone(), value)); From 1345bffcd8533126e350511411b1d0fed4faffab Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 10:19:16 +0300 Subject: [PATCH 048/122] WIP (Corrections) --- juniper/src/macros/reflection.rs | 9 ++++++++- juniper_codegen/src/common/parse/mod.rs | 2 ++ juniper_codegen/src/graphql_union/mod.rs | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index de324e674..f6fb31bb9 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -281,11 +281,14 @@ pub trait Fields { const NAMES: Names; } +/// [`Types`] of the [GraphQL interfaces][1] implemented by this type. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait Implements { const NAMES: Types; } -/// Stores meta information of [GraphQL fields][1]: +/// Stores meta information of [GraphQL field][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. @@ -471,6 +474,8 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +/// Asserts that `#[graphql_interface(for = ...)]` has all types referencing +/// this interface in `impl = ...` attribute section. #[macro_export] macro_rules! assert_implemented_for { ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { @@ -495,6 +500,8 @@ macro_rules! assert_implemented_for { }; } +/// Asserts that `impl = ...` attribute section has all types referencing this +/// type in `#[graphql_interface(for = ...)]`. #[macro_export] macro_rules! assert_interfaces_impls { ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 7f5cda6d5..0ed3c0dfb 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -330,6 +330,8 @@ impl GenericsExt for syn::Generics { }); if is_generic { + // Replacing with `DefaultScalarValue` instead of + // `()` because generic parameter may be scalar. *ty = parse_quote!(::juniper::DefaultScalarValue); } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 42eb6123b..eb882961d 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -320,7 +320,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); } } @@ -619,7 +619,7 @@ impl Definition { /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let variants = self.variants.iter().map(|var| &var.ty); From 6cb5cef71ada1e7beb6cd8194a463ecf581c49be Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 12:27:38 +0300 Subject: [PATCH 049/122] WIP (Corrections) --- .../src/codegen/interface_attr.rs | 4 ++-- juniper/src/macros/reflection.rs | 5 ++++- juniper/src/types/scalars.rs | 6 +++--- juniper_codegen/src/derive_scalar_value.rs | 6 +++--- juniper_codegen/src/graphql_interface/attr.rs | 2 +- juniper_codegen/src/graphql_interface/mod.rs | 19 +++++++++++-------- juniper_codegen/src/impl_scalar.rs | 6 +++--- juniper_codegen/src/util/mod.rs | 12 ++++++------ 8 files changed, 33 insertions(+), 27 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 9666f430c..8303aeb50 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -883,8 +883,8 @@ mod fallible_field { #[graphql_object(impl = CharacterValue)] impl Droid { - fn id(&self) -> String { - self.id.clone() + fn id(&self) -> Result { + Ok(self.id.clone()) } fn primary_function(&self) -> &str { diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index f6fb31bb9..709bbd865 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -210,9 +210,9 @@ impl + ?Sized> BaseSubTypes for Rc { /// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); /// ``` /// +/// [`VALUE`]: Self::VALUE /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Wrapping-Types -/// [`VALUE`]: Self::VALUE pub trait WrappedType { /// [`WrappedValue`] of this type. const VALUE: WrappedValue; @@ -285,6 +285,9 @@ pub trait Fields { /// /// [1]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait Implements { + /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Interfaces const NAMES: Types; } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 87c293d7b..7e7603e79 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -204,15 +204,15 @@ where } impl reflection::WrappedType for str { - const VALUE: u128 = 1; + const VALUE: reflection::WrappedValue = 1; } impl reflection::BaseType for str { - const NAME: &'static str = "String"; + const NAME: reflection::Type = "String"; } impl reflection::BaseSubTypes for str { - const NAMES: &'static [&'static str] = &[>::NAME]; + const NAMES: reflection::Types = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 9e2698d74..1e25ce913 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -241,20 +241,20 @@ fn impl_scalar_struct( impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const NAME: &'static str = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c2a504db4..a34704490 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -128,7 +128,7 @@ fn expand_on_trait( |c| format_ident!("{}Enum", c.inner().to_string()), ); - let description = attr.description.as_ref().map(|c| c.inner().clone()); + let description = attr.description.as_deref().cloned(); let generated_code = Definition { trait_generics: ast.generics.clone(), vis: ast.vis.clone(), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index a26645d54..9c3ceef10 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -989,10 +989,11 @@ impl Definition { } /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. + /// method, which returns name of the underlying [`implementers`][1] GraphQL + /// type contained in this enum. /// /// [0]: juniper::GraphQLValue::concrete_type_name + /// [1]: Self::implementers #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -1024,10 +1025,11 @@ impl Definition { /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`EnumType`] into its underlying [`Implementer`] type + /// downcasts this enum into its underlying [`implementers`][1] type /// asynchronously. /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + /// [1]: Self::implementers #[must_use] fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); @@ -1056,10 +1058,11 @@ impl Definition { } /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`EnumType`] into its underlying - /// [`Implementer`] type synchronously. + /// method, which downcasts this enum into its underlying + /// [`implementers`][1] type synchronously. /// /// [0]: juniper::GraphQLValue::resolve_into_type + /// [1]: Self::implementers #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); @@ -1125,7 +1128,7 @@ impl Definition { } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. + /// similar) implementation of this enum. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. @@ -1184,8 +1187,8 @@ impl Definition { generics } - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant - /// to hold type parameters. + /// Indicates whether this enum has non-exhaustive phantom variant to hold + /// type parameters. #[must_use] fn has_phantom_variant(&self) -> bool { !self.trait_generics.params.is_empty() diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index b28aabedc..880bbd246 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -331,20 +331,20 @@ pub fn build_scalar( impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type #generic_type_bound { - const NAME: &'static str = #name; + const NAME:::juniper::macros::reflection::Type = #name; } impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type #generic_type_bound { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } impl#generic_type_decl ::juniper::macros::reflection::WrappedType<#generic_type> for #impl_for_type #generic_type_bound { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 1a301717c..38722918f 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -910,20 +910,20 @@ impl GraphQLTypeDefiniton { impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: &'static str = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); @@ -1177,14 +1177,14 @@ impl GraphQLTypeDefiniton { for #ty #type_generics_tokens #where_clause { - const NAME: &'static str = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #type_generics_tokens #where_clause { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } @@ -1192,7 +1192,7 @@ impl GraphQLTypeDefiniton { for #ty #type_generics_tokens #where_clause { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); From 41a2dcf7faeefddd208991025fbee45968100be1 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 12:46:11 +0300 Subject: [PATCH 050/122] WIP (CHANGELOG) --- juniper/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 836472fb0..e85c18174 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -10,6 +10,12 @@ - Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) +- Redesign `#[graphql_interface]` macro. ([#1009](https://github.com/graphql-rust/juniper/pull/1009)): + - Remove support for `#[graphql_interface(dyn)]`. + - Describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait`. + - Forbid default impls on non-skipped trait methods. + - Add support for additional nullable arguments on implementer. + - Add support for returning sub-type on implementer. ## Features From eb8c56d538cbfade79fb6750c9543d2c8b78b3a2 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 12:57:12 +0300 Subject: [PATCH 051/122] WIP (Correction) --- juniper_codegen/src/impl_scalar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 880bbd246..523e99e96 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -331,7 +331,7 @@ pub fn build_scalar( impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type #generic_type_bound { - const NAME:::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type From 7f45aef407a17a74e4b3fe837f68878c168f50ef Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 10 Jan 2022 10:16:46 +0300 Subject: [PATCH 052/122] WIP --- .../juniper_tests/src/codegen/impl_scalar.rs | 7 +- .../src/executor_tests/introspection/mod.rs | 4 +- juniper/src/executor_tests/variables.rs | 4 +- juniper/src/integrations/bson.rs | 14 +- juniper/src/integrations/chrono.rs | 35 +- juniper/src/integrations/chrono_tz.rs | 7 +- juniper/src/integrations/time.rs | 25 +- juniper/src/integrations/url.rs | 7 +- juniper/src/integrations/uuid.rs | 7 +- juniper/src/types/scalars.rs | 35 +- juniper_codegen/src/impl_scalar.rs | 575 +++++++++++++++++- juniper_codegen/src/lib.rs | 18 +- 12 files changed, 628 insertions(+), 110 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index b852416ec..39abfdbdd 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -23,11 +23,8 @@ Syntax to validate: */ -#[graphql_scalar] -impl GraphQLScalar for DefaultName -where - S: ScalarValue, -{ +#[graphql_scalar(scalar = S)] +impl GraphQLScalar for DefaultName { fn resolve(&self) -> Value { Value::scalar(self.0) } diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index a5ec22cbd..21dd65be8 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -22,8 +22,8 @@ enum Sample { struct Scalar(i32); -#[graphql_scalar(name = "SampleScalar")] -impl GraphQLScalar for Scalar { +#[graphql_scalar(name = "SampleScalar", scalar = S)] +impl GraphQLScalar for Scalar { fn resolve(&self) -> Value { Value::scalar(self.0) } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index d05727307..0d0a69c64 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -13,8 +13,8 @@ use crate::{ #[derive(Debug)] struct TestComplexScalar; -#[graphql_scalar] -impl GraphQLScalar for TestComplexScalar { +#[graphql_scalar(scalar = S)] +impl GraphQLScalar for TestComplexScalar { fn resolve(&self) -> Value { graphql_value!("SerializedValue") } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index e1c9c1cf5..e68ead871 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -10,11 +10,8 @@ use crate::{ Value, }; -#[graphql_scalar(description = "ObjectId")] -impl GraphQLScalar for ObjectId -where - S: ScalarValue, -{ +#[graphql_scalar(description = "ObjectId", scalar = S)] +impl GraphQLScalar for ObjectId { fn resolve(&self) -> Value { Value::scalar(self.to_hex()) } @@ -36,11 +33,8 @@ where } } -#[graphql_scalar(description = "UtcDateTime")] -impl GraphQLScalar for UtcDateTime -where - S: ScalarValue, -{ +#[graphql_scalar(description = "UtcDateTime", scalar = S)] +impl GraphQLScalar for UtcDateTime { fn resolve(&self) -> Value { Value::scalar((*self).to_chrono().to_rfc3339()) } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 55cfd50ed..6e6b869bb 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -24,11 +24,8 @@ use crate::{ Value, }; -#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")] -impl GraphQLScalar for DateTime -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime", scalar = S)] +impl GraphQLScalar for DateTime { fn resolve(&self) -> Value { Value::scalar(self.to_rfc3339()) } @@ -51,11 +48,8 @@ where } } -#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")] -impl GraphQLScalar for DateTime -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime", scalar = S)] +impl GraphQLScalar for DateTime { fn resolve(&self) -> Value { Value::scalar(self.to_rfc3339()) } @@ -83,11 +77,8 @@ where // inherent lack of precision required for the time zone resolution. // For serialization and deserialization uses, it is best to use // `NaiveDate` instead." -#[crate::graphql_scalar(description = "NaiveDate")] -impl GraphQLScalar for NaiveDate -where - S: ScalarValue, -{ +#[crate::graphql_scalar(description = "NaiveDate", scalar = S)] +impl GraphQLScalar for NaiveDate { fn resolve(&self) -> Value { Value::scalar(self.format("%Y-%m-%d").to_string()) } @@ -111,11 +102,8 @@ where } #[cfg(feature = "scalar-naivetime")] -#[crate::graphql_scalar(description = "NaiveTime")] -impl GraphQLScalar for NaiveTime -where - S: ScalarValue, -{ +#[crate::graphql_scalar(description = "NaiveTime", scalar = S)] +impl GraphQLScalar for NaiveTime { fn resolve(&self) -> Value { Value::scalar(self.format("%H:%M:%S").to_string()) } @@ -140,11 +128,8 @@ where // JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond // datetimes. Values will be truncated to microsecond resolution. -#[crate::graphql_scalar(description = "NaiveDateTime")] -impl GraphQLScalar for NaiveDateTime -where - S: ScalarValue, -{ +#[crate::graphql_scalar(description = "NaiveDateTime", scalar = S)] +impl GraphQLScalar for NaiveDateTime { fn resolve(&self) -> Value { Value::scalar(self.timestamp() as f64) } diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index dd84aa7a8..fb1d316d8 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -12,11 +12,8 @@ use crate::{ Value, }; -#[graphql_scalar(name = "Tz", description = "Timezone")] -impl GraphQLScalar for Tz -where - S: ScalarValue, -{ +#[graphql_scalar(name = "Tz", description = "Timezone", scalar = S)] +impl GraphQLScalar for Tz { fn resolve(&self) -> Value { Value::scalar(self.name().to_owned()) } diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index 562763330..215065c12 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -57,9 +57,10 @@ const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] \n\n\ [1]: https://graphql-scalars.dev/docs/scalars/date\n\ [2]: https://docs.rs/time/*/time/struct.Date.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", + scalar = S )] -impl GraphQLScalar for Date { +impl GraphQLScalar for Date { fn resolve(&self) -> Value { Value::scalar( self.format(DATE_FORMAT) @@ -113,9 +114,10 @@ const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour] \n\n\ [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\ [2]: https://docs.rs/time/*/time/struct.Time.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" + specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", + scalar = S )] -impl GraphQLScalar for LocalTime { +impl GraphQLScalar for LocalTime { fn resolve(&self) -> Value { Value::scalar( if self.millisecond() == 0 { @@ -160,9 +162,10 @@ const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] = \n\n\ See also [`time::PrimitiveDateTime`][2] for details.\ \n\n\ - [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html" + [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html", + scalar = S )] -impl GraphQLScalar for LocalDateTime { +impl GraphQLScalar for LocalDateTime { fn resolve(&self) -> Value { Value::scalar( self.format(LOCAL_DATE_TIME_FORMAT) @@ -203,9 +206,10 @@ impl GraphQLScalar for LocalDateTime { [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", + scalar = S )] -impl GraphQLScalar for DateTime { +impl GraphQLScalar for DateTime { fn resolve(&self) -> Value { Value::scalar( self.to_offset(UtcOffset::UTC) @@ -249,9 +253,10 @@ const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\ [1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\ [2]: https://docs.rs/time/*/time/struct.UtcOffset.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset" + specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", + scalar = S )] -impl GraphQLScalar for UtcOffset { +impl GraphQLScalar for UtcOffset { fn resolve(&self) -> Value { Value::scalar( self.format(UTC_OFFSET_FORMAT) diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 97481bb20..e1167b528 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -7,11 +7,8 @@ use crate::{ Value, }; -#[crate::graphql_scalar(description = "Url")] -impl GraphQLScalar for Url -where - S: ScalarValue, -{ +#[crate::graphql_scalar(description = "Url", scalar = S)] +impl GraphQLScalar for Url { fn resolve(&self) -> Value { Value::scalar(self.as_str().to_owned()) } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index ae0c04a87..e7ce196b5 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -10,11 +10,8 @@ use crate::{ Value, }; -#[crate::graphql_scalar(description = "Uuid")] -impl GraphQLScalar for Uuid -where - S: ScalarValue, -{ +#[crate::graphql_scalar(description = "Uuid", scalar = S)] +impl GraphQLScalar for Uuid { fn resolve(&self) -> Value { Value::scalar(self.to_string()) } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 7e7603e79..3f1050faf 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -51,11 +51,8 @@ impl fmt::Display for ID { } } -#[crate::graphql_scalar(name = "ID")] -impl GraphQLScalar for ID -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "ID", scalar = S)] +impl GraphQLScalar for ID { fn resolve(&self) -> Value { Value::scalar(self.0.clone()) } @@ -76,11 +73,8 @@ where } } -#[crate::graphql_scalar(name = "String")] -impl GraphQLScalar for String -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "String", scalar = S)] +impl GraphQLScalar for String { fn resolve(&self) -> Value { Value::scalar(self.clone()) } @@ -276,11 +270,8 @@ where } } -#[crate::graphql_scalar(name = "Boolean")] -impl GraphQLScalar for bool -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "Boolean", scalar = S)] +impl GraphQLScalar for bool { fn resolve(&self) -> Value { Value::scalar(*self) } @@ -297,11 +288,8 @@ where } } -#[crate::graphql_scalar(name = "Int")] -impl GraphQLScalar for i32 -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "Int", scalar = S)] +impl GraphQLScalar for i32 { fn resolve(&self) -> Value { Value::scalar(*self) } @@ -322,11 +310,8 @@ where } } -#[crate::graphql_scalar(name = "Float")] -impl GraphQLScalar for f64 -where - S: ScalarValue, -{ +#[crate::graphql_scalar(name = "Float", scalar = S)] +impl GraphQLScalar for f64 { fn resolve(&self) -> Value { Value::scalar(*self) } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 523e99e96..c8e39dac2 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -1,12 +1,554 @@ #![allow(clippy::collapsible_if)] +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + ext::IdentExt, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, + token, +}; +use url::Url; + use crate::{ + common::{ + parse::{ + self, + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + scalar, + }, result::GraphQLScope, - util::{self, span_container::SpanContainer}, + util::{self, filter_attrs, get_doc_comment, span_container::SpanContainer, DeprecationAttr}, }; -use proc_macro2::TokenStream; -use quote::quote; -use syn::spanned::Spanned; + +/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. +const ERR: GraphQLScope = GraphQLScope::ImplScalar; + +/// Expands `#[graphql_interface]` macro into generated code. +pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { + if let Ok(mut ast) = syn::parse2::(body) { + let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); + return expand_on_impl_block(attrs, ast); + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_scalar] attribute is applicable to impl trait only", + )) +} + +fn expand_on_impl_block( + attrs: Vec, + mut ast: syn::ItemImpl, +) -> syn::Result { + let attr = Attr::from_attrs("graphql_scalar", &attrs)?; + + let mut self_ty = ast.self_ty.clone(); + if let syn::Type::Group(group) = self_ty.as_ref() { + self_ty = group.elem.clone(); + } + + let name = attr + .name + .map(SpanContainer::into_inner) + .or_else(|| { + if let syn::Type::Path(path) = self_ty.as_ref() { + path.path + .segments + .last() + .map(|last| last.ident.unraw().to_string()) + } else { + None + } + }) + .ok_or_else(|| { + ERR.custom_error( + self_ty.span(), + "unable to find target for implementation target for `GraphQLScalar`", + ) + })?; + + let mut scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + let mut resolve_body: Option = None; + let mut from_input_value_arg: Option = None; + let mut from_input_value_body: Option = None; + let mut from_input_value_result: Option = None; + let mut from_str_arg: Option = None; + let mut from_str_body: Option = None; + let mut from_str_result: Option = None; + for impl_item in &ast.items { + if let syn::ImplItem::Method(method) = impl_item.clone() { + match method.sig.ident.to_string().as_str() { + "resolve" => { + resolve_body = Some(method.block); + } + "from_input_value" => { + from_input_value_arg = get_first_method_arg(method.sig.inputs); + from_input_value_result = get_method_return_type(method.sig.output); + from_input_value_body = Some(method.block); + } + "from_str" => { + from_str_arg = get_first_method_arg(method.sig.inputs); + from_str_result = get_method_return_type(method.sig.output); + from_str_body = Some(method.block); + + if scalar.is_implicit_generic() { + if let Some(sc) = get_scalar(&from_str_result) { + scalar = scalar::Type::Concrete(sc) + } + } + } + _ => (), + } + } + } + + Ok(Definition { + impl_for_type: *ast.self_ty.clone(), + generics: ast.generics.clone(), + name, + description: attr.description.as_deref().cloned(), + scalar, + specified_by_url: attr.specified_by_url.as_deref().cloned(), + resolve_body: resolve_body.ok_or_else(|| { + ERR.custom_error(ast.span(), "unable to find body of `resolve` method") + })?, + from_input_value_arg: from_input_value_arg.ok_or_else(|| { + ERR.custom_error( + ast.span(), + "unable to find argument for `from_input_value` method", + ) + })?, + from_input_value_body: from_input_value_body.ok_or_else(|| { + ERR.custom_error( + ast.span(), + "unable to find body of `from_input_value` method", + ) + })?, + from_input_value_result: from_input_value_result.ok_or_else(|| { + ERR.custom_error( + ast.span(), + "unable to find return type of `from_input_value` method", + ) + })?, + from_str_arg: from_str_arg.ok_or_else(|| { + ERR.custom_error(ast.span(), "unable to find argument for `from_str` method") + })?, + from_str_body: from_str_body.ok_or_else(|| { + ERR.custom_error(ast.span(), "unable to find body of `from_str` method") + })?, + from_str_result: from_str_result.ok_or_else(|| { + ERR.custom_error( + ast.span(), + "unable to find return type of `from_str` method", + ) + })?, + } + .to_token_stream()) +} + +#[derive(Default)] +struct Attr { + pub name: Option>, + pub description: Option>, + pub deprecation: Option>, + pub specified_by_url: Option>, + pub scalar: Option>, +} + +impl Parse for Attr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "specified_by_url" => { + input.parse::()?; + let lit = input.parse::()?; + let url = lit.value().parse::().map_err(|err| { + ERR.custom_error(lit.span(), format!("failed to parse URL: {}", err)) + })?; + out.specified_by_url + .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl Attr { + /// Tries to merge two [`Attr`]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), + deprecation: try_merge_opt!(deprecation: self, another), + specified_by_url: try_merge_opt!(specified_by_url: self, another), + scalar: try_merge_opt!(scalar: self, another), + }) + } + + /// Parses [`Attr`] 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 attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); + } + + Ok(attr) + } +} + +pub struct Definition { + impl_for_type: syn::Type, + generics: syn::Generics, + name: String, + scalar: scalar::Type, + description: Option, + resolve_body: syn::Block, + from_input_value_arg: syn::Ident, + from_input_value_body: syn::Block, + from_input_value_result: syn::Type, + from_str_arg: syn::Ident, + from_str_body: syn::Block, + from_str_result: syn::Type, + specified_by_url: Option, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_output_and_input_type_tokens().to_tokens(into); + self.impl_type_tokens().to_tokens(into); + self.impl_value_tokens().to_tokens(into); + self.impl_value_async().to_tokens(into); + self.impl_to_input_value_tokens().to_tokens(into); + self.impl_from_input_value_tokens().to_tokens(into); + self.impl_parse_scalar_value_tokens().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); + } +} + +impl Definition { + /// 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 + #[must_use] + fn impl_output_and_input_type_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ty + #where_clause { } + + impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty + #where_clause { } + } + } + + fn impl_type_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let name = &self.name; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let description = self + .description + .as_ref() + .map(|val| quote! { .description(#val) }); + let specified_by_url = self.specified_by_url.as_ref().map(|url| { + let url_lit = url.as_str(); + quote! { .specified_by_url(#url_lit) } + }); + + quote! { + impl#impl_gens ::juniper::GraphQLType<#scalar> for #ty + #where_clause + { + fn name(_: &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'__registry>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'__registry, #scalar>, + ) -> ::juniper::meta::MetaType<'__registry, #scalar> + where + #scalar: '__registry, + { + registry.build_scalar_type::(info) + #description + #specified_by_url + .into_meta() + } + } + } + } + + fn impl_value_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + let resolve_body = &self.resolve_body; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ty + #where_clause + { + type Context = (); + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + + fn resolve( + &self, + info: &(), + selection: Option<&[::juniper::Selection<#scalar>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + Ok(#resolve_body) + } + } + } + } + + fn impl_value_async(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty + #where_clause + { + fn resolve_async<'__l>( + &'__l self, + info: &'__l Self::TypeInfo, + selection_set: Option<&'__l [::juniper::Selection<#scalar>]>, + executor: &'__l ::juniper::Executor, + ) -> ::juniper::BoxFuture<'__l, ::juniper::ExecutionResult<#scalar>> { + use ::juniper::futures::future; + let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); + Box::pin(future::ready(v)) + } + } + } + } + + fn impl_to_input_value_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + let resolve_body = &self.resolve_body; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::ToInputValue<#scalar> for #ty + #where_clause + { + fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { + let v = #resolve_body; + ::juniper::ToInputValue::to_input_value(&v) + } + } + } + } + + fn impl_from_input_value_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + let from_input_value_result = &self.from_input_value_result; + let from_input_value_arg = &self.from_input_value_arg; + let from_input_value_body = &self.from_input_value_body; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty + #where_clause + { + type Error = <#from_input_value_result as ::juniper::macros::helper::ExtractError>::Error; + + fn from_input_value(#from_input_value_arg: &::juniper::InputValue<#scalar>) -> #from_input_value_result { + #from_input_value_body + } + } + } + } + + fn impl_parse_scalar_value_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + let from_str_result = &self.from_str_result; + let from_str_arg = &self.from_str_arg; + let from_str_body = &self.from_str_body; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty + #where_clause + { + fn from_str<'a>( + #from_str_arg: ::juniper::parser::ScalarToken<'a>, + ) -> #from_str_result { + #from_str_body + } + } + } + } + + fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + let name = &self.name; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::macros::reflection::BaseType<#scalar> for #ty + #where_clause + { + const NAME: ::juniper::macros::reflection::Type = #name; + } + + impl#impl_gens ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty + #where_clause + { + const NAMES: ::juniper::macros::reflection::Types = + &[>::NAME]; + } + + impl#impl_gens ::juniper::macros::reflection::WrappedType<#scalar> for #ty + #where_clause + { + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this enum. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ty = &self.impl_for_type; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ty#ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} #[derive(Debug)] struct ScalarCodegenInput { @@ -41,6 +583,31 @@ fn get_method_return_type(output: syn::ReturnType) -> Option { } } +// Find the enum type by inspecting the type parameter on the return value +fn get_scalar(return_type: &Option) -> Option { + if let Some(syn::Type::Path(type_path)) = return_type { + let path_segment = type_path + .path + .segments + .iter() + .find(|ps| matches!(ps.arguments, syn::PathArguments::AngleBracketed(_))); + + if let Some(path_segment) = path_segment { + if let syn::PathArguments::AngleBracketed(generic_args) = &path_segment.arguments { + let generic_type_arg = generic_args.args.iter().find(|generic_type_arg| { + matches!(generic_type_arg, syn::GenericArgument::Type(_)) + }); + + if let Some(syn::GenericArgument::Type(scalar)) = generic_type_arg { + return Some(scalar.clone()); + } + } + } + } + + None +} + // Find the enum type by inspecting the type parameter on the return value fn get_enum_type(return_type: &Option) -> Option { if let Some(syn::Type::Path(type_path)) = return_type { diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1c27cf356..92990332d 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -226,11 +226,9 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// description = "An opaque identifier, represented as a string", /// // A specification URL. /// specified_by_url = "https://tools.ietf.org/html/rfc4122", +/// scalar = S, /// )] -/// impl GraphQLScalar for UserID -/// where -/// S: juniper::ScalarValue -/// { +/// impl GraphQLScalar for UserID { /// fn resolve(&self) -> juniper::Value { /// juniper::Value::scalar(self.0.to_owned()) /// } @@ -255,14 +253,10 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// usable as arguments and default values. #[proc_macro_error] #[proc_macro_attribute] -pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { - let args = proc_macro2::TokenStream::from(args); - let input = proc_macro2::TokenStream::from(input); - let gen = impl_scalar::build_scalar(args, input, GraphQLScope::ImplScalar); - match gen { - Ok(gen) => gen.into(), - Err(err) => proc_macro_error::abort!(err), - } +pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { + impl_scalar::expand(attr.into(), body.into()) + .unwrap_or_abort() + .into() } /// `#[graphql_interface]` macro for generating a [GraphQL interface][1] From 81c4df96bcc178552aafeba030b8cef6e90f9739 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 10 Jan 2022 15:25:46 +0300 Subject: [PATCH 053/122] Migrate to real GraphQLScalar trait --- docs/book/content/types/scalars.md | 15 +- .../fail/scalar/impl_invalid_url.rs | 8 +- .../fail/scalar/impl_invalid_url.stderr | 4 +- .../juniper_tests/src/codegen/impl_scalar.rs | 57 +- .../juniper_tests/src/custom_scalar.rs | 14 +- .../src/executor_tests/introspection/mod.rs | 14 +- juniper/src/executor_tests/variables.rs | 16 +- juniper/src/integrations/bson.rs | 28 +- juniper/src/integrations/chrono.rs | 62 ++- juniper/src/integrations/chrono_tz.rs | 14 +- juniper/src/integrations/time.rs | 67 +-- juniper/src/integrations/url.rs | 14 +- juniper/src/integrations/uuid.rs | 14 +- juniper/src/lib.rs | 7 +- juniper/src/types/scalars.rs | 61 +- juniper/src/value/mod.rs | 27 +- juniper_codegen/src/impl_scalar.rs | 519 ++---------------- juniper_codegen/src/lib.rs | 19 +- 18 files changed, 317 insertions(+), 643 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index c8382833f..263e8ae43 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -115,29 +115,32 @@ The example below is used just for illustration. # } # } # -use juniper::{Value, ParseScalarResult, ParseScalarValue}; +use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value}; use date::Date; #[juniper::graphql_scalar(description = "Date")] -impl GraphQLScalar for Date +impl GraphQLScalar for Date where S: ScalarValue { + // Error of the `from_input_value()` method. + // NOTE: Should implement `IntoFieldError`. + type Error = String; + // Define how to convert your custom scalar into a primitive type. - fn resolve(&self) -> Value { + fn resolve(&self) -> Value { Value::scalar(self.to_string()) } // Define how to parse a primitive type into your custom scalar. - // NOTE: The error type should implement `IntoFieldError`. - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) } // Define how to parse a string value. - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs index a71da71b1..15ac91738 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs @@ -4,17 +4,19 @@ struct ScalarSpecifiedByUrl(i32); #[graphql_scalar(specified_by_url = "not an url")] impl GraphQLScalar for ScalarSpecifiedByUrl { + type Error = String; + fn resolve(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(ScalarSpecifiedByUrl) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr index 999a04b6b..24f741b4d 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/impl_invalid_url.rs:5:18 + --> fail/scalar/impl_invalid_url.rs:5:37 | 5 | #[graphql_scalar(specified_by_url = "not an url")] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 39abfdbdd..cf9380505 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -1,6 +1,7 @@ use juniper::{ execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, Object, ParseScalarResult, ParseScalarValue, RootNode, Value, + EmptyMutation, EmptySubscription, GraphQLScalar, InputValue, Object, ParseScalarResult, + ParseScalarValue, RootNode, ScalarToken, ScalarValue, Value, }; use crate::custom_scalar::MyScalarValue; @@ -23,42 +24,48 @@ Syntax to validate: */ -#[graphql_scalar(scalar = S)] -impl GraphQLScalar for DefaultName { - fn resolve(&self) -> Value { +#[graphql_scalar] +impl GraphQLScalar for DefaultName { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(DefaultName) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } #[graphql_scalar] impl GraphQLScalar for OtherOrder { + type Error = String; + fn resolve(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(OtherOrder) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } #[graphql_scalar(name = "ANamedScalar")] impl GraphQLScalar for Named { + type Error = String; + fn resolve(&self) -> Value { Value::scalar(self.0) } @@ -69,30 +76,34 @@ impl GraphQLScalar for Named { .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } #[graphql_scalar(description = "A sample scalar, represented as an integer")] impl GraphQLScalar for ScalarDescription { + type Error = String; + fn resolve(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(ScalarDescription) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } #[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")] impl GraphQLScalar for ScalarSpecifiedByUrl { + type Error = String; + fn resolve(&self) -> Value { Value::scalar(self.0) } @@ -103,7 +114,7 @@ impl GraphQLScalar for ScalarSpecifiedByUrl { .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } @@ -111,22 +122,24 @@ impl GraphQLScalar for ScalarSpecifiedByUrl { macro_rules! impl_scalar { ($name: ident) => { #[graphql_scalar] - impl GraphQLScalar for $name + impl GraphQLScalar for $name where S: ScalarValue, { - fn resolve(&self) -> Value { + type Error = &'static str; + + fn resolve(&self) -> Value { Value::scalar(self.0.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_scalar_value() .and_then(|v| v.as_str()) .and_then(|s| Some(Self(s.to_owned()))) .ok_or_else(|| "Expected `String`") } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } @@ -160,18 +173,20 @@ impl Root { struct WithCustomScalarValue(i32); #[graphql_scalar] -impl GraphQLScalar for WithCustomScalarValue { +impl GraphQLScalar for WithCustomScalarValue { + type Error = String; + fn resolve(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(WithCustomScalarValue) .ok_or_else(|| format!("Expected Int, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { >::from_str(value) } } @@ -220,6 +235,8 @@ fn path_in_resolve_return_type() { #[graphql_scalar] impl GraphQLScalar for ResolvePath { + type Error = String; + fn resolve(&self) -> self::Value { Value::scalar(self.0) } @@ -230,7 +247,7 @@ fn path_in_resolve_return_type() { .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index a59b73002..8a40e3f7a 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -6,8 +6,8 @@ use juniper::{ graphql_vars, parser::{ParseError, ScalarToken, Token}, serde::{de, Deserialize, Deserializer, Serialize}, - EmptyMutation, FieldResult, GraphQLScalarValue, InputValue, Object, ParseScalarResult, - RootNode, ScalarValue, Value, Variables, + EmptyMutation, FieldResult, GraphQLScalar, GraphQLScalarValue, InputValue, Object, + ParseScalarResult, RootNode, ScalarValue, Value, Variables, }; #[derive(GraphQLScalarValue, Clone, Debug, PartialEq, Serialize)] @@ -133,18 +133,20 @@ impl<'de> Deserialize<'de> for MyScalarValue { } #[graphql_scalar(name = "Long")] -impl GraphQLScalar for i64 { - fn resolve(&self) -> Value { +impl GraphQLScalar for i64 { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_scalar_value::() .copied() .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 21dd65be8..90358a159 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::{ParseScalarResult, ParseScalarValue, Value}, - GraphQLEnum, + GraphQLEnum, GraphQLScalar, InputValue, ScalarToken, ScalarValue, }; #[derive(GraphQLEnum)] @@ -22,19 +22,21 @@ enum Sample { struct Scalar(i32); -#[graphql_scalar(name = "SampleScalar", scalar = S)] -impl GraphQLScalar for Scalar { - fn resolve(&self) -> Value { +#[graphql_scalar(name = "SampleScalar")] +impl GraphQLScalar for Scalar { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(Scalar) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 0d0a69c64..50a0b2df0 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,32 +1,34 @@ use crate::{ executor::Variables, graphql_object, graphql_scalar, graphql_value, graphql_vars, - parser::SourcePosition, + parser::{ScalarToken, SourcePosition}, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, validation::RuleError, value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue}, GraphQLError::ValidationError, - GraphQLInputObject, + GraphQLInputObject, GraphQLScalar, InputValue, ScalarValue, Value, }; #[derive(Debug)] struct TestComplexScalar; -#[graphql_scalar(scalar = S)] -impl GraphQLScalar for TestComplexScalar { - fn resolve(&self) -> Value { +#[graphql_scalar] +impl GraphQLScalar for TestComplexScalar { + type Error = String; + + fn resolve(&self) -> Value { graphql_value!("SerializedValue") } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") .map(|_| TestComplexScalar) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index e68ead871..5aecb2f96 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -5,18 +5,20 @@ use chrono::prelude::*; use crate::{ graphql_scalar, - parser::{ParseError, ScalarToken, Token}, + parser::{ParseError, Token}, value::ParseScalarResult, - Value, + GraphQLScalar, InputValue, ScalarToken, ScalarValue, Value, }; -#[graphql_scalar(description = "ObjectId", scalar = S)] -impl GraphQLScalar for ObjectId { - fn resolve(&self) -> Value { +#[graphql_scalar(description = "ObjectId")] +impl GraphQLScalar for ObjectId { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.to_hex()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -24,7 +26,7 @@ impl GraphQLScalar for ObjectId { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(val) = value { Ok(S::from(val.to_owned())) } else { @@ -33,13 +35,15 @@ impl GraphQLScalar for ObjectId { } } -#[graphql_scalar(description = "UtcDateTime", scalar = S)] -impl GraphQLScalar for UtcDateTime { - fn resolve(&self) -> Value { +#[graphql_scalar(description = "UtcDateTime")] +impl GraphQLScalar for UtcDateTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar((*self).to_chrono().to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -49,7 +53,7 @@ impl GraphQLScalar for UtcDateTime { .map(Self::from_chrono) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(val) = value { Ok(S::from(val.to_owned())) } else { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 6e6b869bb..bb9130855 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -21,16 +21,18 @@ use chrono::prelude::*; use crate::{ parser::{ParseError, ScalarToken, Token}, value::{ParseScalarResult, ParseScalarValue}, - Value, + GraphQLScalar, InputValue, ScalarValue, Value, }; -#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime", scalar = S)] -impl GraphQLScalar for DateTime { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")] +impl GraphQLScalar for DateTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result, String> { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -39,7 +41,7 @@ impl GraphQLScalar for DateTime { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -48,13 +50,15 @@ impl GraphQLScalar for DateTime { } } -#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime", scalar = S)] -impl GraphQLScalar for DateTime { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")] +impl GraphQLScalar for DateTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result, String> { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -63,7 +67,7 @@ impl GraphQLScalar for DateTime { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -77,13 +81,15 @@ impl GraphQLScalar for DateTime { // inherent lack of precision required for the time zone resolution. // For serialization and deserialization uses, it is best to use // `NaiveDate` instead." -#[crate::graphql_scalar(description = "NaiveDate", scalar = S)] -impl GraphQLScalar for NaiveDate { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(description = "NaiveDate")] +impl GraphQLScalar for NaiveDate { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.format("%Y-%m-%d").to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -92,7 +98,7 @@ impl GraphQLScalar for NaiveDate { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -102,13 +108,15 @@ impl GraphQLScalar for NaiveDate { } #[cfg(feature = "scalar-naivetime")] -#[crate::graphql_scalar(description = "NaiveTime", scalar = S)] -impl GraphQLScalar for NaiveTime { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(description = "NaiveTime")] +impl GraphQLScalar for NaiveTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.format("%H:%M:%S").to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -117,7 +125,7 @@ impl GraphQLScalar for NaiveTime { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -128,13 +136,15 @@ impl GraphQLScalar for NaiveTime { // JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond // datetimes. Values will be truncated to microsecond resolution. -#[crate::graphql_scalar(description = "NaiveDateTime", scalar = S)] -impl GraphQLScalar for NaiveDateTime { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(description = "NaiveDateTime")] +impl GraphQLScalar for NaiveDateTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.timestamp() as f64) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) .and_then(|f| { @@ -144,7 +154,7 @@ impl GraphQLScalar for NaiveDateTime { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index fb1d316d8..df18c622d 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -9,16 +9,18 @@ use crate::{ graphql_scalar, parser::{ParseError, ScalarToken, Token}, value::ParseScalarResult, - Value, + GraphQLScalar, InputValue, ScalarValue, Value, }; -#[graphql_scalar(name = "Tz", description = "Timezone", scalar = S)] -impl GraphQLScalar for Tz { - fn resolve(&self) -> Value { +#[graphql_scalar(name = "Tz", description = "Timezone")] +impl GraphQLScalar for Tz { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.name().to_owned()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -27,7 +29,7 @@ impl GraphQLScalar for Tz { }) } - fn from_str<'a>(val: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(val: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = val { Ok(S::from(s.to_owned())) } else { diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index 215065c12..78e214b67 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -30,7 +30,7 @@ use crate::{ graphql_scalar, parser::{ParseError, ScalarToken, Token}, value::ParseScalarResult, - Value, + GraphQLScalar, InputValue, ScalarValue, Value, }; pub use time::{ @@ -57,24 +57,25 @@ const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] \n\n\ [1]: https://graphql-scalars.dev/docs/scalars/date\n\ [2]: https://docs.rs/time/*/time/struct.Date.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", - scalar = S + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" )] -impl GraphQLScalar for Date { - fn resolve(&self) -> Value { +impl GraphQLScalar for Date { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar( self.format(DATE_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)), ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -114,11 +115,12 @@ const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour] \n\n\ [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\ [2]: https://docs.rs/time/*/time/struct.Time.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", - scalar = S + specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" )] -impl GraphQLScalar for LocalTime { - fn resolve(&self) -> Value { +impl GraphQLScalar for LocalTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar( if self.millisecond() == 0 { self.format(LOCAL_TIME_FORMAT_NO_MILLIS) @@ -129,7 +131,7 @@ impl GraphQLScalar for LocalTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -143,7 +145,7 @@ impl GraphQLScalar for LocalTime { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -162,18 +164,19 @@ const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] = \n\n\ See also [`time::PrimitiveDateTime`][2] for details.\ \n\n\ - [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html", - scalar = S + [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html" )] -impl GraphQLScalar for LocalDateTime { - fn resolve(&self) -> Value { +impl GraphQLScalar for LocalDateTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar( self.format(LOCAL_DATE_TIME_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)), ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -182,7 +185,7 @@ impl GraphQLScalar for LocalDateTime { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -206,11 +209,12 @@ impl GraphQLScalar for LocalDateTime { [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", - scalar = S + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" )] -impl GraphQLScalar for DateTime { - fn resolve(&self) -> Value { +impl GraphQLScalar for DateTime { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar( self.to_offset(UtcOffset::UTC) .format(&Rfc3339) @@ -218,7 +222,7 @@ impl GraphQLScalar for DateTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -227,7 +231,7 @@ impl GraphQLScalar for DateTime { .map(|dt| dt.to_offset(UtcOffset::UTC)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -253,18 +257,19 @@ const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\ [1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\ [2]: https://docs.rs/time/*/time/struct.UtcOffset.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", - scalar = S + specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset" )] -impl GraphQLScalar for UtcOffset { - fn resolve(&self) -> Value { +impl GraphQLScalar for UtcOffset { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar( self.format(UTC_OFFSET_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)), ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -272,7 +277,7 @@ impl GraphQLScalar for UtcOffset { }) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index e1167b528..eaa0e2c73 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -4,22 +4,24 @@ use url::Url; use crate::{ value::{ParseScalarResult, ParseScalarValue}, - Value, + GraphQLScalar, InputValue, ScalarToken, ScalarValue, Value, }; -#[crate::graphql_scalar(description = "Url", scalar = S)] -impl GraphQLScalar for Url { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(description = "Url")] +impl GraphQLScalar for Url { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.as_str().to_owned()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index e7ce196b5..dff00bcae 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -7,22 +7,24 @@ use uuid::Uuid; use crate::{ parser::{ParseError, ScalarToken, Token}, value::ParseScalarResult, - Value, + GraphQLScalar, InputValue, ScalarValue, Value, }; -#[crate::graphql_scalar(description = "Uuid", scalar = S)] -impl GraphQLScalar for Uuid { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(description = "Uuid")] +impl GraphQLScalar for Uuid { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 406879726..3fda0a904 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -168,7 +168,7 @@ pub use crate::{ subscription::{ExtractTypeFromStream, IntoFieldResult}, AsDynGraphQLValue, }, - parser::{ParseError, Spanning}, + parser::{ParseError, ScalarToken, Spanning}, schema::{ meta, model::{RootNode, SchemaType}, @@ -185,7 +185,10 @@ pub use crate::{ }, }, validation::RuleError, - value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value}, + value::{ + DefaultScalarValue, GraphQLScalar, Object, ParseScalarResult, ParseScalarValue, + ScalarValue, Value, + }, }; /// An error that prevented query execution diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 3f1050faf..880b51a0f 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -16,6 +16,7 @@ use crate::{ subscriptions::GraphQLSubscriptionValue, }, value::{ParseScalarResult, ScalarValue, Value}, + GraphQLScalar, }; /// An ID as defined by the GraphQL specification @@ -51,13 +52,15 @@ impl fmt::Display for ID { } } -#[crate::graphql_scalar(name = "ID", scalar = S)] -impl GraphQLScalar for ID { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "ID")] +impl GraphQLScalar for ID { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.0.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .or_else(|| v.as_int_value().map(|i| i.to_string())) @@ -65,7 +68,7 @@ impl GraphQLScalar for ID { .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { match value { ScalarToken::String(value) | ScalarToken::Int(value) => Ok(S::from(value.to_owned())), _ => Err(ParseError::UnexpectedToken(Token::Scalar(value))), @@ -73,19 +76,21 @@ impl GraphQLScalar for ID { } } -#[crate::graphql_scalar(name = "String", scalar = S)] -impl GraphQLScalar for String { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "String")] +impl GraphQLScalar for String { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(self.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .ok_or_else(|| format!("Expected `String`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); let mut char_iter = value.chars(); @@ -270,36 +275,40 @@ where } } -#[crate::graphql_scalar(name = "Boolean", scalar = S)] -impl GraphQLScalar for bool { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "Boolean")] +impl GraphQLScalar for bool { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_boolean) .ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { // Bools are parsed separately - they shouldn't reach this code path Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -#[crate::graphql_scalar(name = "Int", scalar = S)] -impl GraphQLScalar for i32 { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "Int")] +impl GraphQLScalar for i32 { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) @@ -310,18 +319,20 @@ impl GraphQLScalar for i32 { } } -#[crate::graphql_scalar(name = "Float", scalar = S)] -impl GraphQLScalar for f64 { - fn resolve(&self) -> Value { +#[crate::graphql_scalar(name = "Float")] +impl GraphQLScalar for f64 { + type Error = String; + + fn resolve(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) } - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { match value { ScalarToken::Int(v) => v .parse() diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index e3457c8eb..62da91b58 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -5,7 +5,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem}; use crate::{ ast::{InputValue, ToInputValue}, - parser::Spanning, + parser::{ScalarToken, Spanning}, }; pub use self::{ @@ -13,6 +13,31 @@ pub use self::{ scalar::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue}, }; +/// [GraphQL scalar][1] definition. Implementation of this trait should be +/// attributed with [`graphql_scalar`]. +/// +/// [`graphql_scalar`]: crate::graphql_scalar +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +pub trait GraphQLScalar: Sized { + /// Error of converting [`InputValue`] into this [GraphQL scalar][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + type Error; + + /// Resolves this [GraphQL scalar][1] into [`Value`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn resolve(&self) -> Value; + + /// Parses [`InputValue`] into this [GraphQL scalar][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn from_input_value(v: &InputValue) -> Result; + + /// Resolves [`ScalarToken`] literal into `S`. + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; +} + /// Serializable value returned from query and field execution. /// /// Used by the execution engine and resolvers to build up the response diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index c8e39dac2..f4086bcee 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -21,7 +21,7 @@ use crate::{ scalar, }, result::GraphQLScope, - util::{self, filter_attrs, get_doc_comment, span_container::SpanContainer, DeprecationAttr}, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer, DeprecationAttr}, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -43,7 +43,7 @@ pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, - mut ast: syn::ItemImpl, + ast: syn::ItemImpl, ) -> syn::Result { let attr = Attr::from_attrs("graphql_scalar", &attrs)?; @@ -72,84 +72,55 @@ fn expand_on_impl_block( ) })?; - let mut scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - - let mut resolve_body: Option = None; - let mut from_input_value_arg: Option = None; - let mut from_input_value_body: Option = None; - let mut from_input_value_result: Option = None; - let mut from_str_arg: Option = None; - let mut from_str_body: Option = None; - let mut from_str_result: Option = None; - for impl_item in &ast.items { - if let syn::ImplItem::Method(method) = impl_item.clone() { - match method.sig.ident.to_string().as_str() { - "resolve" => { - resolve_body = Some(method.block); - } - "from_input_value" => { - from_input_value_arg = get_first_method_arg(method.sig.inputs); - from_input_value_result = get_method_return_type(method.sig.output); - from_input_value_body = Some(method.block); - } - "from_str" => { - from_str_arg = get_first_method_arg(method.sig.inputs); - from_str_result = get_method_return_type(method.sig.output); - from_str_body = Some(method.block); - - if scalar.is_implicit_generic() { - if let Some(sc) = get_scalar(&from_str_result) { - scalar = scalar::Type::Concrete(sc) - } + let (_, trait_ty, _) = ast.trait_.as_ref().ok_or_else(|| { + ERR.custom_error( + ast.impl_token.span(), + "expected GraphQLScalar trait implementation", + ) + })?; + + let get_scalar = || { + if let Some(last_seg) = trait_ty.segments.last() { + match &last_seg.arguments { + syn::PathArguments::AngleBracketed(gens) => { + if let Some(syn::GenericArgument::Type(ty)) = gens.args.last() { + let is_generic = ast + .generics + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .find(|gen_par| { + gen_par.to_string() == ty.to_token_stream().to_string() + }); + + return is_generic.map_or_else( + || scalar::Type::Concrete(ty.clone()), + |scalar| scalar::Type::ExplicitGeneric(scalar.clone()), + ); } } - _ => (), + syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => {} } } - } + scalar::Type::Concrete(parse_quote! { ::juniper::DefaultScalarValue }) + }; + let scalar = get_scalar(); - Ok(Definition { + let mut out = ast.to_token_stream(); + Definition { impl_for_type: *ast.self_ty.clone(), generics: ast.generics.clone(), name, description: attr.description.as_deref().cloned(), scalar, specified_by_url: attr.specified_by_url.as_deref().cloned(), - resolve_body: resolve_body.ok_or_else(|| { - ERR.custom_error(ast.span(), "unable to find body of `resolve` method") - })?, - from_input_value_arg: from_input_value_arg.ok_or_else(|| { - ERR.custom_error( - ast.span(), - "unable to find argument for `from_input_value` method", - ) - })?, - from_input_value_body: from_input_value_body.ok_or_else(|| { - ERR.custom_error( - ast.span(), - "unable to find body of `from_input_value` method", - ) - })?, - from_input_value_result: from_input_value_result.ok_or_else(|| { - ERR.custom_error( - ast.span(), - "unable to find return type of `from_input_value` method", - ) - })?, - from_str_arg: from_str_arg.ok_or_else(|| { - ERR.custom_error(ast.span(), "unable to find argument for `from_str` method") - })?, - from_str_body: from_str_body.ok_or_else(|| { - ERR.custom_error(ast.span(), "unable to find body of `from_str` method") - })?, - from_str_result: from_str_result.ok_or_else(|| { - ERR.custom_error( - ast.span(), - "unable to find return type of `from_str` method", - ) - })?, } - .to_token_stream()) + .to_tokens(&mut out); + + Ok(out) } #[derive(Default)] @@ -158,7 +129,6 @@ struct Attr { pub description: Option>, pub deprecation: Option>, pub specified_by_url: Option>, - pub scalar: Option>, } impl Parse for Attr { @@ -193,19 +163,12 @@ impl Parse for Attr { input.parse::()?; let lit = input.parse::()?; let url = lit.value().parse::().map_err(|err| { - ERR.custom_error(lit.span(), format!("failed to parse URL: {}", err)) + syn::Error::new(lit.span(), format!("Invalid URL: {}", err)) })?; out.specified_by_url .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) .none_or_else(|_| err::dup_arg(&ident))? } - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } name => { return Err(err::unknown_arg(&ident, name)); } @@ -225,7 +188,6 @@ impl Attr { description: try_merge_opt!(description: self, another), deprecation: try_merge_opt!(deprecation: self, another), specified_by_url: try_merge_opt!(specified_by_url: self, another), - scalar: try_merge_opt!(scalar: self, another), }) } @@ -250,13 +212,6 @@ pub struct Definition { name: String, scalar: scalar::Type, description: Option, - resolve_body: syn::Block, - from_input_value_arg: syn::Ident, - from_input_value_body: syn::Block, - from_input_value_result: syn::Type, - from_str_arg: syn::Ident, - from_str_body: syn::Block, - from_str_result: syn::Type, specified_by_url: Option, } @@ -340,7 +295,6 @@ impl Definition { fn impl_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; - let resolve_body = &self.resolve_body; let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -362,7 +316,7 @@ impl Definition { selection: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - Ok(#resolve_body) + Ok(::juniper::GraphQLScalar::resolve(self)) } } } @@ -396,7 +350,6 @@ impl Definition { fn impl_to_input_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; - let resolve_body = &self.resolve_body; let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -406,7 +359,7 @@ impl Definition { #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - let v = #resolve_body; + let v = ::juniper::GraphQLScalar::resolve(self); ::juniper::ToInputValue::to_input_value(&v) } } @@ -416,9 +369,6 @@ impl Definition { fn impl_from_input_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; - let from_input_value_result = &self.from_input_value_result; - let from_input_value_arg = &self.from_input_value_arg; - let from_input_value_body = &self.from_input_value_body; let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -427,10 +377,10 @@ impl Definition { impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty #where_clause { - type Error = <#from_input_value_result as ::juniper::macros::helper::ExtractError>::Error; + type Error = >::Error; - fn from_input_value(#from_input_value_arg: &::juniper::InputValue<#scalar>) -> #from_input_value_result { - #from_input_value_body + fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { + ::juniper::GraphQLScalar::from_input_value(input) } } } @@ -439,9 +389,6 @@ impl Definition { fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; - let from_str_result = &self.from_str_result; - let from_str_arg = &self.from_str_arg; - let from_str_body = &self.from_str_body; let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -450,10 +397,10 @@ impl Definition { impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty #where_clause { - fn from_str<'a>( - #from_str_arg: ::juniper::parser::ScalarToken<'a>, - ) -> #from_str_result { - #from_str_body + fn from_str( + token: ::juniper::parser::ScalarToken, + ) -> ::juniper::ParseScalarResult<#scalar> { + >::from_str(token) } } } @@ -549,371 +496,3 @@ impl Definition { generics } } - -#[derive(Debug)] -struct ScalarCodegenInput { - impl_for_type: Option, - custom_data_type: Option, - custom_data_type_is_struct: bool, - resolve_body: Option, - from_input_value_arg: Option, - from_input_value_body: Option, - from_input_value_result: Option, - from_str_arg: Option, - from_str_body: Option, - from_str_result: Option, -} - -fn get_first_method_arg( - inputs: syn::punctuated::Punctuated, -) -> Option { - if let Some(syn::FnArg::Typed(pat_type)) = inputs.first() { - if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { - return Some(pat_ident.ident.clone()); - } - } - - None -} - -fn get_method_return_type(output: syn::ReturnType) -> Option { - match output { - syn::ReturnType::Type(_, return_type) => Some(*return_type), - _ => None, - } -} - -// Find the enum type by inspecting the type parameter on the return value -fn get_scalar(return_type: &Option) -> Option { - if let Some(syn::Type::Path(type_path)) = return_type { - let path_segment = type_path - .path - .segments - .iter() - .find(|ps| matches!(ps.arguments, syn::PathArguments::AngleBracketed(_))); - - if let Some(path_segment) = path_segment { - if let syn::PathArguments::AngleBracketed(generic_args) = &path_segment.arguments { - let generic_type_arg = generic_args.args.iter().find(|generic_type_arg| { - matches!(generic_type_arg, syn::GenericArgument::Type(_)) - }); - - if let Some(syn::GenericArgument::Type(scalar)) = generic_type_arg { - return Some(scalar.clone()); - } - } - } - } - - None -} - -// Find the enum type by inspecting the type parameter on the return value -fn get_enum_type(return_type: &Option) -> Option { - if let Some(syn::Type::Path(type_path)) = return_type { - let path_segment = type_path - .path - .segments - .iter() - .find(|ps| matches!(ps.arguments, syn::PathArguments::AngleBracketed(_))); - - if let Some(path_segment) = path_segment { - if let syn::PathArguments::AngleBracketed(generic_args) = &path_segment.arguments { - let generic_type_arg = generic_args.args.iter().find(|generic_type_arg| { - matches!(generic_type_arg, syn::GenericArgument::Type(_)) - }); - - 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()); - } - } - } - } - } - - None -} - -impl syn::parse::Parse for ScalarCodegenInput { - fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { - let mut impl_for_type: Option = None; - let mut enum_data_type: Option = None; - let mut resolve_body: Option = None; - let mut from_input_value_arg: Option = None; - let mut from_input_value_body: Option = None; - let mut from_input_value_result: Option = None; - let mut from_str_arg: Option = None; - let mut from_str_body: Option = None; - let mut from_str_result: Option = None; - - let parse_custom_scalar_value_impl: syn::ItemImpl = input.parse()?; - // To implement a custom scalar for a struct, it's required to - // specify a generic type and a type bound - let custom_data_type_is_struct: bool = - !parse_custom_scalar_value_impl.generics.params.is_empty(); - - let mut self_ty = *parse_custom_scalar_value_impl.self_ty; - - while let syn::Type::Group(type_group) = self_ty { - self_ty = *type_group.elem; - } - - if let syn::Type::Path(type_path) = 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 { - if let syn::ImplItem::Method(method) = impl_item { - match method.sig.ident.to_string().as_str() { - "resolve" => { - resolve_body = Some(method.block); - } - "from_input_value" => { - from_input_value_arg = get_first_method_arg(method.sig.inputs); - from_input_value_result = get_method_return_type(method.sig.output); - from_input_value_body = Some(method.block); - } - "from_str" => { - from_str_arg = get_first_method_arg(method.sig.inputs); - from_str_result = get_method_return_type(method.sig.output); - - if !custom_data_type_is_struct { - enum_data_type = get_enum_type(&from_str_result); - } - - from_str_body = Some(method.block); - } - _ => (), - } - } - } - - let custom_data_type = if custom_data_type_is_struct { - impl_for_type.clone() - } else { - enum_data_type - }; - - Ok(ScalarCodegenInput { - impl_for_type, - custom_data_type, - custom_data_type_is_struct, - resolve_body, - from_input_value_arg, - from_input_value_body, - from_input_value_result, - from_str_arg, - from_str_body, - from_str_result, - }) - } -} - -/// Generate code for the juniper::graphql_scalar proc macro. -pub fn build_scalar( - attributes: TokenStream, - body: TokenStream, - error: GraphQLScope, -) -> syn::Result { - let body_span = body.span(); - - let attrs = syn::parse2::(attributes)?; - let input = syn::parse2::(body)?; - - let impl_for_type = input.impl_for_type.ok_or_else(|| { - error.custom_error( - body_span, - "unable to find target for implementation target for `GraphQLScalar`", - ) - })?; - let custom_data_type = input - .custom_data_type - .ok_or_else(|| error.custom_error(body_span, "unable to find custom scalar data type"))?; - let resolve_body = input - .resolve_body - .ok_or_else(|| error.custom_error(body_span, "unable to find body of `resolve` method"))?; - let from_input_value_arg = input.from_input_value_arg.ok_or_else(|| { - error.custom_error( - body_span, - "unable to find argument for `from_input_value` method", - ) - })?; - let from_input_value_body = input.from_input_value_body.ok_or_else(|| { - error.custom_error( - body_span, - "unable to find body of `from_input_value` method", - ) - })?; - let from_input_value_result = input.from_input_value_result.ok_or_else(|| { - error.custom_error( - body_span, - "unable to find return type of `from_input_value` method", - ) - })?; - let from_str_arg = input.from_str_arg.ok_or_else(|| { - error.custom_error(body_span, "unable to find argument for `from_str` method") - })?; - let from_str_body = input - .from_str_body - .ok_or_else(|| error.custom_error(body_span, "unable to find body of `from_str` method"))?; - let from_str_result = input.from_str_result.ok_or_else(|| { - error.custom_error(body_span, "unable to find return type of `from_str` method") - })?; - - let name = attrs - .name - .map(SpanContainer::into_inner) - .unwrap_or_else(|| impl_for_type.ident.to_string()); - let description = attrs.description.map(|val| quote!(.description(#val))); - let specified_by_url = attrs.specified_by_url.map(|url| { - let url_lit = url.as_str(); - quote!(.specified_by_url(#url_lit)) - }); - let async_generic_type = match input.custom_data_type_is_struct { - true => quote!(__S), - _ => quote!(#custom_data_type), - }; - let async_generic_type_decl = match input.custom_data_type_is_struct { - true => quote!(<#async_generic_type>), - _ => quote!(), - }; - let generic_type = match input.custom_data_type_is_struct { - true => quote!(S), - _ => quote!(#custom_data_type), - }; - let generic_type_decl = match input.custom_data_type_is_struct { - true => quote!(<#generic_type>), - _ => quote!(), - }; - let generic_type_bound = match input.custom_data_type_is_struct { - true => quote!(where #generic_type: ::juniper::ScalarValue,), - _ => quote!(), - }; - - let _async = quote!( - impl#async_generic_type_decl ::juniper::GraphQLValueAsync<#async_generic_type> for #impl_for_type - where - Self: Sync, - Self::TypeInfo: Sync, - Self::Context: Sync, - #async_generic_type: ::juniper::ScalarValue + Send + Sync, - { - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [::juniper::Selection<#async_generic_type>]>, - executor: &'a ::juniper::Executor, - ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#async_generic_type>> { - use ::juniper::futures::future; - let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - ); - - let content = quote!( - #_async - - impl#generic_type_decl ::juniper::marker::IsInputType<#generic_type> for #impl_for_type - #generic_type_bound { } - - impl#generic_type_decl ::juniper::marker::IsOutputType<#generic_type> for #impl_for_type - #generic_type_bound { } - - impl#generic_type_decl ::juniper::GraphQLType<#generic_type> for #impl_for_type - #generic_type_bound - { - fn name(_: &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #generic_type>, - ) -> ::juniper::meta::MetaType<'r, #generic_type> - where - #generic_type: 'r, - { - registry.build_scalar_type::(info) - #description - #specified_by_url - .into_meta() - } - } - - impl#generic_type_decl ::juniper::GraphQLValue<#generic_type> for #impl_for_type - #generic_type_bound - { - type Context = (); - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - fn resolve( - &self, - info: &(), - selection: Option<&[::juniper::Selection<#generic_type>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#generic_type> { - Ok(#resolve_body) - } - } - - impl#generic_type_decl ::juniper::ToInputValue<#generic_type> for #impl_for_type - #generic_type_bound - { - fn to_input_value(&self) -> ::juniper::InputValue<#generic_type> { - let v = #resolve_body; - ::juniper::ToInputValue::to_input_value(&v) - } - } - - impl#generic_type_decl ::juniper::FromInputValue<#generic_type> for #impl_for_type - #generic_type_bound - { - type Error = <#from_input_value_result as ::juniper::macros::helper::ExtractError>::Error; - - fn from_input_value(#from_input_value_arg: &::juniper::InputValue<#generic_type>) -> #from_input_value_result { - #from_input_value_body - } - } - - impl#generic_type_decl ::juniper::ParseScalarValue<#generic_type> for #impl_for_type - #generic_type_bound - { - fn from_str<'a>( - #from_str_arg: ::juniper::parser::ScalarToken<'a>, - ) -> #from_str_result { - #from_str_body - } - } - - impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type - #generic_type_bound - { - const NAME: ::juniper::macros::reflection::Type = #name; - } - - impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type - #generic_type_bound - { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; - } - - impl#generic_type_decl ::juniper::macros::reflection::WrappedType<#generic_type> for #impl_for_type - #generic_type_bound - { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; - } - ); - - Ok(content) -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 92990332d..c2f51e303 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -215,10 +215,12 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// possible to specify a custom representation. /// /// ```rust +/// # use juniper::{graphql_scalar, GraphQLScalar, ParseScalarResult, ScalarToken, ScalarValue, Value}; +/// # /// // The data type /// struct UserID(String); /// -/// #[juniper::graphql_scalar( +/// #[graphql_scalar( /// // You can rename the type for GraphQL by specifying the name here. /// name = "MyName", /// // You can also specify a description here. @@ -226,21 +228,22 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// description = "An opaque identifier, represented as a string", /// // A specification URL. /// specified_by_url = "https://tools.ietf.org/html/rfc4122", -/// scalar = S, /// )] -/// impl GraphQLScalar for UserID { -/// fn resolve(&self) -> juniper::Value { -/// juniper::Value::scalar(self.0.to_owned()) +/// impl GraphQLScalar for UserID { +/// // NOTE: The Error type should implement `IntoFieldError`. +/// type Error = String; +/// +/// fn resolve(&self) -> Value { +/// Value::scalar(self.0.to_owned()) /// } /// -/// // NOTE: The error type should implement `IntoFieldError`. -/// fn from_input_value(value: &juniper::InputValue) -> Result { +/// fn from_input_value(value: &juniper::InputValue) -> Result { /// value.as_string_value() /// .map(|s| UserID(s.to_owned())) /// .ok_or_else(|| format!("Expected `String`, found: {}", value)) /// } /// -/// fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> { +/// fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { /// >::from_str(value) /// } /// } From d359524ac62908e9ec1d3a6491a442935342a1f3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 10 Jan 2022 15:54:14 +0300 Subject: [PATCH 054/122] Corrections --- docs/book/content/types/scalars.md | 2 +- .../fail/scalar/impl_invalid_url.rs | 2 +- .../juniper_tests/src/codegen/impl_scalar.rs | 28 +++++++++---------- .../juniper_tests/src/custom_scalar.rs | 2 +- .../src/executor_tests/introspection/mod.rs | 4 +-- juniper/src/executor_tests/variables.rs | 4 +-- juniper/src/integrations/bson.rs | 4 +-- juniper/src/integrations/chrono.rs | 8 +++--- juniper/src/integrations/chrono_tz.rs | 2 +- juniper/src/integrations/time.rs | 10 +++---- juniper/src/integrations/url.rs | 4 +-- juniper/src/integrations/uuid.rs | 4 +-- juniper/src/types/scalars.rs | 12 ++++---- 13 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 263e8ae43..f4993bfcd 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -133,7 +133,7 @@ where } // Define how to parse a primitive type into your custom scalar. - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs index 15ac91738..1b0c8b09a 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs @@ -10,7 +10,7 @@ impl GraphQLScalar for ScalarSpecifiedByUrl { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index cf9380505..4217dfb42 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -32,9 +32,9 @@ impl GraphQLScalar for DefaultName { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(DefaultName) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } @@ -51,9 +51,9 @@ impl GraphQLScalar for OtherOrder { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(OtherOrder) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } @@ -70,9 +70,9 @@ impl GraphQLScalar for Named { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(Named) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } @@ -89,7 +89,7 @@ impl GraphQLScalar for ScalarDescription { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .map(ScalarDescription) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) @@ -108,9 +108,9 @@ impl GraphQLScalar for ScalarSpecifiedByUrl { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(ScalarSpecifiedByUrl) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } @@ -132,7 +132,7 @@ macro_rules! impl_scalar { Value::scalar(self.0.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_scalar_value() .and_then(|v| v.as_str()) .and_then(|s| Some(Self(s.to_owned()))) @@ -180,9 +180,9 @@ impl GraphQLScalar for WithCustomScalarValue { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(WithCustomScalarValue) + .map(Self) .ok_or_else(|| format!("Expected Int, found: {}", v)) } @@ -241,9 +241,9 @@ fn path_in_resolve_return_type() { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(ResolvePath) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 8a40e3f7a..a43900386 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -140,7 +140,7 @@ impl GraphQLScalar for i64 { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_scalar_value::() .copied() .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v)) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 90358a159..52bf2a6a9 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -30,9 +30,9 @@ impl GraphQLScalar for Scalar { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(Scalar) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 50a0b2df0..8b0dda6b7 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -21,10 +21,10 @@ impl GraphQLScalar for TestComplexScalar { graphql_value!("SerializedValue") } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") - .map(|_| TestComplexScalar) + .map(|_| Self) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 5aecb2f96..e344006cf 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -18,7 +18,7 @@ impl GraphQLScalar for ObjectId { Value::scalar(self.to_hex()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -43,7 +43,7 @@ impl GraphQLScalar for UtcDateTime { Value::scalar((*self).to_chrono().to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index bb9130855..c019cbd56 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -32,7 +32,7 @@ impl GraphQLScalar for DateTime { Value::scalar(self.to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -58,7 +58,7 @@ impl GraphQLScalar for DateTime { Value::scalar(self.to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result, String> { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -89,7 +89,7 @@ impl GraphQLScalar for NaiveDate { Value::scalar(self.format("%Y-%m-%d").to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -144,7 +144,7 @@ impl GraphQLScalar for NaiveDateTime { Value::scalar(self.timestamp() as f64) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) .and_then(|f| { diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index df18c622d..3c608bcb9 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -20,7 +20,7 @@ impl GraphQLScalar for Tz { Value::scalar(self.name().to_owned()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index 78e214b67..5a80148db 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -69,7 +69,7 @@ impl GraphQLScalar for Date { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) @@ -131,7 +131,7 @@ impl GraphQLScalar for LocalTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -176,7 +176,7 @@ impl GraphQLScalar for LocalDateTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -222,7 +222,7 @@ impl GraphQLScalar for DateTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -269,7 +269,7 @@ impl GraphQLScalar for UtcOffset { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index eaa0e2c73..9ff206d1e 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -15,10 +15,10 @@ impl GraphQLScalar for Url { Value::scalar(self.as_str().to_owned()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) + .and_then(|s| Self::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) } fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index dff00bcae..69a37bba9 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -18,10 +18,10 @@ impl GraphQLScalar for Uuid { Value::scalar(self.to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) + .and_then(|s| Self::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) } fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 880b51a0f..83819e6cb 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -60,11 +60,11 @@ impl GraphQLScalar for ID { Value::scalar(self.0.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .or_else(|| v.as_int_value().map(|i| i.to_string())) - .map(ID) + .map(Self) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } @@ -84,7 +84,7 @@ impl GraphQLScalar for String { Value::scalar(self.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .ok_or_else(|| format!("Expected `String`, found: {}", v)) @@ -283,7 +283,7 @@ impl GraphQLScalar for bool { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_boolean) .ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) @@ -303,7 +303,7 @@ impl GraphQLScalar for i32 { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_int_value() .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } @@ -327,7 +327,7 @@ impl GraphQLScalar for f64 { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) } From 888789a9c5d9dccb6bd360217bd6d49c5a540b5e Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 11 Jan 2022 11:28:29 +0300 Subject: [PATCH 055/122] Corrections --- .../juniper_tests/src/codegen/impl_scalar.rs | 2 +- juniper/src/integrations/chrono.rs | 2 +- juniper_codegen/src/impl_scalar.rs | 208 ++++++++++++------ juniper_codegen/src/lib.rs | 2 +- juniper_codegen/src/util/mod.rs | 25 --- juniper_codegen/src/util/span_container.rs | 9 - 6 files changed, 149 insertions(+), 99 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 4217dfb42..6d8b0ac79 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -91,7 +91,7 @@ impl GraphQLScalar for ScalarDescription { fn from_input_value(v: &InputValue) -> Result { v.as_int_value() - .map(ScalarDescription) + .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index c019cbd56..94a7a0427 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -116,7 +116,7 @@ impl GraphQLScalar for NaiveTime { Value::scalar(self.format("%H:%M:%S").to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input_value(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index f4086bcee..944cb00d2 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -1,4 +1,6 @@ -#![allow(clippy::collapsible_if)] +//! Code generation for [GraphQL scalar][1]. +//! +//! [1]: https://spec.graphql.org/October2021/#sec-Scalars use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; @@ -21,18 +23,18 @@ use crate::{ scalar, }, result::GraphQLScope, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer, DeprecationAttr}, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, }; -/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. +/// [`GraphQLScope`] of errors for `#[graphql_scalar]` macro. const ERR: GraphQLScope = GraphQLScope::ImplScalar; -/// Expands `#[graphql_interface]` macro into generated code. +/// Expands `#[graphql_scalar]` macro into generated code. pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { if let Ok(mut ast) = syn::parse2::(body) { let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); - return expand_on_impl_block(attrs, ast); + return expand_on_impl(attrs, ast); } Err(syn::Error::new( @@ -41,10 +43,8 @@ pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, - ast: syn::ItemImpl, -) -> syn::Result { +/// Expands `#[graphql_scalar]` macro placed on an implementation block. +fn expand_on_impl(attrs: Vec, ast: syn::ItemImpl) -> syn::Result { let attr = Attr::from_attrs("graphql_scalar", &attrs)?; let mut self_ty = ast.self_ty.clone(); @@ -75,39 +75,11 @@ fn expand_on_impl_block( let (_, trait_ty, _) = ast.trait_.as_ref().ok_or_else(|| { ERR.custom_error( ast.impl_token.span(), - "expected GraphQLScalar trait implementation", + "expected `GraphQLScalar` trait implementation", ) })?; - let get_scalar = || { - if let Some(last_seg) = trait_ty.segments.last() { - match &last_seg.arguments { - syn::PathArguments::AngleBracketed(gens) => { - if let Some(syn::GenericArgument::Type(ty)) = gens.args.last() { - let is_generic = ast - .generics - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .find(|gen_par| { - gen_par.to_string() == ty.to_token_stream().to_string() - }); - - return is_generic.map_or_else( - || scalar::Type::Concrete(ty.clone()), - |scalar| scalar::Type::ExplicitGeneric(scalar.clone()), - ); - } - } - syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => {} - } - } - scalar::Type::Concrete(parse_quote! { ::juniper::DefaultScalarValue }) - }; - let scalar = get_scalar(); + let scalar = get_scalar(trait_ty, &ast.generics); let mut out = ast.to_token_stream(); Definition { @@ -123,11 +95,54 @@ fn expand_on_impl_block( Ok(out) } +/// Extracts [`scalar::Type`] from [`GraphQLScalar`] trait. +/// +/// [`GraphQLScalar`]: juniper::GraphQLScalar +fn get_scalar(trait_ty: &syn::Path, generics: &syn::Generics) -> scalar::Type { + if let Some(last_seg) = trait_ty.segments.last() { + match &last_seg.arguments { + syn::PathArguments::AngleBracketed(gens) => { + if let Some(syn::GenericArgument::Type(ty)) = gens.args.last() { + let generic_scalar = generics + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .find(|gen_par| gen_par.to_string() == ty.to_token_stream().to_string()); + + return generic_scalar.map_or_else( + || scalar::Type::Concrete(ty.clone()), + |scalar| scalar::Type::ExplicitGeneric(scalar.clone()), + ); + } + } + syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => {} + } + } + scalar::Type::Concrete(parse_quote! { ::juniper::DefaultScalarValue }) +} + +/// Available arguments behind `#[graphql_scalar]` attribute when generating +/// code for [GraphQL scalar][1] type. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[derive(Default)] struct Attr { + /// Name of this [GraphQL scalar][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars pub name: Option>, + + /// Description of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars pub description: Option>, - pub deprecation: Option>, + + /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars pub specified_by_url: Option>, } @@ -186,7 +201,6 @@ impl Attr { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), - deprecation: try_merge_opt!(deprecation: self, another), specified_by_url: try_merge_opt!(specified_by_url: self, another), }) } @@ -206,13 +220,45 @@ impl Attr { } } -pub struct Definition { +/// Definition of [GraphQL scalar][1] for code generation. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +struct Definition { + /// Name of this [GraphQL scalar][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + name: String, + + /// Rust type that this [GraphQL scalar][1] is represented with. + /// + /// It should contain all its generics, if any. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars impl_for_type: syn::Type, + + /// Generics of the Rust type that this [GraphQL scalar][1] is implemented + /// for. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars generics: syn::Generics, - name: String, - scalar: scalar::Type, + + /// Description of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars description: Option, + + /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars specified_by_url: Option, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL scalar][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + scalar: scalar::Type, } impl ToTokens for Definition { @@ -220,7 +266,7 @@ impl ToTokens for Definition { self.impl_output_and_input_type_tokens().to_tokens(into); self.impl_type_tokens().to_tokens(into); self.impl_value_tokens().to_tokens(into); - self.impl_value_async().to_tokens(into); + self.impl_value_async_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); @@ -229,11 +275,12 @@ impl ToTokens for Definition { } impl Definition { - /// Returns generated code implementing [`marker::IsOutputType`] trait for - /// this [GraphQL interface][1]. + /// Returns generated code implementing [`marker::IsInputType`] and + /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1]. /// + /// [`marker::IsInputType`]: juniper::marker::IsInputType /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[must_use] fn impl_output_and_input_type_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; @@ -251,6 +298,11 @@ impl Definition { } } + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_type_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let name = &self.name; @@ -276,12 +328,12 @@ impl Definition { Some(#name) } - fn meta<'__registry>( + fn meta<'r>( info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'__registry, #scalar>, - ) -> ::juniper::meta::MetaType<'__registry, #scalar> + registry: &mut ::juniper::Registry<'r, #scalar>, + ) -> ::juniper::meta::MetaType<'r, #scalar> where - #scalar: '__registry, + #scalar: 'r, { registry.build_scalar_type::(info) #description @@ -292,6 +344,11 @@ impl Definition { } } + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; @@ -306,7 +363,7 @@ impl Definition { type Context = (); type TypeInfo = (); - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { >::name(info) } @@ -322,7 +379,12 @@ impl Definition { } } - fn impl_value_async(&self) -> TokenStream { + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_value_async_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; @@ -333,12 +395,12 @@ impl Definition { impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { - fn resolve_async<'__l>( - &'__l self, - info: &'__l Self::TypeInfo, - selection_set: Option<&'__l [::juniper::Selection<#scalar>]>, - executor: &'__l ::juniper::Executor, - ) -> ::juniper::BoxFuture<'__l, ::juniper::ExecutionResult<#scalar>> { + fn resolve_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + selection_set: Option<&'b [::juniper::Selection<#scalar>]>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { use ::juniper::futures::future; let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); Box::pin(future::ready(v)) @@ -347,6 +409,11 @@ impl Definition { } } + /// Returns generated code implementing [`InputValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`InputValue`]: juniper::InputValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_to_input_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; @@ -366,6 +433,11 @@ impl Definition { } } + /// Returns generated code implementing [`FromInputValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`FromInputValue`]: juniper::FromInputValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_from_input_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; @@ -386,6 +458,11 @@ impl Definition { } } + /// Returns generated code implementing [`ParseScalarValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`ParseScalarValue`]: juniper::ParseScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; @@ -398,14 +475,21 @@ impl Definition { #where_clause { fn from_str( - token: ::juniper::parser::ScalarToken, - ) -> ::juniper::ParseScalarResult<#scalar> { + token: ::juniper::parser::ScalarToken<'_>, + ) -> ::juniper::ParseScalarResult<'_, #scalar> { >::from_str(token) } } } } + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL scalar][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c2f51e303..49fede62f 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -239,7 +239,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// /// fn from_input_value(value: &juniper::InputValue) -> Result { /// value.as_string_value() -/// .map(|s| UserID(s.to_owned())) +/// .map(|s| Self(s.to_owned())) /// .ok_or_else(|| format!("Expected `String`, found: {}", value)) /// } /// diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 38722918f..c736d0857 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -17,7 +17,6 @@ use syn::{ spanned::Spanned, token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, }; -use url::Url; use crate::common::parse::ParseBufferExt as _; @@ -455,7 +454,6 @@ pub enum FieldAttributeParseMode { enum FieldAttribute { Name(SpanContainer), Description(SpanContainer), - SpecifiedByUrl(SpanContainer), Deprecation(SpanContainer), Skip(SpanContainer), Arguments(HashMap), @@ -490,15 +488,6 @@ impl Parse for FieldAttribute { lit, ))) } - "specified_by_url" => { - input.parse::()?; - let lit = input.parse::()?; - Ok(FieldAttribute::SpecifiedByUrl(SpanContainer::new( - ident.span(), - Some(lit.span()), - lit, - ))) - } "deprecated" | "deprecation" => { let reason = if input.peek(token::Eq) { input.parse::()?; @@ -553,8 +542,6 @@ pub struct FieldAttributes { pub name: Option>, pub description: Option>, pub deprecation: Option>, - /// Only relevant for scalar impl macro. - pub specified_by_url: Option>, /// Only relevant for GraphQLObject derive. pub skip: Option>, /// Only relevant for object macro. @@ -577,18 +564,6 @@ impl Parse for FieldAttributes { FieldAttribute::Description(name) => { output.description = Some(name.map(|val| val.value())); } - FieldAttribute::SpecifiedByUrl(url) => { - output.specified_by_url = Some( - url.map(|val| Url::parse(&val.value())) - .transpose() - .map_err(|e| { - syn::Error::new( - e.span_ident(), - format!("Invalid URL: {}", e.inner()), - ) - })?, - ); - } FieldAttribute::Deprecation(attr) => { output.deprecation = Some(attr); } diff --git a/juniper_codegen/src/util/span_container.rs b/juniper_codegen/src/util/span_container.rs index 2040a48ff..370f17a74 100644 --- a/juniper_codegen/src/util/span_container.rs +++ b/juniper_codegen/src/util/span_container.rs @@ -58,15 +58,6 @@ impl SpanContainer { } } -impl SpanContainer> { - pub fn transpose(self) -> Result, SpanContainer> { - match self.val { - Ok(v) => Ok(SpanContainer::new(self.ident, self.expr, v)), - Err(e) => Err(SpanContainer::new(self.ident, self.expr, e)), - } - } -} - impl AsRef for SpanContainer { fn as_ref(&self) -> &T { &self.val From e28ca92c79e2a9f5c28dcbe4daa7f1ac46deb02f Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 11 Jan 2022 12:51:38 +0300 Subject: [PATCH 056/122] Add generic test and remove redundant lifetime --- integration_tests/juniper_tests/Cargo.toml | 1 + .../juniper_tests/src/codegen/impl_scalar.rs | 72 ++++++++++++++++++- juniper/src/executor/mod.rs | 2 +- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml index 0a6ce2c1c..44cb39aa2 100644 --- a/integration_tests/juniper_tests/Cargo.toml +++ b/integration_tests/juniper_tests/Cargo.toml @@ -5,6 +5,7 @@ edition = "2018" publish = false [dependencies] +chrono = "0.4.19" derive_more = "0.99" futures = "0.3" juniper = { path = "../../juniper" } diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 6d8b0ac79..093def8d3 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -1,3 +1,6 @@ +use std::fmt; + +use chrono::{DateTime, TimeZone, Utc}; use juniper::{ execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLScalar, InputValue, Object, ParseScalarResult, @@ -12,6 +15,7 @@ struct Named(i32); struct ScalarDescription(i32); struct ScalarSpecifiedByUrl(i32); struct Generated(String); +struct CustomDateTime(DateTime); struct Root; @@ -119,6 +123,33 @@ impl GraphQLScalar for ScalarSpecifiedByUrl { } } +#[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc3339")] +impl GraphQLScalar for CustomDateTime +where + Tz: From + TimeZone, + Tz::Offset: fmt::Display, +{ + type Error = String; + + fn resolve(&self) -> Value { + Value::scalar(self.0.to_rfc3339()) + } + + fn from_input_value(v: &InputValue) -> Result { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| Self(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } +} + macro_rules! impl_scalar { ($name: ident) => { #[graphql_scalar] @@ -168,6 +199,9 @@ impl Root { fn generated() -> Generated { Generated("foo".to_owned()) } + fn custom_date_time() -> CustomDateTime { + CustomDateTime(Utc.timestamp_nanos(0)) + } } struct WithCustomScalarValue(i32); @@ -198,6 +232,10 @@ impl RootWithCustomScalarValue { fn with_custom_scalar_value() -> WithCustomScalarValue { WithCustomScalarValue(0) } + + fn with_generic_scalar_value() -> CustomDateTime { + CustomDateTime(Utc.timestamp(0, 0)) + } } async fn run_type_info_query(doc: &str, f: F) @@ -325,6 +363,30 @@ async fn named_introspection() { .await; } +#[tokio::test] +async fn generic_introspection() { + let doc = r#" + { + __type(name: "CustomDateTime") { + name + description + } + } + "#; + + run_type_info_query(doc, |type_info| { + assert_eq!( + type_info.get_field_value("name"), + Some(&graphql_value!("CustomDateTime")), + ); + assert_eq!( + type_info.get_field_value("description"), + Some(&graphql_value!(null)), + ); + }) + .await; +} + #[tokio::test] async fn scalar_description_introspection() { let doc = r#" @@ -404,7 +466,7 @@ async fn generated_scalar_introspection() { #[tokio::test] async fn resolves_with_custom_scalar_value() { - const DOC: &str = r#"{ withCustomScalarValue }"#; + const DOC: &str = r#"{ withCustomScalarValue withGenericScalarValue }"#; let schema = RootNode::<_, _, _, MyScalarValue>::new_with_scalar_value( RootWithCustomScalarValue, @@ -414,6 +476,12 @@ async fn resolves_with_custom_scalar_value() { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"withCustomScalarValue": 0}), vec![])), + Ok(( + graphql_value!({ + "withCustomScalarValue": 0, + "withGenericScalarValue": "1970-01-01T00:00:00+00:00", + }), + vec![] + )), ); } diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 9b9de9497..47d5fead2 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -1252,7 +1252,7 @@ impl<'r, S: 'r> Registry<'r, S> { /// Creates a [`ScalarMeta`] type. pub fn build_scalar_type(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S> where - T: GraphQLType + FromInputValue + ParseScalarValue + 'r, + T: GraphQLType + FromInputValue + ParseScalarValue, T::Error: IntoFieldError, S: ScalarValue, { From f5310379325a73e00d455d213b865a4c35347a3b Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 11 Jan 2022 16:07:08 +0100 Subject: [PATCH 057/122] Some corrections [skip ci] --- .../src/codegen/interface_attr.rs | 129 +++++----- juniper/src/macros/mod.rs | 1 - juniper/src/macros/reflection.rs | 231 ++++++++++-------- juniper/src/tests/fixtures/starwars/schema.rs | 55 +---- juniper_hyper/src/lib.rs | 22 +- juniper_iron/src/lib.rs | 6 +- 6 files changed, 216 insertions(+), 228 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8303aeb50..f7ea0a21b 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -41,7 +41,7 @@ mod no_implers { struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { unimplemented!() @@ -50,48 +50,48 @@ mod no_implers { #[tokio::test] async fn is_graphql_interface() { - let schema = schema(QueryRoot); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { kind } }"#; + let schema = schema(QueryRoot); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), ); } #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } }"#; + let schema = schema(QueryRoot); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { description } }"#; + let schema = schema(QueryRoot); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } @@ -134,7 +134,7 @@ mod trivial { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -153,7 +153,7 @@ mod trivial { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -168,14 +168,17 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -190,14 +193,17 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -220,25 +226,23 @@ mod trivial { #[tokio::test] async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { kind } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), ); } #[tokio::test] async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { possibleTypes { kind @@ -247,8 +251,10 @@ mod trivial { } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"possibleTypes": [ {"kind": "OBJECT", "name": "Droid"}, @@ -290,32 +296,32 @@ mod trivial { #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { description } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } @@ -392,7 +398,10 @@ mod explicit_alias { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -414,7 +423,10 @@ mod explicit_alias { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -513,6 +525,7 @@ mod trivial_async { #[graphql_object(impl = CharacterValue)] impl Droid { + // FIXME: use async fn id(&self) -> &str { &self.id } @@ -528,7 +541,7 @@ mod trivial_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -547,7 +560,7 @@ mod trivial_async { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -569,7 +582,7 @@ mod trivial_async { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -591,7 +604,7 @@ mod trivial_async { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -614,25 +627,23 @@ mod trivial_async { #[tokio::test] async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { kind } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), ); } #[tokio::test] async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { possibleTypes { kind @@ -641,8 +652,10 @@ mod trivial_async { } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"possibleTypes": [ {"kind": "OBJECT", "name": "Droid"}, @@ -684,32 +697,32 @@ mod trivial_async { #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { description } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } @@ -3588,7 +3601,7 @@ mod field_return_union_subtyping { } } -mod additional_nullable_argument { +mod nullable_argument_subtyping { use super::*; #[graphql_interface(for = [Human, Droid])] diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index 182bd2055..f9aa80771 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,7 +1,6 @@ //! Declarative macros and helper definitions for procedural macros. #[doc(hidden)] -#[macro_use] pub mod helper; #[doc(hidden)] #[macro_use] diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 709bbd865..d91355a33 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -1,4 +1,5 @@ -//! Helper traits and macros for compile-time reflection. +//! Helper traits and macros for compile-time reflection of Rust types into +//! GraphQL types. use std::{rc::Rc, sync::Arc}; @@ -8,62 +9,39 @@ use crate::{ Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; -/// Type alias for [GraphQL object][1], [scalar][2] or [interface][3] type name. +/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name +/// in a GraphQL schema. +/// /// See [`BaseType`] for more info. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Scalars -/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Interfaces pub type Type = &'static str; -/// Type alias for slice of [`Type`]s. See [`BaseSubTypes`] for more info. -pub type Types = &'static [Type]; - -/// Type alias for [GraphQL object][1] or [interface][2] [field argument][3] -/// name. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces -/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub type Name = &'static str; - -/// Type alias for slice of [`Name`]s. See [`Fields`] for more info. -pub type Names = &'static [Name]; - -/// Type alias for value of [`WrappedType`]. -pub type WrappedValue = u128; - -/// Type alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. +/// Alias for a slice of [`Type`]s. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub type Argument = (Name, Type, WrappedValue); +/// See [`BaseSubTypes`] for more info. +pub type Types = &'static [Type]; -/// Type alias for [field argument][1]s [`Name`], [`Type`] and -/// [`WrappedValue`]. +/// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub type Arguments = &'static [(Name, Type, WrappedValue)]; - -/// Type alias for constantly hashed [`Name`] for usage in const context. -pub type FieldName = u128; - -/// [GraphQL object][1], [scalar][2] or [interface][3] [`Type`] name. This trait -/// is transparent to the [`Option`], [`Vec`] and other containers, so to fully -/// represent [GraphQL object][1] we additionally use [`WrappedType`]. +/// This trait is transparent to [`Option`], [`Vec`] and other containers, so to +/// fully represent [GraphQL object][1] we additionally use [`WrappedType`]. /// -/// Different Rust type may have the same [`NAME`]. For example [`String`] and -/// &[`str`](prim@str). +/// Different Rust type may have the same [`NAME`]. For example, [`String`] and +/// `&`[`str`](prim@str) share `String!` GraphQL type. /// /// [`NAME`]: Self::NAME -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Scalars -/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Interfaces pub trait BaseType { /// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - /// [2]: https://spec.graphql.org/October2021/#sec-Scalars - /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Scalars + /// [3]: https://spec.graphql.org/October2021#sec-Interfaces const NAME: Type; } @@ -115,16 +93,16 @@ impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } -/// [GraphQL object][1] [sub-types][2]. This trait is transparent to the -/// [`Option`], [`Vec`] and other containers. +/// [Sub-types][2] of a [GraphQL object][1]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC +/// This trait is transparent to [`Option`], [`Vec`] and other containers. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC pub trait BaseSubTypes { - /// [`Types`] for the [GraphQL object][1]s [sub-types][2]. + /// Sub-[`Types`] of the [GraphQL object][1]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC + /// [1]: https://spec.graphql.org/October2021#sec-Objects const NAMES: Types; } @@ -176,19 +154,26 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// To fully represent [GraphQL object][1] it's not enough to use [`Type`], -/// because of the [wrapping types][2]. To work around this we use -/// [`WrappedValue`] which is represented with [`u128`]. +/// Alias for a value of a [`WrappedType`] (combined GraphQL type). +pub type WrappedValue = u128; + +// TODO: Just use `&str`s once they're allowed in `const` generics. +/// Encoding of a composed GraphQL type in numbers. /// -/// - In base case of non-nullable [object] [`VALUE`] is `1`. +/// To fully represent a [GraphQL object][1] it's not enough to use [`Type`], +/// because of the [wrapping types][2]. To work around this we use a +/// [`WrappedValue`] which is represented via [`u128`]. +/// +/// - In base case of non-nullable [object][1] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so /// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so /// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. /// /// This approach allows us to uniquely represent any [GraphQL object][1] with -/// combination of [`Type`] and [`WrappedValue`] and even constantly format it -/// with [`format_type`] macro. +/// combination of [`Type`] and [`WrappedValue`] and even format it via +/// [`format_type!`] macro in `const` context. +/// /// /// # Examples /// @@ -211,8 +196,8 @@ impl + ?Sized> BaseSubTypes for Rc { /// ``` /// /// [`VALUE`]: Self::VALUE -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Wrapping-Types +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Wrapping-Types pub trait WrappedType { /// [`WrappedValue`] of this type. const VALUE: WrappedValue; @@ -266,6 +251,34 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } +/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name. +/// +/// See [`Fields`] for more info. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments +pub type Name = &'static str; + +/// Alias for a slice of [`Name`]s. +/// +/// See [`Fields`] for more info. +pub type Names = &'static [Name]; + +/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments +pub type Argument = (Name, Type, WrappedValue); + +/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and +/// [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments +pub type Arguments = &'static [(Name, Type, WrappedValue)]; + +/// Alias for a `const`-hashed [`Name`] used in `const` context. +pub type FieldName = u128; + /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects @@ -291,7 +304,7 @@ pub trait Implements { const NAMES: Types; } -/// Stores meta information of [GraphQL field][1]: +/// Stores meta information of a [GraphQL field][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. @@ -379,10 +392,11 @@ pub trait AsyncField: FieldMeta { ) -> BoxFuture<'b, ExecutionResult>; } -/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in -/// const generics. See [spec] for more info. +/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in +/// `const` generics. See [spec] for more info. /// /// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +#[must_use] pub const fn fnv1a128(str: Name) -> u128 { const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; @@ -398,49 +412,51 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } -/// Length of the [`format_type`] macro result __in bytes__. -pub const fn type_len_with_wrapped_val(ty: Type, v: WrappedValue) -> usize { +/// Length __in bytes__ of the [`format_type`] macro result. +#[must_use] +pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - let mut current_wrap_val = v; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { + let mut curr = val; + while curr % 10 != 0 { + match curr % 10 { 2 => len -= "!".as_bytes().len(), // remove ! 3 => len += "[]!".as_bytes().len(), // [Type]! _ => {} } - - current_wrap_val /= 10; + curr /= 10; } len } -/// Based on the [`WrappedValue`] checks whether GraphQL [`objects`][1] can be -/// subtypes. +/// Checks whether GraphQL [`objects`][1] can be sub-types, based on the +/// [`WrappedValue`]. /// -/// To fully determine sub-typing relation [`Type`] should be one of the +/// To fully determine the sub-typing relation [`Type`] should be one of the /// [`BaseSubTypes::NAMES`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [1]: https://spec.graphql.org/October2021#sec-Objects +#[must_use] pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; + let ty_curr = ty % 10; + let sub_curr = subtype % 10; - if ty_current == subtype_current { - if ty_current == 1 { + if ty_curr == sub_curr { + if ty_curr == 1 { true } else { can_be_subtype(ty / 10, subtype / 10) } - } else if ty_current == 2 { + } else if ty_curr == 2 { can_be_subtype(ty / 10, subtype) } else { false } } -/// Checks whether `val` exists in `arr`. +/// Checks whether the given `val` exists in the given `arr`. +#[must_use] pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { let mut i = 0; while i < arr.len() { @@ -452,7 +468,7 @@ pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { false } -/// Compares strings in `const` context. +/// Compares strings in a `const` context. /// /// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to /// write custom comparison function. @@ -477,8 +493,8 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } -/// Asserts that `#[graphql_interface(for = ...)]` has all types referencing -/// this interface in `impl = ...` attribute section. +/// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing +/// this interface in the `impl = ...` attribute argument. #[macro_export] macro_rules! assert_implemented_for { ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { @@ -503,8 +519,8 @@ macro_rules! assert_implemented_for { }; } -/// Asserts that `impl = ...` attribute section has all types referencing this -/// type in `#[graphql_interface(for = ...)]`. +/// Asserts that `impl = ...` attribute argument has all the types referencing +/// this GraphQL type in `#[graphql_interface(for = ...)]`. #[macro_export] macro_rules! assert_interfaces_impls { ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { @@ -529,11 +545,14 @@ macro_rules! assert_interfaces_impls { }; } -/// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This -/// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. +/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. +/// +/// This assertion is a combination of [`assert_subtype`] and +/// [`assert_field_args`]. +/// /// See [spec][1] for more info. /// -/// [1]: https://spec.graphql.org/October2021/#IsValidImplementation() +/// [1]: https://spec.graphql.org/October2021#IsValidImplementation() #[macro_export] macro_rules! assert_field { ( @@ -547,10 +566,11 @@ macro_rules! assert_field { }; } -/// Asserts validness of the [`Field`]s return type. See [spec][1] for more -/// info. +/// Asserts validness of a [`Field`] return type. /// -/// [1]: https://spec.graphql.org/October2021/#IsValidImplementationFieldType() +/// See [spec][1] for more info. +/// +/// [1]: https://spec.graphql.org/October2021#IsValidImplementationFieldType() #[macro_export] macro_rules! assert_subtype { ( @@ -805,7 +825,7 @@ macro_rules! assert_field_args { }; } -/// Concatenates const [`str`](prim@str)s in const context. +/// Concatenates `const` [`str`](prim@str)s in a `const` context. #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ @@ -827,15 +847,16 @@ macro_rules! const_concat { } const CON: [u8; LEN] = concat([$($s),*]); - // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one - // after the other byte by byte. + // TODO: Use `str::from_utf8()` once it becomes `const`. + // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one + // after another byte-by-byte. #[allow(unsafe_code)] unsafe { ::std::str::from_utf8_unchecked(&CON) } }}; } -/// Before executing [`fnv1a128`] checks whether `impl_ty` has corresponding -/// [`Field`] impl and panics with understandable message. +/// Ensures that the given `$impl_ty` implements [`Field`] and returns a +/// [`fnv1a128`] hash for it, otherwise panics with understandable message. #[macro_export] macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ @@ -859,7 +880,8 @@ macro_rules! checked_hash { }}; } -/// Formats [`Type`] and [`WrappedValue`] into GraphQL type. +/// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type +/// name. /// /// # Examples /// @@ -893,9 +915,9 @@ macro_rules! format_type { let mut is_null = false; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { - 2 => is_null = true, // Skips writing BANG later. + 2 => is_null = true, // Skips writing `BANG` later. 3 => { - // Write OPENING_BRACKET at current_start. + // Write `OPENING_BRACKET` at `current_start`. let mut i = 0; while i < OPENING_BRACKET.as_bytes().len() { type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; @@ -903,7 +925,7 @@ macro_rules! format_type { } current_start += i; if !is_null { - // Write BANG at current_end. + // Write `BANG` at `current_end`. i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = @@ -912,7 +934,7 @@ macro_rules! format_type { } current_end -= i; } - // Write CLOSING_BRACKET at current_end. + // Write `CLOSING_BRACKET` at `current_end`. i = 0; while i < CLOSING_BRACKET.as_bytes().len() { type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = @@ -928,7 +950,7 @@ macro_rules! format_type { current_wrap_val /= 10; } - // Writes Type at current_start. + // Writes `Type` at `current_start`. let mut i = 0; while i < ty.as_bytes().len() { type_arr[current_start + i] = ty.as_bytes()[i]; @@ -936,7 +958,7 @@ macro_rules! format_type { } i = 0; if !is_null { - // Writes BANG at current_end. + // Writes `BANG` at `current_end`. while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; @@ -948,8 +970,9 @@ macro_rules! format_type { const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one - // after the other byte by byte. + // TODO: Use `str::from_utf8()` once it becomes `const`. + // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one + // after another byte-by-byte. #[allow(unsafe_code)] const TYPE_FORMATTED: &str = unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) }; diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 233e2f347..9e18ea268 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -118,7 +118,7 @@ impl Human { /// The friends of the human fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) + ctx.get_friends(&self.friend_ids) } /// Which movies they appear in @@ -132,28 +132,6 @@ impl Human { } } -impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn name(&self) -> Option<&str> { - Some(&self.name) - } - - fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) - } - - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - - fn friends_ids(&self) -> &[String] { - &self.friend_ids - } -} - #[derive(Clone)] pub struct Droid { id: String, @@ -200,7 +178,7 @@ impl Droid { /// The friends of the droid fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) + ctx.get_friends(&self.friend_ids) } /// Which movies they appear in @@ -214,28 +192,6 @@ impl Droid { } } -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - - fn name(&self) -> Option<&str> { - Some(&self.name) - } - - fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) - } - - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - - fn friends_ids(&self) -> &[String] { - &self.friend_ids - } -} - #[derive(Default, Clone)] pub struct Database { humans: HashMap, @@ -363,10 +319,7 @@ impl Database { } } - pub fn get_friends(&self, c: &dyn Character) -> Vec { - c.friends_ids() - .iter() - .flat_map(|id| self.get_character(id)) - .collect() + pub fn get_friends(&self, ids: &[String]) -> Vec { + ids.iter().flat_map(|id| self.get_character(id)).collect() } } diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 50ccff197..d46df4527 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -287,23 +287,23 @@ enum GraphQLRequestError { impl fmt::Display for GraphQLRequestError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, f), + match self { + GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, f), } } } impl Error for GraphQLRequestError { fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - GraphQLRequestError::BodyHyper(ref err) => Some(err), - GraphQLRequestError::BodyUtf8(ref err) => Some(err), - GraphQLRequestError::BodyJSONError(ref err) => Some(err), - GraphQLRequestError::Variables(ref err) => Some(err), + match self { + GraphQLRequestError::BodyHyper(err) => Some(err), + GraphQLRequestError::BodyUtf8(err) => Some(err), + GraphQLRequestError::BodyJSONError(err) => Some(err), + GraphQLRequestError::Variables(err) => Some(err), GraphQLRequestError::Invalid(_) => None, } } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 40d1f8039..cbef5ace0 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -370,9 +370,9 @@ enum GraphQLIronError { impl fmt::Display for GraphQLIronError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, f), - GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, f), + match self { + GraphQLIronError::Serde(err) => fmt::Display::fmt(err, f), + GraphQLIronError::Url(err) => fmt::Display::fmt(err, f), GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f), } } From 8f169cffd8b9a0276b2f0243f0148f0a04efee9b Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 12 Jan 2022 11:31:28 +0300 Subject: [PATCH 058/122] WIP --- juniper_codegen/src/derive_scalar_value.rs | 459 ++++++++++++++++++++- 1 file changed, 453 insertions(+), 6 deletions(-) diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 1e25ce913..333f300f4 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -1,12 +1,459 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, + token, Data, Fields, Ident, Variant, +}; +use url::Url; + use crate::{ - common::parse::ParseBufferExt as _, + common::{ + parse::{ + attr::{err, OptionExt}, + ParseBufferExt as _, + }, + scalar, + }, result::GraphQLScope, - util::{self, span_container::SpanContainer}, + util::{self, filter_attrs, get_doc_comment, span_container::SpanContainer}, }; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant}; -use url::Url; + +#[derive(Default)] +struct Attr { + name: Option>, + description: Option>, + specified_by_url: Option>, + scalar: Option>, + resolve: Option>, + from_input_value: Option>, + from_str: Option>, +} + +impl Parse for Attr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "specified_by_url" => { + input.parse::()?; + let lit = input.parse::()?; + let url = lit.value().parse::().map_err(|err| { + syn::Error::new(lit.span(), format!("Invalid URL: {}", err)) + })?; + out.specified_by_url + .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "resolve" => { + input.parse::()?; + let scl = input.parse::()?; + out.resolve + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "from_input_value" => { + input.parse::()?; + let scl = input.parse::()?; + out.from_input_value + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "from_str" => { + input.parse::()?; + let scl = input.parse::()?; + out.from_str + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl Attr { + /// Tries to merge two [`Attr`]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), + specified_by_url: try_merge_opt!(specified_by_url: self, another), + scalar: try_merge_opt!(scalar: self, another), + resolve: try_merge_opt!(resolve: self, another), + from_input_value: try_merge_opt!(from_input_value: self, another), + from_str: try_merge_opt!(from_str: self, another), + }) + } + + /// Parses [`Attr`] 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 attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); + } + + Ok(attr) + } +} + +struct Definition { + ident: syn::Ident, + generics: syn::Generics, + field: syn::Field, + name: String, + description: Option, + specified_by_url: Option, + scalar: Option, + resolve: Option, + from_input_value: Option, + from_str: Option, +} + +impl Definition { + /// Returns generated code implementing [`marker::IsInputType`] and + /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1]. + /// + /// [`marker::IsInputType`]: juniper::marker::IsInputType + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + #[must_use] + fn impl_output_and_input_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ident#ty_gens + #where_clause { } + + impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ident#ty_gens + #where_clause { } + } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let name = &self.name; + + let description = self + .description + .as_ref() + .map(|val| quote! { .description(#val) }); + let specified_by_url = self.specified_by_url.as_ref().map(|url| { + let url_lit = url.as_str(); + quote! { .specified_by_url(#url_lit) } + }); + + let generics = self.impl_generics(false); + let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::GraphQLType<#scalar> for #ident#ty_gens + #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, + { + registry.build_scalar_type::(info) + #description + #specified_by_url + .into_meta() + } + } + } + } + + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let field = &self.field.ident; + + let generics = self.impl_generics(false); + let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + + let resolve = self.resolve.map_or_else( + || quote! { ::juniper::GraphQLValue::<#scalar>::resolve(&self.#field, info, selection, executor) }, + |resolve_fn| quote! { Ok(#resolve_fn(self)) }, + ); + + quote! { + impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ident#ty_gens + #where_clause + { + type Context = (); + type TypeInfo = (); + + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { + >::name(info) + } + + fn resolve( + &self, + info: &(), + selection: Option<&[::juniper::Selection<#scalar>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + #resolve + } + } + } + } + + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_value_async_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ident#ty_gens + #where_clause + { + fn resolve_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + selection_set: Option<&'b [::juniper::Selection<#scalar>]>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + use ::juniper::futures::future; + let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); + Box::pin(future::ready(v)) + } + } + } + } + + /// Returns generated code implementing [`InputValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`InputValue`]: juniper::InputValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_to_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let field = &self.field.ident; + + let resolve = self.resolve.map_or_else( + || quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(self.#field) }, + |resolve_fn| { + quote! { + let v = #resolve_fn(self); + ::juniper::ToInputValue::to_input_value(&v) + } + }, + ); + + let generics = self.impl_generics(false); + let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::ToInputValue<#scalar> for #ident#ty_gens + #where_clause + { + fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { + #resolve + } + } + } + } + + /// Returns generated code implementing [`FromInputValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`FromInputValue`]: juniper::FromInputValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_from_input_value_tokens(&self) -> TokenStream { + let ty = &self.impl_for_type; + let scalar = &self.scalar; + let field = &self.field.ident; + let field_ty = &self.field.ty; + + let error_ty = self + .from_input_value + .map_or_else(|| quote! { #field_ty }, |_| quote! { Self }); + let from_input_value = self + .from_input_value + .map_or_else( + || quote! { <#field_ty as :juniper::FromInputValue<#scalar>>::from_input_value(&self.#field) }, + |from_input_value_fn| quote! { #from_input_value(self) } + ); + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty + #where_clause + { + type Error = <#error_ty as ::juniper::GraphQLScalar<#scalar>>::Error; + + fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { + #from_input_value + } + } + } + } + + /// Returns generated code implementing [`ParseScalarValue`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`ParseScalarValue`]: juniper::ParseScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_parse_scalar_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let field_ty = &self.field.ty; + + let from_str = self.from_str.map_or_else( + || quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::from_str(token) }, + |from_str_fn| quote! { #from_str_fn(token) }, + ); + + let generics = self.impl_generics(false); + let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ident#ty_gens + #where_clause + { + fn from_str( + token: ::juniper::parser::ScalarToken<'_>, + ) -> ::juniper::ParseScalarResult<'_, #scalar> { + #from_str + } + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this enum. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ty = &self.ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ty#ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} #[derive(Debug, Default)] struct TransparentAttributes { From a2287dc6853e008ebe7e0ae131a476a4004c797a Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 12 Jan 2022 12:24:57 +0300 Subject: [PATCH 059/122] Minor tests corrections --- .../src/codegen/interface_attr.rs | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index f7ea0a21b..0f31b1aa6 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -772,7 +772,7 @@ mod explicit_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -911,7 +911,7 @@ mod fallible_field { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -999,7 +999,7 @@ mod fallible_field { async fn has_correct_graphql_type() { let schema = schema(QueryRoot::Human); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name kind @@ -1016,7 +1016,7 @@ mod fallible_field { }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": { "name": "Character", @@ -1069,7 +1069,7 @@ mod generic { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -1155,7 +1155,7 @@ mod generic { #[tokio::test] async fn uses_trait_name_without_type_params() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } @@ -1164,7 +1164,7 @@ mod generic { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1207,7 +1207,7 @@ mod generic_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -1293,7 +1293,7 @@ mod generic_async { #[tokio::test] async fn uses_trait_name_without_type_params() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } @@ -1302,7 +1302,7 @@ mod generic_async { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1345,7 +1345,7 @@ mod generic_lifetime_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue<'_, ()> { match self { @@ -1431,7 +1431,7 @@ mod generic_lifetime_async { #[tokio::test] async fn uses_trait_name_without_type_params() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } @@ -1440,7 +1440,7 @@ mod generic_lifetime_async { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1490,7 +1490,7 @@ mod argument { struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { Human { @@ -1534,7 +1534,7 @@ mod argument { async fn camelcases_name() { let schema = schema(QueryRoot); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { name @@ -1545,7 +1545,7 @@ mod argument { } }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [{ "name": "idWide", @@ -1568,7 +1568,7 @@ mod argument { async fn has_no_description() { let schema = schema(QueryRoot); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { args { @@ -1579,7 +1579,7 @@ mod argument { }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [ {"args": [{"description": null}]}, @@ -1594,7 +1594,7 @@ mod argument { async fn has_no_defaults() { let schema = schema(QueryRoot); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { args { @@ -1605,7 +1605,7 @@ mod argument { }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [ {"args": [{"defaultValue": null}]}, @@ -2482,7 +2482,7 @@ mod explicit_generic_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue<__S>)] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -2510,7 +2510,7 @@ mod explicit_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(scalar = S: ScalarValue)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -2632,7 +2632,7 @@ mod bounded_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Clone + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -2787,7 +2787,7 @@ mod explicit_custom_context { Droid, } - #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(context = CustomContext)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -2851,7 +2851,7 @@ mod explicit_custom_context { #[tokio::test] async fn resolves_fields() { - let doc = r#"{ + const DOC: &str = r#"{ character { id info @@ -2869,7 +2869,7 @@ mod explicit_custom_context { let expected_info: &str = *expected_info; let expexted_more: &str = *expexted_more; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, + execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( graphql_value!({"character": { "id": expected_id, @@ -2941,7 +2941,7 @@ mod inferred_custom_context_from_field { Droid, } - #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(context = CustomContext)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3005,7 +3005,7 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_fields() { - let doc = r#"{ + const DOC: &str = r#"{ character { id info @@ -3022,7 +3022,7 @@ mod inferred_custom_context_from_field { let expected_id: &str = *expected_id; let expected_info: &str = *expected_info; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, + execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( graphql_value!({"character": { "id": expected_id, @@ -3042,24 +3042,20 @@ mod executor { #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync; + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; async fn info<'b>( &'b self, arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; + ) -> &'b str; } struct Human { home_planet: String, } - #[graphql_object(impl = CharacterValue<__S>)] + #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] impl Human { async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { executor.look_ahead().field_name() @@ -3165,7 +3161,7 @@ mod executor { #[tokio::test] async fn resolves_fields() { - let doc = r#"{ + const DOC: &str = r#"{ character { id info @@ -3177,7 +3173,7 @@ mod executor { let expected_info: &str = *expected_info; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"character": {"id": "id", "info": expected_info}}), vec![], @@ -3188,7 +3184,7 @@ mod executor { #[tokio::test] async fn not_arg() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { name @@ -3202,7 +3198,7 @@ mod executor { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [ {"name": "id", "args": []}, @@ -3347,7 +3343,7 @@ mod field_return_subtyping { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3496,7 +3492,7 @@ mod field_return_union_subtyping { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3641,7 +3637,7 @@ mod nullable_argument_subtyping { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { From ceb2cf6e94ed04106e986c233e2faf12a2864818 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 12 Jan 2022 15:31:10 +0300 Subject: [PATCH 060/122] Don't inject `#[async_trait]` in case some trait methods are async --- docs/book/content/types/interfaces.md | 10 +- .../src/codegen/interface_attr.rs | 453 +----------------- juniper/src/macros/reflection.rs | 117 +++++ juniper_codegen/src/graphql_interface/attr.rs | 34 +- juniper_codegen/src/graphql_interface/mod.rs | 41 +- juniper_codegen/src/lib.rs | 10 +- 6 files changed, 150 insertions(+), 515 deletions(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index a7d723ae1..3b0698880 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -213,17 +213,13 @@ use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _ trait Character { // If a field argument is named `executor`, it's automatically assumed // as an executor argument. - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ + fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; // Otherwise, you may mark it explicitly as an executor argument. - async fn name<'b>( + fn name<'b>( &'b self, #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; + ) -> &'b str; fn home_planet(&self) -> &str; } diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 0f31b1aa6..a1a11c195 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -119,7 +119,7 @@ mod trivial { #[graphql_object(impl = CharacterValue)] impl Droid { - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } @@ -508,7 +508,7 @@ mod trivial_async { #[graphql_interface(for = [Human, Droid])] trait Character { - async fn id(&self) -> &str; + fn id(&self) -> &str; } #[derive(GraphQLObject)] @@ -525,8 +525,7 @@ mod trivial_async { #[graphql_object(impl = CharacterValue)] impl Droid { - // FIXME: use async - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } @@ -728,144 +727,6 @@ mod trivial_async { } } -mod explicit_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - - async fn info(&self) -> String; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - info: String, - home_planet: String, - } - - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_object(impl = CharacterValue)] - impl Droid { - fn id(&self) -> &str { - &self.id - } - - async fn info(&self) -> String { - format!("Primary function is {}", &self.primary_function) - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - info: "Home planet is earth".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_fields() { - const DOC: &str = r#"{ - character { - id - info - } - }"#; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-32", "Home planet is earth"), - (QueryRoot::Droid, "droid-99", "Primary function is run"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } -} - mod fallible_field { use super::*; @@ -1170,282 +1031,6 @@ mod generic { } } -mod generic_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - async fn id(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - home_planet: String, - } - - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_object(impl = CharacterValue<(), u8>)] - impl Droid { - fn id(&self) -> &str { - &self.id - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn 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, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } -} - -mod generic_lifetime_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character<'me, A> { - async fn id<'a>(&'a self) -> &'a str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue<()>)] - struct Human { - id: String, - home_planet: String, - } - - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_object(impl = CharacterValue<()>)] - impl Droid { - fn id(&self) -> &str { - &self.id - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> CharacterValue<'_, ()> { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn 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, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } -} - mod argument { use super::*; @@ -1453,7 +1038,7 @@ mod argument { trait Character { fn id_wide(&self, is_number: bool) -> &str; - async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; + fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } struct Human { @@ -1471,7 +1056,7 @@ mod argument { &self.home_planet } - fn id_wide(&self, is_number: bool) -> &str { + async fn id_wide(&self, is_number: bool) -> &str { if is_number { &self.id } else { @@ -1627,7 +1212,7 @@ mod default_argument { #[graphql_interface(for = Human)] trait Character { - async fn id( + fn id( &self, #[graphql(default)] first: String, #[graphql(default = "second".to_string())] second: String, @@ -1641,7 +1226,7 @@ mod default_argument { #[graphql_object(impl = CharacterValue)] impl Human { - fn info(&self, coord: Point) -> i32 { + async fn info(&self, coord: Point) -> i32 { coord.x } @@ -2141,9 +1726,9 @@ mod renamed_all_fields_and_args { trait Character { fn id(&self) -> &str; - async fn home_planet(&self, planet_name: String) -> String; + fn home_planet(&self, planet_name: String) -> String; - async fn r#async_info(&self, r#my_num: i32) -> i32; + fn r#async_info(&self, r#my_num: i32) -> i32; } struct Human; @@ -2154,11 +1739,11 @@ mod renamed_all_fields_and_args { "human-32" } - fn home_planet(planet_name: String) -> String { + async fn home_planet(planet_name: String) -> String { planet_name } - fn r#async_info(r#my_num: i32) -> i32 { + async fn r#async_info(r#my_num: i32) -> i32 { r#my_num } } @@ -2356,7 +1941,7 @@ mod custom_scalar { #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] trait Character { - async fn id(&self) -> &str; + fn id(&self) -> &str; } #[derive(GraphQLObject)] @@ -2726,9 +2311,9 @@ mod explicit_custom_context { #[graphql_interface(for = [Human, Droid], context = CustomContext)] trait Character { - async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; + fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - async fn info<'b>(&'b self, ctx: &()) -> &'b str; + fn info<'b>(&'b self, ctx: &()) -> &'b str; fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; } @@ -2740,11 +2325,11 @@ mod explicit_custom_context { #[graphql_object(impl = CharacterValue, context = CustomContext)] impl Human { - fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { + async fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { &self.id } - fn home_planet(&self) -> &str { + async fn home_planet(&self) -> &str { &self.home_planet } @@ -2894,7 +2479,7 @@ mod inferred_custom_context_from_field { trait Character { fn id<'a>(&self, context: &'a CustomContext) -> &'a str; - fn info<'b>(&'b self, context: &()) -> &'b str; + fn info(&self, context: &()) -> &str; } struct Human { @@ -3042,9 +2627,9 @@ mod executor { #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; + fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; - async fn info<'b>( + fn info<'b>( &'b self, arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index d91355a33..7467b22b0 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -493,6 +493,123 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +/// Tried to call [`Field`] implementation of `set_ty` with `info`, `args` and +/// `executor` as arguments if present or panics otherwise. +/// +/// This macro uses [autoref-based specialisation][1]. +/// +/// [1]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html +#[macro_export] +macro_rules! call_field_or_panic { + ( + $field_name: expr, + $self_: expr, + $info: expr, + $args: expr, + $executor: expr $(,)? + ) => {{ + const FIELD_NAME: $crate::macros::reflection::FieldName = + $crate::macros::reflection::fnv1a128($field_name); + + struct Wrap(T); + + impl $crate::macros::reflection::FieldMeta for Wrap<&T> + where + T: $crate::macros::reflection::FieldMeta, + { + type Context = T::Context; + + type TypeInfo = T::TypeInfo; + + const TYPE: $crate::macros::reflection::Type = T::TYPE; + + const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; + + const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; + + const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; + } + + impl $crate::macros::reflection::FieldMeta for &Wrap<&T> + where + T: $crate::macros::reflection::FieldMeta, + { + type Context = T::Context; + + type TypeInfo = T::TypeInfo; + + const TYPE: $crate::macros::reflection::Type = T::TYPE; + + const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; + + const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; + + const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; + } + + /// First, we'll try to call this trait in case [`Field`] impl is present. + trait ViaField: $crate::macros::reflection::FieldMeta { + fn __call( + &self, + info: &Self::TypeInfo, + args: &$crate::Arguments, + executor: &$crate::Executor, + ) -> $crate::ExecutionResult; + } + + impl ViaField for &Wrap<&T> + where + T: $crate::macros::reflection::Field< + S, + { $crate::macros::reflection::fnv1a128($field_name) }, + >, + { + fn __call( + &self, + info: &Self::TypeInfo, + args: &$crate::Arguments, + executor: &$crate::Executor, + ) -> $crate::ExecutionResult { + self.0.call(info, args, executor) + } + } + + /// If [`Field`] impl wasn't found, we'll fallback to [`BasePanic`] + /// trait, which simply panics. + trait BasePanic: $crate::macros::reflection::FieldMeta { + fn __call( + &self, + info: &Self::TypeInfo, + args: &$crate::Arguments, + executor: &$crate::Executor, + ) -> $crate::ExecutionResult; + } + + impl BasePanic for Wrap<&T> + where + T: $crate::macros::reflection::FieldMeta< + S, + { $crate::macros::reflection::fnv1a128($field_name) }, + > + $crate::macros::reflection::BaseType, + { + fn __call( + &self, + _: &Self::TypeInfo, + _: &$crate::Arguments, + _: &$crate::Executor, + ) -> $crate::ExecutionResult { + ::std::panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + $field_name, + T::NAME, + ); + } + } + + (&&Wrap($self_)).__call($info, $args, $executor) + }}; +} + /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. #[macro_export] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a34704490..5696c4aa9 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -16,7 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{inject_async_trait, Definition, TraitAttr}; +use super::{Definition, TraitAttr}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -104,20 +104,6 @@ fn expand_on_trait( }) .unwrap_or_else(|| parse_quote! { () }); - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - let has_default_async_methods = ast.items.iter().any(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), - _ => false, - }); - let enum_alias_ident = attr .r#enum .as_deref() @@ -146,24 +132,6 @@ fn expand_on_trait( .collect(), }; - if is_async_trait { - if has_default_async_methods { - // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits - ast.supertraits.push(parse_quote! { Sync }); - } - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - } - Ok(quote! { #ast #generated_code diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 9c3ceef10..ff2a4cb56 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -903,10 +903,13 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::reflection::Field< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) + ::juniper::call_field_or_panic!( + #field_name, + v, + info, + args, + executor, + ) })* #unreachable_arm } @@ -1194,33 +1197,3 @@ impl Definition { !self.trait_generics.params.is_empty() } } - -/// Injects [`async_trait`] implementation into the given trait definition or -/// trait implementation block, correctly restricting type and lifetime -/// parameters with `'async_trait` lifetime, if required. -fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) -where - M: IntoIterator, -{ - attrs.push(parse_quote! { #[::juniper::async_trait] }); - - for method in methods.into_iter() { - if method.asyncness.is_some() { - let where_clause = &mut method.generics.make_where_clause().predicates; - for p in &generics.params { - let ty_param = match p { - syn::GenericParam::Type(t) => { - let ty_param = &t.ident; - quote! { #ty_param } - } - syn::GenericParam::Lifetime(l) => { - let ty_param = &l.lifetime; - quote! { #ty_param } - } - syn::GenericParam::Const(_) => continue, - }; - where_clause.push(parse_quote! { #ty_param: 'async_trait }); - } - } - } -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1c27cf356..f31e33748 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -482,16 +482,12 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// // NOTICE: Specifying `ScalarValue` as existing type parameter. /// #[graphql_interface(for = Human, scalar = S)] /// trait Character { -/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str -/// where -/// S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ +/// fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; /// -/// async fn name<'b>( +/// fn name<'b>( /// &'b self, /// #[graphql(executor)] another: &Executor<'_, '_, (), S>, -/// ) -> &'b str -/// where -/// S: Send + Sync; +/// ) -> &'b str; /// } /// /// struct Human { From d77a1abf49ba62e3ef161dfba6e7366db2692db8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 13 Jan 2022 08:24:36 +0300 Subject: [PATCH 061/122] Corrections --- docs/book/content/types/interfaces.md | 11 ++++------- .../interface/additional_non_nullable_argument.stderr | 2 +- .../fail/interface/argument_wrong_default_array.rs | 8 ++++++++ .../interface/argument_wrong_default_array.stderr | 11 +++++++++++ juniper/CHANGELOG.md | 2 ++ juniper/src/macros/reflection.rs | 2 +- juniper_codegen/src/lib.rs | 9 +++------ 7 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs create mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 3b0698880..aa9c543e7 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -229,19 +229,16 @@ struct Human { name: String, home_planet: String, } -#[graphql_object(impl = CharacterValue<__S>)] +#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] impl Human { - async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: ScalarValue + Send + Sync, + S: ScalarValue, { executor.look_ahead().field_name() } - async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str - where - S: ScalarValue + Send + Sync, - { + async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str { &self.name } diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr index 0805ac203..589ed21b5 100644 --- a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr @@ -2,6 +2,6 @@ error[E0080]: evaluation of constant value failed --> fail/interface/additional_non_nullable_argument.rs:14:1 | 14 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` not present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs new file mode 100644 index 000000000..00bec5f4c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs @@ -0,0 +1,8 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr new file mode 100644 index 000000000..b745a82d0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied + --> fail/interface/argument_wrong_default_array.rs:3:1 + | +3 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | + = help: the following implementations were found: + <[T; LANES] as From>> + <[bool; LANES] as From>> + = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index e85c18174..631233216 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -12,6 +12,8 @@ - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Redesign `#[graphql_interface]` macro. ([#1009](https://github.com/graphql-rust/juniper/pull/1009)): - Remove support for `#[graphql_interface(dyn)]`. + - Remove support for `downcast`. + - Remove support for `async` trait methods. - Describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait`. - Forbid default impls on non-skipped trait methods. - Add support for additional nullable arguments on implementer. diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 7467b22b0..4c2da2a17 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -930,7 +930,7 @@ macro_rules! assert_field_args { IMPL_ARG_NAME, "` of type `", IMPL_TYPE_FORMATTED, - "` not present on the interface and so has to be nullable." + "` isn't present on the interface and so has to be nullable." ) } }; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index f31e33748..90e59a318 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -494,19 +494,16 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// id: String, /// name: String, /// } -/// #[graphql_object(impl = CharacterValue<__S>)] +/// #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] /// impl Human { /// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where -/// S: ScalarValue + Send + Sync, +/// S: ScalarValue, /// { /// executor.look_ahead().field_name() /// } /// -/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str -/// where -/// S: ScalarValue + Send + Sync, -/// { +/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str { /// &self.name /// } /// } From 8fb45879f654125c35bfb0c2fe572f3e0ff0282a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 13 Jan 2022 12:40:10 +0300 Subject: [PATCH 062/122] WIP --- docs/book/content/types/scalars.md | 4 +- .../fail/scalar/derive_invalid_url.rs | 4 +- .../fail/scalar/mutliple_named_fields.rs | 9 + .../fail/scalar/mutliple_named_fields.stderr | 8 + .../mutliple_named_fields_with_resolver.rs | 16 + ...mutliple_named_fields_with_resolver.stderr | 9 + .../fail/scalar/mutliple_unnamed_fields.rs | 6 + .../scalar/mutliple_unnamed_fields.stderr | 5 + .../codegen_fail/fail/scalar/unit_struct.rs | 6 + .../fail/scalar/unit_struct.stderr | 5 + .../src/codegen/derive_scalar.rs | 65 +- .../src/codegen/scalar_value_transparent.rs | 13 +- .../juniper_tests/src/custom_scalar.rs | 148 +++- juniper/src/lib.rs | 2 +- juniper/src/value/scalar.rs | 270 ++++++- juniper_codegen/src/derive_scalar_value.rs | 685 ++++++++---------- juniper_codegen/src/lib.rs | 18 +- 17 files changed, 835 insertions(+), 438 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/unit_struct.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/unit_struct.stderr diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index f4993bfcd..a0767a3a6 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -49,7 +49,7 @@ serde supports this pattern with `#[serde(transparent)]`. ```rust # extern crate juniper; -#[derive(juniper::GraphQLScalarValue)] +#[derive(juniper::GraphQLScalar)] pub struct UserId(i32); #[derive(juniper::GraphQLObject)] @@ -67,7 +67,7 @@ The macro also allows for more customization: ```rust # extern crate juniper; /// You can use a doc comment to specify a description. -#[derive(juniper::GraphQLScalarValue)] +#[derive(juniper::GraphQLScalar)] #[graphql( transparent, // Overwrite the GraphQL type name. diff --git a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs index 50549f11b..ea56e8e8b 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs @@ -1,6 +1,6 @@ -use juniper::GraphQLScalarValue; +use juniper::GraphQLScalar; -#[derive(GraphQLScalarValue)] +#[derive(GraphQLScalar)] #[graphql(specified_by_url = "not an url")] struct ScalarSpecifiedByUrl(i64); diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.rs new file mode 100644 index 000000000..038fd656b --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.rs @@ -0,0 +1,9 @@ +use juniper::GraphQLScalar; + +#[derive(GraphQLScalar)] +struct Scalar { + id: i32, + another: i32, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr new file mode 100644 index 000000000..7822df81d --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr @@ -0,0 +1,8 @@ +error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 }or all `resolve`, `from_input_value` and `from_str` functions + --> fail/scalar/mutliple_named_fields.rs:4:1 + | +4 | / struct Scalar { +5 | | id: i32, +6 | | another: i32, +7 | | } + | |_^ diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs new file mode 100644 index 000000000..cae720bd0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs @@ -0,0 +1,16 @@ +use juniper::{GraphQLScalar, Value}; + +#[derive(GraphQLScalar)] +#[graphql(resolve = Self::resolve)] +struct Scalar { + id: i32, + another: i32, +} + +impl Scalar { + fn resolve(&self) -> Value { + Value::scalar(self.id) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr new file mode 100644 index 000000000..2a1884dd2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr @@ -0,0 +1,9 @@ +error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 }or all `resolve`, `from_input_value` and `from_str` functions + --> fail/scalar/mutliple_named_fields_with_resolver.rs:4:1 + | +4 | / #[graphql(resolve = Self::resolve)] +5 | | struct Scalar { +6 | | id: i32, +7 | | another: i32, +8 | | } + | |_^ diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.rs new file mode 100644 index 000000000..855d2351f --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLScalar; + +#[derive(GraphQLScalar)] +struct Scalar(i32, i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr new file mode 100644 index 000000000..396c0ede8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar expected exactly 1 field, e.g., Test(i32)or all `resolve`, `from_input_value` and `from_str` functions + --> fail/scalar/mutliple_unnamed_fields.rs:4:1 + | +4 | struct Scalar(i32, i32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/unit_struct.rs b/integration_tests/codegen_fail/fail/scalar/unit_struct.rs new file mode 100644 index 000000000..53964886e --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/unit_struct.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLScalar; + +#[derive(GraphQLScalar)] +struct ScalarSpecifiedByUrl; + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr new file mode 100644 index 000000000..31ae666bb --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar expected exactly 1 field, e.g., `Test(i32)` or `Test { test: i32 }` or all `resolve`, `from_input_value` and `from_str` functions + --> fail/scalar/unit_struct.rs:4:1 + | +4 | struct ScalarSpecifiedByUrl; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index 24e329bcf..97e72e404 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -5,18 +5,24 @@ use juniper::{ use crate::custom_scalar::MyScalarValue; -#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalarValue)] +#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalar)] #[graphql( - transparent, scalar = MyScalarValue, specified_by_url = "https://tools.ietf.org/html/rfc4122", )] pub struct LargeId(i64); +#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalar)] +#[graphql(scalar = MyScalarValue)] +pub struct SmallId { + id: i32, +} + #[derive(juniper::GraphQLObject)] #[graphql(scalar = MyScalarValue)] struct User { id: LargeId, + another_id: SmallId, } struct Query; @@ -24,7 +30,10 @@ struct Query; #[juniper::graphql_object(scalar = MyScalarValue)] impl Query { fn user() -> User { - User { id: LargeId(0) } + User { + id: LargeId(0), + another_id: SmallId { id: 0 }, + } } } @@ -32,8 +41,8 @@ struct Mutation; #[juniper::graphql_object(scalar = MyScalarValue)] impl Mutation { - fn change_user(id: LargeId) -> User { - User { id } + fn change_user(id: LargeId, another_id: SmallId) -> User { + User { id, another_id } } } @@ -53,6 +62,22 @@ fn test_scalar_value_large_id() { assert_eq!(output, InputValue::scalar(num)); } +#[test] +fn test_scalar_value_small_id() { + let num: i32 = i32::MAX; + let id = SmallId { id: num }; + + let input_integer: InputValue = + serde_json::from_value(serde_json::json!(num)).unwrap(); + + let output: SmallId = + FromInputValue::::from_input_value(&input_integer).unwrap(); + assert_eq!(output, id); + + let output = ToInputValue::::to_input_value(&id); + assert_eq!(output, InputValue::scalar(num)); +} + #[tokio::test] async fn test_scalar_value_large_specified_url() { let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value( @@ -85,13 +110,17 @@ async fn test_scalar_value_large_query() { ); let doc = r#"{ - user { id } + user { id anotherId } }"#; - let val = Value::::scalar(0_i64); + let id = Value::::scalar(0_i64); + let another_id = Value::::scalar(0_i32); assert_eq!( execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok((graphql_value!({"user": {"id": val}}), vec![])), + Ok(( + graphql_value!({"user": {"id": id, "anotherId": another_id}}), + vec![], + )), ); } @@ -104,22 +133,30 @@ async fn test_scalar_value_large_mutation() { ); let doc = r#"mutation { - changeUser(id: 1) { id } + changeUser(id: 1, anotherId: 2) { id anotherId } }"#; - let val = Value::::scalar(1_i64); + let id = Value::::scalar(1_i64); + let another_id = Value::::scalar(2_i32); assert_eq!( execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok((graphql_value!({"changeUser": {"id": val}}), vec![])), + Ok(( + graphql_value!({"changeUser": {"id": id, "anotherId": another_id}}), + vec![], + )), ); let doc = r#"mutation { - changeUser(id: 4294967297) { id } + changeUser(id: 4294967297, anotherId: -2147483648) { id anotherId } }"#; - let val = Value::::scalar(4294967297_i64); + let id = Value::::scalar(4294967297_i64); + let another_id = Value::::scalar(i32::MIN); assert_eq!( execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok((graphql_value!({"changeUser": {"id": val}}), vec![])), + Ok(( + graphql_value!({"changeUser": {"id": id, "anotherId": another_id}}), + vec![], + )), ); } 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 828f12d32..f5d0c877c 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs @@ -1,20 +1,19 @@ use fnv::FnvHashMap; use juniper::{ graphql_input_value, graphql_object, DefaultScalarValue, FromInputValue, GraphQLObject, - GraphQLScalarValue, GraphQLType, InputValue, Registry, ToInputValue, + GraphQLScalar, GraphQLType, InputValue, Registry, ToInputValue, }; -#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)] -#[graphql(transparent)] +#[derive(GraphQLScalar, Debug, Eq, PartialEq)] struct UserId(String); -#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)] -#[graphql(transparent, name = "MyUserId", description = "custom description...")] +#[derive(GraphQLScalar, Debug, Eq, PartialEq)] +#[graphql(name = "MyUserId", description = "custom description...")] struct CustomUserId(String); /// The doc comment... -#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)] -#[graphql(transparent, specified_by_url = "https://tools.ietf.org/html/rfc4122")] +#[derive(GraphQLScalar, Debug, Eq, PartialEq)] +#[graphql(specified_by_url = "https://tools.ietf.org/html/rfc4122")] struct IdWithDocComment(i32); #[derive(GraphQLObject)] diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index a43900386..31ee18167 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -6,11 +6,11 @@ use juniper::{ graphql_vars, parser::{ParseError, ScalarToken, Token}, serde::{de, Deserialize, Deserializer, Serialize}, - EmptyMutation, FieldResult, GraphQLScalar, GraphQLScalarValue, InputValue, Object, - ParseScalarResult, RootNode, ScalarValue, Value, Variables, + EmptyMutation, FieldResult, GraphQLScalar, InputValue, Object, ParseScalarResult, RootNode, + ScalarValue, Value, Variables, }; -#[derive(GraphQLScalarValue, Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] pub(crate) enum MyScalarValue { Int(i32), @@ -20,6 +20,148 @@ pub(crate) enum MyScalarValue { Boolean(bool), } +impl From for MyScalarValue { + fn from(v: i32) -> Self { + Self::Int(v) + } +} + +impl From for Option { + fn from(v: MyScalarValue) -> Self { + if let MyScalarValue::Int(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a MyScalarValue> for Option<&'a i32> { + fn from(v: &'a MyScalarValue) -> Self { + if let MyScalarValue::Int(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for MyScalarValue { + fn from(v: i64) -> Self { + Self::Long(v) + } +} + +impl From for Option { + fn from(v: MyScalarValue) -> Self { + if let MyScalarValue::Long(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a MyScalarValue> for Option<&'a i64> { + fn from(v: &'a MyScalarValue) -> Self { + if let MyScalarValue::Long(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for MyScalarValue { + fn from(v: f64) -> Self { + Self::Float(v) + } +} + +impl From for Option { + fn from(v: MyScalarValue) -> Self { + if let MyScalarValue::Float(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a MyScalarValue> for Option<&'a f64> { + fn from(v: &'a MyScalarValue) -> Self { + if let MyScalarValue::Float(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for MyScalarValue { + fn from(v: String) -> Self { + Self::String(v) + } +} + +impl From for Option { + fn from(v: MyScalarValue) -> Self { + if let MyScalarValue::String(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a MyScalarValue> for Option<&'a String> { + fn from(v: &'a MyScalarValue) -> Self { + if let MyScalarValue::String(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for MyScalarValue { + fn from(v: bool) -> Self { + Self::Boolean(v) + } +} + +impl From for Option { + fn from(v: MyScalarValue) -> Self { + if let MyScalarValue::Boolean(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a MyScalarValue> for Option<&'a bool> { + fn from(v: &'a MyScalarValue) -> Self { + if let MyScalarValue::Boolean(v) = v { + Some(v) + } else { + None + } + } +} + +impl fmt::Display for MyScalarValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Int(v) => v.fmt(f), + Self::Long(v) => v.fmt(f), + Self::Float(v) => v.fmt(f), + Self::String(v) => v.fmt(f), + Self::Boolean(v) => v.fmt(f), + } + } +} + impl ScalarValue for MyScalarValue { fn as_int(&self) -> Option { match self { diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 3fda0a904..95ccb8ffb 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,7 +115,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // functionality automatically. pub use juniper_codegen::{ graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, + GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalar, GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index d50172206..abf36f97b 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -2,10 +2,7 @@ use std::{borrow::Cow, fmt}; use serde::{de::DeserializeOwned, Serialize}; -use crate::{ - parser::{ParseError, ScalarToken}, - GraphQLScalarValue, -}; +use crate::parser::{ParseError, ScalarToken}; /// The result of converting a string into a scalar value pub type ParseScalarResult<'a, S = DefaultScalarValue> = Result>; @@ -22,7 +19,7 @@ pub trait ParseScalarValue { /// The main objective of this abstraction is to allow other libraries to /// replace the default representation with something that better fits their /// needs. -/// There is a custom derive (`#[derive(juniper::GraphQLScalarValue)]`) available that implements +/// There is a custom derive (`#[derive(juniper::GraphQLScalar)]`) available that implements /// most of the required traits automatically for a enum representing a scalar value. /// However, [`Serialize`](trait@serde::Serialize) and [`Deserialize`](trait@serde::Deserialize) /// implementations are expected to be provided. @@ -36,9 +33,9 @@ pub trait ParseScalarValue { /// ```rust /// # use std::{fmt, convert::TryInto as _}; /// # use serde::{de, Deserialize, Deserializer, Serialize}; -/// # use juniper::{GraphQLScalarValue, ScalarValue}; +/// # use juniper::ScalarValue; /// # -/// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] +/// #[derive(Clone, Debug, PartialEq, Serialize)] /// #[serde(untagged)] /// enum MyScalarValue { /// Int(i32), @@ -48,6 +45,148 @@ pub trait ParseScalarValue { /// Boolean(bool), /// } /// +/// impl From for MyScalarValue { +/// fn from(v: i32) -> Self { +/// Self::Int(v) +/// } +/// } +/// +/// impl From for Option { +/// fn from(v: MyScalarValue) -> Self { +/// if let MyScalarValue::Int(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl<'a> From<&'a MyScalarValue> for Option<&'a i32> { +/// fn from(v: &'a MyScalarValue) -> Self { +/// if let MyScalarValue::Int(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl From for MyScalarValue { +/// fn from(v: i64) -> Self { +/// Self::Long(v) +/// } +/// } +/// +/// impl From for Option { +/// fn from(v: MyScalarValue) -> Self { +/// if let MyScalarValue::Long(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl<'a> From<&'a MyScalarValue> for Option<&'a i64> { +/// fn from(v: &'a MyScalarValue) -> Self { +/// if let MyScalarValue::Long(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl From for MyScalarValue { +/// fn from(v: f64) -> Self { +/// Self::Float(v) +/// } +/// } +/// +/// impl From for Option { +/// fn from(v: MyScalarValue) -> Self { +/// if let MyScalarValue::Float(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl<'a> From<&'a MyScalarValue> for Option<&'a f64> { +/// fn from(v: &'a MyScalarValue) -> Self { +/// if let MyScalarValue::Float(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl From for MyScalarValue { +/// fn from(v: String) -> Self { +/// Self::String(v) +/// } +/// } +/// +/// impl From for Option { +/// fn from(v: MyScalarValue) -> Self { +/// if let MyScalarValue::String(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl<'a> From<&'a MyScalarValue> for Option<&'a String> { +/// fn from(v: &'a MyScalarValue) -> Self { +/// if let MyScalarValue::String(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl From for MyScalarValue { +/// fn from(v: bool) -> Self { +/// Self::Boolean(v) +/// } +/// } +/// +/// impl From for Option { +/// fn from(v: MyScalarValue) -> Self { +/// if let MyScalarValue::Boolean(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl<'a> From<&'a MyScalarValue> for Option<&'a bool> { +/// fn from(v: &'a MyScalarValue) -> Self { +/// if let MyScalarValue::Boolean(v) = v { +/// Some(v) +/// } else { +/// None +/// } +/// } +/// } +/// +/// impl fmt::Display for MyScalarValue { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// match self { +/// Self::Int(v) => v.fmt(f), +/// Self::Long(v) => v.fmt(f), +/// Self::Float(v) => v.fmt(f), +/// Self::String(v) => v.fmt(f), +/// Self::Boolean(v) => v.fmt(f), +/// } +/// } +/// } +/// /// impl ScalarValue for MyScalarValue { /// fn as_int(&self) -> Option { /// match self { @@ -266,7 +405,7 @@ pub trait ScalarValue: /// These types closely follow the [GraphQL specification][0]. /// /// [0]: https://spec.graphql.org/June2018 -#[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32â€bit numeric nonâ€fractional value. @@ -293,6 +432,121 @@ pub enum DefaultScalarValue { Boolean(bool), } +impl From for DefaultScalarValue { + fn from(v: i32) -> Self { + Self::Int(v) + } +} + +impl From for Option { + fn from(v: DefaultScalarValue) -> Self { + if let DefaultScalarValue::Int(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a DefaultScalarValue> for Option<&'a i32> { + fn from(v: &'a DefaultScalarValue) -> Self { + if let DefaultScalarValue::Int(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for DefaultScalarValue { + fn from(v: f64) -> Self { + Self::Float(v) + } +} + +impl From for Option { + fn from(v: DefaultScalarValue) -> Self { + if let DefaultScalarValue::Float(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a DefaultScalarValue> for Option<&'a f64> { + fn from(v: &'a DefaultScalarValue) -> Self { + if let DefaultScalarValue::Float(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for DefaultScalarValue { + fn from(v: String) -> Self { + Self::String(v) + } +} + +impl From for Option { + fn from(v: DefaultScalarValue) -> Self { + if let DefaultScalarValue::String(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a DefaultScalarValue> for Option<&'a String> { + fn from(v: &'a DefaultScalarValue) -> Self { + if let DefaultScalarValue::String(v) = v { + Some(v) + } else { + None + } + } +} + +impl From for DefaultScalarValue { + fn from(v: bool) -> Self { + Self::Boolean(v) + } +} + +impl From for Option { + fn from(v: DefaultScalarValue) -> Self { + if let DefaultScalarValue::Boolean(v) = v { + Some(v) + } else { + None + } + } +} + +impl<'a> From<&'a DefaultScalarValue> for Option<&'a bool> { + fn from(v: &'a DefaultScalarValue) -> Self { + if let DefaultScalarValue::Boolean(v) = v { + Some(v) + } else { + None + } + } +} + +impl fmt::Display for DefaultScalarValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Int(v) => v.fmt(f), + Self::Float(v) => v.fmt(f), + Self::String(v) => v.fmt(f), + Self::Boolean(v) => v.fmt(f), + } + } +} + impl ScalarValue for DefaultScalarValue { fn as_int(&self) -> Option { match self { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 333f300f4..1736eb8ef 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -1,11 +1,11 @@ -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; +use proc_macro2::{Literal, TokenStream}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned, - token, Data, Fields, Ident, Variant, + token, }; use url::Url; @@ -18,9 +18,223 @@ use crate::{ scalar, }, result::GraphQLScope, - util::{self, filter_attrs, get_doc_comment, span_container::SpanContainer}, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, }; +const ERR: GraphQLScope = GraphQLScope::DeriveScalar; + +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + + let attr = Attr::from_attrs("graphql", &ast.attrs)?; + + let field = match ( + attr.resolve.as_deref().cloned(), + attr.from_input_value.as_deref().cloned(), + attr.from_str.as_deref().cloned(), + ) { + (Some(resolve), Some(from_input_value), Some(from_str)) => { + GraphQLScalarDefinition::Custom { + resolve, + from_input_value, + from_str, + } + } + (resolve, from_input_value, from_str) => { + let data = if let syn::Data::Struct(data) = &ast.data { + data + } else { + return Err(ERR.custom_error( + ast.span(), + "expected single-field struct \ + or all `resolve`, `from_input_value` and `from_str` functions", + )); + }; + let field = match &data.fields { + syn::Fields::Unit => Err(ERR.custom_error( + ast.span(), + "expected exactly 1 field, e.g., `Test(i32)` or `Test { test: i32 }` \ + or all `resolve`, `from_input_value` and `from_str` functions", + )), + syn::Fields::Unnamed(fields) => fields + .unnamed + .first() + .and_then(|f| (fields.unnamed.len() == 1).then(|| Field::Unnamed(f.clone()))) + .ok_or_else(|| { + ERR.custom_error( + ast.span(), + "expected exactly 1 field, e.g., Test(i32)\ + or all `resolve`, `from_input_value` and `from_str` functions", + ) + }), + syn::Fields::Named(fields) => fields + .named + .first() + .and_then(|f| (fields.named.len() == 1).then(|| Field::Named(f.clone()))) + .ok_or_else(|| { + ERR.custom_error( + ast.span(), + "expected exactly 1 field, e.g., Test { test: i32 }\ + or all `resolve`, `from_input_value` and `from_str` functions", + ) + }), + }?; + GraphQLScalarDefinition::Delegated { + resolve, + from_input_value, + from_str, + field, + } + } + }; + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + Ok(Definition { + ident: ast.ident.clone(), + generics: ast.generics.clone(), + field, + name: attr + .name + .as_deref() + .cloned() + .unwrap_or_else(|| ast.ident.to_string()), + description: attr.description.as_deref().cloned(), + specified_by_url: attr.specified_by_url.as_deref().cloned(), + scalar, + } + .to_token_stream()) +} + +enum GraphQLScalarDefinition { + Custom { + resolve: syn::Path, + from_input_value: syn::Path, + from_str: syn::Path, + }, + Delegated { + resolve: Option, + from_input_value: Option, + from_str: Option, + field: Field, + }, +} + +impl GraphQLScalarDefinition { + fn resolve(&self, scalar: &scalar::Type) -> TokenStream { + let (resolve, field) = match self { + GraphQLScalarDefinition::Custom { resolve, .. } => (Some(resolve), None), + GraphQLScalarDefinition::Delegated { resolve, field, .. } => { + (resolve.as_ref(), Some(field)) + } + }; + + resolve.as_ref().map_or_else( + || quote! { ::juniper::GraphQLValue::<#scalar>::resolve(&self.#field, info, selection, executor) }, + |resolve_fn| quote! { Ok(#resolve_fn(self)) }, + ) + } + + fn to_input_value(&self, scalar: &scalar::Type) -> TokenStream { + let (resolve, field) = match self { + GraphQLScalarDefinition::Custom { resolve, .. } => (Some(resolve), None), + GraphQLScalarDefinition::Delegated { resolve, field, .. } => { + (resolve.as_ref(), Some(field)) + } + }; + + resolve.as_ref().map_or_else( + || quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) }, + |resolve_fn| { + quote! { + let v = #resolve_fn(self); + ::juniper::ToInputValue::to_input_value(&v) + } + }, + ) + } + + fn from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { + let err_ty = match self { + GraphQLScalarDefinition::Custom { .. } => parse_quote!(Self), + GraphQLScalarDefinition::Delegated { field, .. } => field.ty().clone(), + }; + + quote! { <#err_ty as ::juniper::GraphQLScalar<#scalar>>::Error } + } + + fn from_input_value(&self, scalar: &scalar::Type) -> TokenStream { + let (from_input_value, field) = match self { + GraphQLScalarDefinition::Custom { + from_input_value, .. + } => (Some(from_input_value), None), + GraphQLScalarDefinition::Delegated { + from_input_value, + field, + .. + } => (from_input_value.as_ref(), Some(field)), + }; + let field_ty = field.map(Field::ty); + let self_constructor = field.map(Field::closure_constructor); + + from_input_value.as_ref().map_or_else( + || { + quote! { + <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) + .map(#self_constructor) + } + }, + |from_input_value_fn| quote! { #from_input_value_fn(input) }, + ) + } + + fn from_str(&self, scalar: &scalar::Type) -> TokenStream { + let (from_str, field) = match self { + GraphQLScalarDefinition::Custom { from_str, .. } => (Some(from_str), None), + GraphQLScalarDefinition::Delegated { + from_str, field, .. + } => (from_str.as_ref(), Some(field)), + }; + let field_ty = field.map(Field::ty); + + from_str.as_ref().map_or_else( + || quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::from_str(token) }, + |from_str_fn| quote! { #from_str_fn(token) }, + ) + } +} + +enum Field { + Named(syn::Field), + Unnamed(syn::Field), +} + +impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Field::Named(f) => f.ident.to_tokens(tokens), + Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + } + } +} + +impl Field { + fn ty(&self) -> &syn::Type { + match self { + Field::Named(f) | Field::Unnamed(f) => &f.ty, + } + } + + fn closure_constructor(&self) -> TokenStream { + match self { + Field::Named(syn::Field { ident, .. }) => { + quote! { |v| Self { #ident: v } } + } + Field::Unnamed(_) => quote! { Self }, + } + } +} + #[derive(Default)] struct Attr { name: Option>, @@ -141,14 +355,24 @@ impl Attr { struct Definition { ident: syn::Ident, generics: syn::Generics, - field: syn::Field, + field: GraphQLScalarDefinition, name: String, description: Option, specified_by_url: Option, - scalar: Option, - resolve: Option, - from_input_value: Option, - from_str: Option, + scalar: scalar::Type, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_output_and_input_type_tokens().to_tokens(into); + self.impl_type_tokens().to_tokens(into); + self.impl_value_tokens().to_tokens(into); + self.impl_value_async_tokens().to_tokens(into); + self.impl_to_input_value_tokens().to_tokens(into); + self.impl_from_input_value_tokens().to_tokens(into); + self.impl_parse_scalar_value_tokens().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); + } } impl Definition { @@ -164,7 +388,8 @@ impl Definition { let scalar = &self.scalar; let generics = self.impl_generics(false); - let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ident#ty_gens @@ -195,7 +420,8 @@ impl Definition { }); let generics = self.impl_generics(false); - let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { impl#impl_gens ::juniper::GraphQLType<#scalar> for #ident#ty_gens @@ -229,15 +455,12 @@ impl Definition { fn impl_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; - let field = &self.field.ident; - let generics = self.impl_generics(false); - let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + let resolve = self.field.resolve(&scalar); - let resolve = self.resolve.map_or_else( - || quote! { ::juniper::GraphQLValue::<#scalar>::resolve(&self.#field, info, selection, executor) }, - |resolve_fn| quote! { Ok(#resolve_fn(self)) }, - ); + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ident#ty_gens @@ -272,7 +495,8 @@ impl Definition { let scalar = &self.scalar; let generics = self.impl_generics(true); - let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ident#ty_gens @@ -300,27 +524,19 @@ impl Definition { fn impl_to_input_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; - let field = &self.field.ident; - let resolve = self.resolve.map_or_else( - || quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(self.#field) }, - |resolve_fn| { - quote! { - let v = #resolve_fn(self); - ::juniper::ToInputValue::to_input_value(&v) - } - }, - ); + let to_input_value = self.field.to_input_value(&scalar); let generics = self.impl_generics(false); - let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { impl#impl_gens ::juniper::ToInputValue<#scalar> for #ident#ty_gens #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - #resolve + #to_input_value } } } @@ -332,29 +548,21 @@ impl Definition { /// [`FromInputValue`]: juniper::FromInputValue /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_from_input_value_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; + let ident = &self.ident; let scalar = &self.scalar; - let field = &self.field.ident; - let field_ty = &self.field.ty; - - let error_ty = self - .from_input_value - .map_or_else(|| quote! { #field_ty }, |_| quote! { Self }); - let from_input_value = self - .from_input_value - .map_or_else( - || quote! { <#field_ty as :juniper::FromInputValue<#scalar>>::from_input_value(&self.#field) }, - |from_input_value_fn| quote! { #from_input_value(self) } - ); + + let error_ty = self.field.from_input_value_err(&scalar); + let from_input_value = self.field.from_input_value(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { - impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty + impl#impl_gens ::juniper::FromInputValue<#scalar> for #ident#ty_gens #where_clause { - type Error = <#error_ty as ::juniper::GraphQLScalar<#scalar>>::Error; + type Error = #error_ty; fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { #from_input_value @@ -371,15 +579,12 @@ impl Definition { fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; - let field_ty = &self.field.ty; - let from_str = self.from_str.map_or_else( - || quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::from_str(token) }, - |from_str_fn| quote! { #from_str_fn(token) }, - ); + let from_str = self.field.from_str(&scalar); let generics = self.impl_generics(false); - let (impl_gens, ty_gens, where_clause) = generics.split_for_impl(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ident#ty_gens @@ -394,6 +599,44 @@ impl Definition { } } + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL scalar][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let name = &self.name; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); + + quote! { + impl#impl_gens ::juniper::macros::reflection::BaseType<#scalar> for #ident#ty_gens + #where_clause + { + const NAME: ::juniper::macros::reflection::Type = #name; + } + + impl#impl_gens ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident#ty_gens + #where_clause + { + const NAMES: ::juniper::macros::reflection::Types = + &[>::NAME]; + } + + impl#impl_gens ::juniper::macros::reflection::WrappedType<#scalar> for #ident#ty_gens + #where_clause + { + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + } + } + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this enum. /// @@ -454,341 +697,3 @@ impl Definition { generics } } - -#[derive(Debug, Default)] -struct TransparentAttributes { - transparent: Option, - name: Option, - description: Option, - specified_by_url: Option, - scalar: Option, -} - -impl syn::parse::Parse for TransparentAttributes { - fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { - let mut output = Self { - transparent: None, - name: None, - description: None, - specified_by_url: None, - scalar: None, - }; - - while !input.is_empty() { - let ident: syn::Ident = input.parse()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let val = input.parse::()?; - output.name = Some(val.value()); - } - "description" => { - input.parse::()?; - let val = input.parse::()?; - output.description = Some(val.value()); - } - "specified_by_url" => { - input.parse::()?; - let val: syn::LitStr = input.parse::()?; - output.specified_by_url = - Some(val.value().parse().map_err(|e| { - syn::Error::new(val.span(), format!("Invalid URL: {}", e)) - })?); - } - "transparent" => { - output.transparent = Some(true); - } - "scalar" | "Scalar" => { - input.parse::()?; - let val = input.parse::()?; - output.scalar = Some(val); - } - _ => return Err(syn::Error::new(ident.span(), "unknown attribute")), - } - input.try_parse::()?; - } - - Ok(output) - } -} - -impl TransparentAttributes { - fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result { - match util::find_graphql_attr(attrs) { - Some(attr) => { - let mut parsed: TransparentAttributes = attr.parse_args()?; - if parsed.description.is_none() { - parsed.description = - util::get_doc_comment(attrs).map(SpanContainer::into_inner); - } - Ok(parsed) - } - None => Ok(Default::default()), - } - } -} - -pub fn impl_scalar_value(ast: &syn::DeriveInput, error: GraphQLScope) -> syn::Result { - let ident = &ast.ident; - - match ast.data { - Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, error), - Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, error), - Data::Union(_) => Err(error.custom_error(ast.span(), "may not be applied to unions")), - } -} - -fn impl_scalar_struct( - ast: &syn::DeriveInput, - data: &syn::DataStruct, - error: GraphQLScope, -) -> syn::Result { - let field = match data.fields { - syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { - fields.unnamed.first().unwrap() - } - _ => { - return Err(error.custom_error( - data.fields.span(), - "requires exact one field, e.g., Test(i32)", - )) - } - }; - let ident = &ast.ident; - let attrs = TransparentAttributes::from_attrs(&ast.attrs)?; - let inner_ty = &field.ty; - let name = attrs.name.unwrap_or_else(|| ident.to_string()); - - let description = attrs.description.map(|val| quote!(.description(#val))); - let specified_by_url = attrs.specified_by_url.map(|url| { - let url_lit = url.as_str(); - quote!(.specified_by_url(#url_lit)) - }); - - let scalar = attrs - .scalar - .as_ref() - .map(|s| quote!( #s )) - .unwrap_or_else(|| quote!(__S)); - - let impl_generics = attrs - .scalar - .as_ref() - .map(|_| quote!()) - .unwrap_or_else(|| quote!(<__S>)); - - let _async = quote!( - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ident - where - Self: Sync, - Self::TypeInfo: Sync, - Self::Context: Sync, - #scalar: ::juniper::ScalarValue + Send + Sync, - { - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [::juniper::Selection<#scalar>]>, - executor: &'a ::juniper::Executor, - ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#scalar>> { - use ::juniper::futures::future; - let v = ::juniper::GraphQLValue::<#scalar>::resolve(self, info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - ); - - let content = quote!( - #_async - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - 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, - { - registry.build_scalar_type::(info) - #description - #specified_by_url - .into_meta() - } - } - - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - type Context = (); - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - fn resolve( - &self, - info: &(), - selection: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - ::juniper::GraphQLValue::<#scalar>::resolve(&self.0, info, selection, executor) - } - } - - impl#impl_generics ::juniper::ToInputValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - ::juniper::ToInputValue::<#scalar>::to_input_value(&self.0) - } - } - - impl#impl_generics ::juniper::FromInputValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - type Error = <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error; - - fn from_input_value( - v: &::juniper::InputValue<#scalar> - ) -> Result<#ident, <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error> { - let inner: #inner_ty = ::juniper::FromInputValue::<#scalar>::from_input_value(v)?; - Ok(#ident(inner)) - } - } - - impl#impl_generics ::juniper::ParseScalarValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - fn from_str<'a>( - value: ::juniper::parser::ScalarToken<'a>, - ) -> ::juniper::ParseScalarResult<'a, #scalar> { - <#inner_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(value) - } - } - - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { } - impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { } - - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { - const NAME: ::juniper::macros::reflection::Type = #name; - } - - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; - } - - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; - } - ); - - Ok(content) -} - -fn impl_scalar_enum( - ident: &syn::Ident, - data: &syn::DataEnum, - error: GraphQLScope, -) -> syn::Result { - let froms = data - .variants - .iter() - .map(|v| derive_from_variant(v, ident, &error)) - .collect::, _>>()?; - - let display = derive_display(data.variants.iter(), ident); - - Ok(quote! { - #(#froms)* - - #display - }) -} - -fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream -where - I: Iterator, -{ - let arms = variants.map(|v| { - let variant = &v.ident; - quote!(#ident::#variant(ref v) => write!(f, "{}", v),) - }); - - quote! { - impl std::fmt::Display for #ident { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - #(#arms)* - } - } - } - } -} - -fn derive_from_variant( - variant: &Variant, - ident: &Ident, - error: &GraphQLScope, -) -> syn::Result { - let ty = match variant.fields { - Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty, - - _ => { - return Err(error.custom_error( - variant.fields.span(), - "requires exact one field, e.g., Test(i32)", - )) - } - }; - - let variant = &variant.ident; - - Ok(quote! { - impl ::std::convert::From<#ty> for #ident { - fn from(t: #ty) -> Self { - #ident::#variant(t) - } - } - - impl<'a> ::std::convert::From<&'a #ident> for std::option::Option<&'a #ty> { - fn from(t: &'a #ident) -> Self { - match *t { - #ident::#variant(ref t) => std::option::Option::Some(t), - _ => std::option::Option::None - } - } - } - - impl ::std::convert::From<#ident> for std::option::Option<#ty> { - fn from(t: #ident) -> Self { - match t { - #ident::#variant(t) => std::option::Option::Some(t), - _ => std::option::Option::None - } - } - } - }) -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c48c11c90..9f8542a9c 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -143,7 +143,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { } } -/// This custom derive macro implements the #[derive(GraphQLScalarValue)] +/// This custom derive macro implements the #[derive(GraphQLScalar)] /// derive. /// /// This can be used for two purposes. @@ -156,7 +156,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// /// ```rust /// // Deriving GraphQLScalar is all that is required. -/// #[derive(juniper::GraphQLScalarValue)] +/// #[derive(juniper::GraphQLScalar)] /// struct UserId(String); /// /// #[derive(juniper::GraphQLObject)] @@ -169,9 +169,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// /// ```rust /// /// Doc comments are used for the GraphQL type description. -/// #[derive(juniper::GraphQLScalarValue)] +/// #[derive(juniper::GraphQLScalar)] /// #[graphql( -/// transparent, /// // Set a custom GraphQL name. /// name= "MyUserId", /// // A description can also specified in the attribute. @@ -188,14 +187,11 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// TODO: write documentation. /// #[proc_macro_error] -#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))] +#[proc_macro_derive(GraphQLScalar, attributes(graphql))] pub fn derive_scalar_value(input: TokenStream) -> TokenStream { - let ast = syn::parse::(input).unwrap(); - let gen = derive_scalar_value::impl_scalar_value(&ast, GraphQLScope::DeriveScalar); - match gen { - Ok(gen) => gen.into(), - Err(err) => proc_macro_error::abort!(err), - } + derive_scalar_value::expand(input.into()) + .unwrap_or_abort() + .into() } /// Expose GraphQL scalars From 52c14b2ea5b310c35cbe20c525f891143007cbb4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 13 Jan 2022 14:35:57 +0300 Subject: [PATCH 063/122] WIP --- .../src/codegen/derive_scalar.rs | 64 ++++++- juniper_codegen/src/derive_scalar_value.rs | 162 +++++++++++------- 2 files changed, 161 insertions(+), 65 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index 97e72e404..ab1aadaea 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -1,6 +1,10 @@ +use std::fmt; + +use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - execute, graphql_value, EmptyMutation, EmptySubscription, FromInputValue, InputValue, RootNode, - ToInputValue, Value, Variables, + execute, graphql_value, EmptyMutation, EmptySubscription, FromInputValue, InputValue, + ParseScalarResult, ParseScalarValue, RootNode, ScalarToken, ScalarValue, ToInputValue, Value, + Variables, }; use crate::custom_scalar::MyScalarValue; @@ -18,6 +22,62 @@ pub struct SmallId { id: i32, } +#[derive(juniper::GraphQLScalar)] +#[graphql( + scalar = S: ScalarValue, + specified_by_url = "https://tools.ietf.org/html/rfc3339", + resolve = resolve_custom_date_time, + from_input_value = custom_dt::from_input_value, + from_input_value_err = String, + from_str = from_str_custom_date_time, +)] +struct CustomDateTime +where + Tz: From + TimeZone, + Tz::Offset: fmt::Display, +{ + dt: DateTime, + _unused: (), +} + +fn from_str_custom_date_time(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> +where + S: ScalarValue, +{ + >::from_str(value) +} + +fn resolve_custom_date_time(dt: &CustomDateTime) -> Value +where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, +{ + Value::scalar(dt.dt.to_rfc3339()) +} + +mod custom_dt { + use super::{fmt, CustomDateTime, DateTime, InputValue, ScalarValue, TimeZone, Utc}; + + pub(super) fn from_input_value(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime { + dt: dt.with_timezone(&Tz::from(Utc)), + _unused: (), + }) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } +} + #[derive(juniper::GraphQLObject)] #[graphql(scalar = MyScalarValue)] struct User { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 1736eb8ef..5b843a230 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -31,16 +31,29 @@ pub fn expand(input: TokenStream) -> syn::Result { let field = match ( attr.resolve.as_deref().cloned(), attr.from_input_value.as_deref().cloned(), + attr.from_input_value_err.as_deref().cloned(), attr.from_str.as_deref().cloned(), ) { - (Some(resolve), Some(from_input_value), Some(from_str)) => { + (Some(resolve), Some(from_input_value), Some(from_input_value_err), Some(from_str)) => { GraphQLScalarDefinition::Custom { resolve, - from_input_value, + from_input_value: (from_input_value, from_input_value_err), from_str, } } - (resolve, from_input_value, from_str) => { + (resolve, from_input_value, from_input_value_err, from_str) => { + let from_input_value = match (from_input_value, from_input_value_err) { + (Some(from_input_value), Some(err)) => Some((from_input_value, err)), + (None, None) => None, + _ => { + return Err(ERR.custom_error( + ast.span(), + "`from_input_value` attribute should be provided in \ + tandem with `from_input_value_err`", + )) + } + }; + let data = if let syn::Data::Struct(data) = &ast.data { data } else { @@ -109,12 +122,12 @@ pub fn expand(input: TokenStream) -> syn::Result { enum GraphQLScalarDefinition { Custom { resolve: syn::Path, - from_input_value: syn::Path, + from_input_value: (syn::Path, syn::Type), from_str: syn::Path, }, Delegated { resolve: Option, - from_input_value: Option, + from_input_value: Option<(syn::Path, syn::Type)>, from_str: Option, field: Field, }, @@ -122,85 +135,99 @@ enum GraphQLScalarDefinition { impl GraphQLScalarDefinition { fn resolve(&self, scalar: &scalar::Type) -> TokenStream { - let (resolve, field) = match self { - GraphQLScalarDefinition::Custom { resolve, .. } => (Some(resolve), None), - GraphQLScalarDefinition::Delegated { resolve, field, .. } => { - (resolve.as_ref(), Some(field)) + match self { + Self::Custom { resolve, .. } + | Self::Delegated { + resolve: Some(resolve), + .. + } => { + quote! { Ok(#resolve(self)) } } - }; - - resolve.as_ref().map_or_else( - || quote! { ::juniper::GraphQLValue::<#scalar>::resolve(&self.#field, info, selection, executor) }, - |resolve_fn| quote! { Ok(#resolve_fn(self)) }, - ) + Self::Delegated { field, .. } => { + quote! { + ::juniper::GraphQLValue::<#scalar>::resolve( + &self.#field, + info, + selection, + executor, + ) + } + } + } } fn to_input_value(&self, scalar: &scalar::Type) -> TokenStream { - let (resolve, field) = match self { - GraphQLScalarDefinition::Custom { resolve, .. } => (Some(resolve), None), - GraphQLScalarDefinition::Delegated { resolve, field, .. } => { - (resolve.as_ref(), Some(field)) - } - }; - - resolve.as_ref().map_or_else( - || quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) }, - |resolve_fn| { + match self { + Self::Custom { resolve, .. } + | Self::Delegated { + resolve: Some(resolve), + .. + } => { quote! { - let v = #resolve_fn(self); + let v = #resolve(self); ::juniper::ToInputValue::to_input_value(&v) } - }, - ) + } + Self::Delegated { field, .. } => { + quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) } + } + } } fn from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { - let err_ty = match self { - GraphQLScalarDefinition::Custom { .. } => parse_quote!(Self), - GraphQLScalarDefinition::Delegated { field, .. } => field.ty().clone(), - }; - - quote! { <#err_ty as ::juniper::GraphQLScalar<#scalar>>::Error } + match self { + Self::Custom { + from_input_value: (_, err), + .. + } + | Self::Delegated { + from_input_value: Some((_, err)), + .. + } => quote! { #err }, + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::Error } + } + } } fn from_input_value(&self, scalar: &scalar::Type) -> TokenStream { - let (from_input_value, field) = match self { - GraphQLScalarDefinition::Custom { - from_input_value, .. - } => (Some(from_input_value), None), - GraphQLScalarDefinition::Delegated { - from_input_value, - field, + match self { + Self::Custom { + from_input_value: (from_input_value, _), .. - } => (from_input_value.as_ref(), Some(field)), - }; - let field_ty = field.map(Field::ty); - let self_constructor = field.map(Field::closure_constructor); - - from_input_value.as_ref().map_or_else( - || { + } + | Self::Delegated { + from_input_value: Some((from_input_value, _)), + .. + } => { + quote! { #from_input_value(input) } + } + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let self_constructor = field.closure_constructor(); quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) .map(#self_constructor) } - }, - |from_input_value_fn| quote! { #from_input_value_fn(input) }, - ) + } + } } fn from_str(&self, scalar: &scalar::Type) -> TokenStream { - let (from_str, field) = match self { - GraphQLScalarDefinition::Custom { from_str, .. } => (Some(from_str), None), - GraphQLScalarDefinition::Delegated { - from_str, field, .. - } => (from_str.as_ref(), Some(field)), - }; - let field_ty = field.map(Field::ty); - - from_str.as_ref().map_or_else( - || quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::from_str(token) }, - |from_str_fn| quote! { #from_str_fn(token) }, - ) + match self { + Self::Custom { from_str, .. } + | Self::Delegated { + from_str: Some(from_str), + .. + } => { + quote! { #from_str(token) } + } + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::from_str(token) } + } + } } } @@ -243,6 +270,7 @@ struct Attr { scalar: Option>, resolve: Option>, from_input_value: Option>, + from_input_value_err: Option>, from_str: Option>, } @@ -305,6 +333,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "from_input_value_err" => { + input.parse::()?; + let scl = input.parse::()?; + out.from_input_value_err + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } "from_str" => { input.parse::()?; let scl = input.parse::()?; @@ -333,6 +368,7 @@ impl Attr { scalar: try_merge_opt!(scalar: self, another), resolve: try_merge_opt!(resolve: self, another), from_input_value: try_merge_opt!(from_input_value: self, another), + from_input_value_err: try_merge_opt!(from_input_value_err: self, another), from_str: try_merge_opt!(from_str: self, another), }) } From 4b6df340dcfd6f6968cda25f38d019c9c418a1c9 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 08:55:20 +0300 Subject: [PATCH 064/122] Corrections --- docs/book/content/types/scalars.md | 6 +- examples/actix_subscriptions/src/main.rs | 2 +- .../fail/scalar/impl_invalid_url.rs | 6 +- .../juniper_tests/src/codegen/impl_scalar.rs | 834 ++++++++++-------- .../juniper_tests/src/custom_scalar.rs | 6 +- .../src/executor_tests/introspection/mod.rs | 6 +- juniper/src/executor_tests/variables.rs | 6 +- juniper/src/integrations/bson.rs | 12 +- juniper/src/integrations/chrono.rs | 30 +- juniper/src/integrations/chrono_tz.rs | 6 +- juniper/src/integrations/time.rs | 30 +- juniper/src/integrations/url.rs | 6 +- juniper/src/integrations/uuid.rs | 6 +- juniper/src/tests/fixtures/starwars/schema.rs | 10 +- juniper/src/types/scalars.rs | 30 +- juniper/src/value/mod.rs | 6 +- juniper_codegen/src/impl_scalar.rs | 12 +- juniper_codegen/src/lib.rs | 6 +- 18 files changed, 582 insertions(+), 438 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index f4993bfcd..19694523a 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -128,19 +128,19 @@ where type Error = String; // Define how to convert your custom scalar into a primitive type. - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.to_string()) } // Define how to parse a primitive type into your custom scalar. - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) } // Define how to parse a string value. - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 6da5ad562..d36c34d5a 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -10,7 +10,7 @@ use actix_web::{ use juniper::{ graphql_object, graphql_subscription, graphql_value, - tests::fixtures::starwars::schema::{Character as _, Database, Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, FieldError, RootNode, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs index 1b0c8b09a..d3894a34c 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs @@ -6,17 +6,17 @@ struct ScalarSpecifiedByUrl(i32); impl GraphQLScalar for ScalarSpecifiedByUrl { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 093def8d3..8d834b62a 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -3,485 +3,629 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, GraphQLScalar, InputValue, Object, ParseScalarResult, + EmptyMutation, EmptySubscription, GraphQLScalar, GraphQLType, InputValue, ParseScalarResult, ParseScalarValue, RootNode, ScalarToken, ScalarValue, Value, }; -use crate::custom_scalar::MyScalarValue; - -struct DefaultName(i32); -struct OtherOrder(i32); -struct Named(i32); -struct ScalarDescription(i32); -struct ScalarSpecifiedByUrl(i32); -struct Generated(String); -struct CustomDateTime(DateTime); +fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> +where + Q: GraphQLType + 'q, +{ + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} -struct Root; +fn schema_with_scalar<'q, S, C, Q>( + query_root: Q, +) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> +where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, +{ + RootNode::new_with_scalar_value( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} -/* +mod trivial { + use super::*; -Syntax to validate: + struct Counter(i32); -* Default name vs. custom name -* Description vs. no description on the scalar + #[graphql_scalar] + impl GraphQLScalar for Counter { + type Error = String; -*/ + fn to_output(&self) -> Value { + Value::scalar(self.0) + } -#[graphql_scalar] -impl GraphQLScalar for DefaultName { - type Error = String; + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } - fn resolve(&self) -> Value { - Value::scalar(self.0) + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { + ::from_str(value) + } } - fn from_input_value(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + struct QueryRoot; - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } } -} -#[graphql_scalar] -impl GraphQLScalar for OtherOrder { - type Error = String; + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; - fn resolve(&self) -> Value { - Value::scalar(self.0) - } + let schema = schema(QueryRoot); - fn from_input_value(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) - } -} + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; -#[graphql_scalar(name = "ANamedScalar")] -impl GraphQLScalar for Named { - type Error = String; + let schema = schema(QueryRoot); - fn resolve(&self) -> Value { - Value::scalar(self.0) + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); } - fn from_input_value(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } -#[graphql_scalar(description = "A sample scalar, represented as an integer")] -impl GraphQLScalar for ScalarDescription { - type Error = String; +mod explicit_name { + use super::*; - fn resolve(&self) -> Value { - Value::scalar(self.0) - } + struct CustomCounter(i32); - fn from_input_value(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + #[graphql_scalar(name = "Counter")] + impl GraphQLScalar for CustomCounter { + type Error = String; - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) - } -} + fn to_output(&self) -> Value { + Value::scalar(self.0) + } -#[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")] -impl GraphQLScalar for ScalarSpecifiedByUrl { - type Error = String; + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } - fn resolve(&self) -> Value { - Value::scalar(self.0) + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { + ::from_str(value) + } } - fn from_input_value(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + struct QueryRoot; - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: CustomCounter) -> CustomCounter { + value + } } -} -#[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc3339")] -impl GraphQLScalar for CustomDateTime -where - Tz: From + TimeZone, - Tz::Offset: fmt::Display, -{ - type Error = String; + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; - fn resolve(&self) -> Value { - Value::scalar(self.0.to_rfc3339()) - } + let schema = schema(QueryRoot); - fn from_input_value(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| Self(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) - }) + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + #[tokio::test] + async fn no_custom_counter() { + const DOC: &str = r#"{ + __type(name: "CustomCounter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!(null), vec![])), + ); } -} -macro_rules! impl_scalar { - ($name: ident) => { - #[graphql_scalar] - impl GraphQLScalar for $name - where - S: ScalarValue, - { - type Error = &'static str; - - fn resolve(&self) -> Value { - Value::scalar(self.0.clone()) - } + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; - fn from_input_value(v: &InputValue) -> Result { - v.as_scalar_value() - .and_then(|v| v.as_str()) - .and_then(|s| Some(Self(s.to_owned()))) - .ok_or_else(|| "Expected `String`") - } + let schema = schema(QueryRoot); - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description } - } - }; -} + }"#; -impl_scalar!(Generated); + let schema = schema(QueryRoot); -#[graphql_object(scalar = DefaultScalarValue)] -impl Root { - fn default_name() -> DefaultName { - DefaultName(0) - } - fn other_order() -> OtherOrder { - OtherOrder(0) - } - fn named() -> Named { - Named(0) - } - fn scalar_description() -> ScalarDescription { - ScalarDescription(0) - } - fn scalar_specified_by_url() -> ScalarSpecifiedByUrl { - ScalarSpecifiedByUrl(0) - } - fn generated() -> Generated { - Generated("foo".to_owned()) - } - fn custom_date_time() -> CustomDateTime { - CustomDateTime(Utc.timestamp_nanos(0)) + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } -struct WithCustomScalarValue(i32); +mod generic { + use super::*; -#[graphql_scalar] -impl GraphQLScalar for WithCustomScalarValue { - type Error = String; + struct CustomDateTime(DateTime); - fn resolve(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input_value(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected Int, found: {}", v)) - } + #[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc3339")] + impl GraphQLScalar for CustomDateTime + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + type Error = String; - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { - >::from_str(value) - } -} + fn to_output(&self) -> Value { + Value::scalar(self.0.to_rfc3339()) + } -struct RootWithCustomScalarValue; + fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| Self(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } -#[graphql_object(scalar = MyScalarValue)] -impl RootWithCustomScalarValue { - fn with_custom_scalar_value() -> WithCustomScalarValue { - WithCustomScalarValue(0) + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } } - fn with_generic_scalar_value() -> CustomDateTime { - CustomDateTime(Utc.timestamp(0, 0)) + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } } -} -async fn run_type_info_query(doc: &str, f: F) -where - F: Fn(&Object) -> (), -{ - let schema = RootNode::new( - Root {}, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - let (result, errs) = execute(doc, None, &schema, &graphql_vars! {}, &()) - .await - .expect("Execution failed"); + let schema = schema(QueryRoot); - assert_eq!(errs, []); + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } - println!("Result: {:#?}", result); + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; - let type_info = result - .as_object_value() - .expect("Result is not an object") - .get_field_value("__type") - .expect("__type field missing") - .as_object_value() - .expect("__type field not an object value"); + let schema = schema(QueryRoot); - f(type_info); + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } } -#[test] -fn path_in_resolve_return_type() { - struct ResolvePath(i32); +mod description_from_doc_comment { + use super::*; + + struct Counter(i32); + /// Doc comment. #[graphql_scalar] - impl GraphQLScalar for ResolvePath { + impl GraphQLScalar for Counter { type Error = String; - fn resolve(&self) -> self::Value { + fn to_output(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { ::from_str(value) } } -} -#[tokio::test] -async fn default_name_introspection() { - let doc = r#" - { - __type(name: "DefaultName") { - name - description + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value } } - "#; - run_type_info_query(doc, |type_info| { + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("DefaultName")), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + assert_eq!( - type_info.get_field_value("description"), - Some(&graphql_value!(null)), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Doc comment."}}), + vec![], + )), ); - }) - .await; + } } -#[tokio::test] -async fn other_order_introspection() { - let doc = r#" - { - __type(name: "OtherOrder") { - name - description +mod description_from_attribute { + use super::*; + + struct Counter(i32); + + /// Doc comment. + #[graphql_scalar(desc = "Doc comment from attribute.")] + #[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")] + impl GraphQLScalar for Counter { + type Error = String; + + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { + ::from_str(value) } } - "#; - run_type_info_query(doc, |type_info| { - assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("OtherOrder")), - ); - assert_eq!( - type_info.get_field_value("description"), - Some(&graphql_value!(null)), - ); - }) - .await; -} + struct QueryRoot; -#[tokio::test] -async fn named_introspection() { - let doc = r#" - { - __type(name: "ANamedScalar") { - name - description + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value } } - "#; - run_type_info_query(doc, |type_info| { + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("ANamedScalar")), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), ); + } + + #[tokio::test] + async fn has_description_and_url() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + specifiedByUrl + } + }"#; + + let schema = schema(QueryRoot); + assert_eq!( - type_info.get_field_value("description"), - Some(&graphql_value!(null)), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({ + "__type": { + "description": "Doc comment from attribute.", + "specifiedByUrl": "https://tools.ietf.org/html/rfc4122", + } + }), + vec![], + )), ); - }) - .await; + } } -#[tokio::test] -async fn generic_introspection() { - let doc = r#" - { - __type(name: "CustomDateTime") { - name - description +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + struct Counter(i32); + + #[graphql_scalar] + impl GraphQLScalar for Counter { + type Error = String; + + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { + >::from_str(value) } } - "#; - run_type_info_query(doc, |type_info| { - assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("CustomDateTime")), - ); - assert_eq!( - type_info.get_field_value("description"), - Some(&graphql_value!(null)), - ); - }) - .await; -} + struct QueryRoot; -#[tokio::test] -async fn scalar_description_introspection() { - let doc = r#" - { - __type(name: "ScalarDescription") { - name - description - specifiedByUrl + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value } } - "#; - run_type_info_query(doc, |type_info| { + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("ScalarDescription")), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("description"), - Some(&graphql_value!( - "A sample scalar, represented as an integer", - )), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("specifiedByUrl"), - Some(&graphql_value!(null)), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); - }) - .await; + } } -#[tokio::test] -async fn scalar_specified_by_url_introspection() { - let doc = r#"{ - __type(name: "ScalarSpecifiedByUrl") { - name - specifiedByUrl +mod generic_scalar { + use super::*; + + struct Counter(i32); + + #[graphql_scalar] + impl GraphQLScalar for Counter + where + S: ScalarValue, + { + type Error = String; + + fn to_output(&self) -> Value { + Value::scalar(self.0) } - }"#; - run_type_info_query(doc, |type_info| { + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("ScalarSpecifiedByUrl")), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("specifiedByUrl"), - Some(&graphql_value!("https://tools.ietf.org/html/rfc4122")), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), ); - }) - .await; + } } -#[tokio::test] -async fn generated_scalar_introspection() { - let doc = r#" +mod bounded_generic_scalar { + use super::*; + + struct Counter(i32); + + #[graphql_scalar] + impl GraphQLScalar for Counter + where + S: ScalarValue + Clone, { - __type(name: "Generated") { - name - description + type Error = String; + + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) } } - "#; - run_type_info_query(doc, |type_info| { + struct QueryRoot; + + #[graphql_object(scalar = S: ScalarValue + Clone)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!("Generated")), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + assert_eq!( - type_info.get_field_value("description"), - Some(&graphql_value!(null)), + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), ); - }) - .await; -} - -#[tokio::test] -async fn resolves_with_custom_scalar_value() { - const DOC: &str = r#"{ withCustomScalarValue withGenericScalarValue }"#; - - let schema = RootNode::<_, _, _, MyScalarValue>::new_with_scalar_value( - RootWithCustomScalarValue, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({ - "withCustomScalarValue": 0, - "withGenericScalarValue": "1970-01-01T00:00:00+00:00", - }), - vec![] - )), - ); + } } diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index a43900386..d32f51dd6 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -136,17 +136,17 @@ impl<'de> Deserialize<'de> for MyScalarValue { impl GraphQLScalar for i64 { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_scalar_value::() .copied() .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 52bf2a6a9..a4561ceea 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -26,17 +26,17 @@ struct Scalar(i32); impl GraphQLScalar for Scalar { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.0) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() .map(Self) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 8b0dda6b7..75c5d1596 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -17,18 +17,18 @@ struct TestComplexScalar; impl GraphQLScalar for TestComplexScalar { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { graphql_value!("SerializedValue") } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") .map(|_| Self) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index e344006cf..a927aff2f 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -14,11 +14,11 @@ use crate::{ impl GraphQLScalar for ObjectId { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.to_hex()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -26,7 +26,7 @@ impl GraphQLScalar for ObjectId { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(val) = value { Ok(S::from(val.to_owned())) } else { @@ -39,11 +39,11 @@ impl GraphQLScalar for ObjectId { impl GraphQLScalar for UtcDateTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar((*self).to_chrono().to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -53,7 +53,7 @@ impl GraphQLScalar for UtcDateTime { .map(Self::from_chrono) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(val) = value { Ok(S::from(val.to_owned())) } else { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 94a7a0427..c69796e2e 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -28,11 +28,11 @@ use crate::{ impl GraphQLScalar for DateTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -41,7 +41,7 @@ impl GraphQLScalar for DateTime { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -54,11 +54,11 @@ impl GraphQLScalar for DateTime { impl GraphQLScalar for DateTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.to_rfc3339()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -67,7 +67,7 @@ impl GraphQLScalar for DateTime { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -85,11 +85,11 @@ impl GraphQLScalar for DateTime { impl GraphQLScalar for NaiveDate { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.format("%Y-%m-%d").to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -98,7 +98,7 @@ impl GraphQLScalar for NaiveDate { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -112,11 +112,11 @@ impl GraphQLScalar for NaiveDate { impl GraphQLScalar for NaiveTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.format("%H:%M:%S").to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -125,7 +125,7 @@ impl GraphQLScalar for NaiveTime { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { @@ -140,11 +140,11 @@ impl GraphQLScalar for NaiveTime { impl GraphQLScalar for NaiveDateTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.timestamp() as f64) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) .and_then(|f| { @@ -154,7 +154,7 @@ impl GraphQLScalar for NaiveDateTime { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 3c608bcb9..7d91ecf8c 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -16,11 +16,11 @@ use crate::{ impl GraphQLScalar for Tz { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.name().to_owned()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -29,7 +29,7 @@ impl GraphQLScalar for Tz { }) } - fn from_str(val: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(val: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = val { Ok(S::from(s.to_owned())) } else { diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index 5a80148db..f45173523 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -62,20 +62,20 @@ const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] impl GraphQLScalar for Date { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar( self.format(DATE_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)), ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -120,7 +120,7 @@ const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour] impl GraphQLScalar for LocalTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar( if self.millisecond() == 0 { self.format(LOCAL_TIME_FORMAT_NO_MILLIS) @@ -131,7 +131,7 @@ impl GraphQLScalar for LocalTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -145,7 +145,7 @@ impl GraphQLScalar for LocalTime { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -169,14 +169,14 @@ const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] = impl GraphQLScalar for LocalDateTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar( self.format(LOCAL_DATE_TIME_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)), ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -185,7 +185,7 @@ impl GraphQLScalar for LocalDateTime { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -214,7 +214,7 @@ impl GraphQLScalar for LocalDateTime { impl GraphQLScalar for DateTime { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar( self.to_offset(UtcOffset::UTC) .format(&Rfc3339) @@ -222,7 +222,7 @@ impl GraphQLScalar for DateTime { ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -231,7 +231,7 @@ impl GraphQLScalar for DateTime { .map(|dt| dt.to_offset(UtcOffset::UTC)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { @@ -262,14 +262,14 @@ const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = impl GraphQLScalar for UtcOffset { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar( self.format(UTC_OFFSET_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)), ) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -277,7 +277,7 @@ impl GraphQLScalar for UtcOffset { }) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(s) = value { Ok(S::from(s.to_owned())) } else { diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 9ff206d1e..74445d609 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -11,17 +11,17 @@ use crate::{ impl GraphQLScalar for Url { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.as_str().to_owned()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Self::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { >::from_str(value) } } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 69a37bba9..0a255a749 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -14,17 +14,17 @@ use crate::{ impl GraphQLScalar for Uuid { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.to_string()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Self::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { Ok(S::from(value.to_owned())) } else { diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 9e18ea268..5bbb5b263 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -107,27 +107,27 @@ impl Human { #[graphql_object(context = Database, impl = CharacterValue)] impl Human { /// The id of the human - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the human - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the human - fn friends(&self, ctx: &Database) -> Vec { + pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The home planet of the human - fn home_planet(&self) -> &Option { + pub fn home_planet(&self) -> &Option { &self.home_planet } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 83819e6cb..64ca5fd80 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -56,11 +56,11 @@ impl fmt::Display for ID { impl GraphQLScalar for ID { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.0.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .or_else(|| v.as_int_value().map(|i| i.to_string())) @@ -68,7 +68,7 @@ impl GraphQLScalar for ID { .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { match value { ScalarToken::String(value) | ScalarToken::Int(value) => Ok(S::from(value.to_owned())), _ => Err(ParseError::UnexpectedToken(Token::Scalar(value))), @@ -80,17 +80,17 @@ impl GraphQLScalar for ID { impl GraphQLScalar for String { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.clone()) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .ok_or_else(|| format!("Expected `String`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); let mut char_iter = value.chars(); @@ -279,17 +279,17 @@ where impl GraphQLScalar for bool { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_boolean) .ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { // Bools are parsed separately - they shouldn't reach this code path Err(ParseError::UnexpectedToken(Token::Scalar(value))) } @@ -299,16 +299,16 @@ impl GraphQLScalar for bool { impl GraphQLScalar for i32 { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) @@ -323,16 +323,16 @@ impl GraphQLScalar for i32 { impl GraphQLScalar for f64 { type Error = String; - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(*self) } - fn from_input_value(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) } - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { match value { ScalarToken::Int(v) => v .parse() diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 62da91b58..80b5e92f4 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -27,15 +27,15 @@ pub trait GraphQLScalar: Sized { /// Resolves this [GraphQL scalar][1] into [`Value`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn resolve(&self) -> Value; + fn to_output(&self) -> Value; /// Parses [`InputValue`] into this [GraphQL scalar][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn from_input_value(v: &InputValue) -> Result; + fn from_input(v: &InputValue) -> Result; /// Resolves [`ScalarToken`] literal into `S`. - fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; } /// Serializable value returned from query and field execution. diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 944cb00d2..d45f607de 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -39,7 +39,7 @@ pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - Ok(::juniper::GraphQLScalar::resolve(self)) + Ok(::juniper::GraphQLScalar::to_output(self)) } } } @@ -426,7 +426,7 @@ impl Definition { #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - let v = ::juniper::GraphQLScalar::resolve(self); + let v = ::juniper::GraphQLScalar::to_output(self); ::juniper::ToInputValue::to_input_value(&v) } } @@ -452,7 +452,7 @@ impl Definition { type Error = >::Error; fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { - ::juniper::GraphQLScalar::from_input_value(input) + ::juniper::GraphQLScalar::from_input(input) } } } @@ -477,7 +477,7 @@ impl Definition { fn from_str( token: ::juniper::parser::ScalarToken<'_>, ) -> ::juniper::ParseScalarResult<'_, #scalar> { - >::from_str(token) + >::parse_token(token) } } } @@ -521,7 +521,7 @@ impl Definition { } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this enum. + /// similar) implementation. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c48c11c90..a90e8a0cb 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -233,17 +233,17 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// // NOTE: The Error type should implement `IntoFieldError`. /// type Error = String; /// -/// fn resolve(&self) -> Value { +/// fn to_output(&self) -> Value { /// Value::scalar(self.0.to_owned()) /// } /// -/// fn from_input_value(value: &juniper::InputValue) -> Result { +/// fn from_input(value: &juniper::InputValue) -> Result { /// value.as_string_value() /// .map(|s| Self(s.to_owned())) /// .ok_or_else(|| format!("Expected `String`, found: {}", value)) /// } /// -/// fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { +/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { /// >::from_str(value) /// } /// } From 0cb101d38e99a29b9f527f7536e8412ad558c76b Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 09:44:19 +0300 Subject: [PATCH 065/122] Corrections --- examples/actix_subscriptions/src/main.rs | 2 +- juniper/src/macros/reflection.rs | 117 ------------------ juniper/src/tests/fixtures/starwars/schema.rs | 20 +-- juniper_codegen/src/common/field/mod.rs | 22 ---- juniper_codegen/src/graphql_interface/mod.rs | 41 ++---- juniper_codegen/src/graphql_object/mod.rs | 85 ++++++------- 6 files changed, 59 insertions(+), 228 deletions(-) diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 6da5ad562..d36c34d5a 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -10,7 +10,7 @@ use actix_web::{ use juniper::{ graphql_object, graphql_subscription, graphql_value, - tests::fixtures::starwars::schema::{Character as _, Database, Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, FieldError, RootNode, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 4c2da2a17..30e4f74f4 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -493,123 +493,6 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } -/// Tried to call [`Field`] implementation of `set_ty` with `info`, `args` and -/// `executor` as arguments if present or panics otherwise. -/// -/// This macro uses [autoref-based specialisation][1]. -/// -/// [1]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html -#[macro_export] -macro_rules! call_field_or_panic { - ( - $field_name: expr, - $self_: expr, - $info: expr, - $args: expr, - $executor: expr $(,)? - ) => {{ - const FIELD_NAME: $crate::macros::reflection::FieldName = - $crate::macros::reflection::fnv1a128($field_name); - - struct Wrap(T); - - impl $crate::macros::reflection::FieldMeta for Wrap<&T> - where - T: $crate::macros::reflection::FieldMeta, - { - type Context = T::Context; - - type TypeInfo = T::TypeInfo; - - const TYPE: $crate::macros::reflection::Type = T::TYPE; - - const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; - - const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; - - const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; - } - - impl $crate::macros::reflection::FieldMeta for &Wrap<&T> - where - T: $crate::macros::reflection::FieldMeta, - { - type Context = T::Context; - - type TypeInfo = T::TypeInfo; - - const TYPE: $crate::macros::reflection::Type = T::TYPE; - - const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; - - const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; - - const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; - } - - /// First, we'll try to call this trait in case [`Field`] impl is present. - trait ViaField: $crate::macros::reflection::FieldMeta { - fn __call( - &self, - info: &Self::TypeInfo, - args: &$crate::Arguments, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult; - } - - impl ViaField for &Wrap<&T> - where - T: $crate::macros::reflection::Field< - S, - { $crate::macros::reflection::fnv1a128($field_name) }, - >, - { - fn __call( - &self, - info: &Self::TypeInfo, - args: &$crate::Arguments, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult { - self.0.call(info, args, executor) - } - } - - /// If [`Field`] impl wasn't found, we'll fallback to [`BasePanic`] - /// trait, which simply panics. - trait BasePanic: $crate::macros::reflection::FieldMeta { - fn __call( - &self, - info: &Self::TypeInfo, - args: &$crate::Arguments, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult; - } - - impl BasePanic for Wrap<&T> - where - T: $crate::macros::reflection::FieldMeta< - S, - { $crate::macros::reflection::fnv1a128($field_name) }, - > + $crate::macros::reflection::BaseType, - { - fn __call( - &self, - _: &Self::TypeInfo, - _: &$crate::Arguments, - _: &$crate::Executor, - ) -> $crate::ExecutionResult { - ::std::panic!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - $field_name, - T::NAME, - ); - } - } - - (&&Wrap($self_)).__call($info, $args, $executor) - }}; -} - /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. #[macro_export] diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 9e18ea268..d9c795de6 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -107,27 +107,27 @@ impl Human { #[graphql_object(context = Database, impl = CharacterValue)] impl Human { /// The id of the human - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the human - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the human - fn friends(&self, ctx: &Database) -> Vec { + pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The home planet of the human - fn home_planet(&self) -> &Option { + pub fn home_planet(&self) -> &Option { &self.home_planet } } @@ -167,27 +167,27 @@ impl Droid { #[graphql_object(context = Database, impl = CharacterValue)] impl Droid { /// The id of the droid - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the droid - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the droid - fn friends(&self, ctx: &Database) -> Vec { + pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The primary function of the droid - fn primary_function(&self) -> &Option { + pub fn primary_function(&self) -> &Option { &self.primary_function } } diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 4272bffa0..bf299b458 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -251,28 +251,6 @@ impl Definition { } } - /// Returns generated code that errors about [GraphQL fields][1] tried to be - /// resolved asynchronously in the [`GraphQLValue::resolve_field`] method - /// (which is synchronous itself). - /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_err_async_field_tokens( - field_names: &[&str], - scalar: &scalar::Type, - ty_name: &str, - ) -> TokenStream { - quote! { - #( #field_names )|* => return Err(::juniper::FieldError::from(format!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - field, - >::name(info) - .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?, - ))), - } - } - /// Returns generated code for the [`marker::IsOutputType::mark`] method, /// which performs static checks for this [GraphQL field][1]. /// diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index ff2a4cb56..6f0b956e5 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -579,11 +579,7 @@ impl Definition { let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self.fields.iter().filter_map(|f| { - if f.is_async { - return None; - } - + let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; Some(quote! { #name => { @@ -594,18 +590,7 @@ impl Definition { } }) }); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, trait_name, - ) - }) - }; + let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); @@ -635,7 +620,6 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* - #async_fields_err _ => #no_field_err, } } @@ -865,11 +849,7 @@ impl Definition { self.fields .iter() - .filter_map(|field| { - if field.is_async { - return None; - } - + .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); @@ -882,7 +862,7 @@ impl Definition { quote! { _ => unreachable!() } }); - Some(quote! { + quote! { #[allow(non_snake_case)] impl#impl_generics ::juniper::macros::reflection::Field< #scalar, @@ -903,19 +883,16 @@ impl Definition { #field_name, ); - ::juniper::call_field_or_panic!( - #field_name, - v, - info, - args, - executor, - ) + <_ as ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) })* #unreachable_arm } } } - }) + } }) .collect() } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index d306d37a4..e2d752f8b 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -633,34 +633,45 @@ impl Definition { self.fields .iter() - .filter_map(|field| { - if field.is_async { - return None; - } - + .map(|field| { let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); - let res = if field.is_method() { - let args = field - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, false)); - - let rcv = field.has_receiver.then(|| { - quote! { self, } - }); - - quote! { Self::#ident(#rcv #( #args ),*) } + let resolve = if field.is_async { + quote! { + ::std::panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + #name, + >::NAME, + ); + } } else { - res_ty = parse_quote! { _ }; - quote! { &self.#ident } + let res = if field.is_method() { + let args = field + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + + let resolving_code = gen::sync_resolving_code(); + + quote! { + let res: #res_ty = #res; + #resolving_code + } }; - let resolving_code = gen::sync_resolving_code(); - - Some(quote! { + quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflection::Field< @@ -675,11 +686,10 @@ impl Definition { args: &::juniper::Arguments<#scalar>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - let res: #res_ty = #res; - #resolving_code + #resolve } } - }) + } }) .collect() } @@ -762,34 +772,18 @@ impl Definition { let name = &self.name; - let fields_resolvers = self.fields.iter().filter_map(|f| { - if f.is_async { - return None; - } - + let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; - Some(quote! { + quote! { #name => { ::juniper::macros::reflection::Field::< #scalar, { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } - }) + } }); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, &ty_name, - ) - }) - }; let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -814,7 +808,6 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* - #async_fields_err _ => #no_field_err, } } From 7635a1ea335e9b608716a0d888be18511d081e5a Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 10:59:20 +0300 Subject: [PATCH 066/122] Corrections --- docs/book/content/types/scalars.md | 1 - .../src/codegen/derive_scalar.rs | 775 ++++++++++++++---- juniper_codegen/src/derive_scalar_value.rs | 114 +-- 3 files changed, 659 insertions(+), 231 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 3f52f0159..6eeb886de 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -69,7 +69,6 @@ The macro also allows for more customization: /// You can use a doc comment to specify a description. #[derive(juniper::GraphQLScalar)] #[graphql( - transparent, // Overwrite the GraphQL type name. name = "MyUserId", // Specify a custom description. diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index ab1aadaea..74404fbaa 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -2,221 +2,650 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - execute, graphql_value, EmptyMutation, EmptySubscription, FromInputValue, InputValue, - ParseScalarResult, ParseScalarValue, RootNode, ScalarToken, ScalarValue, ToInputValue, Value, - Variables, + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, + EmptySubscription, GraphQLScalar, GraphQLType, InputValue, ParseScalarResult, ParseScalarValue, + RootNode, ScalarToken, ScalarValue, Value, }; -use crate::custom_scalar::MyScalarValue; - -#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalar)] -#[graphql( - scalar = MyScalarValue, - specified_by_url = "https://tools.ietf.org/html/rfc4122", -)] -pub struct LargeId(i64); - -#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalar)] -#[graphql(scalar = MyScalarValue)] -pub struct SmallId { - id: i32, -} - -#[derive(juniper::GraphQLScalar)] -#[graphql( - scalar = S: ScalarValue, - specified_by_url = "https://tools.ietf.org/html/rfc3339", - resolve = resolve_custom_date_time, - from_input_value = custom_dt::from_input_value, - from_input_value_err = String, - from_str = from_str_custom_date_time, -)] -struct CustomDateTime +fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> where - Tz: From + TimeZone, - Tz::Offset: fmt::Display, + Q: GraphQLType + 'q, { - dt: DateTime, - _unused: (), + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) } -fn from_str_custom_date_time(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> +fn schema_with_scalar<'q, S, C, Q>( + query_root: Q, +) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> where - S: ScalarValue, + Q: GraphQLType + 'q, + S: ScalarValue + 'q, { - >::from_str(value) + RootNode::new_with_scalar_value( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) } -fn resolve_custom_date_time(dt: &CustomDateTime) -> Value -where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, -{ - Value::scalar(dt.dt.to_rfc3339()) +mod trivial_unnamed { + use super::*; + + #[derive(GraphQLScalar)] + struct Counter(i32); + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial_named { + use super::*; + + #[derive(GraphQLScalar)] + struct Counter { + value: i32, + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod explicit_name { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(name = "Counter")] + struct CustomCounter(i32); + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: CustomCounter) -> CustomCounter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn no_custom_counter() { + const DOC: &str = r#"{ + __type(name: "CustomCounter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!(null), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod custom_to_output { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(to_output_with = to_output)] + struct Increment(i32); + + fn to_output(val: &Increment) -> Value { + let ret = val.0 + 1; + ret.to_output() + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn increment(value: Increment) -> Increment { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Increment") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ increment(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"increment": 1}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Increment") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } } -mod custom_dt { - use super::{fmt, CustomDateTime, DateTime, InputValue, ScalarValue, TimeZone, Utc}; +mod generic_with_all_resolvers { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + to_output_with = Self::to_output, + from_input_with = Self::from_input, + from_input_err = String, + )] + #[graphql( + parse_token_with = Self::parse_token, + specified_by_url = "https://tools.ietf.org/html/rfc3339" + )] + struct CustomDateTime + where + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + dt: DateTime, + _unused: (), + } - pub(super) fn from_input_value(v: &InputValue) -> Result, String> + impl GraphQLScalar for CustomDateTime where S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime { - dt: dt.with_timezone(&Tz::from(Utc)), - _unused: (), - }) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) - }) + type Error = String; + + fn to_output(&self) -> Value { + Value::scalar(self.dt.to_rfc3339()) + } + + fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| Self { + dt: dt.with_timezone(&Tz::from(Utc)), + _unused: (), + }) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } } -} -#[derive(juniper::GraphQLObject)] -#[graphql(scalar = MyScalarValue)] -struct User { - id: LargeId, - another_id: SmallId, + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } + } + + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } } -struct Query; +mod description_from_doc_comment { + use super::*; + + /// Doc comment. + #[derive(GraphQLScalar)] + struct Counter(i32); + + struct QueryRoot; -#[juniper::graphql_object(scalar = MyScalarValue)] -impl Query { - fn user() -> User { - User { - id: LargeId(0), - another_id: SmallId { id: 0 }, + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value } } -} -struct Mutation; + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); -#[juniper::graphql_object(scalar = MyScalarValue)] -impl Mutation { - fn change_user(id: LargeId, another_id: SmallId) -> User { - User { id, another_id } + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Doc comment."}}), + vec![], + )), + ); } } -#[test] -fn test_scalar_value_large_id() { - let num: i64 = 4294967297; +mod description_from_attribute { + use super::*; - let input_integer: InputValue = - serde_json::from_value(serde_json::json!(num)).unwrap(); + /// Doc comment. + #[derive(GraphQLScalar)] + #[graphql(desc = "Doc comment from attribute.")] + #[graphql(specified_by_url = "https://tools.ietf.org/html/rfc4122")] + struct Counter(i32); - let output: LargeId = - FromInputValue::::from_input_value(&input_integer).unwrap(); - assert_eq!(output, LargeId(num)); + struct QueryRoot; - let id = LargeId(num); - let output = ToInputValue::::to_input_value(&id); - assert_eq!(output, InputValue::scalar(num)); -} + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } -#[test] -fn test_scalar_value_small_id() { - let num: i32 = i32::MAX; - let id = SmallId { id: num }; + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; - let input_integer: InputValue = - serde_json::from_value(serde_json::json!(num)).unwrap(); + let schema = schema(QueryRoot); - let output: SmallId = - FromInputValue::::from_input_value(&input_integer).unwrap(); - assert_eq!(output, id); + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } - let output = ToInputValue::::to_input_value(&id); - assert_eq!(output, InputValue::scalar(num)); + #[tokio::test] + async fn has_description_and_url() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + specifiedByUrl + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({ + "__type": { + "description": "Doc comment from attribute.", + "specifiedByUrl": "https://tools.ietf.org/html/rfc4122", + } + }), + vec![], + )), + ); + } } -#[tokio::test] -async fn test_scalar_value_large_specified_url() { - let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - let doc = r#"{ - __type(name: "LargeId") { - specifiedByUrl +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(scalar = MyScalarValue)] + struct Counter(i32); + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value } - }"#; - - assert_eq!( - execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc4122"}}), - vec![], - )), - ); + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } } -#[tokio::test] -async fn test_scalar_value_large_query() { - let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - let doc = r#"{ - user { id anotherId } - }"#; - - let id = Value::::scalar(0_i64); - let another_id = Value::::scalar(0_i32); - assert_eq!( - execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok(( - graphql_value!({"user": {"id": id, "anotherId": another_id}}), - vec![], - )), - ); +mod generic_scalar { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(scalar = S: ScalarValue)] + struct Counter(i32); + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } } -#[tokio::test] -async fn test_scalar_value_large_mutation() { - let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value( - Query, - Mutation, - EmptySubscription::<()>::new(), - ); - - let doc = r#"mutation { - changeUser(id: 1, anotherId: 2) { id anotherId } - }"#; - - let id = Value::::scalar(1_i64); - let another_id = Value::::scalar(2_i32); - assert_eq!( - execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok(( - graphql_value!({"changeUser": {"id": id, "anotherId": another_id}}), - vec![], - )), - ); - - let doc = r#"mutation { - changeUser(id: 4294967297, anotherId: -2147483648) { id anotherId } - }"#; - - let id = Value::::scalar(4294967297_i64); - let another_id = Value::::scalar(i32::MIN); - assert_eq!( - execute(doc, None, &schema, &Variables::::new(), &()).await, - Ok(( - graphql_value!({"changeUser": {"id": id, "anotherId": another_id}}), - vec![], - )), - ); +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(scalar = S: ScalarValue + Clone)] + struct Counter(i32); + + struct QueryRoot; + + #[graphql_object(scalar = S: ScalarValue + Clone)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } } diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 5b843a230..68b873838 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -29,27 +29,27 @@ pub fn expand(input: TokenStream) -> syn::Result { let attr = Attr::from_attrs("graphql", &ast.attrs)?; let field = match ( - attr.resolve.as_deref().cloned(), - attr.from_input_value.as_deref().cloned(), - attr.from_input_value_err.as_deref().cloned(), - attr.from_str.as_deref().cloned(), + attr.to_output.as_deref().cloned(), + attr.from_input.as_deref().cloned(), + attr.from_input_err.as_deref().cloned(), + attr.parse_token.as_deref().cloned(), ) { - (Some(resolve), Some(from_input_value), Some(from_input_value_err), Some(from_str)) => { + (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token)) => { GraphQLScalarDefinition::Custom { - resolve, - from_input_value: (from_input_value, from_input_value_err), - from_str, + to_output, + from_input: (from_input, from_input_err), + parse_token, } } - (resolve, from_input_value, from_input_value_err, from_str) => { - let from_input_value = match (from_input_value, from_input_value_err) { - (Some(from_input_value), Some(err)) => Some((from_input_value, err)), + (to_output, from_input, from_input_err, parse_token) => { + let from_input = match (from_input, from_input_err) { + (Some(from_input), Some(err)) => Some((from_input, err)), (None, None) => None, _ => { return Err(ERR.custom_error( ast.span(), - "`from_input_value` attribute should be provided in \ - tandem with `from_input_value_err`", + "`from_input_with` attribute should be provided in \ + tandem with `from_input_err`", )) } }; @@ -93,9 +93,9 @@ pub fn expand(input: TokenStream) -> syn::Result { }), }?; GraphQLScalarDefinition::Delegated { - resolve, - from_input_value, - from_str, + to_output, + from_input, + parse_token, field, } } @@ -121,14 +121,14 @@ pub fn expand(input: TokenStream) -> syn::Result { enum GraphQLScalarDefinition { Custom { - resolve: syn::Path, - from_input_value: (syn::Path, syn::Type), - from_str: syn::Path, + to_output: syn::ExprPath, + from_input: (syn::ExprPath, syn::Type), + parse_token: syn::ExprPath, }, Delegated { - resolve: Option, - from_input_value: Option<(syn::Path, syn::Type)>, - from_str: Option, + to_output: Option, + from_input: Option<(syn::ExprPath, syn::Type)>, + parse_token: Option, field: Field, }, } @@ -136,12 +136,12 @@ enum GraphQLScalarDefinition { impl GraphQLScalarDefinition { fn resolve(&self, scalar: &scalar::Type) -> TokenStream { match self { - Self::Custom { resolve, .. } + Self::Custom { to_output, .. } | Self::Delegated { - resolve: Some(resolve), + to_output: Some(to_output), .. } => { - quote! { Ok(#resolve(self)) } + quote! { Ok(#to_output(self)) } } Self::Delegated { field, .. } => { quote! { @@ -158,13 +158,13 @@ impl GraphQLScalarDefinition { fn to_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { - Self::Custom { resolve, .. } + Self::Custom { to_output, .. } | Self::Delegated { - resolve: Some(resolve), + to_output: Some(to_output), .. } => { quote! { - let v = #resolve(self); + let v = #to_output(self); ::juniper::ToInputValue::to_input_value(&v) } } @@ -177,16 +177,16 @@ impl GraphQLScalarDefinition { fn from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { - from_input_value: (_, err), + from_input: (_, err), .. } | Self::Delegated { - from_input_value: Some((_, err)), + from_input: Some((_, err)), .. } => quote! { #err }, Self::Delegated { field, .. } => { let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::Error } + quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::Error } } } } @@ -194,14 +194,14 @@ impl GraphQLScalarDefinition { fn from_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { - from_input_value: (from_input_value, _), + from_input: (from_input, _), .. } | Self::Delegated { - from_input_value: Some((from_input_value, _)), + from_input: Some((from_input, _)), .. } => { - quote! { #from_input_value(input) } + quote! { #from_input(input) } } Self::Delegated { field, .. } => { let field_ty = field.ty(); @@ -216,16 +216,16 @@ impl GraphQLScalarDefinition { fn from_str(&self, scalar: &scalar::Type) -> TokenStream { match self { - Self::Custom { from_str, .. } + Self::Custom { parse_token, .. } | Self::Delegated { - from_str: Some(from_str), + parse_token: Some(parse_token), .. } => { - quote! { #from_str(token) } + quote! { #parse_token(token) } } Self::Delegated { field, .. } => { let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::GraphQLScalar<#scalar>>::from_str(token) } + quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) } } } } @@ -268,10 +268,10 @@ struct Attr { description: Option>, specified_by_url: Option>, scalar: Option>, - resolve: Option>, - from_input_value: Option>, - from_input_value_err: Option>, - from_str: Option>, + to_output: Option>, + from_input: Option>, + from_input_err: Option>, + parse_token: Option>, } impl Parse for Attr { @@ -319,31 +319,31 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } - "resolve" => { + "to_output_with" => { input.parse::()?; - let scl = input.parse::()?; - out.resolve + let scl = input.parse::()?; + out.to_output .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } - "from_input_value" => { + "from_input_with" => { input.parse::()?; - let scl = input.parse::()?; - out.from_input_value + let scl = input.parse::()?; + out.from_input .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } - "from_input_value_err" => { + "from_input_err" => { input.parse::()?; let scl = input.parse::()?; - out.from_input_value_err + out.from_input_err .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } - "from_str" => { + "parse_token_with" => { input.parse::()?; - let scl = input.parse::()?; - out.from_str + let scl = input.parse::()?; + out.parse_token .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -366,10 +366,10 @@ impl Attr { description: try_merge_opt!(description: self, another), specified_by_url: try_merge_opt!(specified_by_url: self, another), scalar: try_merge_opt!(scalar: self, another), - resolve: try_merge_opt!(resolve: self, another), - from_input_value: try_merge_opt!(from_input_value: self, another), - from_input_value_err: try_merge_opt!(from_input_value_err: self, another), - from_str: try_merge_opt!(from_str: self, another), + to_output: try_merge_opt!(to_output: self, another), + from_input: try_merge_opt!(from_input: self, another), + from_input_err: try_merge_opt!(from_input_err: self, another), + parse_token: try_merge_opt!(parse_token: self, another), }) } From 3da33baa16be3ef8820524acd01fb4509fdb1f06 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 12:30:11 +0300 Subject: [PATCH 067/122] Corrections --- .../src/codegen/derive_scalar.rs | 149 +++++++ juniper_codegen/src/derive_scalar_value.rs | 377 ++++++++++-------- 2 files changed, 368 insertions(+), 158 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index 74404fbaa..152c0b0fa 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -297,6 +297,155 @@ mod custom_to_output { } } +mod delegated_parse_token { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + )] + #[graphql( + parse_token = String, + specified_by_url = "https://tools.ietf.org/html/rfc3339" + )] + struct CustomDateTime(DateTime) + where + Tz: From + TimeZone, + Tz::Offset: fmt::Display; + + fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.0.to_rfc3339()) + } + + fn from_input(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } + } + + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } +} + +mod multiple_delegated_parse_token { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + parse_token(String, i32), + )] + enum StringOrInt { + String(String), + Int(i32), + } + + fn to_output(v: &StringOrInt) -> Value { + match v { + StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::Int(i) => Value::scalar(*i), + } + } + + fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .map(|s| StringOrInt::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn string_or_int(value: StringOrInt) -> StringOrInt { + value + } + } + + #[tokio::test] + async fn resolves_string() { + const DOC: &str = r#"{ stringOrInt(value: "test") }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"stringOrInt": "test"}), vec![],)), + ); + } + + #[tokio::test] + async fn resolves_int() { + const DOC: &str = r#"{ stringOrInt(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"stringOrInt": 0}), vec![],)), + ); + } +} + mod generic_with_all_resolvers { use super::*; diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 68b873838..e00e2ecf8 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -2,6 +2,7 @@ use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ ext::IdentExt as _, + parenthesized, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned, @@ -59,15 +60,14 @@ pub fn expand(input: TokenStream) -> syn::Result { } else { return Err(ERR.custom_error( ast.span(), - "expected single-field struct \ - or all `resolve`, `from_input_value` and `from_str` functions", + "expected all custom resolvers or single-field struct", )); }; let field = match &data.fields { syn::Fields::Unit => Err(ERR.custom_error( ast.span(), - "expected exactly 1 field, e.g., `Test(i32)` or `Test { test: i32 }` \ - or all `resolve`, `from_input_value` and `from_str` functions", + "expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` \ + or all custom resolvers", )), syn::Fields::Unnamed(fields) => fields .unnamed @@ -76,8 +76,8 @@ pub fn expand(input: TokenStream) -> syn::Result { .ok_or_else(|| { ERR.custom_error( ast.span(), - "expected exactly 1 field, e.g., Test(i32)\ - or all `resolve`, `from_input_value` and `from_str` functions", + "expected exactly 1 field, e.g., Test(i32) \ + or all custom resolvers", ) }), syn::Fields::Named(fields) => fields @@ -87,8 +87,8 @@ pub fn expand(input: TokenStream) -> syn::Result { .ok_or_else(|| { ERR.custom_error( ast.span(), - "expected exactly 1 field, e.g., Test { test: i32 }\ - or all `resolve`, `from_input_value` and `from_str` functions", + "expected exactly 1 field, e.g., Test { test: i32 } \ + or all custom resolvers", ) }), }?; @@ -119,149 +119,6 @@ pub fn expand(input: TokenStream) -> syn::Result { .to_token_stream()) } -enum GraphQLScalarDefinition { - Custom { - to_output: syn::ExprPath, - from_input: (syn::ExprPath, syn::Type), - parse_token: syn::ExprPath, - }, - Delegated { - to_output: Option, - from_input: Option<(syn::ExprPath, syn::Type)>, - parse_token: Option, - field: Field, - }, -} - -impl GraphQLScalarDefinition { - fn resolve(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { Ok(#to_output(self)) } - } - Self::Delegated { field, .. } => { - quote! { - ::juniper::GraphQLValue::<#scalar>::resolve( - &self.#field, - info, - selection, - executor, - ) - } - } - } - } - - fn to_input_value(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { - let v = #to_output(self); - ::juniper::ToInputValue::to_input_value(&v) - } - } - Self::Delegated { field, .. } => { - quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) } - } - } - } - - fn from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { - from_input: (_, err), - .. - } - | Self::Delegated { - from_input: Some((_, err)), - .. - } => quote! { #err }, - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::Error } - } - } - } - - fn from_input_value(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { - from_input: (from_input, _), - .. - } - | Self::Delegated { - from_input: Some((from_input, _)), - .. - } => { - quote! { #from_input(input) } - } - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - let self_constructor = field.closure_constructor(); - quote! { - <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) - .map(#self_constructor) - } - } - } - } - - fn from_str(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { parse_token, .. } - | Self::Delegated { - parse_token: Some(parse_token), - .. - } => { - quote! { #parse_token(token) } - } - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) } - } - } - } -} - -enum Field { - Named(syn::Field), - Unnamed(syn::Field), -} - -impl ToTokens for Field { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Field::Named(f) => f.ident.to_tokens(tokens), - Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), - } - } -} - -impl Field { - fn ty(&self) -> &syn::Type { - match self { - Field::Named(f) | Field::Unnamed(f) => &f.ty, - } - } - - fn closure_constructor(&self) -> TokenStream { - match self { - Field::Named(syn::Field { ident, .. }) => { - quote! { |v| Self { #ident: v } } - } - Field::Unnamed(_) => quote! { Self }, - } - } -} - #[derive(Default)] struct Attr { name: Option>, @@ -271,7 +128,7 @@ struct Attr { to_output: Option>, from_input: Option>, from_input_err: Option>, - parse_token: Option>, + parse_token: Option>, } impl Parse for Attr { @@ -344,7 +201,36 @@ impl Parse for Attr { input.parse::()?; let scl = input.parse::()?; out.parse_token - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .replace(SpanContainer::new( + ident.span(), + Some(scl.span()), + ParseToken::Custom(scl), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "parse_token" => { + let (span, parsed_types) = if input.parse::().is_ok() { + let scl = input.parse::()?; + (scl.span(), vec![scl]) + } else { + let types; + let _ = parenthesized!(types in input); + let parsed_types = + types.parse_terminated::<_, token::Comma>(syn::Type::parse)?; + + if parsed_types.is_empty() { + return Err(syn::Error::new(ident.span(), "expected at least 1 type.")); + } + + (parsed_types.span(), parsed_types.into_iter().collect()) + }; + + out.parse_token + .replace(SpanContainer::new( + ident.span(), + Some(span), + ParseToken::Delegate(parsed_types), + )) .none_or_else(|_| err::dup_arg(&ident))? } name => { @@ -492,7 +378,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let resolve = self.field.resolve(&scalar); + let resolve = self.field.expand_resolve(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -561,7 +447,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let to_input_value = self.field.to_input_value(&scalar); + let to_input_value = self.field.expand_to_input_value(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -587,8 +473,8 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let error_ty = self.field.from_input_value_err(&scalar); - let from_input_value = self.field.from_input_value(&scalar); + let error_ty = self.field.expand_from_input_value_err(&scalar); + let from_input_value = self.field.expand_from_input_value(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -616,7 +502,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let from_str = self.field.from_str(&scalar); + let from_str = self.field.expand_from_str(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -733,3 +619,178 @@ impl Definition { generics } } + +enum GraphQLScalarDefinition { + Custom { + to_output: syn::ExprPath, + from_input: (syn::ExprPath, syn::Type), + parse_token: ParseToken, + }, + Delegated { + to_output: Option, + from_input: Option<(syn::ExprPath, syn::Type)>, + parse_token: Option, + field: Field, + }, +} + +impl GraphQLScalarDefinition { + fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { Ok(#to_output(self)) } + } + Self::Delegated { field, .. } => { + quote! { + ::juniper::GraphQLValue::<#scalar>::resolve( + &self.#field, + info, + selection, + executor, + ) + } + } + } + } + + fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { + let v = #to_output(self); + ::juniper::ToInputValue::to_input_value(&v) + } + } + Self::Delegated { field, .. } => { + quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) } + } + } + } + + fn expand_from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { + from_input: (_, err), + .. + } + | Self::Delegated { + from_input: Some((_, err)), + .. + } => quote! { #err }, + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::Error } + } + } + } + + fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { + from_input: (from_input, _), + .. + } + | Self::Delegated { + from_input: Some((from_input, _)), + .. + } => { + quote! { #from_input(input) } + } + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let self_constructor = field.closure_constructor(); + quote! { + <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) + .map(#self_constructor) + } + } + } + } + + fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { parse_token, .. } + | Self::Delegated { + parse_token: Some(parse_token), + .. + } => { + let parse_token = parse_token.expand_from_str(scalar); + quote! { #parse_token } + } + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) } + } + } + } +} + +#[derive(Clone)] +enum ParseToken { + Custom(syn::ExprPath), + Delegate(Vec), +} + +impl ParseToken { + fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { + match self { + ParseToken::Custom(parse_token) => { + quote! { #parse_token(token) } + } + ParseToken::Delegate(delegated) => delegated + .iter() + .fold(None, |acc, ty| { + acc.map_or_else( + || Some(quote! { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }), + |prev| { + Some(quote! { + #prev.or_else(|_| { + <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) + }) + }) + } + ) + }) + .unwrap_or_default(), + } + } +} + +enum Field { + Named(syn::Field), + Unnamed(syn::Field), +} + +impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Field::Named(f) => f.ident.to_tokens(tokens), + Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + } + } +} + +impl Field { + fn ty(&self) -> &syn::Type { + match self { + Field::Named(f) | Field::Unnamed(f) => &f.ty, + } + } + + fn closure_constructor(&self) -> TokenStream { + match self { + Field::Named(syn::Field { ident, .. }) => { + quote! { |v| Self { #ident: v } } + } + Field::Unnamed(_) => quote! { Self }, + } + } +} From cc365d3015515a04a6f5782c2dd25b8443c35dc8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 13:51:37 +0300 Subject: [PATCH 068/122] Corrections --- .../fail/scalar/mutliple_named_fields.stderr | 2 +- .../mutliple_named_fields_with_resolver.rs | 4 +- ...mutliple_named_fields_with_resolver.stderr | 4 +- .../scalar/mutliple_unnamed_fields.stderr | 2 +- .../fail/scalar/unit_struct.stderr | 2 +- juniper_codegen/src/graphql_scalar/attr.rs | 109 +++++++++++ .../derive.rs} | 174 ++++++++++++++++-- .../{impl_scalar.rs => graphql_scalar/mod.rs} | 109 +---------- juniper_codegen/src/lib.rs | 7 +- 9 files changed, 284 insertions(+), 129 deletions(-) create mode 100644 juniper_codegen/src/graphql_scalar/attr.rs rename juniper_codegen/src/{derive_scalar_value.rs => graphql_scalar/derive.rs} (80%) rename juniper_codegen/src/{impl_scalar.rs => graphql_scalar/mod.rs} (82%) diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr index 7822df81d..10cd4753d 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 }or all `resolve`, `from_input_value` and `from_str` functions +error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } or all custom resolvers --> fail/scalar/mutliple_named_fields.rs:4:1 | 4 | / struct Scalar { diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs index cae720bd0..4dad2d4bd 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs @@ -1,14 +1,14 @@ use juniper::{GraphQLScalar, Value}; #[derive(GraphQLScalar)] -#[graphql(resolve = Self::resolve)] +#[graphql(to_output_with = Self::to_output)] struct Scalar { id: i32, another: i32, } impl Scalar { - fn resolve(&self) -> Value { + fn to_output(&self) -> Value { Value::scalar(self.id) } } diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr index 2a1884dd2..7f14cd806 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr @@ -1,7 +1,7 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 }or all `resolve`, `from_input_value` and `from_str` functions +error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } or all custom resolvers --> fail/scalar/mutliple_named_fields_with_resolver.rs:4:1 | -4 | / #[graphql(resolve = Self::resolve)] +4 | / #[graphql(to_output_with = Self::to_output)] 5 | | struct Scalar { 6 | | id: i32, 7 | | another: i32, diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr index 396c0ede8..9363c34cb 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test(i32)or all `resolve`, `from_input_value` and `from_str` functions +error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) or all custom resolvers --> fail/scalar/mutliple_unnamed_fields.rs:4:1 | 4 | struct Scalar(i32, i32); diff --git a/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr index 31ae666bb..c85950261 100644 --- a/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr +++ b/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., `Test(i32)` or `Test { test: i32 }` or all `resolve`, `from_input_value` and `from_str` functions +error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` or all custom resolvers --> fail/scalar/unit_struct.rs:4:1 | 4 | struct ScalarSpecifiedByUrl; diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs new file mode 100644 index 000000000..c4978eb0f --- /dev/null +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -0,0 +1,109 @@ +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens as _; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; + +use crate::{ + common::{parse, scalar}, + util::span_container::SpanContainer, + GraphQLScope, +}; + +use super::{Attr, Definition}; + +/// [`GraphQLScope`] of errors for `#[graphql_scalar]` macro. +const ERR: GraphQLScope = GraphQLScope::ImplScalar; + +/// Expands `#[graphql_scalar]` macro into generated code. +pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { + if let Ok(mut ast) = syn::parse2::(body) { + let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); + return expand_on_impl(attrs, ast); + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_scalar] attribute is applicable to impl trait block only", + )) +} + +/// Expands `#[graphql_scalar]` macro placed on an implementation block. +fn expand_on_impl(attrs: Vec, ast: syn::ItemImpl) -> syn::Result { + let attr = Attr::from_attrs("graphql_scalar", &attrs)?; + + let mut self_ty = ast.self_ty.clone(); + if let syn::Type::Group(group) = self_ty.as_ref() { + self_ty = group.elem.clone(); + } + + let name = attr + .name + .map(SpanContainer::into_inner) + .or_else(|| { + if let syn::Type::Path(path) = self_ty.as_ref() { + path.path + .segments + .last() + .map(|last| last.ident.unraw().to_string()) + } else { + None + } + }) + .ok_or_else(|| { + ERR.custom_error( + self_ty.span(), + "unable to find target for implementation target for `GraphQLScalar`", + ) + })?; + + let (_, trait_ty, _) = ast.trait_.as_ref().ok_or_else(|| { + ERR.custom_error( + ast.impl_token.span(), + "expected `GraphQLScalar` trait implementation", + ) + })?; + + let scalar = get_scalar(trait_ty, &ast.generics); + + let mut out = ast.to_token_stream(); + Definition { + impl_for_type: *ast.self_ty.clone(), + generics: ast.generics.clone(), + name, + description: attr.description.as_deref().cloned(), + scalar, + specified_by_url: attr.specified_by_url.as_deref().cloned(), + } + .to_tokens(&mut out); + + Ok(out) +} + +/// Extracts [`scalar::Type`] from [`GraphQLScalar`] trait. +/// +/// [`GraphQLScalar`]: juniper::GraphQLScalar +fn get_scalar(trait_ty: &syn::Path, generics: &syn::Generics) -> scalar::Type { + if let Some(last_seg) = trait_ty.segments.last() { + match &last_seg.arguments { + syn::PathArguments::AngleBracketed(gens) => { + if let Some(syn::GenericArgument::Type(ty)) = gens.args.last() { + let generic_scalar = generics + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .find(|gen_par| gen_par.to_string() == ty.to_token_stream().to_string()); + + return generic_scalar.map_or_else( + || scalar::Type::Concrete(ty.clone()), + |scalar| scalar::Type::ExplicitGeneric(scalar.clone()), + ); + } + } + syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => {} + } + } + scalar::Type::Concrete(parse_quote! { ::juniper::DefaultScalarValue }) +} diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/graphql_scalar/derive.rs similarity index 80% rename from juniper_codegen/src/derive_scalar_value.rs rename to juniper_codegen/src/graphql_scalar/derive.rs index e00e2ecf8..16c1690d3 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -1,8 +1,9 @@ +//! Code generation for `#[derive(GraphQLScalar)]` macro. + use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ ext::IdentExt as _, - parenthesized, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned, @@ -22,8 +23,10 @@ use crate::{ util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, }; +/// [`GraphQLScope`] of errors for `#[derive(GraphQLScalar)]` macro. const ERR: GraphQLScope = GraphQLScope::DeriveScalar; +/// Expands `#[derive(GraphQLScalar)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; @@ -36,7 +39,7 @@ pub fn expand(input: TokenStream) -> syn::Result { attr.parse_token.as_deref().cloned(), ) { (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token)) => { - GraphQLScalarDefinition::Custom { + GraphQLScalarMethods::Custom { to_output, from_input: (from_input, from_input_err), parse_token, @@ -92,7 +95,7 @@ pub fn expand(input: TokenStream) -> syn::Result { ) }), }?; - GraphQLScalarDefinition::Delegated { + GraphQLScalarMethods::Delegated { to_output, from_input, parse_token, @@ -106,7 +109,7 @@ pub fn expand(input: TokenStream) -> syn::Result { Ok(Definition { ident: ast.ident.clone(), generics: ast.generics.clone(), - field, + methods: field, name: attr .name .as_deref() @@ -119,15 +122,61 @@ pub fn expand(input: TokenStream) -> syn::Result { .to_token_stream()) } +/// Available arguments behind `#[graphql]` attribute when generating +/// code for `#[derive(GraphQLScalar)]`. #[derive(Default)] struct Attr { + /// Name of this [GraphQL scalar][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars name: Option>, + + /// Description of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars description: Option>, + + /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars specified_by_url: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to use for resolving this [GraphQL scalar][1] type with. + /// + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type, which, in turn, requires all [scalar][1] fields to + /// be generic over any [`ScalarValue`] type too. That's why this type + /// should be specified only if one of the variants implements + /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars scalar: Option>, + + /// Explicitly specified function to be used instead of + /// [`GraphQLScalar::to_output`]. + /// + /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output to_output: Option>, + + /// Explicitly specified function to be used instead of + /// [`GraphQLScalar::from_input`]. + /// + /// [`GraphQLScalar::from_input`]: juniper::GraphQLScalar::from_input from_input: Option>, + + /// Explicitly specified type to be used instead of + /// [`GraphQLScalar::Error`]. + /// + /// [`GraphQLScalar::Error`]: juniper::GraphQLScalar::Error from_input_err: Option>, + + /// Explicitly specified resolver to be used instead of + /// [`GraphQLScalar::parse_token`]. + /// + /// [`GraphQLScalar::parse_token`]: juniper::GraphQLScalar::parse_token parse_token: Option>, } @@ -214,7 +263,7 @@ impl Parse for Attr { (scl.span(), vec![scl]) } else { let types; - let _ = parenthesized!(types in input); + let _ = syn::parenthesized!(types in input); let parsed_types = types.parse_terminated::<_, token::Comma>(syn::Type::parse)?; @@ -274,13 +323,48 @@ impl Attr { } } +/// Definition of [GraphQL scalar][1] for code generation. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars struct Definition { + /// Name of this [GraphQL scalar][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + name: String, + + /// Rust type [`Ident`] that this [GraphQL scalar][1] is represented with. + /// + /// [`Ident`]: syn::Ident + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars ident: syn::Ident, + + /// Generics of the Rust type that this [GraphQL scalar][1] is implemented + /// for. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars generics: syn::Generics, - field: GraphQLScalarDefinition, - name: String, + + /// [`GraphQLScalarDefinition`] representing [GraphQL scalar][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + methods: GraphQLScalarMethods, + + /// Description of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars description: Option, + + /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars specified_by_url: Option, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL scalar][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars scalar: scalar::Type, } @@ -378,7 +462,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let resolve = self.field.expand_resolve(&scalar); + let resolve = self.methods.expand_resolve(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -447,7 +531,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let to_input_value = self.field.expand_to_input_value(&scalar); + let to_input_value = self.methods.expand_to_input_value(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -473,8 +557,8 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let error_ty = self.field.expand_from_input_value_err(&scalar); - let from_input_value = self.field.expand_from_input_value(&scalar); + let error_ty = self.methods.expand_from_input_value_err(&scalar); + let from_input_value = self.methods.expand_from_input_value(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -502,7 +586,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let from_str = self.field.expand_from_str(&scalar); + let from_str = self.methods.expand_from_str(&scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -620,21 +704,51 @@ impl Definition { } } -enum GraphQLScalarDefinition { +/// Methods representing [GraphQL scalar][1]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +enum GraphQLScalarMethods { + /// [GraphQL scalar][1] represented with only custom resolvers. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars Custom { + /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: syn::ExprPath, + + /// Function and return type provided with + /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. from_input: (syn::ExprPath, syn::Type), + + /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` + /// or `#[graphql(parse_token(...))]`. parse_token: ParseToken, }, + + /// [GraphQL scalar][1] maybe partially represented with custom resolver. + /// Other methods are used from [`Field`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars Delegated { + /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: Option, + + /// Function and return type provided with + /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. from_input: Option<(syn::ExprPath, syn::Type)>, + + /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` + /// or `#[graphql(parse_token(...))]`. parse_token: Option, + + /// [`Field`] to resolve not provided methods. field: Field, }, } -impl GraphQLScalarDefinition { +impl GraphQLScalarMethods { + /// Expands [`GraphQLValue::resolve`] method. + /// + /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { to_output, .. } @@ -657,6 +771,9 @@ impl GraphQLScalarDefinition { } } + /// Expands [`ToInputValue::to_input_value`] method. + /// + /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { to_output, .. } @@ -675,6 +792,9 @@ impl GraphQLScalarDefinition { } } + /// Expands [`FromInputValue::Error`] type. + /// + /// [`FromInputValue::Error`]: juniper::FromInputValue::Error fn expand_from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { @@ -692,6 +812,9 @@ impl GraphQLScalarDefinition { } } + /// Expands [`FromInputValue::from_input_value`][1] method. + /// + /// [1]: juniper::FromInputValue::from_input_value fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { @@ -715,6 +838,9 @@ impl GraphQLScalarDefinition { } } + /// Expands [`ParseScalarValue::from_str`] method. + /// + /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { parse_token, .. } @@ -733,13 +859,25 @@ impl GraphQLScalarDefinition { } } +/// Representation of [`ParseScalarValue::from_str`] method. +/// +/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str #[derive(Clone)] enum ParseToken { + /// Custom method. Custom(syn::ExprPath), + + /// Tries to parse using [`syn::Type`]s [`ParseScalarValue`] impls until + /// first success. + /// + /// [`ParseScalarValue`]: juniper::ParseScalarValue Delegate(Vec), } impl ParseToken { + /// Expands [`ParseScalarValue::from_str`] method. + /// + /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { match self { ParseToken::Custom(parse_token) => { @@ -764,8 +902,12 @@ impl ParseToken { } } +/// Struct field to resolve not provided methods. enum Field { + /// Named [`Field`]. Named(syn::Field), + + /// Unnamed [`Field`]. Unnamed(syn::Field), } @@ -779,12 +921,16 @@ impl ToTokens for Field { } impl Field { + /// [`syn::Type`] of this [`Field`]. fn ty(&self) -> &syn::Type { match self { Field::Named(f) | Field::Unnamed(f) => &f.ty, } } + /// Closure to construct [GraphQL scalar][1] struct from [`Field`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn closure_constructor(&self) -> TokenStream { match self { Field::Named(syn::Field { ident, .. }) => { diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/graphql_scalar/mod.rs similarity index 82% rename from juniper_codegen/src/impl_scalar.rs rename to juniper_codegen/src/graphql_scalar/mod.rs index d45f607de..a1053f55e 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -2,127 +2,28 @@ //! //! [1]: https://spec.graphql.org/October2021/#sec-Scalars -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ - ext::IdentExt, + ext::IdentExt as _, parse::{Parse, ParseStream}, - parse_quote, - spanned::Spanned, - token, + parse_quote, token, }; use url::Url; use crate::{ common::{ parse::{ - self, attr::{err, OptionExt as _}, ParseBufferExt as _, }, scalar, }, - result::GraphQLScope, util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, }; -/// [`GraphQLScope`] of errors for `#[graphql_scalar]` macro. -const ERR: GraphQLScope = GraphQLScope::ImplScalar; - -/// Expands `#[graphql_scalar]` macro into generated code. -pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body) { - let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); - return expand_on_impl(attrs, ast); - } - - Err(syn::Error::new( - Span::call_site(), - "#[graphql_scalar] attribute is applicable to impl trait block only", - )) -} - -/// Expands `#[graphql_scalar]` macro placed on an implementation block. -fn expand_on_impl(attrs: Vec, ast: syn::ItemImpl) -> syn::Result { - let attr = Attr::from_attrs("graphql_scalar", &attrs)?; - - let mut self_ty = ast.self_ty.clone(); - if let syn::Type::Group(group) = self_ty.as_ref() { - self_ty = group.elem.clone(); - } - - let name = attr - .name - .map(SpanContainer::into_inner) - .or_else(|| { - if let syn::Type::Path(path) = self_ty.as_ref() { - path.path - .segments - .last() - .map(|last| last.ident.unraw().to_string()) - } else { - None - } - }) - .ok_or_else(|| { - ERR.custom_error( - self_ty.span(), - "unable to find target for implementation target for `GraphQLScalar`", - ) - })?; - - let (_, trait_ty, _) = ast.trait_.as_ref().ok_or_else(|| { - ERR.custom_error( - ast.impl_token.span(), - "expected `GraphQLScalar` trait implementation", - ) - })?; - - let scalar = get_scalar(trait_ty, &ast.generics); - - let mut out = ast.to_token_stream(); - Definition { - impl_for_type: *ast.self_ty.clone(), - generics: ast.generics.clone(), - name, - description: attr.description.as_deref().cloned(), - scalar, - specified_by_url: attr.specified_by_url.as_deref().cloned(), - } - .to_tokens(&mut out); - - Ok(out) -} - -/// Extracts [`scalar::Type`] from [`GraphQLScalar`] trait. -/// -/// [`GraphQLScalar`]: juniper::GraphQLScalar -fn get_scalar(trait_ty: &syn::Path, generics: &syn::Generics) -> scalar::Type { - if let Some(last_seg) = trait_ty.segments.last() { - match &last_seg.arguments { - syn::PathArguments::AngleBracketed(gens) => { - if let Some(syn::GenericArgument::Type(ty)) = gens.args.last() { - let generic_scalar = generics - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .find(|gen_par| gen_par.to_string() == ty.to_token_stream().to_string()); - - return generic_scalar.map_or_else( - || scalar::Type::Concrete(ty.clone()), - |scalar| scalar::Type::ExplicitGeneric(scalar.clone()), - ); - } - } - syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => {} - } - } - scalar::Type::Concrete(parse_quote! { ::juniper::DefaultScalarValue }) -} +pub mod attr; +pub mod derive; /// Available arguments behind `#[graphql_scalar]` attribute when generating /// code for [GraphQL scalar][1] type. diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 013616200..4220a9238 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -108,8 +108,7 @@ macro_rules! try_merge_hashset { mod derive_enum; mod derive_input_object; -mod derive_scalar_value; -mod impl_scalar; +mod graphql_scalar; mod common; mod graphql_interface; @@ -189,7 +188,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { #[proc_macro_error] #[proc_macro_derive(GraphQLScalar, attributes(graphql))] pub fn derive_scalar_value(input: TokenStream) -> TokenStream { - derive_scalar_value::expand(input.into()) + graphql_scalar::derive::expand(input.into()) .unwrap_or_abort() .into() } @@ -253,7 +252,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { #[proc_macro_error] #[proc_macro_attribute] pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { - impl_scalar::expand(attr.into(), body.into()) + graphql_scalar::attr::expand(attr.into(), body.into()) .unwrap_or_abort() .into() } From 30117a848b374862d3c56f3664608c53bd5e099e Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 15:14:09 +0300 Subject: [PATCH 069/122] Docs and the book --- docs/book/content/types/scalars.md | 94 +++++++++++++++ juniper_codegen/src/graphql_scalar/derive.rs | 10 +- juniper_codegen/src/lib.rs | 118 +++++++++++++++++-- 3 files changed, 206 insertions(+), 16 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 6eeb886de..eee15e0fd 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -80,6 +80,100 @@ pub struct UserId(i32); # fn main() {} ``` +All the methods used from newtype's field can be replaced with attributes mirroring +[`GraphQLScalar`](https://docs.rs/juniper/*/juniper/trait.GraphQLScalar.html) methods: + +#### `#[graphql(to_output_with = ...)]` attribute + +```rust +# use juniper::{GraphQLScalar, ScalarValue, Value}; +# +#[derive(GraphQLScalar)] +#[graphql(to_output_with = to_output)] +struct Incremented(i32); + +/// Increments [`Incremented`] before converting into a [`Value`]. +fn to_output(v: &Incremented) -> Value { + let inc = v.0 + 1; + inc.to_output() +} +``` + +#### `#[graphql(from_input_with = ..., from_input_err = ...)]` attributes + +```rust +# use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; +# +#[derive(GraphQLScalar)] +#[graphql(scalar = DefaultScalarValue)] +#[graphql(from_input_with = Self::from_input, from_input_err = String)] +// Unfortunately for now there is no way to infer this ^^^^^^ +struct UserId(String); + +impl UserId { + /// Checks whether [`InputValue`] is `String` beginning with `id: ` and + /// strips it. + fn from_input(input: &InputValue) -> Result { + input.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", input)) + .and_then(|str| { + str.strip_prefix("id: ") + .ok_or_else(|| { + format!( + "Expected `UserId` to begin with `id: `, \ + found: {}", + input, + ) + }) + }) + .map(|id| Self(id.to_owned())) + } +} + ``` + +#### `#[graphql(parse_token_with = ...]` or `#[graphql(parse_token(...)]` attributes + + ```rust +# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +# +#[derive(GraphQLScalar)] +#[graphql( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + parse_token_with = parse_token, + // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, 32)` + // which tries to parse as `String` and then as `i32` + // if prior fails. +)] +enum StringOrInt { + String(String), + Int(i32), +} + +fn to_output(v: &StringOrInt) -> Value { + match v { + StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::Int(i) => Value::scalar(*i), + } +} + +fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .map(|s| StringOrInt::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +} + +fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::parse_token(value) + .or_else(|_| >::parse_token(value)) +} +``` + +> __NOTE:__ As you can see, once you provide all 3 custom resolvers, there is no +> need to follow newtype pattern. + ## Custom scalars For more complex situations where you also need custom parsing or validation, diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index 16c1690d3..64e4814d1 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -462,7 +462,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let resolve = self.methods.expand_resolve(&scalar); + let resolve = self.methods.expand_resolve(scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -531,7 +531,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let to_input_value = self.methods.expand_to_input_value(&scalar); + let to_input_value = self.methods.expand_to_input_value(scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -557,8 +557,8 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let error_ty = self.methods.expand_from_input_value_err(&scalar); - let from_input_value = self.methods.expand_from_input_value(&scalar); + let error_ty = self.methods.expand_from_input_value_err(scalar); + let from_input_value = self.methods.expand_from_input_value(scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -586,7 +586,7 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let from_str = self.methods.expand_from_str(&scalar); + let from_str = self.methods.expand_from_str(scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 4220a9238..e8b94aa96 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -142,15 +142,12 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { } } -/// This custom derive macro implements the #[derive(GraphQLScalar)] -/// derive. +/// This custom derive macro to implement custom [GraphQL scalars][1]. /// -/// This can be used for two purposes. +/// > __NOTE:__ This macro by itself doesn't implement [`GraphQLScalar`] trait. /// -/// ## Transparent Newtype Wrapper -/// -/// Sometimes, you want to create a custerm scalar type by wrapping -/// an existing type. In Rust, this is often called the "newtype" pattern. +/// Sometimes, you want to create a custom [GraphQL scalar][1] type by wrapping +/// an existing type. In Rust, this is often called the `newtype` pattern. /// Thanks to this custom derive, this becomes really easy: /// /// ```rust @@ -158,9 +155,15 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// #[derive(juniper::GraphQLScalar)] /// struct UserId(String); /// +/// #[derive(juniper::GraphQLScalar)] +/// struct DroidId { +/// value: String, +/// } +/// /// #[derive(juniper::GraphQLObject)] -/// struct User { -/// id: UserId, +/// struct Pair { +/// user_id: UserId, +/// droid_id: DroidId, /// } /// ``` /// @@ -177,14 +180,107 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// description = "...", /// // A specification URL. /// specified_by_url = "https://tools.ietf.org/html/rfc4122", +/// // Explicit generic scalar. +/// scalar = S: juniper::ScalarValue, /// )] /// struct UserId(String); /// ``` /// -/// ### Base ScalarValue Enum +/// All of the methods used from `newtype`'s field can be replaced with attributes +/// mirroring [`GraphQLScalar`] methods: +/// +/// #### `#[graphql(to_output_with = ...)]` attribute +/// +/// ```rust +/// # use juniper::{GraphQLScalar, ScalarValue, Value}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql(to_output_with = to_output)] +/// struct Incremented(i32); +/// +/// /// Increments [`Incremented`] before converting into a [`Value`]. +/// fn to_output(v: &Incremented) -> Value { +/// let inc = v.0 + 1; +/// inc.to_output() +/// } +/// ``` +/// +/// #### `#[graphql(from_input_with = ..., from_input_err = ...)]` attributes +/// +/// ```rust +/// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql(scalar = DefaultScalarValue)] +/// #[graphql(from_input_with = Self::from_input, from_input_err = String)] +/// // Unfortunately for now there is no way to infer this ^^^^^^ +/// struct UserId(String); +/// +/// impl UserId { +/// /// Checks whether [`InputValue`] is `String` beginning with `id: ` and +/// /// strips it. +/// fn from_input(input: &InputValue) -> Result { +/// input.as_string_value() +/// .ok_or_else(|| format!("Expected `String`, found: {}", input)) +/// .and_then(|str| { +/// str.strip_prefix("id: ") +/// .ok_or_else(|| { +/// format!( +/// "Expected `UserId` to begin with `id: `, \ +/// found: {}", +/// input, +/// ) +/// }) +/// }) +/// .map(|id| Self(id.to_owned())) +/// } +/// } +/// ``` +/// +/// #### `#[graphql(parse_token_with = ...]` or `#[graphql(parse_token(...)]` attributes +/// +/// ```rust +/// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql( +/// to_output_with = to_output, +/// from_input_with = from_input, +/// from_input_err = String, +/// parse_token_with = parse_token, +/// // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, 32)` +/// // which tries to parse as `String` and then as `i32` +/// // if prior fails. +/// )] +/// enum StringOrInt { +/// String(String), +/// Int(i32), +/// } +/// +/// fn to_output(v: &StringOrInt) -> Value { +/// match v { +/// StringOrInt::String(str) => Value::scalar(str.to_owned()), +/// StringOrInt::Int(i) => Value::scalar(*i), +/// } +/// } +/// +/// fn from_input(v: &InputValue) -> Result { +/// v.as_string_value() +/// .map(|s| StringOrInt::String(s.to_owned())) +/// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// } +/// +/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { +/// >::parse_token(value) +/// .or_else(|_| >::parse_token(value)) +/// } +/// ``` /// -/// TODO: write documentation. +/// > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there +/// > is no need to follow `newtype` pattern. /// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[proc_macro_error] #[proc_macro_derive(GraphQLScalar, attributes(graphql))] pub fn derive_scalar_value(input: TokenStream) -> TokenStream { From 2362b273befdc06e4fe6a8ee995314a2391e67bc Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 15:24:55 +0300 Subject: [PATCH 070/122] Fix The Book --- docs/book/content/types/scalars.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index eee15e0fd..b2aef0a34 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -97,6 +97,8 @@ fn to_output(v: &Incremented) -> Value { let inc = v.0 + 1; inc.to_output() } +# +# fn main() {} ``` #### `#[graphql(from_input_with = ..., from_input_err = ...)]` attributes @@ -129,6 +131,8 @@ impl UserId { .map(|id| Self(id.to_owned())) } } +# +# fn main() {} ``` #### `#[graphql(parse_token_with = ...]` or `#[graphql(parse_token(...)]` attributes @@ -169,6 +173,8 @@ fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, >::parse_token(value) .or_else(|_| >::parse_token(value)) } +# +# fn main() {} ``` > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there is no From 86f1c10c23dfd6dd08db0b27182c7f5dd6257201 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 28 Jan 2022 14:06:41 +0300 Subject: [PATCH 071/122] CHANGELOG --- .../juniper_tests/src/codegen/impl_scalar.rs | 107 +- .../src/codegen/interface_attr.rs | 29 +- integration_tests/juniper_tests/src/lib.rs | 32 +- juniper/CHANGELOG.md | 7 + juniper/src/macros/reflection.rs | 982 ------------------ juniper/src/types/scalars.rs | 6 +- juniper_codegen/src/impl_scalar.rs | 4 +- 7 files changed, 128 insertions(+), 1039 deletions(-) delete mode 100644 juniper/src/macros/reflection.rs diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 8d834b62a..33d363840 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -3,34 +3,11 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, GraphQLScalar, GraphQLType, InputValue, ParseScalarResult, - ParseScalarValue, RootNode, ScalarToken, ScalarValue, Value, + GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, + Value, }; -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} +use crate::util::{schema, schema_with_scalar}; mod trivial { use super::*; @@ -110,6 +87,84 @@ mod trivial { } } +mod renamed_trait { + use super::{GraphQLScalar as CustomGraphQLScalar, *}; + + struct Counter(i32); + + #[graphql_scalar] + impl CustomGraphQLScalar for Counter { + type Error = String; + + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .map(Self) + .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { + ::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + mod explicit_name { use super::*; diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 7750a1501..e5bdc4fce 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2,34 +2,11 @@ use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, - GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue, + Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion, + IntoFieldError, ScalarValue, }; -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} +use crate::util::{schema, schema_with_scalar}; mod no_implers { use super::*; diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index 0d166d2b7..2e59d8c30 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -37,7 +37,37 @@ mod pre_parse; /// Common utilities used across tests. pub(crate) mod util { use futures::StreamExt as _; - use juniper::{graphql_value, ExecutionError, GraphQLError, ScalarValue, Value, ValuesStream}; + use juniper::{ + graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, ExecutionError, + GraphQLError, GraphQLType, RootNode, ScalarValue, Value, ValuesStream, + }; + + pub(crate) fn schema<'q, C, Q>( + query_root: Q, + ) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> + where + Q: GraphQLType + 'q, + { + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) + } + + pub(crate) fn schema_with_scalar<'q, S, C, Q>( + query_root: Q, + ) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> + where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, + { + RootNode::new_with_scalar_value( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) + } /// Extracts a single next value from the result returned by /// [`juniper::resolve_into_stream()`] and transforms it into a regular diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 27e00faff..36fb9a1f1 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -21,6 +21,13 @@ - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). - Forbid default impls on non-ignored trait methods. - Support coercion of additional nullable arguments and return sub-typing on implementer. +- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) + - Support generic scalars. + - Introduce actual `GraphQLScalar` trait. + - Add `Error` associated type to the `GraphQLScalar` trait. + - Rename `resolve` method to `to_output`. + - Rename `from_input_value` method to `from_input`. + - Rename `from_str` method to `parse_token`. ## Features diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs deleted file mode 100644 index 30e4f74f4..000000000 --- a/juniper/src/macros/reflection.rs +++ /dev/null @@ -1,982 +0,0 @@ -//! Helper traits and macros for compile-time reflection of Rust types into -//! GraphQL types. - -use std::{rc::Rc, sync::Arc}; - -use futures::future::BoxFuture; - -use crate::{ - Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, -}; - -/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name -/// in a GraphQL schema. -/// -/// See [`BaseType`] for more info. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sec-Scalars -/// [3]: https://spec.graphql.org/October2021#sec-Interfaces -pub type Type = &'static str; - -/// Alias for a slice of [`Type`]s. -/// -/// See [`BaseSubTypes`] for more info. -pub type Types = &'static [Type]; - -/// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. -/// -/// This trait is transparent to [`Option`], [`Vec`] and other containers, so to -/// fully represent [GraphQL object][1] we additionally use [`WrappedType`]. -/// -/// Different Rust type may have the same [`NAME`]. For example, [`String`] and -/// `&`[`str`](prim@str) share `String!` GraphQL type. -/// -/// [`NAME`]: Self::NAME -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sec-Scalars -/// [3]: https://spec.graphql.org/October2021#sec-Interfaces -pub trait BaseType { - /// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3]. - /// - /// [1]: https://spec.graphql.org/October2021#sec-Objects - /// [2]: https://spec.graphql.org/October2021#sec-Scalars - /// [3]: https://spec.graphql.org/October2021#sec-Interfaces - const NAME: Type; -} - -impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { - const NAME: Type = T::NAME; -} - -impl<'ctx, S, T> BaseType for (&'ctx T::Context, T) -where - S: ScalarValue, - T: BaseType + GraphQLValue, -{ - const NAME: Type = T::NAME; -} - -impl> BaseType for Option { - const NAME: Type = T::NAME; -} - -impl> BaseType for Nullable { - const NAME: Type = T::NAME; -} - -impl, E> BaseType for Result { - const NAME: Type = T::NAME; -} - -impl> BaseType for Vec { - const NAME: Type = T::NAME; -} - -impl> BaseType for [T] { - const NAME: Type = T::NAME; -} - -impl, const N: usize> BaseType for [T; N] { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Box { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Arc { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Rc { - const NAME: Type = T::NAME; -} - -/// [Sub-types][2] of a [GraphQL object][1]. -/// -/// This trait is transparent to [`Option`], [`Vec`] and other containers. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC -pub trait BaseSubTypes { - /// Sub-[`Types`] of the [GraphQL object][1]. - /// - /// [1]: https://spec.graphql.org/October2021#sec-Objects - const NAMES: Types; -} - -impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { - const NAMES: Types = T::NAMES; -} - -impl<'ctx, S, T> BaseSubTypes for (&'ctx T::Context, T) -where - S: ScalarValue, - T: BaseSubTypes + GraphQLValue, -{ - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Option { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Nullable { - const NAMES: Types = T::NAMES; -} - -impl, E> BaseSubTypes for Result { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Vec { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for [T] { - const NAMES: Types = T::NAMES; -} - -impl, const N: usize> BaseSubTypes for [T; N] { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Box { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Arc { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Rc { - const NAMES: Types = T::NAMES; -} - -/// Alias for a value of a [`WrappedType`] (combined GraphQL type). -pub type WrappedValue = u128; - -// TODO: Just use `&str`s once they're allowed in `const` generics. -/// Encoding of a composed GraphQL type in numbers. -/// -/// To fully represent a [GraphQL object][1] it's not enough to use [`Type`], -/// because of the [wrapping types][2]. To work around this we use a -/// [`WrappedValue`] which is represented via [`u128`]. -/// -/// - In base case of non-nullable [object][1] [`VALUE`] is `1`. -/// - To represent nullability we "append" `2` to the [`VALUE`], so -/// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. -/// - To represent list we "append" `3` to the [`VALUE`], so -/// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. -/// -/// This approach allows us to uniquely represent any [GraphQL object][1] with -/// combination of [`Type`] and [`WrappedValue`] and even format it via -/// [`format_type!`] macro in `const` context. -/// -/// -/// # Examples -/// -/// ```rust -/// # use juniper::{macros::reflection::{WrappedType, BaseType, WrappedValue, Type}, DefaultScalarValue, format_type}; -/// # -/// assert_eq!( as WrappedType>::VALUE, 12); -/// assert_eq!( as WrappedType>::VALUE, 13); -/// assert_eq!(> as WrappedType>::VALUE, 123); -/// assert_eq!(> as WrappedType>::VALUE, 132); -/// assert_eq!(>> as WrappedType>::VALUE, 1232); -/// -/// const TYPE_STRING: Type = >> as BaseType>::NAME; -/// const WRAP_VAL_STRING: WrappedValue = >> as WrappedType>::VALUE; -/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); -/// -/// const TYPE_STR: Type = >> as BaseType>::NAME; -/// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::VALUE; -/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); -/// ``` -/// -/// [`VALUE`]: Self::VALUE -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sec-Wrapping-Types -pub trait WrappedType { - /// [`WrappedValue`] of this type. - const VALUE: WrappedValue; -} - -impl<'ctx, S, T: WrappedType> WrappedType for (&'ctx T::Context, T) -where - S: ScalarValue, - T: GraphQLValue, -{ - const VALUE: u128 = T::VALUE; -} - -impl> WrappedType for Option { - const VALUE: u128 = T::VALUE * 10 + 2; -} - -impl> WrappedType for Nullable { - const VALUE: u128 = T::VALUE * 10 + 2; -} - -impl, E> WrappedType for Result { - const VALUE: u128 = T::VALUE; -} - -impl> WrappedType for Vec { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl> WrappedType for [T] { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl, const N: usize> WrappedType for [T; N] { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Box { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Arc { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Rc { - const VALUE: u128 = T::VALUE; -} - -/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name. -/// -/// See [`Fields`] for more info. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -/// [2]: https://spec.graphql.org/October2021#sec-Interfaces -/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments -pub type Name = &'static str; - -/// Alias for a slice of [`Name`]s. -/// -/// See [`Fields`] for more info. -pub type Names = &'static [Name]; - -/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments -pub type Argument = (Name, Type, WrappedValue); - -/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and -/// [`WrappedValue`]. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments -pub type Arguments = &'static [(Name, Type, WrappedValue)]; - -/// Alias for a `const`-hashed [`Name`] used in `const` context. -pub type FieldName = u128; - -/// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces -/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub trait Fields { - /// [`Names`] of the [GraphQL object][1] or [interface][2] - /// [field arguments][3]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces - /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments - const NAMES: Names; -} - -/// [`Types`] of the [GraphQL interfaces][1] implemented by this type. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Interfaces -pub trait Implements { - /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Interfaces - const NAMES: Types; -} - -/// Stores meta information of a [GraphQL field][1]: -/// - [`Context`] and [`TypeInfo`]. -/// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. -/// - [`ARGUMENTS`]. -/// -/// [`ARGUMENTS`]: Self::ARGUMENTS -/// [`Context`]: Self::Context -/// [`SUB_TYPES`]: Self::SUB_TYPES -/// [`TYPE`]: Self::TYPE -/// [`TypeInfo`]: Self::TypeInfo -/// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields -pub trait FieldMeta { - /// [`GraphQLValue::Context`] of this [`Field`][1]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields - type Context; - - /// [`GraphQLValue::TypeInfo`] of this [GraphQL field][1]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields - type TypeInfo; - - /// [`Types`] of [GraphQL field's][1] return type. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields - const TYPE: Type; - - /// [Sub-Types][1] of [GraphQL field's][2] return type. - /// - /// [1]: BaseSubTypes - /// [2]: https://spec.graphql.org/October2021/#sec-Language.Fields - const SUB_TYPES: Types; - - /// [`WrappedValue`] of [GraphQL field's][1] return type. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields - const WRAPPED_VALUE: WrappedValue; - - /// [GraphQL field's][1] [`Arguments`]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields - const ARGUMENTS: Arguments; -} - -/// Synchronous field of a [GraphQL object][1] or [interface][2]. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces -pub trait Field: FieldMeta { - /// Resolves the [`Value`] of this synchronous [`Field`]. - /// - /// The `arguments` object contains all the specified arguments, with - /// default values being substituted for the ones not provided by the query. - /// - /// The `executor` can be used to drive selections into sub-[objects][1]. - /// - /// [`Value`]: crate::Value - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - fn call( - &self, - info: &Self::TypeInfo, - args: &FieldArguments, - executor: &Executor, - ) -> ExecutionResult; -} - -/// Asynchronous field of a GraphQL [`object`][1] or [`interface`][2]. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces -pub trait AsyncField: FieldMeta { - /// Resolves the [`Value`] of this asynchronous [`AsyncField`]. - /// - /// The `arguments` object contains all the specified arguments, with - /// default values being substituted for the ones not provided by the query. - /// - /// The `executor` can be used to drive selections into sub-[objects][1]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b FieldArguments, - executor: &'b Executor, - ) -> BoxFuture<'b, ExecutionResult>; -} - -/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in -/// `const` generics. See [spec] for more info. -/// -/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -#[must_use] -pub const fn fnv1a128(str: Name) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash -} - -/// Length __in bytes__ of the [`format_type`] macro result. -#[must_use] -pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { - let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - - let mut curr = val; - while curr % 10 != 0 { - match curr % 10 { - 2 => len -= "!".as_bytes().len(), // remove ! - 3 => len += "[]!".as_bytes().len(), // [Type]! - _ => {} - } - curr /= 10; - } - - len -} - -/// Checks whether GraphQL [`objects`][1] can be sub-types, based on the -/// [`WrappedValue`]. -/// -/// To fully determine the sub-typing relation [`Type`] should be one of the -/// [`BaseSubTypes::NAMES`]. -/// -/// [1]: https://spec.graphql.org/October2021#sec-Objects -#[must_use] -pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_curr = ty % 10; - let sub_curr = subtype % 10; - - if ty_curr == sub_curr { - if ty_curr == 1 { - true - } else { - can_be_subtype(ty / 10, subtype / 10) - } - } else if ty_curr == 2 { - can_be_subtype(ty / 10, subtype) - } else { - false - } -} - -/// Checks whether the given `val` exists in the given `arr`. -#[must_use] -pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { - let mut i = 0; - while i < arr.len() { - if str_eq(val, arr[i]) { - return true; - } - i += 1; - } - false -} - -/// Compares strings in a `const` context. -/// -/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to -/// write custom comparison function. -/// -/// [`Eq`]: std::cmp::Eq -// TODO: Remove once `Eq` trait is allowed in `const` context. -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - -/// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing -/// this interface in the `impl = ...` attribute argument. -#[macro_export] -macro_rules! assert_implemented_for { - ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { - const _: () = { - $({ - let is_present = $crate::macros::reflection::str_exists_in_arr( - <$implementor as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, - <$interfaces as ::juniper::macros::reflection::BaseSubTypes<$scalar>>::NAMES, - ); - if !is_present { - const MSG: &str = $crate::const_concat!( - "Failed to implement interface `", - <$interfaces as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "` on `", - <$implementor as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "`: missing implementer reference in interface's `for` attribute.", - ); - ::std::panic!("{}", MSG); - } - })* - }; - }; -} - -/// Asserts that `impl = ...` attribute argument has all the types referencing -/// this GraphQL type in `#[graphql_interface(for = ...)]`. -#[macro_export] -macro_rules! assert_interfaces_impls { - ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { - const _: () = { - $({ - let is_present = $crate::macros::reflection::str_exists_in_arr( - <$interface as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, - <$implementers as ::juniper::macros::reflection::Implements<$scalar>>::NAMES, - ); - if !is_present { - const MSG: &str = $crate::const_concat!( - "Failed to implement interface `", - <$interface as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "` on `", - <$implementers as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "`: missing interface reference in implementer's `impl` attribute.", - ); - ::std::panic!("{}", MSG); - } - })* - }; - }; -} - -/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. -/// -/// This assertion is a combination of [`assert_subtype`] and -/// [`assert_field_args`]. -/// -/// See [spec][1] for more info. -/// -/// [1]: https://spec.graphql.org/October2021#IsValidImplementation() -#[macro_export] -macro_rules! assert_field { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); - $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); - }; -} - -/// Asserts validness of a [`Field`] return type. -/// -/// See [spec][1] for more info. -/// -/// [1]: https://spec.graphql.org/October2021#IsValidImplementationFieldType() -#[macro_export] -macro_rules! assert_subtype { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - const _: () = { - const BASE_TY: $crate::macros::reflection::Type = - <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; - const IMPL_TY: $crate::macros::reflection::Type = - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_TY, - "` on `", - IMPL_TY, - "`: ", - ); - - const FIELD_NAME: $crate::macros::reflection::Name = - $field_name; - - const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = - <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::WRAPPED_VALUE; - const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = - <$impl_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::WRAPPED_VALUE; - - const BASE_RETURN_TY: $crate::macros::reflection::Type = - <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::TYPE; - const IMPL_RETURN_TY: $crate::macros::reflection::Type = - <$impl_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::TYPE; - - const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = - <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::SUB_TYPES; - - let is_subtype = $crate::macros::reflection::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) - && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); - if !is_subtype { - const MSG: &str = $crate::const_concat!( - ERR_PREFIX, - "Field `", - FIELD_NAME, - "`: implementor is expected to return a subtype of interface's return object: `", - $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), - "` is not a subtype of `", - $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), - "`.", - ); - ::std::panic!("{}", MSG); - } - }; - }; -} - -/// Asserts validness of the [`Field`]s arguments. See [spec][1] for more -/// info. -/// -/// [1]: https://spec.graphql.org/October2021/#sel-IAHZhCHCDEEFAAADHD8Cxob -#[macro_export] -macro_rules! assert_field_args { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - const _: () = { - const BASE_NAME: &str = - <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; - const IMPL_NAME: &str = - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_NAME, - "` on `", - IMPL_NAME, - "`: ", - ); - - const FIELD_NAME: &str = $field_name; - - const BASE_ARGS: ::juniper::macros::reflection::Arguments = - <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - const IMPL_ARGS: ::juniper::macros::reflection::Arguments = - <$impl_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - - struct Error { - cause: Cause, - base: ::juniper::macros::reflection::Argument, - implementation: ::juniper::macros::reflection::Argument, - } - - enum Cause { - RequiredField, - AdditionalNonNullableField, - TypeMismatch, - } - - const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { - match v { - // Unfortunately we can't use `unreachable!()` here, as this - // branch will be executed either way. - Ok(()) => Error { - cause: Cause::RequiredField, - base: ("unreachable", "unreachable", 1), - implementation: ("unreachable", "unreachable", 1), - }, - Err(err) => err, - } - } - - const fn check() -> Result<(), Error> { - let mut base_i = 0; - while base_i < BASE_ARGS.len() { - let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; - - let mut impl_i = 0; - let mut was_found = false; - while impl_i < IMPL_ARGS.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; - - if $crate::macros::reflection::str_eq(base_name, impl_name) { - if $crate::macros::reflection::str_eq(base_type, impl_type) - && base_wrap_val == impl_wrap_val - { - was_found = true; - break; - } else { - return Err(Error { - cause: Cause::TypeMismatch, - base: (base_name, base_type, base_wrap_val), - implementation: (impl_name, impl_type, impl_wrap_val), - }); - } - } - - impl_i += 1; - } - - if !was_found { - return Err(Error { - cause: Cause::RequiredField, - base: (base_name, base_type, base_wrap_val), - implementation: (base_name, base_type, base_wrap_val), - }); - } - - base_i += 1; - } - - let mut impl_i = 0; - while impl_i < IMPL_ARGS.len() { - let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; - impl_i += 1; - - if impl_wrapped_val % 10 == 2 { - continue; - } - - let mut base_i = 0; - let mut was_found = false; - while base_i < BASE_ARGS.len() { - let (base_name, _, _) = BASE_ARGS[base_i]; - if $crate::macros::reflection::str_eq(base_name, impl_name) { - was_found = true; - break; - } - base_i += 1; - } - if !was_found { - return Err(Error { - cause: Cause::AdditionalNonNullableField, - base: (impl_name, impl_type, impl_wrapped_val), - implementation: (impl_name, impl_type, impl_wrapped_val), - }); - } - } - - Ok(()) - } - - const RES: ::std::result::Result<(), Error> = check(); - if RES.is_err() { - const ERROR: Error = unwrap_error(RES); - - const BASE_ARG_NAME: &str = ERROR.base.0; - const IMPL_ARG_NAME: &str = ERROR.implementation.0; - - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); - const IMPL_TYPE_FORMATTED: &str = - $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); - - const MSG: &str = match ERROR.cause { - Cause::TypeMismatch => { - $crate::const_concat!( - "Argument `", - BASE_ARG_NAME, - "`: expected type `", - BASE_TYPE_FORMATTED, - "`, found: `", - IMPL_TYPE_FORMATTED, - "`.", - ) - } - Cause::RequiredField => { - $crate::const_concat!( - "Argument `", - BASE_ARG_NAME, - "` of type `", - BASE_TYPE_FORMATTED, - "` was expected, but not found." - ) - } - Cause::AdditionalNonNullableField => { - $crate::const_concat!( - "Argument `", - IMPL_ARG_NAME, - "` of type `", - IMPL_TYPE_FORMATTED, - "` isn't present on the interface and so has to be nullable." - ) - } - }; - const ERROR_MSG: &str = - $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); - ::std::panic!("{}", ERROR_MSG); - } - }; - }; -} - -/// Concatenates `const` [`str`](prim@str)s in a `const` context. -#[macro_export] -macro_rules! const_concat { - ($($s:expr),* $(,)?) => {{ - const LEN: usize = 0 $(+ $s.as_bytes().len())*; - const CNT: usize = [$($s),*].len(); - const fn concat(input: [&str; CNT]) -> [u8; LEN] { - let mut bytes = [0; LEN]; - let (mut i, mut byte) = (0, 0); - while i < CNT { - let mut b = 0; - while b < input[i].len() { - bytes[byte] = input[i].as_bytes()[b]; - byte += 1; - b += 1; - } - i += 1; - } - bytes - } - const CON: [u8; LEN] = concat([$($s),*]); - - // TODO: Use `str::from_utf8()` once it becomes `const`. - // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one - // after another byte-by-byte. - #[allow(unsafe_code)] - unsafe { ::std::str::from_utf8_unchecked(&CON) } - }}; -} - -/// Ensures that the given `$impl_ty` implements [`Field`] and returns a -/// [`fnv1a128`] hash for it, otherwise panics with understandable message. -#[macro_export] -macro_rules! checked_hash { - ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ - let exists = $crate::macros::reflection::str_exists_in_arr( - $field_name, - <$impl_ty as $crate::macros::reflection::Fields<$scalar>>::NAMES, - ); - if exists { - $crate::macros::reflection::fnv1a128(FIELD_NAME) - } else { - const MSG: &str = $crate::const_concat!( - $($prefix,)? - "Field `", - $field_name, - "` isn't implemented on `", - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "`." - ); - ::std::panic!("{}", MSG) - } - }}; -} - -/// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type -/// name. -/// -/// # Examples -/// -/// ``` -/// # use juniper::format_type; -/// # -/// assert_eq!(format_type!("String", 123), "[String]!"); -/// assert_eq!(format_type!("🦀", 123), "[🦀]!"); -/// ``` -#[macro_export] -macro_rules! format_type { - ($ty: expr, $wrapped_value: expr $(,)?) => {{ - const TYPE: ( - $crate::macros::reflection::Type, - $crate::macros::reflection::WrappedValue, - ) = ($ty, $wrapped_value); - const RES_LEN: usize = - $crate::macros::reflection::type_len_with_wrapped_val(TYPE.0, TYPE.1); - - const OPENING_BRACKET: &str = "["; - const CLOSING_BRACKET: &str = "]"; - const BANG: &str = "!"; - - const fn format_type_arr() -> [u8; RES_LEN] { - let (ty, wrap_val) = TYPE; - let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; - - let mut current_start = 0; - let mut current_end = RES_LEN - 1; - let mut current_wrap_val = wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, // Skips writing `BANG` later. - 3 => { - // Write `OPENING_BRACKET` at `current_start`. - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - if !is_null { - // Write `BANG` at `current_end`. - i = 0; - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - } - // Write `CLOSING_BRACKET` at `current_end`. - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - is_null = false; - } - _ => {} - } - - current_wrap_val /= 10; - } - - // Writes `Type` at `current_start`. - let mut i = 0; - while i < ty.as_bytes().len() { - type_arr[current_start + i] = ty.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - // Writes `BANG` at `current_end`. - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; - } - } - - type_arr - } - - const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - - // TODO: Use `str::from_utf8()` once it becomes `const`. - // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one - // after another byte-by-byte. - #[allow(unsafe_code)] - const TYPE_FORMATTED: &str = - unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) }; - - TYPE_FORMATTED - }}; -} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 4a0adf5b1..24512ba0b 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -276,11 +276,13 @@ where } #[crate::graphql_scalar(name = "Boolean")] -impl GraphQLScalar for bool +impl GraphQLScalar for bool where S: ScalarValue, { - fn resolve(&self) -> Value { + type Error = String; + + fn to_output(&self) -> Value { Value::scalar(*self) } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 2afb99642..bf45ebce3 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -270,7 +270,7 @@ impl ToTokens for Definition { self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); } } @@ -490,7 +490,7 @@ impl Definition { /// [`BaseType`]: juniper::macros::reflect::BaseType /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.impl_for_type; let scalar = &self.scalar; let name = &self.name; From 7db2adc84734ef1ad305f9b646037bf693967795 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 28 Jan 2022 14:17:15 +0300 Subject: [PATCH 072/122] Sprinkle `#[automatically_derived]` attributes --- juniper_codegen/src/impl_scalar.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index bf45ebce3..8a6098f0c 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -290,9 +290,11 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ty #where_clause { } + #[automatically_derived] impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { } } @@ -321,6 +323,7 @@ impl Definition { }); quote! { + #[automatically_derived] impl#impl_gens ::juniper::GraphQLType<#scalar> for #ty #where_clause { @@ -357,6 +360,7 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ty #where_clause { @@ -392,6 +396,7 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { @@ -422,6 +427,7 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::ToInputValue<#scalar> for #ty #where_clause { @@ -446,6 +452,7 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty #where_clause { @@ -471,6 +478,7 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty #where_clause { @@ -499,6 +507,7 @@ impl Definition { let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { From 6fb0dbf0d21e81ba2213b016c6f3501613d4187f Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 2 Feb 2022 10:53:50 +0300 Subject: [PATCH 073/122] Implement `#[graphql(with)]` attribute --- docs/book/content/types/scalars.md | 118 +++++- .../src/codegen/derive_scalar.rs | 165 +++++++-- .../juniper_tests/src/custom_scalar.rs | 1 + juniper_codegen/src/derive_scalar_value.rs | 347 ------------------ juniper_codegen/src/graphql_scalar/derive.rs | 131 ++++++- juniper_codegen/src/lib.rs | 112 +++++- 6 files changed, 453 insertions(+), 421 deletions(-) delete mode 100644 juniper_codegen/src/derive_scalar_value.rs diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index b2aef0a34..366e4c8b6 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -40,7 +40,9 @@ crates. They are enabled via features that are on by default. * url::Url * bson::oid::ObjectId -## newtype pattern +## Custom scalars + +### `#[derive(GraphQLScalar)]` Often, you might need a custom scalar that just wraps an existing type. @@ -56,7 +58,7 @@ pub struct UserId(i32); struct User { id: UserId, } - +# # fn main() {} ``` @@ -76,14 +78,14 @@ The macro also allows for more customization: description = "My user id description", )] pub struct UserId(i32); - +# # fn main() {} ``` All the methods used from newtype's field can be replaced with attributes mirroring [`GraphQLScalar`](https://docs.rs/juniper/*/juniper/trait.GraphQLScalar.html) methods: -#### `#[graphql(to_output_with = ...)]` attribute +#### `#[graphql(to_output_with = )]` attribute ```rust # use juniper::{GraphQLScalar, ScalarValue, Value}; @@ -101,7 +103,7 @@ fn to_output(v: &Incremented) -> Value { # fn main() {} ``` -#### `#[graphql(from_input_with = ..., from_input_err = ...)]` attributes +#### `#[graphql(from_input_with = , from_input_err = )]` attributes ```rust # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; @@ -135,9 +137,9 @@ impl UserId { # fn main() {} ``` -#### `#[graphql(parse_token_with = ...]` or `#[graphql(parse_token(...)]` attributes +#### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes - ```rust +```rust # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; # #[derive(GraphQLScalar)] @@ -146,7 +148,7 @@ impl UserId { from_input_with = from_input, from_input_err = String, parse_token_with = parse_token, - // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, 32)` + // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)` // which tries to parse as `String` and then as `i32` // if prior fails. )] @@ -177,10 +179,104 @@ fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, # fn main() {} ``` -> __NOTE:__ As you can see, once you provide all 3 custom resolvers, there is no -> need to follow newtype pattern. +> __NOTE:__ As you can see, once you provide all 3 custom resolvers, there +> is no need to follow `newtype` pattern. -## Custom scalars +#### `#[graphql(with = )]` attribute + +Instead of providing all custom resolvers, you can provide module with `to_output`, `from_input`, `parse_token` functions and `Error` struct or type alias. + +```rust +# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +# +#[derive(GraphQLScalar)] +#[graphql(with = string_or_int)] +enum StringOrInt { + String(String), + Int(i32), +} + +mod string_or_int { + # use super::*; + # + pub(super) type Error = String; + + pub(super) fn to_output(v: &StringOrInt) -> Value + where + S: ScalarValue, + { + match v { + StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::Int(i) => Value::scalar(*i), + } + } + + pub(super) fn from_input(v: &InputValue) -> Result + where + S: ScalarValue, + { + v.as_string_value() + .map(|s| StringOrInt::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } + + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> + where + S: ScalarValue, + { + >::parse_token(value) + .or_else(|_| >::parse_token(value)) + } +} +# +# fn main() {} +``` + +Also, you can partially override `#[graphql(with)]` attribute with other custom scalars. + +```rust +# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +# +#[derive(GraphQLScalar)] +#[graphql( + with = string_or_int, + from_input_err = String, + parse_token(String, i32) +)] +enum StringOrInt { + String(String), + Int(i32), +} + +mod string_or_int { + # use super::*; + # + pub(super) fn to_output(v: &StringOrInt) -> Value + where + S: ScalarValue, + { + match v { + StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::Int(i) => Value::scalar(*i), + } + } + + pub(super) fn from_input(v: &InputValue) -> Result + where + S: ScalarValue, + { + v.as_string_value() + .map(|s| StringOrInt::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } +} +# +# fn main() {} +``` + +### `#[graphql_scalar]` attribute For more complex situations where you also need custom parsing or validation, you can use the `graphql_scalar` proc macro. diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index 152c0b0fa..f77a1c004 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -2,35 +2,11 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, - EmptySubscription, GraphQLScalar, GraphQLType, InputValue, ParseScalarResult, ParseScalarValue, - RootNode, ScalarToken, ScalarValue, Value, + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, GraphQLScalar, + InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} +use crate::util::{schema, schema_with_scalar}; mod trivial_unnamed { use super::*; @@ -451,12 +427,12 @@ mod generic_with_all_resolvers { #[derive(GraphQLScalar)] #[graphql( - to_output_with = Self::to_output, - from_input_with = Self::from_input, - from_input_err = String, + to_output_with = custom_date_time::to_output, + from_input_with = custom_date_time::from_input, + from_input_err = custom_date_time::Error, )] #[graphql( - parse_token_with = Self::parse_token, + parse_token_with = custom_date_time::parse_token, specified_by_url = "https://tools.ietf.org/html/rfc3339" )] struct CustomDateTime @@ -468,24 +444,132 @@ mod generic_with_all_resolvers { _unused: (), } - impl GraphQLScalar for CustomDateTime + mod custom_date_time { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.dt.to_rfc3339()) + } + + pub(super) fn from_input(v: &InputValue) -> Result, Error> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime { + dt: dt.with_timezone(&Tz::from(Utc)), + _unused: (), + }) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> + where + S: ScalarValue, + { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } + } + + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } +} + +mod generic_with_module { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + with = custom_date_time, + specified_by_url = "https://tools.ietf.org/html/rfc3339" + )] + struct CustomDateTime where - S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - type Error = String; + dt: DateTime, + _unused: (), + } + + mod custom_date_time { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { - Value::scalar(self.dt.to_rfc3339()) + pub(super) fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.dt.to_rfc3339()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result, Error> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { DateTime::parse_from_rfc3339(s) - .map(|dt| Self { + .map(|dt| CustomDateTime { dt: dt.with_timezone(&Tz::from(Utc)), _unused: (), }) @@ -493,7 +577,10 @@ mod generic_with_all_resolvers { }) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> + where + S: ScalarValue, + { >::from_str(value) } } diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 63b362102..910acf849 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -20,6 +20,7 @@ pub(crate) enum MyScalarValue { Boolean(bool), } +// TODO: replace all underlying `From` impls with `GraphQLScalarValue` macro. impl From for MyScalarValue { fn from(v: i32) -> Self { Self::Int(v) diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs deleted file mode 100644 index 3888d757b..000000000 --- a/juniper_codegen/src/derive_scalar_value.rs +++ /dev/null @@ -1,347 +0,0 @@ -use crate::{ - common::parse::ParseBufferExt as _, - result::GraphQLScope, - util::{self, span_container::SpanContainer}, -}; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant}; -use url::Url; - -#[derive(Debug, Default)] -struct TransparentAttributes { - transparent: Option, - name: Option, - description: Option, - specified_by_url: Option, - scalar: Option, -} - -impl syn::parse::Parse for TransparentAttributes { - fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { - let mut output = Self { - transparent: None, - name: None, - description: None, - specified_by_url: None, - scalar: None, - }; - - while !input.is_empty() { - let ident: syn::Ident = input.parse()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let val = input.parse::()?; - output.name = Some(val.value()); - } - "description" => { - input.parse::()?; - let val = input.parse::()?; - output.description = Some(val.value()); - } - "specified_by_url" => { - input.parse::()?; - let val: syn::LitStr = input.parse::()?; - output.specified_by_url = - Some(val.value().parse().map_err(|e| { - syn::Error::new(val.span(), format!("Invalid URL: {}", e)) - })?); - } - "transparent" => { - output.transparent = Some(true); - } - "scalar" | "Scalar" => { - input.parse::()?; - let val = input.parse::()?; - output.scalar = Some(val); - } - _ => return Err(syn::Error::new(ident.span(), "unknown attribute")), - } - input.try_parse::()?; - } - - Ok(output) - } -} - -impl TransparentAttributes { - fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result { - match util::find_graphql_attr(attrs) { - Some(attr) => { - let mut parsed: TransparentAttributes = attr.parse_args()?; - if parsed.description.is_none() { - parsed.description = - util::get_doc_comment(attrs).map(SpanContainer::into_inner); - } - Ok(parsed) - } - None => Ok(Default::default()), - } - } -} - -pub fn impl_scalar_value(ast: &syn::DeriveInput, error: GraphQLScope) -> syn::Result { - let ident = &ast.ident; - - match ast.data { - Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, error), - Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, error), - Data::Union(_) => Err(error.custom_error(ast.span(), "may not be applied to unions")), - } -} - -fn impl_scalar_struct( - ast: &syn::DeriveInput, - data: &syn::DataStruct, - error: GraphQLScope, -) -> syn::Result { - let field = match data.fields { - syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { - fields.unnamed.first().unwrap() - } - _ => { - return Err(error.custom_error( - data.fields.span(), - "requires exact one field, e.g., Test(i32)", - )) - } - }; - let ident = &ast.ident; - let attrs = TransparentAttributes::from_attrs(&ast.attrs)?; - let inner_ty = &field.ty; - let name = attrs.name.unwrap_or_else(|| ident.to_string()); - - let description = attrs.description.map(|val| quote!(.description(#val))); - let specified_by_url = attrs.specified_by_url.map(|url| { - let url_lit = url.as_str(); - quote!(.specified_by_url(#url_lit)) - }); - - let scalar = attrs - .scalar - .as_ref() - .map(|s| quote!( #s )) - .unwrap_or_else(|| quote!(__S)); - - let impl_generics = attrs - .scalar - .as_ref() - .map(|_| quote!()) - .unwrap_or_else(|| quote!(<__S>)); - - let _async = quote!( - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ident - where - Self: Sync, - Self::TypeInfo: Sync, - Self::Context: Sync, - #scalar: ::juniper::ScalarValue + Send + Sync, - { - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [::juniper::Selection<#scalar>]>, - executor: &'a ::juniper::Executor, - ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#scalar>> { - use ::juniper::futures::future; - let v = ::juniper::GraphQLValue::<#scalar>::resolve(self, info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - ); - - let content = quote!( - #_async - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - 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, - { - registry.build_scalar_type::(info) - #description - #specified_by_url - .into_meta() - } - } - - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - type Context = (); - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - fn resolve( - &self, - info: &(), - selection: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - ::juniper::GraphQLValue::<#scalar>::resolve(&self.0, info, selection, executor) - } - } - - impl#impl_generics ::juniper::ToInputValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - ::juniper::ToInputValue::<#scalar>::to_input_value(&self.0) - } - } - - impl#impl_generics ::juniper::FromInputValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - type Error = <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error; - - fn from_input_value( - v: &::juniper::InputValue<#scalar> - ) -> Result<#ident, <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error> { - let inner: #inner_ty = ::juniper::FromInputValue::<#scalar>::from_input_value(v)?; - Ok(#ident(inner)) - } - } - - impl#impl_generics ::juniper::ParseScalarValue<#scalar> for #ident - where - #scalar: ::juniper::ScalarValue, - { - fn from_str<'a>( - value: ::juniper::parser::ScalarToken<'a>, - ) -> ::juniper::ParseScalarResult<'a, #scalar> { - <#inner_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(value) - } - } - - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { } - impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { } - - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { - const NAME: ::juniper::macros::reflect::Type = #name; - } - - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { - const NAMES: ::juniper::macros::reflect::Types = - &[>::NAME]; - } - - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident - where #scalar: ::juniper::ScalarValue, - { - const VALUE: ::juniper::macros::reflect::WrappedValue = 1; - } - ); - - Ok(content) -} - -fn impl_scalar_enum( - ident: &syn::Ident, - data: &syn::DataEnum, - error: GraphQLScope, -) -> syn::Result { - let froms = data - .variants - .iter() - .map(|v| derive_from_variant(v, ident, &error)) - .collect::, _>>()?; - - let display = derive_display(data.variants.iter(), ident); - - Ok(quote! { - #(#froms)* - - #display - }) -} - -fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream -where - I: Iterator, -{ - let arms = variants.map(|v| { - let variant = &v.ident; - quote!(#ident::#variant(ref v) => write!(f, "{}", v),) - }); - - quote! { - impl std::fmt::Display for #ident { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - #(#arms)* - } - } - } - } -} - -fn derive_from_variant( - variant: &Variant, - ident: &Ident, - error: &GraphQLScope, -) -> syn::Result { - let ty = match variant.fields { - Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty, - - _ => { - return Err(error.custom_error( - variant.fields.span(), - "requires exact one field, e.g., Test(i32)", - )) - } - }; - - let variant = &variant.ident; - - Ok(quote! { - impl ::std::convert::From<#ty> for #ident { - fn from(t: #ty) -> Self { - #ident::#variant(t) - } - } - - impl<'a> ::std::convert::From<&'a #ident> for std::option::Option<&'a #ty> { - fn from(t: &'a #ident) -> Self { - match *t { - #ident::#variant(ref t) => std::option::Option::Some(t), - _ => std::option::Option::None - } - } - } - - impl ::std::convert::From<#ident> for std::option::Option<#ty> { - fn from(t: #ident) -> Self { - match t { - #ident::#variant(t) => std::option::Option::Some(t), - _ => std::option::Option::None - } - } - } - }) -} diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index 64e4814d1..610a4c22d 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -37,15 +37,27 @@ pub fn expand(input: TokenStream) -> syn::Result { attr.from_input.as_deref().cloned(), attr.from_input_err.as_deref().cloned(), attr.parse_token.as_deref().cloned(), + attr.with.as_deref().cloned(), ) { - (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token)) => { + (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token), None) => { GraphQLScalarMethods::Custom { to_output, from_input: (from_input, from_input_err), parse_token, } } - (to_output, from_input, from_input_err, parse_token) => { + (to_output, from_input, from_input_err, parse_token, Some(module)) => { + GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: ( + from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + from_input_err.unwrap_or_else(|| parse_quote! { #module::Error }), + ), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + } + } + (to_output, from_input, from_input_err, parse_token, None) => { let from_input = match (from_input, from_input_err) { (Some(from_input), Some(err)) => Some((from_input, err)), (None, None) => None, @@ -178,6 +190,11 @@ struct Attr { /// /// [`GraphQLScalar::parse_token`]: juniper::GraphQLScalar::parse_token parse_token: Option>, + + /// Explicitly specified module with all custom resolvers for + /// [`Self::to_output`], [`Self::from_input`], [`Self::from_input_err`] and + /// [`Self::parse_token`]. + with: Option>, } impl Parse for Attr { @@ -282,6 +299,13 @@ impl Parse for Attr { )) .none_or_else(|_| err::dup_arg(&ident))? } + "with" => { + input.parse::()?; + let scl = input.parse::()?; + out.with + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } name => { return Err(err::unknown_arg(&ident, name)); } @@ -305,6 +329,7 @@ impl Attr { from_input: try_merge_opt!(from_input: self, another), from_input_err: try_merge_opt!(from_input_err: self, another), parse_token: try_merge_opt!(parse_token: self, another), + with: try_merge_opt!(with: self, another), }) } @@ -377,6 +402,7 @@ impl ToTokens for Definition { self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); + self.impl_graphql_scalar_tokens().to_tokens(into); self.impl_traits_for_reflection_tokens().to_tokens(into); } } @@ -398,9 +424,11 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ident#ty_gens #where_clause { } + #[automatically_derived] impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ident#ty_gens #where_clause { } } @@ -430,6 +458,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::GraphQLType<#scalar> for #ident#ty_gens #where_clause { @@ -469,6 +498,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ident#ty_gens #where_clause { @@ -505,6 +535,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ident#ty_gens #where_clause { @@ -538,6 +569,7 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::ToInputValue<#scalar> for #ident#ty_gens #where_clause { @@ -557,14 +589,15 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let error_ty = self.methods.expand_from_input_value_err(scalar); - let from_input_value = self.methods.expand_from_input_value(scalar); + let error_ty = self.methods.expand_from_input_err(scalar); + let from_input_value = self.methods.expand_from_input(scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::FromInputValue<#scalar> for #ident#ty_gens #where_clause { @@ -586,13 +619,14 @@ impl Definition { let ident = &self.ident; let scalar = &self.scalar; - let from_str = self.methods.expand_from_str(scalar); + let from_str = self.methods.expand_parse_token(scalar); let generics = self.impl_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { + #[automatically_derived] impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ident#ty_gens #where_clause { @@ -605,6 +639,50 @@ impl Definition { } } + /// Returns generated code implementing [`GraphQLScalar`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLScalar`]: juniper::GraphQLScalar + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_graphql_scalar_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + let (_, ty_gens, _) = self.generics.split_for_impl(); + + let to_output = self.methods.expand_to_output(scalar); + let from_input_err = self.methods.expand_from_input_err(scalar); + let from_input = self.methods.expand_from_input(scalar); + let parse_token = self.methods.expand_parse_token(scalar); + + quote! { + #[automatically_derived] + impl#impl_gens ::juniper::GraphQLScalar<#scalar> for #ident#ty_gens + #where_clause + { + type Error = #from_input_err; + + fn to_output(&self) -> ::juniper::Value<#scalar> { + #to_output + } + + fn from_input( + input: &::juniper::InputValue<#scalar> + ) -> Result { + #from_input + } + + fn parse_token( + token: ::juniper::ScalarToken<'_> + ) -> ::juniper::ParseScalarResult<'_, #scalar> { + #parse_token + } + } + } + } + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL scalar][1]. /// @@ -622,23 +700,26 @@ impl Definition { let (_, ty_gens, _) = self.generics.split_for_impl(); quote! { - impl#impl_gens ::juniper::macros::reflection::BaseType<#scalar> for #ident#ty_gens + #[automatically_derived] + impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ident#ty_gens #where_clause { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } - impl#impl_gens ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident#ty_gens + #[automatically_derived] + impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident#ty_gens #where_clause { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; } - impl#impl_gens ::juniper::macros::reflection::WrappedType<#scalar> for #ident#ty_gens + #[automatically_derived] + impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ident#ty_gens #where_clause { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } } } @@ -771,6 +852,26 @@ impl GraphQLScalarMethods { } } + /// Expands [`GraphQLScalar::to_output`] method. + /// + /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output + fn expand_to_output(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { #to_output(self) } + } + Self::Delegated { field, .. } => { + quote! { + ::juniper::GraphQLScalar::<#scalar>::to_output(&self.#field) + } + } + } + } + /// Expands [`ToInputValue::to_input_value`] method. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value @@ -795,7 +896,7 @@ impl GraphQLScalarMethods { /// Expands [`FromInputValue::Error`] type. /// /// [`FromInputValue::Error`]: juniper::FromInputValue::Error - fn expand_from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_from_input_err(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { from_input: (_, err), @@ -815,7 +916,7 @@ impl GraphQLScalarMethods { /// Expands [`FromInputValue::from_input_value`][1] method. /// /// [1]: juniper::FromInputValue::from_input_value - fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_from_input(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { from_input: (from_input, _), @@ -841,7 +942,7 @@ impl GraphQLScalarMethods { /// Expands [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str - fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_parse_token(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { parse_token, .. } | Self::Delegated { diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c62dab06d..c844059ec 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -142,9 +142,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { } } -/// This custom derive macro to implement custom [GraphQL scalars][1]. -/// -/// > __NOTE:__ This macro by itself doesn't implement [`GraphQLScalar`] trait. +/// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][1] +/// implementation. /// /// Sometimes, you want to create a custom [GraphQL scalar][1] type by wrapping /// an existing type. In Rust, this is often called the `newtype` pattern. @@ -174,7 +173,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// #[derive(juniper::GraphQLScalar)] /// #[graphql( /// // Set a custom GraphQL name. -/// name= "MyUserId", +/// name = "MyUserId", /// // A description can also specified in the attribute. /// // This will the doc comment, if one exists. /// description = "...", @@ -189,7 +188,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// All of the methods used from `newtype`'s field can be replaced with attributes /// mirroring [`GraphQLScalar`] methods: /// -/// #### `#[graphql(to_output_with = ...)]` attribute +/// #### `#[graphql(to_output_with = )]` attribute /// /// ```rust /// # use juniper::{GraphQLScalar, ScalarValue, Value}; @@ -205,7 +204,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// #### `#[graphql(from_input_with = ..., from_input_err = ...)]` attributes +/// #### `#[graphql(from_input_with = , from_input_err = )]` attributes /// /// ```rust /// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; @@ -237,7 +236,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// #### `#[graphql(parse_token_with = ...]` or `#[graphql(parse_token(...)]` attributes +/// #### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes /// /// ```rust /// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; @@ -248,7 +247,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// from_input_with = from_input, /// from_input_err = String, /// parse_token_with = parse_token, -/// // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, 32)` +/// // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)` /// // which tries to parse as `String` and then as `i32` /// // if prior fails. /// )] @@ -276,10 +275,105 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// .or_else(|_| >::parse_token(value)) /// } /// ``` -/// /// > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there /// > is no need to follow `newtype` pattern. /// +/// #### `#[graphql(with = )]` attribute +/// +/// Instead of providing all custom resolvers, you can provide module with +/// `to_output`, `from_input`, `parse_token` functions and `Error` struct or +/// type alias. +/// +/// ```rust +/// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql(with = string_or_int)] +/// enum StringOrInt { +/// String(String), +/// Int(i32), +/// } +/// +/// mod string_or_int { +/// # use super::*; +/// # +/// pub(super) type Error = String; +/// +/// pub(super) fn to_output(v: &StringOrInt) -> Value +/// where +/// S: ScalarValue, +/// { +/// match v { +/// StringOrInt::String(str) => Value::scalar(str.to_owned()), +/// StringOrInt::Int(i) => Value::scalar(*i), +/// } +/// } +/// +/// pub(super) fn from_input(v: &InputValue) -> Result +/// where +/// S: ScalarValue, +/// { +/// v.as_string_value() +/// .map(|s| StringOrInt::String(s.to_owned())) +/// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// } +/// +/// pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> +/// where +/// S: ScalarValue, +/// { +/// >::parse_token(value) +/// .or_else(|_| >::parse_token(value)) +/// } +/// } +/// # +/// # fn main() {} +/// ``` +/// +/// Also you can partially override `with` attribute with other custom scalars. +/// +/// ```rust +/// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +/// # +/// #[derive(GraphQLScalar)] +/// #[graphql( +/// with = string_or_int, +/// from_input_err = String, +/// parse_token(String, i32) +/// )] +/// enum StringOrInt { +/// String(String), +/// Int(i32), +/// } +/// +/// mod string_or_int { +/// # use super::*; +/// # +/// pub(super) fn to_output(v: &StringOrInt) -> Value +/// where +/// S: ScalarValue, +/// { +/// match v { +/// StringOrInt::String(str) => Value::scalar(str.to_owned()), +/// StringOrInt::Int(i) => Value::scalar(*i), +/// } +/// } +/// +/// pub(super) fn from_input(v: &InputValue) -> Result +/// where +/// S: ScalarValue, +/// { +/// v.as_string_value() +/// .map(|s| StringOrInt::String(s.to_owned())) +/// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// } +/// } +/// # +/// # fn main() {} +/// ``` +/// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[proc_macro_error] #[proc_macro_derive(GraphQLScalar, attributes(graphql))] From f063728e45fa03f4d837afbdd4f95cb467ae0f24 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 2 Feb 2022 11:17:43 +0300 Subject: [PATCH 074/122] CHANGELOG --- docs/book/content/types/scalars.md | 2 ++ juniper/CHANGELOG.md | 1 + juniper/src/value/scalar.rs | 3 ++- juniper_codegen/src/graphql_scalar/derive.rs | 10 +++++----- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 366e4c8b6..fce29fce1 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -276,6 +276,8 @@ mod string_or_int { # fn main() {} ``` +--- + ### `#[graphql_scalar]` attribute For more complex situations where you also need custom parsing or validation, diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 36fb9a1f1..c7b019808 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -28,6 +28,7 @@ - Rename `resolve` method to `to_output`. - Rename `from_input_value` method to `from_input`. - Rename `from_str` method to `parse_token`. +- Split `#[derive(GraphQLScalarValue)]` into `#[derive(GraphQLScalar)]` and `#[derive(GraphQLScalarValue)]` macros: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) ## Features diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index abf36f97b..47743e822 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -13,13 +13,14 @@ pub trait ParseScalarValue { fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; } +// TODO: revisit this doc, once `GraphQLScalarValue` macro is re-implemented. /// A trait marking a type that could be used as internal representation of /// scalar values in juniper /// /// The main objective of this abstraction is to allow other libraries to /// replace the default representation with something that better fits their /// needs. -/// There is a custom derive (`#[derive(juniper::GraphQLScalar)]`) available that implements +/// There is a custom derive (`#[derive(juniper::GraphQLScalarValue)]`) available that implements /// most of the required traits automatically for a enum representing a scalar value. /// However, [`Serialize`](trait@serde::Serialize) and [`Deserialize`](trait@serde::Deserialize) /// implementations are expected to be provided. diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index 610a4c22d..ce1517993 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -295,7 +295,7 @@ impl Parse for Attr { .replace(SpanContainer::new( ident.span(), Some(span), - ParseToken::Delegate(parsed_types), + ParseToken::Delegated(parsed_types), )) .none_or_else(|_| err::dup_arg(&ident))? } @@ -403,7 +403,7 @@ impl ToTokens for Definition { self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); self.impl_graphql_scalar_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); } } @@ -690,7 +690,7 @@ impl Definition { /// [`BaseType`]: juniper::macros::reflection::BaseType /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + fn impl_reflection_traits_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; let name = &self.name; @@ -972,7 +972,7 @@ enum ParseToken { /// first success. /// /// [`ParseScalarValue`]: juniper::ParseScalarValue - Delegate(Vec), + Delegated(Vec), } impl ParseToken { @@ -984,7 +984,7 @@ impl ParseToken { ParseToken::Custom(parse_token) => { quote! { #parse_token(token) } } - ParseToken::Delegate(delegated) => delegated + ParseToken::Delegated(delegated) => delegated .iter() .fold(None, |acc, ty| { acc.map_or_else( From 10bea73ba80ff9e67426ec6ccfc2d186b39a1ab4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 3 Feb 2022 13:21:00 +0300 Subject: [PATCH 075/122] Implement `GraphQLScalarValue` derive macro --- .../src/codegen/derive_scalar_value.rs | 105 ++++ .../juniper_tests/src/codegen/mod.rs | 1 + .../juniper_tests/src/custom_scalar.rs | 198 +------- juniper/src/lib.rs | 3 +- juniper/src/value/scalar.rs | 393 +-------------- juniper_codegen/Cargo.toml | 1 + juniper_codegen/src/derive_scalar_value.rs | 450 ++++++++++++++++++ juniper_codegen/src/lib.rs | 111 ++++- juniper_codegen/src/result.rs | 3 + 9 files changed, 705 insertions(+), 560 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs create mode 100644 juniper_codegen/src/derive_scalar_value.rs diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs new file mode 100644 index 000000000..17a810fc7 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs @@ -0,0 +1,105 @@ +use juniper::{DefaultScalarValue, GraphQLScalarValue, ScalarValue as _}; +use serde::{Deserialize, Serialize}; + +mod trivial { + use super::*; + + #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] + #[serde(untagged)] + pub enum ScalarValue { + #[graphql(as_int, as_float)] + Int(i32), + #[graphql(as_float)] + Float(f64), + #[graphql(as_str, as_string, into_string)] + String(String), + #[graphql(as_boolean)] + Boolean(bool), + } + + #[test] + fn into_another() { + assert!(ScalarValue::from(5) + .into_another::() + .is_type::()); + assert!(ScalarValue::from(0.5_f64) + .into_another::() + .is_type::()); + assert!(ScalarValue::from("str".to_owned()) + .into_another::() + .is_type::()); + assert!(ScalarValue::from(true) + .into_another::() + .is_type::()); + } +} + +mod named_fields { + use super::*; + + #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] + #[serde(untagged)] + pub enum ScalarValue { + #[graphql(as_int, as_float)] + Int { int: i32 }, + #[graphql(as_float)] + Float(f64), + #[graphql(as_str, as_string, into_string)] + String(String), + #[graphql(as_boolean)] + Boolean { v: bool }, + } + + #[test] + fn into_another() { + assert!(ScalarValue::from(5) + .into_another::() + .is_type::()); + assert!(ScalarValue::from(0.5_f64) + .into_another::() + .is_type::()); + assert!(ScalarValue::from("str".to_owned()) + .into_another::() + .is_type::()); + assert!(ScalarValue::from(true) + .into_another::() + .is_type::()); + } +} + +mod custom_fn { + use super::*; + + #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] + #[serde(untagged)] + pub enum ScalarValue { + #[graphql(as_int, as_float)] + Int(i32), + #[graphql(as_float)] + Float(f64), + #[graphql( + as_str, + as_string = str::to_owned, + into_string = std::convert::identity, + )] + String(String), + #[graphql(as_boolean)] + Boolean(bool), + } + + #[test] + fn into_another() { + assert!(ScalarValue::from(5) + .into_another::() + .is_type::()); + assert!(ScalarValue::from(0.5_f64) + .into_another::() + .is_type::()); + assert!(ScalarValue::from("str".to_owned()) + .into_another::() + .is_type::()); + assert!(ScalarValue::from(true) + .into_another::() + .is_type::()); + } +} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 6348a66c8..aacc02f62 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -2,6 +2,7 @@ mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; mod derive_scalar; +mod derive_scalar_value; mod impl_scalar; mod interface_attr; mod object_attr; diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 910acf849..fdbd2227f 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -6,208 +6,24 @@ use juniper::{ graphql_vars, parser::{ParseError, ScalarToken, Token}, serde::{de, Deserialize, Deserializer, Serialize}, - EmptyMutation, FieldResult, GraphQLScalar, InputValue, Object, ParseScalarResult, RootNode, - ScalarValue, Value, Variables, + EmptyMutation, FieldResult, GraphQLScalar, GraphQLScalarValue, InputValue, Object, + ParseScalarResult, RootNode, Value, Variables, }; -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] #[serde(untagged)] pub(crate) enum MyScalarValue { + #[graphql(as_int, as_float)] Int(i32), Long(i64), + #[graphql(as_float)] Float(f64), + #[graphql(as_string, into_string, as_str)] String(String), + #[graphql(as_boolean)] Boolean(bool), } -// TODO: replace all underlying `From` impls with `GraphQLScalarValue` macro. -impl From for MyScalarValue { - fn from(v: i32) -> Self { - Self::Int(v) - } -} - -impl From for Option { - fn from(v: MyScalarValue) -> Self { - if let MyScalarValue::Int(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a MyScalarValue> for Option<&'a i32> { - fn from(v: &'a MyScalarValue) -> Self { - if let MyScalarValue::Int(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for MyScalarValue { - fn from(v: i64) -> Self { - Self::Long(v) - } -} - -impl From for Option { - fn from(v: MyScalarValue) -> Self { - if let MyScalarValue::Long(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a MyScalarValue> for Option<&'a i64> { - fn from(v: &'a MyScalarValue) -> Self { - if let MyScalarValue::Long(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for MyScalarValue { - fn from(v: f64) -> Self { - Self::Float(v) - } -} - -impl From for Option { - fn from(v: MyScalarValue) -> Self { - if let MyScalarValue::Float(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a MyScalarValue> for Option<&'a f64> { - fn from(v: &'a MyScalarValue) -> Self { - if let MyScalarValue::Float(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for MyScalarValue { - fn from(v: String) -> Self { - Self::String(v) - } -} - -impl From for Option { - fn from(v: MyScalarValue) -> Self { - if let MyScalarValue::String(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a MyScalarValue> for Option<&'a String> { - fn from(v: &'a MyScalarValue) -> Self { - if let MyScalarValue::String(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for MyScalarValue { - fn from(v: bool) -> Self { - Self::Boolean(v) - } -} - -impl From for Option { - fn from(v: MyScalarValue) -> Self { - if let MyScalarValue::Boolean(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a MyScalarValue> for Option<&'a bool> { - fn from(v: &'a MyScalarValue) -> Self { - if let MyScalarValue::Boolean(v) = v { - Some(v) - } else { - None - } - } -} - -impl fmt::Display for MyScalarValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Int(v) => v.fmt(f), - Self::Long(v) => v.fmt(f), - Self::Float(v) => v.fmt(f), - Self::String(v) => v.fmt(f), - Self::Boolean(v) => v.fmt(f), - } - } -} - -impl ScalarValue for MyScalarValue { - fn as_int(&self) -> Option { - match self { - Self::Int(i) => Some(*i), - _ => None, - } - } - - fn as_string(&self) -> Option { - match self { - Self::String(s) => Some(s.clone()), - _ => None, - } - } - - fn into_string(self) -> Option { - match self { - Self::String(s) => Some(s), - _ => None, - } - } - - fn as_str(&self) -> Option<&str> { - match self { - Self::String(s) => Some(s.as_str()), - _ => None, - } - } - - fn as_float(&self) -> Option { - match self { - Self::Int(i) => Some(f64::from(*i)), - Self::Float(f) => Some(*f), - _ => None, - } - } - - fn as_boolean(&self) -> Option { - match self { - Self::Boolean(b) => Some(*b), - _ => None, - } - } -} - impl<'de> Deserialize<'de> for MyScalarValue { fn deserialize>(de: D) -> Result { struct Visitor; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 95ccb8ffb..76f6e66b8 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,7 +115,8 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // functionality automatically. pub use juniper_codegen::{ graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalar, GraphQLUnion, + GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalar, GraphQLScalarValue, + GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 47743e822..899b9507a 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -2,7 +2,10 @@ use std::{borrow::Cow, fmt}; use serde::{de::DeserializeOwned, Serialize}; -use crate::parser::{ParseError, ScalarToken}; +use crate::{ + parser::{ParseError, ScalarToken}, + GraphQLScalarValue, +}; /// The result of converting a string into a scalar value pub type ParseScalarResult<'a, S = DefaultScalarValue> = Result>; @@ -13,226 +16,44 @@ pub trait ParseScalarValue { fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; } -// TODO: revisit this doc, once `GraphQLScalarValue` macro is re-implemented. /// A trait marking a type that could be used as internal representation of /// scalar values in juniper /// /// The main objective of this abstraction is to allow other libraries to /// replace the default representation with something that better fits their /// needs. -/// There is a custom derive (`#[derive(juniper::GraphQLScalarValue)]`) available that implements -/// most of the required traits automatically for a enum representing a scalar value. -/// However, [`Serialize`](trait@serde::Serialize) and [`Deserialize`](trait@serde::Deserialize) +/// There is a custom derive (`#[derive(juniper::`[`GraphQLScalarValue`]`)]`) +/// available that implements most of the required traits automatically for a +/// enum representing a scalar value. However, [`Serialize`] and [`Deserialize`] /// implementations are expected to be provided. /// /// # Implementing a new scalar value representation /// The preferred way to define a new scalar value representation is -/// defining a enum containing a variant for each type that needs to be represented -/// at the lowest level. -/// The following example introduces an new variant that is able to store 64 bit integers. +/// defining a enum containing a variant for each type that needs to be +/// represented at the lowest level. +/// The following example introduces an new variant that is able to store 64 bit +/// integers. /// /// ```rust /// # use std::{fmt, convert::TryInto as _}; +/// # /// # use serde::{de, Deserialize, Deserializer, Serialize}; -/// # use juniper::ScalarValue; +/// # use juniper::{ScalarValue, GraphQLScalarValue}; /// # -/// #[derive(Clone, Debug, PartialEq, Serialize)] +/// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] /// #[serde(untagged)] /// enum MyScalarValue { +/// #[graphql(as_int, as_float)] /// Int(i32), /// Long(i64), +/// #[graphql(as_float)] /// Float(f64), +/// #[graphql(as_string, into_string, as_str)] /// String(String), +/// #[graphql(as_boolean)] /// Boolean(bool), /// } /// -/// impl From for MyScalarValue { -/// fn from(v: i32) -> Self { -/// Self::Int(v) -/// } -/// } -/// -/// impl From for Option { -/// fn from(v: MyScalarValue) -> Self { -/// if let MyScalarValue::Int(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl<'a> From<&'a MyScalarValue> for Option<&'a i32> { -/// fn from(v: &'a MyScalarValue) -> Self { -/// if let MyScalarValue::Int(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl From for MyScalarValue { -/// fn from(v: i64) -> Self { -/// Self::Long(v) -/// } -/// } -/// -/// impl From for Option { -/// fn from(v: MyScalarValue) -> Self { -/// if let MyScalarValue::Long(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl<'a> From<&'a MyScalarValue> for Option<&'a i64> { -/// fn from(v: &'a MyScalarValue) -> Self { -/// if let MyScalarValue::Long(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl From for MyScalarValue { -/// fn from(v: f64) -> Self { -/// Self::Float(v) -/// } -/// } -/// -/// impl From for Option { -/// fn from(v: MyScalarValue) -> Self { -/// if let MyScalarValue::Float(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl<'a> From<&'a MyScalarValue> for Option<&'a f64> { -/// fn from(v: &'a MyScalarValue) -> Self { -/// if let MyScalarValue::Float(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl From for MyScalarValue { -/// fn from(v: String) -> Self { -/// Self::String(v) -/// } -/// } -/// -/// impl From for Option { -/// fn from(v: MyScalarValue) -> Self { -/// if let MyScalarValue::String(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl<'a> From<&'a MyScalarValue> for Option<&'a String> { -/// fn from(v: &'a MyScalarValue) -> Self { -/// if let MyScalarValue::String(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl From for MyScalarValue { -/// fn from(v: bool) -> Self { -/// Self::Boolean(v) -/// } -/// } -/// -/// impl From for Option { -/// fn from(v: MyScalarValue) -> Self { -/// if let MyScalarValue::Boolean(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl<'a> From<&'a MyScalarValue> for Option<&'a bool> { -/// fn from(v: &'a MyScalarValue) -> Self { -/// if let MyScalarValue::Boolean(v) = v { -/// Some(v) -/// } else { -/// None -/// } -/// } -/// } -/// -/// impl fmt::Display for MyScalarValue { -/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -/// match self { -/// Self::Int(v) => v.fmt(f), -/// Self::Long(v) => v.fmt(f), -/// Self::Float(v) => v.fmt(f), -/// Self::String(v) => v.fmt(f), -/// Self::Boolean(v) => v.fmt(f), -/// } -/// } -/// } -/// -/// impl ScalarValue for MyScalarValue { -/// fn as_int(&self) -> Option { -/// match self { -/// Self::Int(i) => Some(*i), -/// _ => None, -/// } -/// } -/// -/// fn as_string(&self) -> Option { -/// match self { -/// Self::String(s) => Some(s.clone()), -/// _ => None, -/// } -/// } -/// -/// fn into_string(self) -> Option { -/// match self { -/// Self::String(s) => Some(s), -/// _ => None, -/// } -/// } -/// -/// fn as_str(&self) -> Option<&str> { -/// match self { -/// Self::String(s) => Some(s.as_str()), -/// _ => None, -/// } -/// } -/// -/// fn as_float(&self) -> Option { -/// match self { -/// Self::Int(i) => Some(f64::from(*i)), -/// Self::Float(f) => Some(*f), -/// _ => None, -/// } -/// } -/// -/// fn as_boolean(&self) -> Option { -/// match self { -/// Self::Boolean(b) => Some(*b), -/// _ => None, -/// } -/// } -/// } -/// /// impl<'de> Deserialize<'de> for MyScalarValue { /// fn deserialize>(de: D) -> Result { /// struct Visitor; @@ -298,6 +119,9 @@ pub trait ParseScalarValue { /// } /// } /// ``` +/// +/// [`Deserialize`]: trait@serde::Deserialize +/// [`Serialize`]: trait@serde::Serialize pub trait ScalarValue: fmt::Debug + fmt::Display @@ -406,12 +230,13 @@ pub trait ScalarValue: /// These types closely follow the [GraphQL specification][0]. /// /// [0]: https://spec.graphql.org/June2018 -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] #[serde(untagged)] pub enum DefaultScalarValue { /// [`Int` scalar][0] as a signed 32â€bit numeric nonâ€fractional value. /// /// [0]: https://spec.graphql.org/June2018/#sec-Int + #[graphql(as_int, as_float)] Int(i32), /// [`Float` scalar][0] as a signed doubleâ€precision fractional values as @@ -419,189 +244,23 @@ pub enum DefaultScalarValue { /// /// [0]: https://spec.graphql.org/June2018/#sec-Float /// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point + #[graphql(as_float)] Float(f64), /// [`String` scalar][0] as a textual data, represented as UTFâ€8 character /// sequences. /// /// [0]: https://spec.graphql.org/June2018/#sec-String + #[graphql(as_str, as_string, into_string)] String(String), /// [`Boolean` scalar][0] as a `true` or `false` value. /// /// [0]: https://spec.graphql.org/June2018/#sec-Boolean + #[graphql(as_boolean)] Boolean(bool), } -impl From for DefaultScalarValue { - fn from(v: i32) -> Self { - Self::Int(v) - } -} - -impl From for Option { - fn from(v: DefaultScalarValue) -> Self { - if let DefaultScalarValue::Int(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a DefaultScalarValue> for Option<&'a i32> { - fn from(v: &'a DefaultScalarValue) -> Self { - if let DefaultScalarValue::Int(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for DefaultScalarValue { - fn from(v: f64) -> Self { - Self::Float(v) - } -} - -impl From for Option { - fn from(v: DefaultScalarValue) -> Self { - if let DefaultScalarValue::Float(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a DefaultScalarValue> for Option<&'a f64> { - fn from(v: &'a DefaultScalarValue) -> Self { - if let DefaultScalarValue::Float(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for DefaultScalarValue { - fn from(v: String) -> Self { - Self::String(v) - } -} - -impl From for Option { - fn from(v: DefaultScalarValue) -> Self { - if let DefaultScalarValue::String(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a DefaultScalarValue> for Option<&'a String> { - fn from(v: &'a DefaultScalarValue) -> Self { - if let DefaultScalarValue::String(v) = v { - Some(v) - } else { - None - } - } -} - -impl From for DefaultScalarValue { - fn from(v: bool) -> Self { - Self::Boolean(v) - } -} - -impl From for Option { - fn from(v: DefaultScalarValue) -> Self { - if let DefaultScalarValue::Boolean(v) = v { - Some(v) - } else { - None - } - } -} - -impl<'a> From<&'a DefaultScalarValue> for Option<&'a bool> { - fn from(v: &'a DefaultScalarValue) -> Self { - if let DefaultScalarValue::Boolean(v) = v { - Some(v) - } else { - None - } - } -} - -impl fmt::Display for DefaultScalarValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Int(v) => v.fmt(f), - Self::Float(v) => v.fmt(f), - Self::String(v) => v.fmt(f), - Self::Boolean(v) => v.fmt(f), - } - } -} - -impl ScalarValue for DefaultScalarValue { - fn as_int(&self) -> Option { - match self { - Self::Int(i) => Some(*i), - _ => None, - } - } - - fn as_float(&self) -> Option { - match self { - Self::Int(i) => Some(f64::from(*i)), - Self::Float(f) => Some(*f), - _ => None, - } - } - - fn as_str(&self) -> Option<&str> { - match self { - Self::String(s) => Some(s.as_str()), - _ => None, - } - } - - fn as_string(&self) -> Option { - match self { - Self::String(s) => Some(s.clone()), - _ => None, - } - } - - fn into_string(self) -> Option { - match self { - Self::String(s) => Some(s), - _ => None, - } - } - - fn as_boolean(&self) -> Option { - match self { - Self::Boolean(b) => Some(*b), - _ => None, - } - } - - fn into_another(self) -> S { - match self { - Self::Int(i) => S::from(i), - Self::Float(f) => S::from(f), - Self::String(s) => S::from(s), - Self::Boolean(b) => S::from(b), - } - } -} - impl<'a> From<&'a str> for DefaultScalarValue { fn from(s: &'a str) -> Self { Self::String(s.into()) diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 746c54944..c1c26f50a 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -28,3 +28,4 @@ url = "2.0" derive_more = "0.99.7" futures = "0.3" juniper = { version = "0.15.7", path = "../juniper" } +serde = "1.0" diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs new file mode 100644 index 000000000..a4889bdaf --- /dev/null +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -0,0 +1,450 @@ +//! Code generation for `#[derive(GraphQLScalarValue)]` macro. +//! +use std::{collections::HashMap, convert::TryFrom}; + +use proc_macro2::{Literal, TokenStream}; +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, + token, + visit::Visit, +}; + +use crate::{ + common::parse::{attr::err, ParseBufferExt}, + util::{filter_attrs, span_container::SpanContainer}, + GraphQLScope, +}; + +/// [`GraphQLScope`] of errors for `#[derive(GraphQLScalarValue)]` macro. +const ERR: GraphQLScope = GraphQLScope::DeriveScalarValue; + +/// Expands `#[derive(GraphQLScalarValue)]` macro into generated code. +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + + let data_enum = match ast.data { + syn::Data::Enum(e) => e, + _ => return Err(ERR.custom_error(ast.span(), "can only be derived for enums")), + }; + + let mut methods = HashMap::>::new(); + for var in data_enum.variants.clone() { + let (ident, field) = (var.ident, Field::try_from(var.fields)?); + for attr in VariantAttr::from_attrs("graphql", &var.attrs)?.0 { + let (method, expr) = attr.into_inner(); + methods.entry(method).or_default().push(Variant { + ident: ident.clone(), + field: field.clone(), + expr, + }); + } + } + + Ok(Definition { + ident: ast.ident, + generics: ast.generics, + variants: data_enum.variants.into_iter().collect(), + methods, + } + .into_token_stream()) +} + +/// Possible attribute names of the `#[derive(GraphQLScalarValue)]`. +#[derive(Eq, Hash, PartialEq)] +enum Method { + /// `#[graphql(as_int)]`. + AsInt, + + /// `#[graphql(as_float)]`. + AsFloat, + + /// `#[graphql(as_str)]`. + AsStr, + + /// `#[graphql(as_string)]`. + AsString, + + /// `#[graphql(into_string)]`. + IntoString, + + /// `#[graphql(as_boolean)]`. + AsBoolean, +} + +/// Available arguments behind `#[graphql]` attribute when generating code for +/// enum variant. +#[derive(Default)] +struct VariantAttr(Vec)>>); + +impl Parse for VariantAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Vec::new(); + while !input.is_empty() { + let ident = input.parse::()?; + let method = match ident.to_string().as_str() { + "as_int" => Method::AsInt, + "as_float" => Method::AsFloat, + "as_str" => Method::AsStr, + "as_string" => Method::AsString, + "into_string" => Method::IntoString, + "as_bool" | "as_boolean" => Method::AsBoolean, + name => { + return Err(err::unknown_arg(&ident, name)); + } + }; + let expr = input + .parse::() + .ok() + .map(|_| input.parse::()) + .transpose()?; + out.push(SpanContainer::new( + ident.span(), + expr.as_ref().map(|e| e.span()), + (method, expr), + )); + input.try_parse::()?; + } + Ok(VariantAttr(out)) + } +} + +impl VariantAttr { + /// Tries to merge two [`VariantAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(mut self, mut another: Self) -> syn::Result { + let dup = another.0.iter().find(|m| self.0.contains(m)); + if let Some(dup) = dup { + Err(err::dup_arg(dup.span_ident())) + } else { + self.0.append(&mut another.0); + Ok(self) + } + } + + /// Parses [`VariantAttr`] from the given multiple `name`d + /// [`syn::Attribute`]s placed on a enum variant. + 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 the [`ScalarValue`]. +/// +/// [`ScalarValue`]: juniper::ScalarValue +struct Definition { + /// [`syn::Ident`] of the enum representing [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + ident: syn::Ident, + + /// [`syn::Generics`] of the enum representing [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + generics: syn::Generics, + + /// [`syn::Variant`]s of the enum representing [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + variants: Vec, + + /// [`Variant`]s marked with [`Method`] attribute. + methods: HashMap>, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_scalar_value_tokens().to_tokens(into); + self.impl_from_tokens().to_tokens(into); + self.impl_display_tokens().to_tokens(into); + } +} + +impl Definition { + /// Returns generated code implementing [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + fn impl_scalar_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + + let methods = [ + ( + Method::AsInt, + quote! { fn as_int(&self) -> Option }, + quote! { i32::from(*v) }, + ), + ( + Method::AsFloat, + quote! { fn as_float(&self) -> Option }, + quote! { f64::from(*v) }, + ), + ( + Method::AsStr, + quote! { fn as_str(&self) -> Option<&str> }, + quote! { std::convert::AsRef::as_ref(v) }, + ), + ( + Method::AsString, + quote! { fn as_string(&self) -> Option }, + quote! { std::string::ToString::to_string(v) }, + ), + ( + Method::IntoString, + quote! { fn into_string(self) -> Option }, + quote! { std::string::String::from(v) }, + ), + ( + Method::AsBoolean, + quote! { fn as_boolean(&self) -> Option }, + quote! { bool::from(*v) }, + ), + ]; + let methods = methods.iter().map(|(m, sig, def)| { + let arms = self.methods.get(&m).into_iter().flatten().map(|v| { + let arm = v.match_arm(); + let call = v.expr.as_ref().map_or(def.clone(), |f| quote! { #f(v) }); + quote! { #arm => Some(#call), } + }); + quote! { + #sig { + match self { + #(#arms)* + _ => None, + } + } + } + }); + + quote! { + #[automatically_derived] + impl#impl_gens ::juniper::ScalarValue for #ident#ty_gens + #where_clause + { + #(#methods)* + } + } + } + + /// Returns generated code implementing: + /// - [`From`] each variant into enum itself. + /// - [`From`] enum into [`Option`] of each variant. + /// - [`From`] enum reference into [`Option`] of each variant reference. + fn impl_from_tokens(&self) -> TokenStream { + let ty_ident = &self.ident; + let (impl_gen, ty_gen, where_clause) = self.generics.split_for_impl(); + + // We don't impose additional bounds on generic parameters, because + // `ScalarValue` itself has `'static` bound. + let mut generics = self.generics.clone(); + generics.params.push(parse_quote! { '___a }); + let (lf_impl_gen, _, _) = generics.split_for_impl(); + + self.variants + .iter() + .map(|v| { + let var_ident = &v.ident; + let field = v.fields.iter().next().unwrap(); + let var_ty = &field.ty; + let var_field = field + .ident + .as_ref() + .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); + + quote! { + #[automatically_derived] + impl#impl_gen std::convert::From<#var_ty> for #ty_ident#ty_gen + #where_clause + { + fn from(v: #var_ty) -> Self { + Self::#var_ident#var_field + } + } + + #[automatically_derived] + impl#impl_gen std::convert::From<#ty_ident#ty_gen> for Option<#var_ty> + #where_clause + { + fn from(ty: #ty_ident#ty_gen) -> Self { + if let #ty_ident::#var_ident#var_field = ty { + Some(v) + } else { + None + } + } + } + + #[automatically_derived] + impl#lf_impl_gen std::convert::From<&'___a #ty_ident#ty_gen> for Option<&'___a #var_ty> + #where_clause + { + fn from(ty: &'___a #ty_ident#ty_gen) -> Self { + if let #ty_ident::#var_ident#var_field = ty { + Some(v) + } else { + None + } + } + } + } + }) + .collect() + } + + /// Returns generated code implementing [`Display`] by matching over each + /// enum variant. + /// + /// [`Display`]: std::fmt::Display + fn impl_display_tokens(&self) -> TokenStream { + let ident = &self.ident; + + let mut generics = self.generics.clone(); + generics.make_where_clause(); + for var in &self.variants { + let var_ty = &var.fields.iter().next().unwrap().ty; + let mut check = IsVariantGeneric::new(&self.generics); + check.visit_type(var_ty); + if check.res { + generics + .where_clause + .as_mut() + .unwrap() + .predicates + .push(parse_quote! { #var_ty: std::fmt::Display }); + } + } + let (impl_gen, ty_gen, where_clause) = generics.split_for_impl(); + + let arms = self.variants.iter().map(|v| { + let var_ident = &v.ident; + let field = v.fields.iter().next().unwrap(); + let var_field = field + .ident + .as_ref() + .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); + + quote! { Self::#var_ident#var_field => std::fmt::Display::fmt(v, f), } + }); + + quote! { + impl#impl_gen std::fmt::Display for #ident#ty_gen + #where_clause + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #(#arms)* + } + } + } + } + } +} + +/// Single-[`Field`] enum variant. +#[derive(Clone)] +struct Variant { + /// [`Variant`] [`syn::Ident`]. + ident: syn::Ident, + + /// Single [`Variant`] [`Field`]. + field: Field, + + /// Optional resolver provided by [`VariantAttr`]. + expr: Option, +} + +impl Variant { + /// Returns generated code for matching over this [`Variant`]. + fn match_arm(&self) -> TokenStream { + let (ident, field) = (&self.ident, &self.field.match_arg()); + quote! { + Self::#ident#field + } + } +} + +/// Enum [`Variant`] field. +#[derive(Clone)] +enum Field { + /// Named [`Field`]. + Named(syn::Field), + + /// Unnamed [`Field`]. + Unnamed(syn::Field), +} + +impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Field::Named(f) => f.ident.to_tokens(tokens), + Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + } + } +} + +impl TryFrom for Field { + type Error = syn::Error; + + fn try_from(value: syn::Fields) -> Result { + match value { + syn::Fields::Named(mut f) if f.named.len() == 1 => { + Ok(Self::Named(f.named.pop().unwrap().into_value())) + } + syn::Fields::Unnamed(mut f) if f.unnamed.len() == 1 => { + Ok(Self::Unnamed(f.unnamed.pop().unwrap().into_value())) + } + _ => Err(ERR.custom_error(value.span(), "expected exactly 1 field")), + } + } +} + +impl Field { + /// Returns [`Field`] for constructing or matching over [`Variant`]. + fn match_arg(&self) -> TokenStream { + match self { + Field::Named(_) => quote! { { #self: v } }, + Field::Unnamed(_) => quote! { (v) }, + } + } +} + +/// [`Visit`]or to check whether [`Variant`] [`Field`] contains generic +/// parameters. +struct IsVariantGeneric<'a> { + /// Indicates whether [`Variant`] [`Field`] contains generic parameters. + res: bool, + + /// [`syn::Generics`] to search parameters. + generics: &'a syn::Generics, +} + +impl<'a> IsVariantGeneric<'a> { + /// Construct a new [`IsVariantGeneric`]. + fn new(generics: &'a syn::Generics) -> Self { + Self { + res: false, + generics, + } + } +} + +impl<'ast, 'gen> Visit<'ast> for IsVariantGeneric<'gen> { + fn visit_path(&mut self, path: &'ast syn::Path) { + if let Some(ident) = path.get_ident() { + let is_generic = self.generics.params.iter().any(|par| { + if let syn::GenericParam::Type(ty) = par { + ty.ident == *ident + } else { + false + } + }); + if is_generic { + self.res = true; + } + } + } +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c844059ec..c7e897237 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -111,6 +111,7 @@ mod derive_input_object; mod graphql_scalar; mod common; +mod derive_scalar_value; mod graphql_interface; mod graphql_object; mod graphql_subscription; @@ -377,12 +378,120 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[proc_macro_error] #[proc_macro_derive(GraphQLScalar, attributes(graphql))] -pub fn derive_scalar_value(input: TokenStream) -> TokenStream { +pub fn derive_scalar(input: TokenStream) -> TokenStream { graphql_scalar::derive::expand(input.into()) .unwrap_or_abort() .into() } +/// `#[derive(GraphQLScalarValue)]` macro for deriving a [`ScalarValue`] +/// implementation. +/// +/// To derive [`ScalarValue`] on enum you should mark corresponding enum +/// variants with `as_int`/`as_float`/`as_string`/`into_string`/`as_str`/ +/// `as_boolean` attributes (names correspond to [`ScalarValue`] required +/// methods). +/// +/// ```rust +/// # use std::{fmt, convert::TryInto as _}; +/// # +/// # use serde::{de, Deserialize, Deserializer, Serialize}; +/// # use juniper::{ScalarValue, GraphQLScalarValue}; +/// # +/// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] +/// #[serde(untagged)] +/// enum MyScalarValue { +/// #[graphql(as_int, as_float)] +/// Int(i32), +/// Long(i64), +/// #[graphql(as_float)] +/// Float(f64), +/// #[graphql( +/// into_string, +/// as_str, +/// as_string = String::clone, +/// // ^^^^^^^^^^^^^ You can provide custom resolvers. +/// )] +/// String(String), +/// #[graphql(as_boolean)] +/// Boolean(bool), +/// } +/// +/// impl<'de> Deserialize<'de> for MyScalarValue { +/// fn deserialize>(de: D) -> Result { +/// struct Visitor; +/// +/// impl<'de> de::Visitor<'de> for Visitor { +/// type Value = MyScalarValue; +/// +/// fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { +/// f.write_str("a valid input value") +/// } +/// +/// fn visit_bool(self, b: bool) -> Result { +/// Ok(MyScalarValue::Boolean(b)) +/// } +/// +/// fn visit_i32(self, n: i32) -> Result { +/// Ok(MyScalarValue::Int(n)) +/// } +/// +/// fn visit_i64(self, n: i64) -> Result { +/// if n <= i64::from(i32::MAX) { +/// self.visit_i32(n.try_into().unwrap()) +/// } else { +/// Ok(MyScalarValue::Long(n)) +/// } +/// } +/// +/// fn visit_u32(self, n: u32) -> Result { +/// if n <= i32::MAX as u32 { +/// self.visit_i32(n.try_into().unwrap()) +/// } else { +/// self.visit_u64(n.into()) +/// } +/// } +/// +/// fn visit_u64(self, n: u64) -> Result { +/// if n <= i64::MAX as u64 { +/// self.visit_i64(n.try_into().unwrap()) +/// } else { +/// // Browser's `JSON.stringify()` serialize all numbers +/// // having no fractional part as integers (no decimal +/// // point), so we must parse large integers as floating +/// // point, otherwise we would error on transferring large +/// // floating point numbers. +/// Ok(MyScalarValue::Float(n as f64)) +/// } +/// } +/// +/// fn visit_f64(self, f: f64) -> Result { +/// Ok(MyScalarValue::Float(f)) +/// } +/// +/// fn visit_str(self, s: &str) -> Result { +/// self.visit_string(s.into()) +/// } +/// +/// fn visit_string(self, s: String) -> Result { +/// Ok(MyScalarValue::String(s)) +/// } +/// } +/// +/// de.deserialize_any(Visitor) +/// } +/// } +/// ``` +/// +/// [`ScalarValue`]: juniper::ScalarValue +#[proc_macro_error] +#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))] +pub fn derive_scalar_value(input: TokenStream) -> TokenStream { + derive_scalar_value::expand(input.into()) + .unwrap_or_abort() + .into() +} + /// Expose GraphQL scalars /// /// The GraphQL language defines a number of built-in scalars: strings, numbers, and diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index f15e180ef..33dd2533f 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -17,6 +17,7 @@ pub enum GraphQLScope { UnionDerive, DeriveInputObject, DeriveEnum, + DeriveScalarValue, DeriveScalar, ImplScalar, } @@ -29,6 +30,7 @@ impl GraphQLScope { Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveInputObject => "#sec-Input-Objects", Self::DeriveEnum => "#sec-Enums", + Self::DeriveScalarValue => "#sec-Scalars.Built-in-Scalars", Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars", } } @@ -42,6 +44,7 @@ impl fmt::Display for GraphQLScope { Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveInputObject => "input object", Self::DeriveEnum => "enum", + Self::DeriveScalarValue => "built-in scalars", Self::DeriveScalar | Self::ImplScalar => "scalar", }; From 512f37795e78d2c9137caaa076ad9d7780945949 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 3 Feb 2022 14:10:18 +0300 Subject: [PATCH 076/122] Add `codegen_fail` tests --- .../codegen_fail/fail/scalar_value/derive_on_struct.rs | 4 ++++ .../codegen_fail/fail/scalar_value/derive_on_struct.stderr | 5 +++++ .../codegen_fail/fail/scalar_value/multiple_named_fields.rs | 6 ++++++ .../fail/scalar_value/multiple_named_fields.stderr | 5 +++++ .../fail/scalar_value/multiple_unnamed_fields.rs | 6 ++++++ .../fail/scalar_value/multiple_unnamed_fields.stderr | 5 +++++ juniper_codegen/src/derive_scalar_value.rs | 3 ++- 7 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs create mode 100644 integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.rs create mode 100644 integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.rs create mode 100644 integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs b/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs new file mode 100644 index 000000000..68b63b706 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs @@ -0,0 +1,4 @@ +#[derive(juniper::GraphQLScalarValue)] +struct ScalarValue; + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr b/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr new file mode 100644 index 000000000..8d2e85510 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr @@ -0,0 +1,5 @@ +error: GraphQL built-in scalars can only be derived for enums + --> fail/scalar_value/derive_on_struct.rs:2:1 + | +2 | struct ScalarValue; + | ^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.rs new file mode 100644 index 000000000..2704e3a7a --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.rs @@ -0,0 +1,6 @@ +#[derive(juniper::GraphQLScalarValue)] +enum ScalarValue { + Variant { first: i32, second: u64 }, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.stderr new file mode 100644 index 000000000..b328b593e --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.stderr @@ -0,0 +1,5 @@ +error: GraphQL built-in scalars expected exactly 1 field + --> fail/scalar_value/multiple_named_fields.rs:3:13 + | +3 | Variant { first: i32, second: u64 }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.rs new file mode 100644 index 000000000..427f56d56 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.rs @@ -0,0 +1,6 @@ +#[derive(juniper::GraphQLScalarValue)] +enum ScalarValue { + Variant(u32, i64), +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.stderr new file mode 100644 index 000000000..b37357841 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.stderr @@ -0,0 +1,5 @@ +error: GraphQL built-in scalars expected exactly 1 field + --> fail/scalar_value/multiple_unnamed_fields.rs:3:12 + | +3 | Variant(u32, i64), + | ^^^^^^^^^^ diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index a4889bdaf..385b5d1ba 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -279,7 +279,8 @@ impl Definition { } #[automatically_derived] - impl#lf_impl_gen std::convert::From<&'___a #ty_ident#ty_gen> for Option<&'___a #var_ty> + impl#lf_impl_gen std::convert::From<&'___a #ty_ident#ty_gen> for + Option<&'___a #var_ty> #where_clause { fn from(ty: &'___a #ty_ident#ty_gen) -> Self { From 60fcbbcec78c42fbc60c2bf9863ec55b0cba49ee Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 4 Feb 2022 10:24:29 +0300 Subject: [PATCH 077/122] WIP --- .../src/codegen/derive_scalar.rs | 93 +- .../juniper_tests/src/codegen/impl_scalar.rs | 603 ++++++-- .../juniper_tests/src/custom_scalar.rs | 22 +- .../src/executor_tests/introspection/mod.rs | 26 +- juniper/src/executor_tests/variables.rs | 28 +- juniper/src/integrations/bson.rs | 68 +- juniper/src/integrations/chrono.rs | 138 +- juniper/src/integrations/chrono_tz.rs | 31 +- juniper/src/integrations/time.rs | 265 ++-- juniper/src/integrations/url.rs | 27 +- juniper/src/integrations/uuid.rs | 32 +- juniper/src/types/scalars.rs | 117 +- juniper_codegen/src/graphql_scalar/attr.rs | 141 +- juniper_codegen/src/graphql_scalar/derive.rs | 1375 ++++++----------- juniper_codegen/src/graphql_scalar/mod.rs | 609 +++++++- 15 files changed, 1929 insertions(+), 1646 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index f77a1c004..a317271bb 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -422,6 +422,90 @@ mod multiple_delegated_parse_token { } } +mod where_attribute { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + )] + #[graphql( + parse_token = String, + where(Tz: From, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", + )] + struct CustomDateTime(DateTime); + + fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.0.to_rfc3339()) + } + + fn from_input(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } + } + + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } +} + mod generic_with_all_resolvers { use super::*; @@ -433,13 +517,10 @@ mod generic_with_all_resolvers { )] #[graphql( parse_token_with = custom_date_time::parse_token, - specified_by_url = "https://tools.ietf.org/html/rfc3339" + where(Tz: From, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", )] - struct CustomDateTime - where - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { + struct CustomDateTime { dt: DateTime, _unused: (), } diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 33d363840..0761f55ae 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -3,34 +3,41 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, - GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, - Value, + InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; -use crate::util::{schema, schema_with_scalar}; +use crate::{ + custom_scalar::MyScalarValue, + util::{schema, schema_with_scalar}, +}; -mod trivial { +mod all_custom_resolvers { use super::*; - struct Counter(i32); + struct CustomCounter(i32); - #[graphql_scalar] - impl GraphQLScalar for Counter { - type Error = String; + #[graphql_scalar( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + )] + #[graphql_scalar( + parse_token_with = parse_token, + )] + type Counter = CustomCounter; - fn to_output(&self) -> Value { - Value::scalar(self.0) - } + fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) + } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) - } + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) } struct QueryRoot; @@ -87,35 +94,39 @@ mod trivial { } } -mod renamed_trait { - use super::{GraphQLScalar as CustomGraphQLScalar, *}; +mod explicit_name { + use super::*; - struct Counter(i32); + struct CustomCounter(i32); - #[graphql_scalar] - impl CustomGraphQLScalar for Counter { - type Error = String; + #[graphql_scalar( + name = "Counter", + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + parse_token_with = parse_token, + )] + type CounterScalar = CustomCounter; - fn to_output(&self) -> Value { - Value::scalar(self.0) - } + fn to_output(v: &CounterScalar) -> Value { + Value::scalar(v.0) + } - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) + } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) - } + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) } struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn counter(value: Counter) -> Counter { + fn counter(value: CounterScalar) -> CounterScalar { value } } @@ -136,6 +147,27 @@ mod renamed_trait { ); } + #[tokio::test] + async fn no_custom_counter() { + for name in ["CustomCounter", "CustomScalar"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!(null), vec![])), + ); + } + } + #[tokio::test] async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; @@ -165,35 +197,34 @@ mod renamed_trait { } } -mod explicit_name { +mod delegated_parse_token { use super::*; struct CustomCounter(i32); - #[graphql_scalar(name = "Counter")] - impl GraphQLScalar for CustomCounter { - type Error = String; - - fn to_output(&self) -> Value { - Value::scalar(self.0) - } + #[graphql_scalar( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + parse_token = i32, + )] + type Counter = CustomCounter; - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } + fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) - } + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) } struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn counter(value: CustomCounter) -> CustomCounter { + fn counter(value: Counter) -> Counter { value } } @@ -215,10 +246,22 @@ mod explicit_name { } #[tokio::test] - async fn no_custom_counter() { + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { const DOC: &str = r#"{ - __type(name: "CustomCounter") { - kind + __type(name: "Counter") { + description } }"#; @@ -226,75 +269,204 @@ mod explicit_name { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!(null), vec![])), + Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } +} + +mod multiple_delegated_parse_token { + use super::*; + + enum StringOrIntScalar { + String(String), + Int(i32), + } + + #[graphql_scalar( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + parse_token(String, i32), + )] + type StringOrInt = StringOrIntScalar; + + fn to_output(v: &StringOrInt) -> Value { + match v { + StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::Int(i) => Value::scalar(*i), + } + } + + fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .map(|s| StringOrInt::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn string_or_int(value: StringOrInt) -> StringOrInt { + value + } + } #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; + async fn resolves_string() { + const DOC: &str = r#"{ stringOrInt(value: "test") }"#; let schema = schema(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), + Ok((graphql_value!({"stringOrInt": "test"}), vec![],)), ); } #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; + async fn resolves_int() { + const DOC: &str = r#"{ stringOrInt(value: 0) }"#; let schema = schema(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), + Ok((graphql_value!({"stringOrInt": 0}), vec![],)), ); } } -mod generic { +mod where_attribute { use super::*; - struct CustomDateTime(DateTime); + struct CustomDateTimeScalar(DateTime); - #[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc3339")] - impl GraphQLScalar for CustomDateTime + #[graphql_scalar( + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, + parse_token = String, + where(Tz: From + TimeZone, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", + )] + type CustomDateTime = CustomDateTimeScalar; + + fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.0.to_rfc3339()) + } + + fn from_input(v: &InputValue) -> Result, String> where S: ScalarValue, Tz: From + TimeZone, Tz::Offset: fmt::Display, { - type Error = String; + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + struct QueryRoot; - fn to_output(&self) -> Value { - Value::scalar(self.0.to_rfc3339()) + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value } + } - fn from_input(v: &InputValue) -> Result { + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } +} + +mod with_module { + use super::*; + + struct CustomDateTimeScalar(DateTime); + + #[graphql_scalar( + with = custom_date_time, + parse_token = String, + where(Tz: From + TimeZone, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", + )] + type CustomDateTime = CustomDateTimeScalar; + + mod custom_date_time { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.0.to_rfc3339()) + } + + pub(super) fn from_input(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { DateTime::parse_from_rfc3339(s) - .map(|dt| Self(dt.with_timezone(&Tz::from(Utc)))) + .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -305,7 +477,7 @@ mod generic { async fn resolves_custom_date_time() { const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -324,7 +496,7 @@ mod generic { } }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -339,25 +511,29 @@ mod generic { mod description_from_doc_comment { use super::*; - struct Counter(i32); + struct CustomCounter(i32); + + /// Description + #[graphql_scalar(with = counter, from_input_err = String)] + type Counter = CustomCounter; - /// Doc comment. - #[graphql_scalar] - impl GraphQLScalar for Counter { - type Error = String; + mod counter { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.0) + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) } } @@ -370,6 +546,22 @@ mod description_from_doc_comment { } } + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + #[tokio::test] async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; @@ -395,8 +587,8 @@ mod description_from_doc_comment { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"description": "Doc comment."}}), - vec![], + graphql_value!({"__type": {"description": "Description"}}), + vec![] )), ); } @@ -405,26 +597,33 @@ mod description_from_doc_comment { mod description_from_attribute { use super::*; - struct Counter(i32); + struct CustomCounter(i32); + + /// Doc comment + #[graphql_scalar( + description = "Description from attribute", + with = counter, + from_input_err = String + )] + type Counter = CustomCounter; - /// Doc comment. - #[graphql_scalar(desc = "Doc comment from attribute.")] - #[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")] - impl GraphQLScalar for Counter { - type Error = String; + mod counter { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.0) + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) } } @@ -437,6 +636,22 @@ mod description_from_attribute { } } + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + #[tokio::test] async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; @@ -450,11 +665,10 @@ mod description_from_attribute { } #[tokio::test] - async fn has_description_and_url() { + async fn has_description() { const DOC: &str = r#"{ __type(name: "Counter") { description - specifiedByUrl } }"#; @@ -463,41 +677,43 @@ mod description_from_attribute { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({ - "__type": { - "description": "Doc comment from attribute.", - "specifiedByUrl": "https://tools.ietf.org/html/rfc4122", - } - }), - vec![], + graphql_value!({"__type": {"description": "Description from attribute"}}), + vec![] )), ); } } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; - use super::*; - struct Counter(i32); + struct CustomCounter(i32); + + /// Description + #[graphql_scalar( + scalar = MyScalarValue, + with = counter, + from_input_err = String + )] + type Counter = CustomCounter; - #[graphql_scalar] - impl GraphQLScalar for Counter { - type Error = String; + mod counter { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.0) + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { - >::from_str(value) + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) } } @@ -539,7 +755,7 @@ mod custom_scalar { } #[tokio::test] - async fn has_no_description() { + async fn has_description() { const DOC: &str = r#"{ __type(name: "Counter") { description @@ -550,7 +766,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), ); } } @@ -558,33 +777,39 @@ mod custom_scalar { mod generic_scalar { use super::*; - struct Counter(i32); + struct CustomCounter(i32); + + /// Description + #[graphql_scalar( + scalar = S: ScalarValue, + with = counter, + from_input_err = String + )] + type Counter = CustomCounter; - #[graphql_scalar] - impl GraphQLScalar for Counter - where - S: ScalarValue, - { - type Error = String; + mod counter { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.0) + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { >::from_str(value) } } struct QueryRoot; - #[graphql_object] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -599,7 +824,7 @@ mod generic_scalar { } }"#; - let schema = schema_with_scalar::(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -611,45 +836,70 @@ mod generic_scalar { async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; - let schema = schema_with_scalar::(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"counter": 0}), vec![])), ); } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } } mod bounded_generic_scalar { use super::*; - struct Counter(i32); + struct CustomCounter(i32); - #[graphql_scalar] - impl GraphQLScalar for Counter - where - S: ScalarValue + Clone, - { - type Error = String; + /// Description + #[graphql_scalar( + scalar = S: ScalarValue + Clone, + with = counter, + from_input_err = String + )] + type Counter = CustomCounter; + + mod counter { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.0) + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(CustomCounter) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { >::from_str(value) } } struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Clone)] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -664,7 +914,7 @@ mod bounded_generic_scalar { } }"#; - let schema = schema_with_scalar::(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -676,11 +926,30 @@ mod bounded_generic_scalar { async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; - let schema = schema_with_scalar::(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"counter": 0}), vec![])), ); } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } } diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 910acf849..eea960af9 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -6,8 +6,8 @@ use juniper::{ graphql_vars, parser::{ParseError, ScalarToken, Token}, serde::{de, Deserialize, Deserializer, Serialize}, - EmptyMutation, FieldResult, GraphQLScalar, InputValue, Object, ParseScalarResult, RootNode, - ScalarValue, Value, Variables, + EmptyMutation, FieldResult, InputValue, Object, ParseScalarResult, RootNode, ScalarValue, + Value, Variables, }; #[derive(Clone, Debug, PartialEq, Serialize)] @@ -275,21 +275,25 @@ impl<'de> Deserialize<'de> for MyScalarValue { } } -#[graphql_scalar(name = "Long")] -impl GraphQLScalar for i64 { - type Error = String; +#[graphql_scalar(with = long, scalar = MyScalarValue)] +type Long = i64; - fn to_output(&self) -> Value { - Value::scalar(*self) +mod long { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &Long) -> Value { + Value::scalar(*v) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value::() .copied() .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index a4561ceea..59b335a5a 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -6,11 +6,10 @@ mod input_object; use self::input_object::{NamedPublic, NamedPublicWithDescription}; use crate::{ - graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars, + graphql_interface, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::{ParseScalarResult, ParseScalarValue, Value}, - GraphQLEnum, GraphQLScalar, InputValue, ScalarToken, ScalarValue, + GraphQLEnum, GraphQLScalar, InputValue, ScalarValue, Value, }; #[derive(GraphQLEnum)] @@ -20,25 +19,24 @@ enum Sample { Two, } +#[derive(GraphQLScalar)] +#[graphql(name = "SampleScalar", with = scalar, parse_token = i32)] struct Scalar(i32); -#[graphql_scalar(name = "SampleScalar")] -impl GraphQLScalar for Scalar { - type Error = String; +mod scalar { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.0) + pub(super) type Error = String; + + pub(super) fn to_output(v: &Scalar) -> Value { + Value::scalar(v.0) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .map(Self) + .map(Scalar) .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } } /// A sample interface diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 75c5d1596..9243c7d70 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,36 +1,36 @@ use crate::{ executor::Variables, - graphql_object, graphql_scalar, graphql_value, graphql_vars, - parser::{ScalarToken, SourcePosition}, + graphql_object, graphql_value, graphql_vars, + parser::SourcePosition, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, validation::RuleError, - value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue}, + value::{DefaultScalarValue, Object}, GraphQLError::ValidationError, GraphQLInputObject, GraphQLScalar, InputValue, ScalarValue, Value, }; -#[derive(Debug)] +#[derive(Debug, GraphQLScalar)] +#[graphql(with = test_complex_scalar, parse_token = String)] struct TestComplexScalar; -#[graphql_scalar] -impl GraphQLScalar for TestComplexScalar { - type Error = String; +mod test_complex_scalar { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { + pub(super) fn to_output(_: &TestComplexScalar) -> Value { graphql_value!("SerializedValue") } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input( + v: &InputValue, + ) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") - .map(|_| Self) + .map(|_| TestComplexScalar) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } } #[derive(GraphQLInputObject, Debug)] diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index a927aff2f..a640ec8bc 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -1,64 +1,58 @@ //! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types. -use bson::{oid::ObjectId, DateTime as UtcDateTime}; use chrono::prelude::*; -use crate::{ - graphql_scalar, - parser::{ParseError, Token}, - value::ParseScalarResult, - GraphQLScalar, InputValue, ScalarToken, ScalarValue, Value, -}; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar(description = "ObjectId")] -impl GraphQLScalar for ObjectId { - type Error = String; +#[graphql_scalar( + description = "ObjectId", + with = object_id, + parse_token = String, +)] +type ObjectId = bson::oid::ObjectId; - fn to_output(&self) -> Value { - Value::scalar(self.to_hex()) +mod object_id { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &ObjectId) -> Value { + Value::scalar(v.to_hex()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e)) + ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(val) = value { - Ok(S::from(val.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } -#[graphql_scalar(description = "UtcDateTime")] -impl GraphQLScalar for UtcDateTime { - type Error = String; +#[graphql_scalar( + description = "UtcDateTime", + with = utc_date_time, + parse_token = String, +)] +type UtcDateTime = bson::DateTime; - fn to_output(&self) -> Value { - Value::scalar((*self).to_chrono().to_rfc3339()) +mod utc_date_time { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &UtcDateTime) -> Value { + Value::scalar((*v).to_chrono().to_rfc3339()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { s.parse::>() .map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e)) }) - .map(Self::from_chrono) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(val) = value { - Ok(S::from(val.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } + .map(UtcDateTime::from_chrono) } } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index c69796e2e..c2ea59fdb 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -16,64 +16,58 @@ */ #![allow(clippy::needless_lifetimes)] -use chrono::prelude::*; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -use crate::{ - parser::{ParseError, ScalarToken, Token}, - value::{ParseScalarResult, ParseScalarValue}, - GraphQLScalar, InputValue, ScalarValue, Value, -}; +#[graphql_scalar( + with = date_time_fixed_offset, + parse_token = String, +)] +type DateTimeFixedOffset = chrono::DateTime; -#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")] -impl GraphQLScalar for DateTime { - type Error = String; +mod date_time_fixed_offset { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.to_rfc3339()) + pub(super) type Error = String; + + pub(super) fn to_output(v: &DateTimeFixedOffset) -> Value { + Value::scalar(v.to_rfc3339()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input( + v: &InputValue, + ) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - DateTime::parse_from_rfc3339(s) + DateTimeFixedOffset::parse_from_rfc3339(s) .map_err(|e| format!("Failed to parse `DateTimeFixedOffset`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } -#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")] -impl GraphQLScalar for DateTime { - type Error = String; +#[graphql_scalar( + with = date_time_utc, + parse_token = String +)] +type DateTimeUtc = chrono::DateTime; + +mod date_time_utc { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { - Value::scalar(self.to_rfc3339()) + pub(super) fn to_output(v: &DateTimeUtc) -> Value { + Value::scalar(v.to_rfc3339()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - s.parse::>() + s.parse::() .map_err(|e| format!("Failed to parse `DateTimeUtc`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } // Don't use `Date` as the docs say: @@ -81,15 +75,22 @@ impl GraphQLScalar for DateTime { // inherent lack of precision required for the time zone resolution. // For serialization and deserialization uses, it is best to use // `NaiveDate` instead." -#[crate::graphql_scalar(description = "NaiveDate")] -impl GraphQLScalar for NaiveDate { - type Error = String; +#[graphql_scalar( + with = naive_date, + parse_token = String, +)] +type NaiveDate = chrono::NaiveDate; - fn to_output(&self) -> Value { - Value::scalar(self.format("%Y-%m-%d").to_string()) +mod naive_date { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &NaiveDate) -> Value { + Value::scalar(v.format("%Y-%m-%d").to_string()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -97,26 +98,23 @@ impl GraphQLScalar for NaiveDate { .map_err(|e| format!("Failed to parse `NaiveDate`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } #[cfg(feature = "scalar-naivetime")] -#[crate::graphql_scalar(description = "NaiveTime")] -impl GraphQLScalar for NaiveTime { - type Error = String; +#[graphql_scalar(with = naive_time, parse_token = String)] +type NaiveTime = chrono::NaiveTime; + +#[cfg(feature = "scalar-naivetime")] +mod naive_time { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { - Value::scalar(self.format("%H:%M:%S").to_string()) + pub(super) fn to_output(v: &NaiveTime) -> Value { + Value::scalar(v.format("%H:%M:%S").to_string()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -124,27 +122,23 @@ impl GraphQLScalar for NaiveTime { .map_err(|e| format!("Failed to parse `NaiveTime`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } // JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond // datetimes. Values will be truncated to microsecond resolution. -#[crate::graphql_scalar(description = "NaiveDateTime")] -impl GraphQLScalar for NaiveDateTime { - type Error = String; +#[graphql_scalar(with = naive_date_time, parse_token = f64)] +type NaiveDateTime = chrono::NaiveDateTime; + +mod naive_date_time { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.timestamp() as f64) + pub(super) type Error = String; + + pub(super) fn to_output(v: &NaiveDateTime) -> Value { + Value::scalar(v.timestamp() as f64) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) .and_then(|f| { @@ -153,10 +147,6 @@ impl GraphQLScalar for NaiveDateTime { .ok_or_else(|| format!("Out-of-range number of seconds: {}", secs)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } } #[cfg(test)] diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 7d91ecf8c..25b8bbb30 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -3,24 +3,21 @@ //! [`Tz`]: chrono_tz::Tz //! [1]: http://www.iana.org/time-zones -use chrono_tz::Tz; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -use crate::{ - graphql_scalar, - parser::{ParseError, ScalarToken, Token}, - value::ParseScalarResult, - GraphQLScalar, InputValue, ScalarValue, Value, -}; +#[graphql_scalar(description = "Timezone", with = tz, parse_token = String)] +type Tz = chrono_tz::Tz; -#[graphql_scalar(name = "Tz", description = "Timezone")] -impl GraphQLScalar for Tz { - type Error = String; +mod tz { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.name().to_owned()) + pub(super) type Error = String; + + pub(super) fn to_output(v: &Tz) -> Value { + Value::scalar(v.name().to_owned()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -28,14 +25,6 @@ impl GraphQLScalar for Tz { .map_err(|e| format!("Failed to parse `Tz`: {}", e)) }) } - - fn parse_token(val: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(s) = val { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(val))) - } - } } #[cfg(test)] diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index f45173523..50115366c 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -26,61 +26,47 @@ use time::{ macros::format_description, }; -use crate::{ - graphql_scalar, - parser::{ParseError, ScalarToken, Token}, - value::ParseScalarResult, - GraphQLScalar, InputValue, ScalarValue, Value, -}; - -pub use time::{ - Date, OffsetDateTime as DateTime, PrimitiveDateTime as LocalDateTime, Time as LocalTime, - UtcOffset, -}; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// Format of a [`Date` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]"); +/// Date in the proleptic Gregorian calendar (without time zone). +/// +/// Represents a description of the date (as used for birthdays, for example). +/// It cannot represent an instant on the time-line. +/// +/// [`Date` scalar][1] compliant. +/// +/// See also [`time::Date`][2] for details. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/date +/// [2]: https://docs.rs/time/*/time/struct.Date.html #[graphql_scalar( - description = "Date in the proleptic Gregorian calendar (without time \ - zone).\ - \n\n\ - Represents a description of the date (as used for birthdays, - for example). It cannot represent an instant on the \ - time-line.\ - \n\n\ - [`Date` scalar][1] compliant.\ - \n\n\ - See also [`time::Date`][2] for details.\ - \n\n\ - [1]: https://graphql-scalars.dev/docs/scalars/date\n\ - [2]: https://docs.rs/time/*/time/struct.Date.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" + with = date, + parse_token = String, + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", )] -impl GraphQLScalar for Date { - type Error = String; +pub type Date = time::Date; - fn to_output(&self) -> Value { +mod date { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &Date) -> Value { Value::scalar( - self.format(DATE_FORMAT) + v.format(DATE_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)), ) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } + .and_then(|s| Date::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) } } @@ -101,143 +87,126 @@ const LOCAL_TIME_FORMAT_NO_MILLIS: &[FormatItem<'_>] = /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour]:[minute]"); -#[graphql_scalar( - description = "Clock time within a given date (without time zone) in \ - `HH:mm[:ss[.SSS]]` format.\ - \n\n\ - All minutes are assumed to have exactly 60 seconds; no \ - attempt is made to handle leap seconds (either positive or \ - negative).\ - \n\n\ - [`LocalTime` scalar][1] compliant.\ - \n\n\ - See also [`time::Time`][2] for details.\ - \n\n\ - [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\ - [2]: https://docs.rs/time/*/time/struct.Time.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" -)] -impl GraphQLScalar for LocalTime { - type Error = String; +/// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]` +/// format. +/// +/// All minutes are assumed to have exactly 60 seconds; no attempt is made to +/// handle leap seconds (either positive or negative). +/// +/// [`LocalTime` scalar][1] compliant. +/// +/// See also [`time::Time`][2] for details. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/local-time +/// [2]: https://docs.rs/time/*/time/struct.Time.html +#[graphql_scalar(with = local_time, parse_token = String)] +pub type LocalTime = time::Time; + +mod local_time { + use super::*; - fn to_output(&self) -> Value { + pub(super) type Error = String; + + pub(super) fn to_output(v: &LocalTime) -> Value { Value::scalar( - if self.millisecond() == 0 { - self.format(LOCAL_TIME_FORMAT_NO_MILLIS) + if v.millisecond() == 0 { + v.format(LOCAL_TIME_FORMAT_NO_MILLIS) } else { - self.format(LOCAL_TIME_FORMAT) + v.format(LOCAL_TIME_FORMAT) } .unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {}", e)), ) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing // error to be most informative. - Self::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS) - .or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT_NO_SECS)) - .or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT)) + LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS) + .or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_SECS)) + .or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT)) .map_err(|e| format!("Invalid `LocalTime`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } /// Format of a [`LocalDateTime`] scalar. const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); -#[graphql_scalar( - description = "Combined date and time (without time zone) in `yyyy-MM-dd \ - HH:mm:ss` format.\ - \n\n\ - See also [`time::PrimitiveDateTime`][2] for details.\ - \n\n\ - [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html" -)] -impl GraphQLScalar for LocalDateTime { - type Error = String; +/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format. +/// +/// See also [`time::PrimitiveDateTime`][2] for details. +/// +/// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html +#[graphql_scalar(with = local_date_time, parse_token = String)] +pub type LocalDateTime = time::PrimitiveDateTime; + +mod local_date_time { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { + pub(super) fn to_output(v: &LocalDateTime) -> Value { Value::scalar( - self.format(LOCAL_DATE_TIME_FORMAT) + v.format(LOCAL_DATE_TIME_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)), ) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse(s, LOCAL_DATE_TIME_FORMAT) + LocalDateTime::parse(s, LOCAL_DATE_TIME_FORMAT) .map_err(|e| format!("Invalid `LocalDateTime`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } +/// Combined date and time (with time zone) in [RFC 3339][0] format. +/// +/// Represents a description of an exact instant on the time-line (such as the +/// instant that a user account was created). +/// +/// [`DateTime` scalar][1] compliant. +/// +/// See also [`time::OffsetDateTime`][2] for details. +/// +/// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 +/// [1]: https://graphql-scalars.dev/docs/scalars/date-time +/// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html #[graphql_scalar( - description = "Combined date and time (with time zone) in [RFC 3339][0] \ - format.\ - \n\n\ - Represents a description of an exact instant on the \ - time-line (such as the instant that a user account was \ - created).\ - \n\n\ - [`DateTime` scalar][1] compliant.\ - \n\n\ - See also [`time::OffsetDateTime`][2] for details.\ - \n\n\ - [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ - [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ - [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" + with = date_time, + parse_token = String, + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", )] -impl GraphQLScalar for DateTime { - type Error = String; +pub type DateTime = time::OffsetDateTime; + +mod date_time { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { + pub(super) fn to_output(v: &DateTime) -> Value { Value::scalar( - self.to_offset(UtcOffset::UTC) + v.to_offset(UtcOffset::UTC) .format(&Rfc3339) .unwrap_or_else(|e| panic!("Failed to format `DateTime`: {}", e)), ) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e)) + DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e)) }) .map(|dt| dt.to_offset(UtcOffset::UTC)) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } /// Format of a [`UtcOffset` scalar][1]. @@ -246,44 +215,42 @@ impl GraphQLScalar for DateTime { const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = format_description!("[offset_hour sign:mandatory]:[offset_minute]"); +/// Offset from UTC in `±hh:mm` format. See [list of database time zones][0]. +/// +/// [`UtcOffset` scalar][1] compliant. +/// +/// See also [`time::UtcOffset`][2] for details. +/// +/// [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +/// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset +/// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html #[graphql_scalar( - description = "Offset from UTC in `±hh:mm` format. See [list of database \ - time zones][0].\ - \n\n\ - [`UtcOffset` scalar][1] compliant.\ - \n\n\ - See also [`time::UtcOffset`][2] for details.\ - \n\n\ - [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\ - [1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\ - [2]: https://docs.rs/time/*/time/struct.UtcOffset.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset" + with = utc_offset, + parse_token = String, + specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", )] -impl GraphQLScalar for UtcOffset { - type Error = String; +pub type UtcOffset = time::UtcOffset; + +mod utc_offset { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { + pub(super) fn to_output(v: &UtcOffset) -> Value { Value::scalar( - self.format(UTC_OFFSET_FORMAT) + v.format(UTC_OFFSET_FORMAT) .unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)), ) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse(s, UTC_OFFSET_FORMAT).map_err(|e| format!("Invalid `UtcOffset`: {}", e)) + UtcOffset::parse(s, UTC_OFFSET_FORMAT) + .map_err(|e| format!("Invalid `UtcOffset`: {}", e)) }) } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } #[cfg(test)] diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 74445d609..aa585d642 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -1,28 +1,23 @@ //! GraphQL support for [url](https://github.com/servo/rust-url) types. -use url::Url; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -use crate::{ - value::{ParseScalarResult, ParseScalarValue}, - GraphQLScalar, InputValue, ScalarToken, ScalarValue, Value, -}; +#[graphql_scalar(with = url_scalar, parse_token = String)] +type Url = url::Url; -#[crate::graphql_scalar(description = "Url")] -impl GraphQLScalar for Url { - type Error = String; +mod url_scalar { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.as_str().to_owned()) + pub(super) type Error = String; + + pub(super) fn to_output(v: &Url) -> Value { + Value::scalar(v.as_str().to_owned()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Self::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) } } diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 0a255a749..5e7289486 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -2,34 +2,24 @@ #![allow(clippy::needless_lifetimes)] -use uuid::Uuid; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -use crate::{ - parser::{ParseError, ScalarToken, Token}, - value::ParseScalarResult, - GraphQLScalar, InputValue, ScalarValue, Value, -}; +#[graphql_scalar(with = uuid_scalar, parse_token = String)] +type Uuid = uuid::Uuid; -#[crate::graphql_scalar(description = "Uuid")] -impl GraphQLScalar for Uuid { - type Error = String; +mod uuid_scalar { + use super::*; - fn to_output(&self) -> Value { - Value::scalar(self.to_string()) + pub(super) type Error = String; + + pub(super) fn to_output(v: &Uuid) -> Value { + Value::scalar(v.to_string()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| Self::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - if let ScalarToken::String(value) = value { - Ok(S::from(value.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } + .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 24512ba0b..0468d2808 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, + graphql_scalar, macros::reflect, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, @@ -22,9 +23,28 @@ use crate::{ /// An ID as defined by the GraphQL specification /// /// Represented as a string, but can be converted _to_ from an integer as well. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, GraphQLScalar, PartialEq, Serialize, Deserialize)] +#[graphql(with = id, parse_token(String, i32))] pub struct ID(String); +mod id { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &ID) -> Value { + Value::scalar(v.0.clone()) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .map(str::to_owned) + .or_else(|| v.as_int_value().map(|i| i.to_string())) + .map(ID) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } +} + impl From for ID { fn from(s: String) -> ID { ID(s) @@ -52,45 +72,25 @@ impl fmt::Display for ID { } } -#[crate::graphql_scalar(name = "ID")] -impl GraphQLScalar for ID { - type Error = String; - - fn to_output(&self) -> Value { - Value::scalar(self.0.clone()) - } - - fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .map(str::to_owned) - .or_else(|| v.as_int_value().map(|i| i.to_string())) - .map(Self) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) - } +#[graphql_scalar(with = string)] +type String = std::string::String; - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - match value { - ScalarToken::String(value) | ScalarToken::Int(value) => Ok(S::from(value.to_owned())), - _ => Err(ParseError::UnexpectedToken(Token::Scalar(value))), - } - } -} +mod string { + use super::*; -#[crate::graphql_scalar(name = "String")] -impl GraphQLScalar for String { - type Error = String; + pub(super) type Error = String; - fn to_output(&self) -> Value { - Value::scalar(self.clone()) + pub(super) fn to_output(v: &String) -> Value { + Value::scalar(v.clone()) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .ok_or_else(|| format!("Expected `String`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::String(value) = value { let mut ret = String::with_capacity(value.len()); let mut char_iter = value.chars(); @@ -275,43 +275,48 @@ where } } -#[crate::graphql_scalar(name = "Boolean")] -impl GraphQLScalar for bool -where - S: ScalarValue, -{ - type Error = String; +#[graphql_scalar(with = boolean)] +type Boolean = bool; + +mod boolean { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { - Value::scalar(*self) + pub(super) fn to_output(v: &Boolean) -> Value { + Value::scalar(*v) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_boolean) .ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { // Bools are parsed separately - they shouldn't reach this code path Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -#[crate::graphql_scalar(name = "Int")] -impl GraphQLScalar for i32 { - type Error = String; +#[graphql_scalar(with = int)] +type Int = i32; - fn to_output(&self) -> Value { - Value::scalar(*self) +mod int { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &Int) -> Value { + Value::scalar(*v) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { if let ScalarToken::Int(v) = value { v.parse() .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) @@ -322,20 +327,24 @@ impl GraphQLScalar for i32 { } } -#[crate::graphql_scalar(name = "Float")] -impl GraphQLScalar for f64 { - type Error = String; +#[graphql_scalar(with = float)] +type Float = f64; + +mod float { + use super::*; + + pub(super) type Error = String; - fn to_output(&self) -> Value { - Value::scalar(*self) + pub(super) fn to_output(v: &Float) -> Value { + Value::scalar(*v) } - fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { match value { ScalarToken::Int(v) => v .parse() diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index c4978eb0f..6beaf54f3 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -1,109 +1,94 @@ use proc_macro2::{Span, TokenStream}; -use quote::ToTokens as _; -use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; +use quote::{quote, ToTokens}; +use syn::{parse_quote, spanned::Spanned}; use crate::{ common::{parse, scalar}, - util::span_container::SpanContainer, + graphql_scalar::TypeOrIdent, GraphQLScope, }; -use super::{Attr, Definition}; +use super::{Attr, Definition, GraphQLScalarMethods, ParseToken}; -/// [`GraphQLScope`] of errors for `#[graphql_scalar]` macro. const ERR: GraphQLScope = GraphQLScope::ImplScalar; /// Expands `#[graphql_scalar]` macro into generated code. pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body) { + if let Ok(mut ast) = syn::parse2::(body) { let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); - return expand_on_impl(attrs, ast); + return expand_on_type_alias(attrs, ast); } Err(syn::Error::new( Span::call_site(), - "#[graphql_scalar] attribute is applicable to impl trait block only", + "#[graphql_scalar] attribute is applicable to type alias only", )) } -/// Expands `#[graphql_scalar]` macro placed on an implementation block. -fn expand_on_impl(attrs: Vec, ast: syn::ItemImpl) -> syn::Result { +/// Expands `#[graphql_scalar]` macro placed on a type alias. +fn expand_on_type_alias( + attrs: Vec, + ast: syn::ItemType, +) -> syn::Result { let attr = Attr::from_attrs("graphql_scalar", &attrs)?; - let mut self_ty = ast.self_ty.clone(); - if let syn::Type::Group(group) = self_ty.as_ref() { - self_ty = group.elem.clone(); - } - - let name = attr - .name - .map(SpanContainer::into_inner) - .or_else(|| { - if let syn::Type::Path(path) = self_ty.as_ref() { - path.path - .segments - .last() - .map(|last| last.ident.unraw().to_string()) - } else { - None + let field = match ( + attr.to_output.as_deref().cloned(), + attr.from_input.as_deref().cloned(), + attr.from_input_err.as_deref().cloned(), + attr.parse_token.as_deref().cloned(), + attr.with.as_deref().cloned(), + ) { + (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token), None) => { + GraphQLScalarMethods::Custom { + to_output, + from_input: (from_input, from_input_err), + parse_token, } - }) - .ok_or_else(|| { - ERR.custom_error( - self_ty.span(), - "unable to find target for implementation target for `GraphQLScalar`", - ) - })?; - - let (_, trait_ty, _) = ast.trait_.as_ref().ok_or_else(|| { - ERR.custom_error( - ast.impl_token.span(), - "expected `GraphQLScalar` trait implementation", - ) - })?; + } + (to_output, from_input, from_input_err, parse_token, Some(module)) => { + GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: ( + from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + from_input_err.unwrap_or_else(|| parse_quote! { #module::Error }), + ), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + } + } + _ => { + return Err(ERR.custom_error( + ast.span(), + "all custom resolvers have to be provided via `with` or \ + `to_output_with`, `from_input_with`, `from_input_err_with`, + `parse_token_with` attributes", + )); + } + }; - let scalar = get_scalar(trait_ty, &ast.generics); + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - let mut out = ast.to_token_stream(); - Definition { - impl_for_type: *ast.self_ty.clone(), + let def = Definition { + ty: TypeOrIdent::Type(*ast.ty.clone()), + where_clause: attr + .where_clause + .map_or_else(|| Vec::new(), |cl| cl.into_inner()), generics: ast.generics.clone(), - name, + methods: field, + name: attr + .name + .as_deref() + .cloned() + .unwrap_or_else(|| ast.ident.to_string()), description: attr.description.as_deref().cloned(), - scalar, specified_by_url: attr.specified_by_url.as_deref().cloned(), + scalar, } - .to_tokens(&mut out); - - Ok(out) -} - -/// Extracts [`scalar::Type`] from [`GraphQLScalar`] trait. -/// -/// [`GraphQLScalar`]: juniper::GraphQLScalar -fn get_scalar(trait_ty: &syn::Path, generics: &syn::Generics) -> scalar::Type { - if let Some(last_seg) = trait_ty.segments.last() { - match &last_seg.arguments { - syn::PathArguments::AngleBracketed(gens) => { - if let Some(syn::GenericArgument::Type(ty)) = gens.args.last() { - let generic_scalar = generics - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .find(|gen_par| gen_par.to_string() == ty.to_token_stream().to_string()); - - return generic_scalar.map_or_else( - || scalar::Type::Concrete(ty.clone()), - |scalar| scalar::Type::ExplicitGeneric(scalar.clone()), - ); - } - } - syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => {} - } - } - scalar::Type::Concrete(parse_quote! { ::juniper::DefaultScalarValue }) + .to_token_stream(); + Ok(quote! { + #ast + #def + }) } diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index ce1517993..7ab032509 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -1,27 +1,12 @@ //! Code generation for `#[derive(GraphQLScalar)]` macro. -use proc_macro2::{Literal, TokenStream}; -use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use syn::{ - ext::IdentExt as _, - parse::{Parse, ParseStream}, - parse_quote, - spanned::Spanned, - token, -}; -use url::Url; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{parse_quote, spanned::Spanned}; -use crate::{ - common::{ - parse::{ - attr::{err, OptionExt}, - ParseBufferExt as _, - }, - scalar, - }, - result::GraphQLScope, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, -}; +use crate::{common::scalar, result::GraphQLScope}; + +use super::{Attr, Definition, Field, GraphQLScalarMethods, ParseToken, TypeOrIdent}; /// [`GraphQLScope`] of errors for `#[derive(GraphQLScalar)]` macro. const ERR: GraphQLScope = GraphQLScope::DeriveScalar; @@ -119,7 +104,10 @@ pub fn expand(input: TokenStream) -> syn::Result { let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); Ok(Definition { - ident: ast.ident.clone(), + ty: TypeOrIdent::Ident(ast.ident.clone()), + where_clause: attr + .where_clause + .map_or_else(|| Vec::new(), |cl| cl.into_inner()), generics: ast.generics.clone(), methods: field, name: attr @@ -134,910 +122,439 @@ pub fn expand(input: TokenStream) -> syn::Result { .to_token_stream()) } -/// Available arguments behind `#[graphql]` attribute when generating -/// code for `#[derive(GraphQLScalar)]`. -#[derive(Default)] -struct Attr { - /// Name of this [GraphQL scalar][1] in GraphQL schema. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - name: Option>, - - /// Description of this [GraphQL scalar][1] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - description: Option>, - - /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - specified_by_url: Option>, - - /// Explicitly specified type (or type parameter with its bounds) of - /// [`ScalarValue`] to use for resolving this [GraphQL scalar][1] type with. - /// - /// If [`None`], then generated code will be generic over any - /// [`ScalarValue`] type, which, in turn, requires all [scalar][1] fields to - /// be generic over any [`ScalarValue`] type too. That's why this type - /// should be specified only if one of the variants implements - /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - scalar: Option>, - - /// Explicitly specified function to be used instead of - /// [`GraphQLScalar::to_output`]. - /// - /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output - to_output: Option>, - - /// Explicitly specified function to be used instead of - /// [`GraphQLScalar::from_input`]. - /// - /// [`GraphQLScalar::from_input`]: juniper::GraphQLScalar::from_input - from_input: Option>, - - /// Explicitly specified type to be used instead of - /// [`GraphQLScalar::Error`]. - /// - /// [`GraphQLScalar::Error`]: juniper::GraphQLScalar::Error - from_input_err: Option>, - - /// Explicitly specified resolver to be used instead of - /// [`GraphQLScalar::parse_token`]. - /// - /// [`GraphQLScalar::parse_token`]: juniper::GraphQLScalar::parse_token - parse_token: Option>, - - /// Explicitly specified module with all custom resolvers for - /// [`Self::to_output`], [`Self::from_input`], [`Self::from_input_err`] and - /// [`Self::parse_token`]. - with: Option>, -} - -impl Parse for Attr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let name = input.parse::()?; - out.name - .replace(SpanContainer::new( - ident.span(), - Some(name.span()), - name.value(), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "desc" | "description" => { - input.parse::()?; - let desc = input.parse::()?; - out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "specified_by_url" => { - input.parse::()?; - let lit = input.parse::()?; - let url = lit.value().parse::().map_err(|err| { - syn::Error::new(lit.span(), format!("Invalid URL: {}", err)) - })?; - out.specified_by_url - .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "to_output_with" => { - input.parse::()?; - let scl = input.parse::()?; - out.to_output - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "from_input_with" => { - input.parse::()?; - let scl = input.parse::()?; - out.from_input - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "from_input_err" => { - input.parse::()?; - let scl = input.parse::()?; - out.from_input_err - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "parse_token_with" => { - input.parse::()?; - let scl = input.parse::()?; - out.parse_token - .replace(SpanContainer::new( - ident.span(), - Some(scl.span()), - ParseToken::Custom(scl), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "parse_token" => { - let (span, parsed_types) = if input.parse::().is_ok() { - let scl = input.parse::()?; - (scl.span(), vec![scl]) - } else { - let types; - let _ = syn::parenthesized!(types in input); - let parsed_types = - types.parse_terminated::<_, token::Comma>(syn::Type::parse)?; - - if parsed_types.is_empty() { - return Err(syn::Error::new(ident.span(), "expected at least 1 type.")); - } - - (parsed_types.span(), parsed_types.into_iter().collect()) - }; - - out.parse_token - .replace(SpanContainer::new( - ident.span(), - Some(span), - ParseToken::Delegated(parsed_types), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "with" => { - input.parse::()?; - let scl = input.parse::()?; - out.with - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - Ok(out) - } -} - -impl Attr { - /// Tries to merge two [`Attr`]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), - specified_by_url: try_merge_opt!(specified_by_url: self, another), - scalar: try_merge_opt!(scalar: self, another), - to_output: try_merge_opt!(to_output: self, another), - from_input: try_merge_opt!(from_input: self, another), - from_input_err: try_merge_opt!(from_input_err: self, another), - parse_token: try_merge_opt!(parse_token: self, another), - with: try_merge_opt!(with: self, another), - }) - } - - /// Parses [`Attr`] 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 attr = filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - - if attr.description.is_none() { - attr.description = get_doc_comment(attrs); - } - - Ok(attr) - } -} - -/// Definition of [GraphQL scalar][1] for code generation. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars -struct Definition { - /// Name of this [GraphQL scalar][1] in GraphQL schema. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - name: String, - - /// Rust type [`Ident`] that this [GraphQL scalar][1] is represented with. - /// - /// [`Ident`]: syn::Ident - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - ident: syn::Ident, - - /// Generics of the Rust type that this [GraphQL scalar][1] is implemented - /// for. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - generics: syn::Generics, - - /// [`GraphQLScalarDefinition`] representing [GraphQL scalar][1]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - methods: GraphQLScalarMethods, - - /// Description of this [GraphQL scalar][1] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - description: Option, - - /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - specified_by_url: Option, - - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] - /// implementation with for this [GraphQL scalar][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - scalar: scalar::Type, -} - -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - self.impl_output_and_input_type_tokens().to_tokens(into); - self.impl_type_tokens().to_tokens(into); - self.impl_value_tokens().to_tokens(into); - self.impl_value_async_tokens().to_tokens(into); - self.impl_to_input_value_tokens().to_tokens(into); - self.impl_from_input_value_tokens().to_tokens(into); - self.impl_parse_scalar_value_tokens().to_tokens(into); - self.impl_graphql_scalar_tokens().to_tokens(into); - self.impl_reflection_traits_tokens().to_tokens(into); - } -} - -impl Definition { - /// Returns generated code implementing [`marker::IsInputType`] and - /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1]. - /// - /// [`marker::IsInputType`]: juniper::marker::IsInputType - /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - #[must_use] - fn impl_output_and_input_type_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ident#ty_gens - #where_clause { } - - #[automatically_derived] - impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ident#ty_gens - #where_clause { } - } - } - - /// Returns generated code implementing [`GraphQLType`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_type_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - let name = &self.name; - - let description = self - .description - .as_ref() - .map(|val| quote! { .description(#val) }); - let specified_by_url = self.specified_by_url.as_ref().map(|url| { - let url_lit = url.as_str(); - quote! { .specified_by_url(#url_lit) } - }); - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::GraphQLType<#scalar> for #ident#ty_gens - #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, - { - registry.build_scalar_type::(info) - #description - #specified_by_url - .into_meta() - } - } - } - } - - /// Returns generated code implementing [`GraphQLValue`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_value_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let resolve = self.methods.expand_resolve(scalar); - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ident#ty_gens - #where_clause - { - type Context = (); - type TypeInfo = (); - - fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { - >::name(info) - } - - fn resolve( - &self, - info: &(), - selection: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - #resolve - } - } - } - } - - /// Returns generated code implementing [`GraphQLValueAsync`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_value_async_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(true); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ident#ty_gens - #where_clause - { - fn resolve_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - selection_set: Option<&'b [::juniper::Selection<#scalar>]>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - use ::juniper::futures::future; - let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - } - } - - /// Returns generated code implementing [`InputValue`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`InputValue`]: juniper::InputValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_to_input_value_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let to_input_value = self.methods.expand_to_input_value(scalar); - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::ToInputValue<#scalar> for #ident#ty_gens - #where_clause - { - fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - #to_input_value - } - } - } - } - - /// Returns generated code implementing [`FromInputValue`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`FromInputValue`]: juniper::FromInputValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_from_input_value_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let error_ty = self.methods.expand_from_input_err(scalar); - let from_input_value = self.methods.expand_from_input(scalar); - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::FromInputValue<#scalar> for #ident#ty_gens - #where_clause - { - type Error = #error_ty; - - fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { - #from_input_value - } - } - } - } - - /// Returns generated code implementing [`ParseScalarValue`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`ParseScalarValue`]: juniper::ParseScalarValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_parse_scalar_value_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let from_str = self.methods.expand_parse_token(scalar); - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ident#ty_gens - #where_clause - { - fn from_str( - token: ::juniper::parser::ScalarToken<'_>, - ) -> ::juniper::ParseScalarResult<'_, #scalar> { - #from_str - } - } - } - } - - /// Returns generated code implementing [`GraphQLScalar`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`GraphQLScalar`]: juniper::GraphQLScalar - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_graphql_scalar_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - let to_output = self.methods.expand_to_output(scalar); - let from_input_err = self.methods.expand_from_input_err(scalar); - let from_input = self.methods.expand_from_input(scalar); - let parse_token = self.methods.expand_parse_token(scalar); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::GraphQLScalar<#scalar> for #ident#ty_gens - #where_clause - { - type Error = #from_input_err; - - fn to_output(&self) -> ::juniper::Value<#scalar> { - #to_output - } - - fn from_input( - input: &::juniper::InputValue<#scalar> - ) -> Result { - #from_input - } - - fn parse_token( - token: ::juniper::ScalarToken<'_> - ) -> ::juniper::ParseScalarResult<'_, #scalar> { - #parse_token - } - } - } - } - - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL scalar][1]. - /// - /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflection::BaseType - /// [`WrappedType`]: juniper::macros::reflection::WrappedType - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_reflection_traits_tokens(&self) -> TokenStream { - let ident = &self.ident; - let scalar = &self.scalar; - let name = &self.name; - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - let (_, ty_gens, _) = self.generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ident#ty_gens - #where_clause - { - const NAME: ::juniper::macros::reflect::Type = #name; - } - - #[automatically_derived] - impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident#ty_gens - #where_clause - { - const NAMES: ::juniper::macros::reflect::Types = - &[>::NAME]; - } - - #[automatically_derived] - impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ident#ty_gens - #where_clause - { - const VALUE: ::juniper::macros::reflect::WrappedValue = 1; - } - } - } - - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this enum. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.generics.clone(); - - let scalar = &self.scalar; - if scalar.is_implicit_generic() { - generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - if let Some(bound) = scalar.bounds() { - generics.make_where_clause().predicates.push(bound); - } - - if for_async { - let self_ty = if self.generics.lifetimes().next().is_some() { - // Modify lifetime names to omit "lifetime name `'a` shadows a - // lifetime name that is already in scope" error. - let mut generics = self.generics.clone(); - for lt in generics.lifetimes_mut() { - let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); - } - - let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.ident; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } - } else { - quote! { Self } - }; - generics - .make_where_clause() - .predicates - .push(parse_quote! { #self_ty: Sync }); - - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - } - - generics - } -} - -/// Methods representing [GraphQL scalar][1]. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars -enum GraphQLScalarMethods { - /// [GraphQL scalar][1] represented with only custom resolvers. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - Custom { - /// Function provided with `#[graphql(to_output_with = ...)]`. - to_output: syn::ExprPath, - - /// Function and return type provided with - /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. - from_input: (syn::ExprPath, syn::Type), - - /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` - /// or `#[graphql(parse_token(...))]`. - parse_token: ParseToken, - }, - - /// [GraphQL scalar][1] maybe partially represented with custom resolver. - /// Other methods are used from [`Field`]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - Delegated { - /// Function provided with `#[graphql(to_output_with = ...)]`. - to_output: Option, - - /// Function and return type provided with - /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. - from_input: Option<(syn::ExprPath, syn::Type)>, - - /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` - /// or `#[graphql(parse_token(...))]`. - parse_token: Option, - - /// [`Field`] to resolve not provided methods. - field: Field, - }, -} - -impl GraphQLScalarMethods { - /// Expands [`GraphQLValue::resolve`] method. - /// - /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve - fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { Ok(#to_output(self)) } - } - Self::Delegated { field, .. } => { - quote! { - ::juniper::GraphQLValue::<#scalar>::resolve( - &self.#field, - info, - selection, - executor, - ) - } - } - } - } - - /// Expands [`GraphQLScalar::to_output`] method. - /// - /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output - fn expand_to_output(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { #to_output(self) } - } - Self::Delegated { field, .. } => { - quote! { - ::juniper::GraphQLScalar::<#scalar>::to_output(&self.#field) - } - } - } - } - - /// Expands [`ToInputValue::to_input_value`] method. - /// - /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value - fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { - let v = #to_output(self); - ::juniper::ToInputValue::to_input_value(&v) - } - } - Self::Delegated { field, .. } => { - quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) } - } - } - } - - /// Expands [`FromInputValue::Error`] type. - /// - /// [`FromInputValue::Error`]: juniper::FromInputValue::Error - fn expand_from_input_err(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { - from_input: (_, err), - .. - } - | Self::Delegated { - from_input: Some((_, err)), - .. - } => quote! { #err }, - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::Error } - } - } - } - - /// Expands [`FromInputValue::from_input_value`][1] method. - /// - /// [1]: juniper::FromInputValue::from_input_value - fn expand_from_input(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { - from_input: (from_input, _), - .. - } - | Self::Delegated { - from_input: Some((from_input, _)), - .. - } => { - quote! { #from_input(input) } - } - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - let self_constructor = field.closure_constructor(); - quote! { - <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) - .map(#self_constructor) - } - } - } - } - - /// Expands [`ParseScalarValue::from_str`] method. - /// - /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str - fn expand_parse_token(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { parse_token, .. } - | Self::Delegated { - parse_token: Some(parse_token), - .. - } => { - let parse_token = parse_token.expand_from_str(scalar); - quote! { #parse_token } - } - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) } - } - } - } -} - -/// Representation of [`ParseScalarValue::from_str`] method. -/// -/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str -#[derive(Clone)] -enum ParseToken { - /// Custom method. - Custom(syn::ExprPath), - - /// Tries to parse using [`syn::Type`]s [`ParseScalarValue`] impls until - /// first success. - /// - /// [`ParseScalarValue`]: juniper::ParseScalarValue - Delegated(Vec), -} - -impl ParseToken { - /// Expands [`ParseScalarValue::from_str`] method. - /// - /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str - fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { - match self { - ParseToken::Custom(parse_token) => { - quote! { #parse_token(token) } - } - ParseToken::Delegated(delegated) => delegated - .iter() - .fold(None, |acc, ty| { - acc.map_or_else( - || Some(quote! { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }), - |prev| { - Some(quote! { - #prev.or_else(|_| { - <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) - }) - }) - } - ) - }) - .unwrap_or_default(), - } - } -} - -/// Struct field to resolve not provided methods. -enum Field { - /// Named [`Field`]. - Named(syn::Field), - - /// Unnamed [`Field`]. - Unnamed(syn::Field), -} - -impl ToTokens for Field { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Field::Named(f) => f.ident.to_tokens(tokens), - Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), - } - } -} - -impl Field { - /// [`syn::Type`] of this [`Field`]. - fn ty(&self) -> &syn::Type { - match self { - Field::Named(f) | Field::Unnamed(f) => &f.ty, - } - } - - /// Closure to construct [GraphQL scalar][1] struct from [`Field`]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn closure_constructor(&self) -> TokenStream { - match self { - Field::Named(syn::Field { ident, .. }) => { - quote! { |v| Self { #ident: v } } - } - Field::Unnamed(_) => quote! { Self }, - } - } -} +// /// Definition of [GraphQL scalar][1] for code generation. +// /// +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// struct Definition { +// /// Name of this [GraphQL scalar][1] in GraphQL schema. +// /// +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// name: String, +// +// /// Rust type [`Ident`] that this [GraphQL scalar][1] is represented with. +// /// +// /// [`Ident`]: syn::Ident +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// ident: syn::Ident, +// +// /// Generics of the Rust type that this [GraphQL scalar][1] is implemented +// /// for. +// /// +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// generics: syn::Generics, +// +// /// [`GraphQLScalarDefinition`] representing [GraphQL scalar][1]. +// /// +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// methods: GraphQLScalarMethods, +// +// /// Description of this [GraphQL scalar][1] to put into GraphQL schema. +// /// +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// description: Option, +// +// /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. +// /// +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// specified_by_url: Option, +// +// /// [`ScalarValue`] parametrization to generate [`GraphQLType`] +// /// implementation with for this [GraphQL scalar][1]. +// /// +// /// [`GraphQLType`]: juniper::GraphQLType +// /// [`ScalarValue`]: juniper::ScalarValue +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// scalar: scalar::Type, +// } +// +// impl ToTokens for Definition { +// fn to_tokens(&self, into: &mut TokenStream) { +// self.impl_output_and_input_type_tokens().to_tokens(into); +// self.impl_type_tokens().to_tokens(into); +// self.impl_value_tokens().to_tokens(into); +// self.impl_value_async_tokens().to_tokens(into); +// self.impl_to_input_value_tokens().to_tokens(into); +// self.impl_from_input_value_tokens().to_tokens(into); +// self.impl_parse_scalar_value_tokens().to_tokens(into); +// self.impl_graphql_scalar_tokens().to_tokens(into); +// self.impl_reflection_traits_tokens().to_tokens(into); +// } +// } +// +// impl Definition { +// /// Returns generated code implementing [`marker::IsInputType`] and +// /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1]. +// /// +// /// [`marker::IsInputType`]: juniper::marker::IsInputType +// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// #[must_use] +// fn impl_output_and_input_type_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ident#ty_gens +// #where_clause { } +// +// #[automatically_derived] +// impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ident#ty_gens +// #where_clause { } +// } +// } +// +// /// Returns generated code implementing [`GraphQLType`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`GraphQLType`]: juniper::GraphQLType +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_type_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// let name = &self.name; +// +// let description = self +// .description +// .as_ref() +// .map(|val| quote! { .description(#val) }); +// let specified_by_url = self.specified_by_url.as_ref().map(|url| { +// let url_lit = url.as_str(); +// quote! { .specified_by_url(#url_lit) } +// }); +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::GraphQLType<#scalar> for #ident#ty_gens +// #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, +// { +// registry.build_scalar_type::(info) +// #description +// #specified_by_url +// .into_meta() +// } +// } +// } +// } +// +// /// Returns generated code implementing [`GraphQLValue`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`GraphQLValue`]: juniper::GraphQLValue +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_value_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let resolve = self.methods.expand_resolve(scalar); +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ident#ty_gens +// #where_clause +// { +// type Context = (); +// type TypeInfo = (); +// +// fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { +// >::name(info) +// } +// +// fn resolve( +// &self, +// info: &(), +// selection: Option<&[::juniper::Selection<#scalar>]>, +// executor: &::juniper::Executor, +// ) -> ::juniper::ExecutionResult<#scalar> { +// #resolve +// } +// } +// } +// } +// +// /// Returns generated code implementing [`GraphQLValueAsync`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_value_async_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let generics = self.impl_generics(true); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ident#ty_gens +// #where_clause +// { +// fn resolve_async<'b>( +// &'b self, +// info: &'b Self::TypeInfo, +// selection_set: Option<&'b [::juniper::Selection<#scalar>]>, +// executor: &'b ::juniper::Executor, +// ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { +// use ::juniper::futures::future; +// let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); +// Box::pin(future::ready(v)) +// } +// } +// } +// } +// +// /// Returns generated code implementing [`InputValue`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`InputValue`]: juniper::InputValue +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_to_input_value_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let to_input_value = self.methods.expand_to_input_value(scalar); +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::ToInputValue<#scalar> for #ident#ty_gens +// #where_clause +// { +// fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { +// #to_input_value +// } +// } +// } +// } +// +// /// Returns generated code implementing [`FromInputValue`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`FromInputValue`]: juniper::FromInputValue +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_from_input_value_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let error_ty = self.methods.expand_from_input_err(scalar); +// let from_input_value = self.methods.expand_from_input(scalar); +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::FromInputValue<#scalar> for #ident#ty_gens +// #where_clause +// { +// type Error = #error_ty; +// +// fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { +// #from_input_value +// } +// } +// } +// } +// +// /// Returns generated code implementing [`ParseScalarValue`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`ParseScalarValue`]: juniper::ParseScalarValue +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_parse_scalar_value_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let from_str = self.methods.expand_parse_token(scalar); +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ident#ty_gens +// #where_clause +// { +// fn from_str( +// token: ::juniper::parser::ScalarToken<'_>, +// ) -> ::juniper::ParseScalarResult<'_, #scalar> { +// #from_str +// } +// } +// } +// } +// +// /// Returns generated code implementing [`GraphQLScalar`] trait for this +// /// [GraphQL scalar][1]. +// /// +// /// [`GraphQLScalar`]: juniper::GraphQLScalar +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_graphql_scalar_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// let to_output = self.methods.expand_to_output(scalar); +// let from_input_err = self.methods.expand_from_input_err(scalar); +// let from_input = self.methods.expand_from_input(scalar); +// let parse_token = self.methods.expand_parse_token(scalar); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::GraphQLScalar<#scalar> for #ident#ty_gens +// #where_clause +// { +// type Error = #from_input_err; +// +// fn to_output(&self) -> ::juniper::Value<#scalar> { +// #to_output +// } +// +// fn from_input( +// input: &::juniper::InputValue<#scalar> +// ) -> Result { +// #from_input +// } +// +// fn parse_token( +// token: ::juniper::ScalarToken<'_> +// ) -> ::juniper::ParseScalarResult<'_, #scalar> { +// #parse_token +// } +// } +// } +// } +// +// /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and +// /// [`WrappedType`] traits for this [GraphQL scalar][1]. +// /// +// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes +// /// [`BaseType`]: juniper::macros::reflection::BaseType +// /// [`WrappedType`]: juniper::macros::reflection::WrappedType +// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +// fn impl_reflection_traits_tokens(&self) -> TokenStream { +// let ident = &self.ident; +// let scalar = &self.scalar; +// let name = &self.name; +// +// let generics = self.impl_generics(false); +// let (impl_gens, _, where_clause) = generics.split_for_impl(); +// let (_, ty_gens, _) = self.generics.split_for_impl(); +// +// quote! { +// #[automatically_derived] +// impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ident#ty_gens +// #where_clause +// { +// const NAME: ::juniper::macros::reflect::Type = #name; +// } +// +// #[automatically_derived] +// impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident#ty_gens +// #where_clause +// { +// const NAMES: ::juniper::macros::reflect::Types = +// &[>::NAME]; +// } +// +// #[automatically_derived] +// impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ident#ty_gens +// #where_clause +// { +// const VALUE: ::juniper::macros::reflect::WrappedValue = 1; +// } +// } +// } +// +// /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and +// /// similar) implementation of this enum. +// /// +// /// If `for_async` is `true`, then additional predicates are added to suit +// /// the [`GraphQLAsyncValue`] trait (and similar) requirements. +// /// +// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue +// /// [`GraphQLType`]: juniper::GraphQLType +// #[must_use] +// fn impl_generics(&self, for_async: bool) -> syn::Generics { +// let mut generics = self.generics.clone(); +// +// let scalar = &self.scalar; +// if scalar.is_implicit_generic() { +// generics.params.push(parse_quote! { #scalar }); +// } +// if scalar.is_generic() { +// generics +// .make_where_clause() +// .predicates +// .push(parse_quote! { #scalar: ::juniper::ScalarValue }); +// } +// if let Some(bound) = scalar.bounds() { +// generics.make_where_clause().predicates.push(bound); +// } +// +// if for_async { +// let self_ty = if self.generics.lifetimes().next().is_some() { +// // Modify lifetime names to omit "lifetime name `'a` shadows a +// // lifetime name that is already in scope" error. +// let mut generics = self.generics.clone(); +// for lt in generics.lifetimes_mut() { +// let ident = lt.lifetime.ident.unraw(); +// lt.lifetime.ident = format_ident!("__fa__{}", ident); +// } +// +// let lifetimes = generics.lifetimes().map(|lt| <.lifetime); +// let ty = &self.ident; +// let (_, ty_generics, _) = generics.split_for_impl(); +// +// quote! { for<#( #lifetimes ),*> #ty#ty_generics } +// } else { +// quote! { Self } +// }; +// generics +// .make_where_clause() +// .predicates +// .push(parse_quote! { #self_ty: Sync }); +// +// if scalar.is_generic() { +// generics +// .make_where_clause() +// .predicates +// .push(parse_quote! { #scalar: Send + Sync }); +// } +// } +// +// generics +// } +// } diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 968ec78cf..724db158f 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -2,12 +2,15 @@ //! //! [1]: https://spec.graphql.org/October2021/#sec-Scalars -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use proc_macro2::{Literal, TokenStream}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, - parse_quote, token, + parse_quote, + spanned::Spanned as _, + token, + visit_mut::VisitMut, }; use url::Url; @@ -25,26 +28,70 @@ use crate::{ pub mod attr; pub mod derive; -/// Available arguments behind `#[graphql_scalar]` attribute when generating -/// code for [GraphQL scalar][1] type. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars -#[derive(Default)] +/// Available arguments behind `#[graphql]` attribute when generating +/// code for `#[derive(GraphQLScalar)]`. +#[derive(Debug, Default)] struct Attr { /// Name of this [GraphQL scalar][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - pub name: Option>, + name: Option>, /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - pub description: Option>, + description: Option>, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - pub specified_by_url: Option>, + specified_by_url: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to use for resolving this [GraphQL scalar][1] type with. + /// + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type, which, in turn, requires all [scalar][1] fields to + /// be generic over any [`ScalarValue`] type too. That's why this type + /// should be specified only if one of the variants implements + /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + scalar: Option>, + + /// Explicitly specified function to be used instead of + /// [`GraphQLScalar::to_output`]. + /// + /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output + to_output: Option>, + + /// Explicitly specified function to be used instead of + /// [`GraphQLScalar::from_input`]. + /// + /// [`GraphQLScalar::from_input`]: juniper::GraphQLScalar::from_input + from_input: Option>, + + /// Explicitly specified type to be used instead of + /// [`GraphQLScalar::Error`]. + /// + /// [`GraphQLScalar::Error`]: juniper::GraphQLScalar::Error + from_input_err: Option>, + + /// Explicitly specified resolver to be used instead of + /// [`GraphQLScalar::parse_token`]. + /// + /// [`GraphQLScalar::parse_token`]: juniper::GraphQLScalar::parse_token + parse_token: Option>, + + /// Explicitly specified module with all custom resolvers for + /// [`Self::to_output`], [`Self::from_input`], [`Self::from_input_err`] and + /// [`Self::parse_token`]. + with: Option>, + + /// TODO + where_clause: Option>>, } impl Parse for Attr { @@ -85,6 +132,108 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(lit.span()), url)) .none_or_else(|_| err::dup_arg(&ident))? } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "to_output_with" => { + input.parse::()?; + let scl = input.parse::()?; + out.to_output + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "from_input_with" => { + input.parse::()?; + let scl = input.parse::()?; + out.from_input + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "from_input_err" => { + input.parse::()?; + let scl = input.parse::()?; + out.from_input_err + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "parse_token_with" => { + input.parse::()?; + let scl = input.parse::()?; + out.parse_token + .replace(SpanContainer::new( + ident.span(), + Some(scl.span()), + ParseToken::Custom(scl), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "parse_token" => { + let (span, parsed_types) = if input.parse::().is_ok() { + let scl = input.parse::()?; + (scl.span(), vec![scl]) + } else { + let types; + let _ = syn::parenthesized!(types in input); + let parsed_types = + types.parse_terminated::<_, token::Comma>(syn::Type::parse)?; + + if parsed_types.is_empty() { + return Err(syn::Error::new(ident.span(), "expected at least 1 type.")); + } + + (parsed_types.span(), parsed_types.into_iter().collect()) + }; + + out.parse_token + .replace(SpanContainer::new( + ident.span(), + Some(span), + ParseToken::Delegated(parsed_types), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "with" => { + input.parse::()?; + let scl = input.parse::()?; + out.with + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "where" => { + let (span, parsed_predicates) = if input.parse::().is_ok() { + let pred = input.parse::()?; + (pred.span(), vec![pred]) + } else { + let predicates; + let _ = syn::parenthesized!(predicates in input); + let parsed_predicates = predicates + .parse_terminated::<_, token::Comma>(syn::WherePredicate::parse)?; + + if parsed_predicates.is_empty() { + return Err(syn::Error::new( + ident.span(), + "expected at least 1 where predicate.", + )); + } + + ( + parsed_predicates.span(), + parsed_predicates.into_iter().collect(), + ) + }; + + out.where_clause + .replace(SpanContainer::new( + ident.span(), + Some(span), + parsed_predicates, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } name => { return Err(err::unknown_arg(&ident, name)); } @@ -103,6 +252,13 @@ impl Attr { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), specified_by_url: try_merge_opt!(specified_by_url: self, another), + scalar: try_merge_opt!(scalar: self, another), + to_output: try_merge_opt!(to_output: self, another), + from_input: try_merge_opt!(from_input: self, another), + from_input_err: try_merge_opt!(from_input_err: self, another), + parse_token: try_merge_opt!(parse_token: self, another), + with: try_merge_opt!(with: self, another), + where_clause: try_merge_opt!(where_clause: self, another), }) } @@ -121,6 +277,12 @@ impl Attr { } } +#[derive(Clone)] +enum TypeOrIdent { + Type(syn::Type), + Ident(syn::Ident), +} + /// Definition of [GraphQL scalar][1] for code generation. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars @@ -130,12 +292,10 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021/#sec-Scalars name: String, - /// Rust type that this [GraphQL scalar][1] is represented with. - /// - /// It should contain all its generics, if any. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - impl_for_type: syn::Type, + /// TODO + ty: TypeOrIdent, + + where_clause: Vec, /// Generics of the Rust type that this [GraphQL scalar][1] is implemented /// for. @@ -143,6 +303,11 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021/#sec-Scalars generics: syn::Generics, + /// [`GraphQLScalarDefinition`] representing [GraphQL scalar][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + methods: GraphQLScalarMethods, + /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars @@ -171,6 +336,7 @@ impl ToTokens for Definition { self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); + self.impl_graphql_scalar_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); } } @@ -184,10 +350,9 @@ impl Definition { /// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[must_use] fn impl_output_and_input_type_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; - let generics = self.impl_generics(false); + let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -207,12 +372,8 @@ impl Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_type_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; - let name = &self.name; let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); + let name = &self.name; let description = self .description @@ -223,6 +384,9 @@ impl Definition { quote! { .specified_by_url(#url_lit) } }); + let (ty, generics) = self.impl_self_and_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + quote! { #[automatically_derived] impl#impl_gens ::juniper::GraphQLType<#scalar> for #ty @@ -254,10 +418,11 @@ impl Definition { /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_value_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; - let generics = self.impl_generics(false); + let resolve = self.methods.expand_resolve(scalar); + + let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -278,7 +443,7 @@ impl Definition { selection: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - Ok(::juniper::GraphQLScalar::to_output(self)) + #resolve } } } @@ -290,10 +455,9 @@ impl Definition { /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_value_async_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; - let generics = self.impl_generics(true); + let (ty, generics) = self.impl_self_and_generics(true); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -321,10 +485,11 @@ impl Definition { /// [`InputValue`]: juniper::InputValue /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_to_input_value_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; - let generics = self.impl_generics(false); + let to_input_value = self.methods.expand_to_input_value(scalar); + + let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -333,8 +498,7 @@ impl Definition { #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - let v = ::juniper::GraphQLScalar::to_output(self); - ::juniper::ToInputValue::to_input_value(&v) + #to_input_value } } } @@ -346,10 +510,12 @@ impl Definition { /// [`FromInputValue`]: juniper::FromInputValue /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_from_input_value_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; - let generics = self.impl_generics(false); + let error_ty = self.methods.expand_from_input_err(scalar); + let from_input_value = self.methods.expand_from_input(scalar); + + let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -357,10 +523,10 @@ impl Definition { impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty #where_clause { - type Error = >::Error; + type Error = #error_ty; fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { - ::juniper::GraphQLScalar::from_input(input) + #from_input_value } } } @@ -372,10 +538,11 @@ impl Definition { /// [`ParseScalarValue`]: juniper::ParseScalarValue /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_parse_scalar_value_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; - let generics = self.impl_generics(false); + let from_str = self.methods.expand_parse_token(scalar); + + let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -386,7 +553,49 @@ impl Definition { fn from_str( token: ::juniper::parser::ScalarToken<'_>, ) -> ::juniper::ParseScalarResult<'_, #scalar> { - >::parse_token(token) + #from_str + } + } + } + } + + /// Returns generated code implementing [`GraphQLScalar`] trait for this + /// [GraphQL scalar][1]. + /// + /// [`GraphQLScalar`]: juniper::GraphQLScalar + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn impl_graphql_scalar_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (ty, generics) = self.impl_self_and_generics(false); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let to_output = self.methods.expand_to_output(scalar); + let from_input_err = self.methods.expand_from_input_err(scalar); + let from_input = self.methods.expand_from_input(scalar); + let parse_token = self.methods.expand_parse_token(scalar); + + quote! { + #[automatically_derived] + impl#impl_gens ::juniper::GraphQLScalar<#scalar> for #ty + #where_clause + { + type Error = #from_input_err; + + fn to_output(&self) -> ::juniper::Value<#scalar> { + #to_output + } + + fn from_input( + input: &::juniper::InputValue<#scalar> + ) -> Result { + #from_input + } + + fn parse_token( + token: ::juniper::ScalarToken<'_> + ) -> ::juniper::ParseScalarResult<'_, #scalar> { + #parse_token } } } @@ -395,16 +604,15 @@ impl Definition { /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL scalar][1]. /// - /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflect::BaseType - /// [`WrappedType`]: juniper::macros::reflect::WrappedType + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/October2021/#sec-Scalars fn impl_reflection_traits_tokens(&self) -> TokenStream { - let ty = &self.impl_for_type; let scalar = &self.scalar; let name = &self.name; - let generics = self.impl_generics(false); + let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); quote! { @@ -415,6 +623,7 @@ impl Definition { const NAME: ::juniper::macros::reflect::Type = #name; } + #[automatically_derived] impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { @@ -422,6 +631,7 @@ impl Definition { &[>::NAME]; } + #[automatically_derived] impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { @@ -430,8 +640,8 @@ impl Definition { } } - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation. + /// Returns prepared self type and [`syn::Generics`] for [`GraphQLType`] + /// trait (and similar) implementation. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. @@ -439,9 +649,24 @@ impl Definition { /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { + fn impl_self_and_generics(&self, for_async: bool) -> (TokenStream, syn::Generics) { let mut generics = self.generics.clone(); + let ty = match &self.ty { + TypeOrIdent::Type(ty) => ty.into_token_stream(), + TypeOrIdent::Ident(ident) => { + let (_, ty_gen, _) = self.generics.split_for_impl(); + quote! { #ident#ty_gen } + } + }; + + if !self.where_clause.is_empty() { + generics + .make_where_clause() + .predicates + .extend(self.where_clause.clone()) + } + let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); @@ -461,16 +686,21 @@ impl Definition { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. let mut generics = self.generics.clone(); - for lt in generics.lifetimes_mut() { - let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); - } + ModifyLifetimes.visit_generics_mut(&mut generics); let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.impl_for_type; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } + let ty = match self.ty.clone() { + TypeOrIdent::Type(mut ty) => { + ModifyLifetimes.visit_type_mut(&mut ty); + ty.into_token_stream() + } + TypeOrIdent::Ident(ident) => { + let (_, ty_gens, _) = generics.split_for_impl(); + quote! { #ident#ty_gens } + } + }; + + quote! { for<#( #lifetimes ),*> #ty } } else { quote! { Self } }; @@ -487,6 +717,271 @@ impl Definition { } } - generics + (ty, generics) + } +} + +struct ModifyLifetimes; + +impl VisitMut for ModifyLifetimes { + fn visit_lifetime_mut(&mut self, lf: &mut syn::Lifetime) { + lf.ident = format_ident!("__fa__{}", lf.ident.unraw()); + } +} + +/// Methods representing [GraphQL scalar][1]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +enum GraphQLScalarMethods { + /// [GraphQL scalar][1] represented with only custom resolvers. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + Custom { + /// Function provided with `#[graphql(to_output_with = ...)]`. + to_output: syn::ExprPath, + + /// Function and return type provided with + /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. + from_input: (syn::ExprPath, syn::Type), + + /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` + /// or `#[graphql(parse_token(...))]`. + parse_token: ParseToken, + }, + + /// [GraphQL scalar][1] maybe partially represented with custom resolver. + /// Other methods are used from [`Field`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + Delegated { + /// Function provided with `#[graphql(to_output_with = ...)]`. + to_output: Option, + + /// Function and return type provided with + /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. + from_input: Option<(syn::ExprPath, syn::Type)>, + + /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` + /// or `#[graphql(parse_token(...))]`. + parse_token: Option, + + /// [`Field`] to resolve not provided methods. + field: Field, + }, +} + +impl GraphQLScalarMethods { + /// Expands [`GraphQLValue::resolve`] method. + /// + /// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve + fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { Ok(#to_output(self)) } + } + Self::Delegated { field, .. } => { + quote! { + ::juniper::GraphQLValue::<#scalar>::resolve( + &self.#field, + info, + selection, + executor, + ) + } + } + } + } + + /// Expands [`GraphQLScalar::to_output`] method. + /// + /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output + fn expand_to_output(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { #to_output(self) } + } + Self::Delegated { field, .. } => { + quote! { + ::juniper::GraphQLScalar::<#scalar>::to_output(&self.#field) + } + } + } + } + + /// Expands [`ToInputValue::to_input_value`] method. + /// + /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value + fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { + let v = #to_output(self); + ::juniper::ToInputValue::to_input_value(&v) + } + } + Self::Delegated { field, .. } => { + quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) } + } + } + } + + /// Expands [`FromInputValue::Error`] type. + /// + /// [`FromInputValue::Error`]: juniper::FromInputValue::Error + fn expand_from_input_err(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { + from_input: (_, err), + .. + } + | Self::Delegated { + from_input: Some((_, err)), + .. + } => quote! { #err }, + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::Error } + } + } + } + + /// Expands [`FromInputValue::from_input_value`][1] method. + /// + /// [1]: juniper::FromInputValue::from_input_value + fn expand_from_input(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { + from_input: (from_input, _), + .. + } + | Self::Delegated { + from_input: Some((from_input, _)), + .. + } => { + quote! { #from_input(input) } + } + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + let self_constructor = field.closure_constructor(); + quote! { + <#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input) + .map(#self_constructor) + } + } + } + } + + /// Expands [`ParseScalarValue::from_str`] method. + /// + /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str + fn expand_parse_token(&self, scalar: &scalar::Type) -> TokenStream { + match self { + Self::Custom { parse_token, .. } + | Self::Delegated { + parse_token: Some(parse_token), + .. + } => { + let parse_token = parse_token.expand_from_str(scalar); + quote! { #parse_token } + } + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) } + } + } + } +} + +/// Representation of [`ParseScalarValue::from_str`] method. +/// +/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str +#[derive(Clone, Debug)] +enum ParseToken { + /// Custom method. + Custom(syn::ExprPath), + + /// Tries to parse using [`syn::Type`]s [`ParseScalarValue`] impls until + /// first success. + /// + /// [`ParseScalarValue`]: juniper::ParseScalarValue + Delegated(Vec), +} + +impl ParseToken { + /// Expands [`ParseScalarValue::from_str`] method. + /// + /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str + fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { + match self { + ParseToken::Custom(parse_token) => { + quote! { #parse_token(token) } + } + ParseToken::Delegated(delegated) => delegated + .iter() + .fold(None, |acc, ty| { + acc.map_or_else( + || Some(quote! { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }), + |prev| { + Some(quote! { + #prev.or_else(|_| { + <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) + }) + }) + } + ) + }) + .unwrap_or_default(), + } + } +} + +/// Struct field to resolve not provided methods. +enum Field { + /// Named [`Field`]. + Named(syn::Field), + + /// Unnamed [`Field`]. + Unnamed(syn::Field), +} + +impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Field::Named(f) => f.ident.to_tokens(tokens), + Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + } + } +} + +impl Field { + /// [`syn::Type`] of this [`Field`]. + fn ty(&self) -> &syn::Type { + match self { + Field::Named(f) | Field::Unnamed(f) => &f.ty, + } + } + + /// Closure to construct [GraphQL scalar][1] struct from [`Field`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + fn closure_constructor(&self) -> TokenStream { + match self { + Field::Named(syn::Field { ident, .. }) => { + quote! { |v| Self { #ident: v } } + } + Field::Unnamed(_) => quote! { Self }, + } } } From 87060439382357fe8101d96c2e2c9447618d260a Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 4 Feb 2022 13:05:30 +0300 Subject: [PATCH 078/122] Corrections and tests --- docs/book/content/types/scalars.md | 105 ++--- ...lds.rs => derive_multiple_named_fields.rs} | 0 ...rr => derive_multiple_named_fields.stderr} | 2 +- ...ve_multiple_named_fields_with_resolver.rs} | 0 ...ultiple_named_fields_with_resolver.stderr} | 2 +- ...s.rs => derive_multiple_unnamed_fields.rs} | 0 ... => derive_multiple_unnamed_fields.stderr} | 2 +- ...e_multiple_unnamed_fields_with_resolver.rs | 13 + ...ltiple_unnamed_fields_with_resolver.stderr | 6 + .../{unit_struct.rs => derive_unit_struct.rs} | 0 ...truct.stderr => derive_unit_struct.stderr} | 2 +- .../fail/scalar/impl_invalid_url.rs | 32 +- .../fail/scalar/impl_invalid_url.stderr | 6 +- .../fail/scalar/impl_with_resolver.rs | 14 + .../fail/scalar/impl_with_resolver.stderr | 5 + .../fail/scalar/impl_without_resolvers.rs | 8 + .../fail/scalar/impl_without_resolvers.stderr | 5 + .../src/codegen/derive_scalar.rs | 14 +- juniper/CHANGELOG.md | 12 +- .../src/executor_tests/introspection/mod.rs | 20 +- juniper/src/integrations/chrono_tz.rs | 3 +- juniper/src/lib.rs | 5 +- juniper/src/value/mod.rs | 27 +- juniper_codegen/src/graphql_scalar/attr.rs | 9 +- juniper_codegen/src/graphql_scalar/derive.rs | 441 +----------------- juniper_codegen/src/graphql_scalar/mod.rs | 101 +--- juniper_codegen/src/lib.rs | 131 +++--- 27 files changed, 252 insertions(+), 713 deletions(-) rename integration_tests/codegen_fail/fail/scalar/{mutliple_named_fields.rs => derive_multiple_named_fields.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/{mutliple_named_fields.stderr => derive_multiple_named_fields.stderr} (77%) rename integration_tests/codegen_fail/fail/scalar/{mutliple_named_fields_with_resolver.rs => derive_multiple_named_fields_with_resolver.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/{mutliple_named_fields_with_resolver.stderr => derive_multiple_named_fields_with_resolver.stderr} (77%) rename integration_tests/codegen_fail/fail/scalar/{mutliple_unnamed_fields.rs => derive_multiple_unnamed_fields.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/{mutliple_unnamed_fields.stderr => derive_multiple_unnamed_fields.stderr} (73%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr rename integration_tests/codegen_fail/fail/scalar/{unit_struct.rs => derive_unit_struct.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/{unit_struct.stderr => derive_unit_struct.stderr} (80%) create mode 100644 integration_tests/codegen_fail/fail/scalar/impl_with_resolver.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index fce29fce1..ebe010e20 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -82,8 +82,7 @@ pub struct UserId(i32); # fn main() {} ``` -All the methods used from newtype's field can be replaced with attributes mirroring -[`GraphQLScalar`](https://docs.rs/juniper/*/juniper/trait.GraphQLScalar.html) methods: +All the methods used from newtype's field can be replaced with attributes: #### `#[graphql(to_output_with = )]` attribute @@ -97,7 +96,7 @@ struct Incremented(i32); /// Increments [`Incremented`] before converting into a [`Value`]. fn to_output(v: &Incremented) -> Value { let inc = v.0 + 1; - inc.to_output() + Value::from(inc) } # # fn main() {} @@ -140,7 +139,7 @@ impl UserId { #### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes ```rust -# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarValue, ScalarToken, Value}; # #[derive(GraphQLScalar)] #[graphql( @@ -148,9 +147,9 @@ impl UserId { from_input_with = from_input, from_input_err = String, parse_token_with = parse_token, - // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)` - // which tries to parse as `String` and then as `i32` - // if prior fails. +// ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)` +// which tries to parse as `String` and then as `i32` +// if prior fails. )] enum StringOrInt { String(String), @@ -172,8 +171,8 @@ fn from_input(v: &InputValue) -> Result } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::parse_token(value) - .or_else(|_| >::parse_token(value)) + >::from_str(value) + .or_else(|_| >::from_str(value)) } # # fn main() {} @@ -187,7 +186,7 @@ fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, Instead of providing all custom resolvers, you can provide module with `to_output`, `from_input`, `parse_token` functions and `Error` struct or type alias. ```rust -# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarValue, ScalarToken, Value}; # #[derive(GraphQLScalar)] #[graphql(with = string_or_int)] @@ -197,8 +196,8 @@ enum StringOrInt { } mod string_or_int { - # use super::*; - # + use super::*; + pub(super) type Error = String; pub(super) fn to_output(v: &StringOrInt) -> Value @@ -225,8 +224,8 @@ mod string_or_int { where S: ScalarValue, { - >::parse_token(value) - .or_else(|_| >::parse_token(value)) + >::from_str(value) + .or_else(|_| >::from_str(value)) } } # @@ -250,8 +249,8 @@ enum StringOrInt { } mod string_or_int { - # use super::*; - # + use super::*; + pub(super) fn to_output(v: &StringOrInt) -> Value where S: ScalarValue, @@ -280,31 +279,22 @@ mod string_or_int { ### `#[graphql_scalar]` attribute -For more complex situations where you also need custom parsing or validation, -you can use the `graphql_scalar` proc macro. - -Typically, you represent your custom scalars as strings. - -The example below implements a custom scalar for a custom `Date` type. - -Note: juniper already has built-in support for the `chrono::DateTime` type -via `chrono` feature, which is enabled by default and should be used for this -purpose. - -The example below is used just for illustration. - -**Note**: the example assumes that the `Date` type implements -`std::fmt::Display` and `std::str::FromStr`. +For implementing custom scalars on foreign types there is `#[graphql_scalar]` attribute macro. +> __NOTE:__ To satisfy [orphan rule] you should provide local [`ScalarValue`] implementation. ```rust # extern crate juniper; # mod date { # pub struct Date; # impl std::str::FromStr for Date { -# type Err = String; fn from_str(_value: &str) -> Result { unimplemented!() } +# type Err = String; +# +# fn from_str(_value: &str) -> Result { +# unimplemented!() +# } # } -# // And we define how to represent date as a string. +# # impl std::fmt::Display for Date { # fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { # unimplemented!() @@ -312,35 +302,36 @@ The example below is used just for illustration. # } # } # -use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value}; -use date::Date; - -#[juniper::graphql_scalar(description = "Date")] -impl GraphQLScalar for Date -where - S: ScalarValue -{ - // Error of the `from_input_value()` method. - // NOTE: Should implement `IntoFieldError`. - type Error = String; - - // Define how to convert your custom scalar into a primitive type. - fn to_output(&self) -> Value { - Value::scalar(self.to_string()) - } +# use juniper::DefaultScalarValue as CustomScalarValue; +use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; + +#[graphql_scalar( + with = date_scalar, + parse_token = String, + scalar = CustomScalarValue, +// ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation. +)] +type Date = date::Date; +// ^^^^^^^^^^ Type from another crate. - // Define how to parse a primitive type into your custom scalar. - fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) +mod date_scalar { + use super::*; + + pub(super) type Error = String; + + pub(super) fn to_output(v: &Date) -> Value { + Value::scalar(v.to_string()) } - // Define how to parse a string value. - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) } } # # fn main() {} ``` + +[orphan rule]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules +[`ScalarValue`]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.rs rename to integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.stderr similarity index 77% rename from integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.stderr index 10cd4753d..8a27aaf9e 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.stderr @@ -1,5 +1,5 @@ error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } or all custom resolvers - --> fail/scalar/mutliple_named_fields.rs:4:1 + --> fail/scalar/derive_multiple_named_fields.rs:4:1 | 4 | / struct Scalar { 5 | | id: i32, diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.rs rename to integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.rs diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr similarity index 77% rename from integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr index 7f14cd806..4604ddc5c 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_named_fields_with_resolver.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr @@ -1,5 +1,5 @@ error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } or all custom resolvers - --> fail/scalar/mutliple_named_fields_with_resolver.rs:4:1 + --> fail/scalar/derive_multiple_named_fields_with_resolver.rs:4:1 | 4 | / #[graphql(to_output_with = Self::to_output)] 5 | | struct Scalar { diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.rs rename to integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.stderr similarity index 73% rename from integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.stderr index 9363c34cb..c848368cb 100644 --- a/integration_tests/codegen_fail/fail/scalar/mutliple_unnamed_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.stderr @@ -1,5 +1,5 @@ error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) or all custom resolvers - --> fail/scalar/mutliple_unnamed_fields.rs:4:1 + --> fail/scalar/derive_multiple_unnamed_fields.rs:4:1 | 4 | struct Scalar(i32, i32); | ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs new file mode 100644 index 000000000..ebe38d052 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs @@ -0,0 +1,13 @@ +use juniper::{GraphQLScalar, Value}; + +#[derive(GraphQLScalar)] +#[graphql(to_output_with = Self::to_output)] +struct Scalar(i32, i32); + +impl Scalar { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr new file mode 100644 index 000000000..97bb0b871 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr @@ -0,0 +1,6 @@ +error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) or all custom resolvers + --> fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs:4:1 + | +4 | / #[graphql(to_output_with = Self::to_output)] +5 | | struct Scalar(i32, i32); + | |________________________^ diff --git a/integration_tests/codegen_fail/fail/scalar/unit_struct.rs b/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/unit_struct.rs rename to integration_tests/codegen_fail/fail/scalar/derive_unit_struct.rs diff --git a/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/scalar/unit_struct.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_unit_struct.stderr index c85950261..8b678d2a0 100644 --- a/integration_tests/codegen_fail/fail/scalar/unit_struct.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.stderr @@ -1,5 +1,5 @@ error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` or all custom resolvers - --> fail/scalar/unit_struct.rs:4:1 + --> fail/scalar/derive_unit_struct.rs:4:1 | 4 | struct ScalarSpecifiedByUrl; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs index d3894a34c..962cec680 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs @@ -1,23 +1,27 @@ -use juniper::graphql_scalar; +use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; -struct ScalarSpecifiedByUrl(i32); +struct ScalarSpecifiedByUrl; -#[graphql_scalar(specified_by_url = "not an url")] -impl GraphQLScalar for ScalarSpecifiedByUrl { - type Error = String; +#[graphql_scalar( + specified_by_url = "not an url", + with = scalar, + parse_token = i32, +)] +type Scalar = ScalarSpecifiedByUrl; - fn to_output(&self) -> Value { - Value::scalar(self.0) - } +mod scalar { + use super::*; + + pub(super) type Error = String; - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) + pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { + Value::scalar(0) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_> { - ::from_str(value) + pub(super) fn from_input( + _: &InputValue, + ) -> Result { + Ok(ScalarSpecifiedByUrl) } } diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr index 24f741b4d..6b1640f73 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/impl_invalid_url.rs:5:37 + --> fail/scalar/impl_invalid_url.rs:6:24 | -5 | #[graphql_scalar(specified_by_url = "not an url")] - | ^^^^^^^^^^^^ +6 | specified_by_url = "not an url", + | ^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.rs new file mode 100644 index 000000000..3b37e214b --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_scalar, Value}; + +struct Scalar; + +#[graphql_scalar(to_output_with = Scalar::to_output)] +type CustomScalar = Scalar; + +impl Scalar { + fn to_output(&self) -> Value { + Value::scalar(0) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr new file mode 100644 index 000000000..50d3d2a7b --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `from_input_err_with`, `parse_token_with` attributes + --> fail/scalar/impl_with_resolver.rs:6:1 + | +6 | type CustomScalar = Scalar; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.rs new file mode 100644 index 000000000..e428deb2a --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.rs @@ -0,0 +1,8 @@ +use juniper::graphql_scalar; + +struct Scalar; + +#[graphql_scalar] +type CustomScalar = Scalar; + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr new file mode 100644 index 000000000..ea7a9c368 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `from_input_err_with`, `parse_token_with` attributes + --> fail/scalar/impl_without_resolvers.rs:6:1 + | +6 | type CustomScalar = Scalar; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index a317271bb..6b3609224 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -216,7 +216,7 @@ mod custom_to_output { fn to_output(val: &Increment) -> Value { let ret = val.0 + 1; - ret.to_output() + Value::from(ret) } struct QueryRoot; @@ -427,14 +427,14 @@ mod where_attribute { #[derive(GraphQLScalar)] #[graphql( - to_output_with = to_output, - from_input_with = from_input, - from_input_err = String, + to_output_with = to_output, + from_input_with = from_input, + from_input_err = String, )] #[graphql( - parse_token = String, - where(Tz: From, Tz::Offset: fmt::Display), - specified_by_url = "https://tools.ietf.org/html/rfc3339", + parse_token = String, + where(Tz: From, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", )] struct CustomDateTime(DateTime); diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index ada3107e8..b19564da9 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -21,14 +21,12 @@ - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). - Forbid default impls on non-ignored trait methods. - Support coercion of additional nullable arguments and return sub-typing on implementer. -- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) +- Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - Support generic scalars. - - Introduce actual `GraphQLScalar` trait. - - Add `Error` associated type to the `GraphQLScalar` trait. - - Rename `resolve` method to `to_output`. - - Rename `from_input_value` method to `from_input`. - - Rename `from_str` method to `parse_token`. -- Split `#[derive(GraphQLScalarValue)]` into `#[derive(GraphQLScalar)]` and `#[derive(GraphQLScalarValue)]` macros: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) + - Support single named field structs. + - Support for overriding resolvers. +- Redesign `#[graphql_scalar]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) + - Use on type aliases only in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). ## Features diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 59b335a5a..925b2fd72 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -9,7 +9,7 @@ use crate::{ graphql_interface, graphql_object, graphql_value, graphql_vars, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - GraphQLEnum, GraphQLScalar, InputValue, ScalarValue, Value, + GraphQLEnum, GraphQLScalar, }; #[derive(GraphQLEnum)] @@ -20,25 +20,9 @@ enum Sample { } #[derive(GraphQLScalar)] -#[graphql(name = "SampleScalar", with = scalar, parse_token = i32)] +#[graphql(name = "SampleScalar")] struct Scalar(i32); -mod scalar { - use super::*; - - pub(super) type Error = String; - - pub(super) fn to_output(v: &Scalar) -> Value { - Value::scalar(v.0) - } - - pub(super) fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Scalar) - .ok_or_else(|| format!("Expected `Int`, found: {}", v)) - } -} - /// A sample interface #[graphql_interface(name = "SampleInterface", for = Root)] trait Interface { diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 25b8bbb30..1f96cb3be 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -5,7 +5,8 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar(description = "Timezone", with = tz, parse_token = String)] +/// Timezone +#[graphql_scalar(with = tz, parse_token = String)] type Tz = chrono_tz::Tz; mod tz { diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 3856e0285..ff430e325 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -185,10 +185,7 @@ pub use crate::{ }, }, validation::RuleError, - value::{ - DefaultScalarValue, GraphQLScalar, Object, ParseScalarResult, ParseScalarValue, - ScalarValue, Value, - }, + value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value}, }; /// An error that prevented query execution diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index 80b5e92f4..e3457c8eb 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -5,7 +5,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem}; use crate::{ ast::{InputValue, ToInputValue}, - parser::{ScalarToken, Spanning}, + parser::Spanning, }; pub use self::{ @@ -13,31 +13,6 @@ pub use self::{ scalar::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, ScalarValue}, }; -/// [GraphQL scalar][1] definition. Implementation of this trait should be -/// attributed with [`graphql_scalar`]. -/// -/// [`graphql_scalar`]: crate::graphql_scalar -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars -pub trait GraphQLScalar: Sized { - /// Error of converting [`InputValue`] into this [GraphQL scalar][1]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - type Error; - - /// Resolves this [GraphQL scalar][1] into [`Value`]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn to_output(&self) -> Value; - - /// Parses [`InputValue`] into this [GraphQL scalar][1]. - /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn from_input(v: &InputValue) -> Result; - - /// Resolves [`ScalarToken`] literal into `S`. - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>; -} - /// Serializable value returned from query and field execution. /// /// Used by the execution engine and resolvers to build up the response diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index 6beaf54f3..f4d422286 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -62,8 +62,8 @@ fn expand_on_type_alias( return Err(ERR.custom_error( ast.span(), "all custom resolvers have to be provided via `with` or \ - `to_output_with`, `from_input_with`, `from_input_err_with`, - `parse_token_with` attributes", + combination of `to_output_with`, `from_input_with`, \ + `from_input_err_with`, `parse_token_with` attributes", )); } }; @@ -71,10 +71,10 @@ fn expand_on_type_alias( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); let def = Definition { - ty: TypeOrIdent::Type(*ast.ty.clone()), + ty: TypeOrIdent::Type(ast.ty.clone()), where_clause: attr .where_clause - .map_or_else(|| Vec::new(), |cl| cl.into_inner()), + .map_or_else(Vec::new, |cl| cl.into_inner()), generics: ast.generics.clone(), methods: field, name: attr @@ -87,6 +87,7 @@ fn expand_on_type_alias( scalar, } .to_token_stream(); + Ok(quote! { #ast #def diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index 7ab032509..c41c14137 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -96,7 +96,7 @@ pub fn expand(input: TokenStream) -> syn::Result { to_output, from_input, parse_token, - field, + field: Box::new(field), } } }; @@ -107,7 +107,7 @@ pub fn expand(input: TokenStream) -> syn::Result { ty: TypeOrIdent::Ident(ast.ident.clone()), where_clause: attr .where_clause - .map_or_else(|| Vec::new(), |cl| cl.into_inner()), + .map_or_else(Vec::new, |cl| cl.into_inner()), generics: ast.generics.clone(), methods: field, name: attr @@ -121,440 +121,3 @@ pub fn expand(input: TokenStream) -> syn::Result { } .to_token_stream()) } - -// /// Definition of [GraphQL scalar][1] for code generation. -// /// -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// struct Definition { -// /// Name of this [GraphQL scalar][1] in GraphQL schema. -// /// -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// name: String, -// -// /// Rust type [`Ident`] that this [GraphQL scalar][1] is represented with. -// /// -// /// [`Ident`]: syn::Ident -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// ident: syn::Ident, -// -// /// Generics of the Rust type that this [GraphQL scalar][1] is implemented -// /// for. -// /// -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// generics: syn::Generics, -// -// /// [`GraphQLScalarDefinition`] representing [GraphQL scalar][1]. -// /// -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// methods: GraphQLScalarMethods, -// -// /// Description of this [GraphQL scalar][1] to put into GraphQL schema. -// /// -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// description: Option, -// -// /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. -// /// -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// specified_by_url: Option, -// -// /// [`ScalarValue`] parametrization to generate [`GraphQLType`] -// /// implementation with for this [GraphQL scalar][1]. -// /// -// /// [`GraphQLType`]: juniper::GraphQLType -// /// [`ScalarValue`]: juniper::ScalarValue -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// scalar: scalar::Type, -// } -// -// impl ToTokens for Definition { -// fn to_tokens(&self, into: &mut TokenStream) { -// self.impl_output_and_input_type_tokens().to_tokens(into); -// self.impl_type_tokens().to_tokens(into); -// self.impl_value_tokens().to_tokens(into); -// self.impl_value_async_tokens().to_tokens(into); -// self.impl_to_input_value_tokens().to_tokens(into); -// self.impl_from_input_value_tokens().to_tokens(into); -// self.impl_parse_scalar_value_tokens().to_tokens(into); -// self.impl_graphql_scalar_tokens().to_tokens(into); -// self.impl_reflection_traits_tokens().to_tokens(into); -// } -// } -// -// impl Definition { -// /// Returns generated code implementing [`marker::IsInputType`] and -// /// [`marker::IsOutputType`] trait for this [GraphQL scalar][1]. -// /// -// /// [`marker::IsInputType`]: juniper::marker::IsInputType -// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// #[must_use] -// fn impl_output_and_input_type_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ident#ty_gens -// #where_clause { } -// -// #[automatically_derived] -// impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ident#ty_gens -// #where_clause { } -// } -// } -// -// /// Returns generated code implementing [`GraphQLType`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`GraphQLType`]: juniper::GraphQLType -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_type_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// let name = &self.name; -// -// let description = self -// .description -// .as_ref() -// .map(|val| quote! { .description(#val) }); -// let specified_by_url = self.specified_by_url.as_ref().map(|url| { -// let url_lit = url.as_str(); -// quote! { .specified_by_url(#url_lit) } -// }); -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::GraphQLType<#scalar> for #ident#ty_gens -// #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, -// { -// registry.build_scalar_type::(info) -// #description -// #specified_by_url -// .into_meta() -// } -// } -// } -// } -// -// /// Returns generated code implementing [`GraphQLValue`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`GraphQLValue`]: juniper::GraphQLValue -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_value_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let resolve = self.methods.expand_resolve(scalar); -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ident#ty_gens -// #where_clause -// { -// type Context = (); -// type TypeInfo = (); -// -// fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { -// >::name(info) -// } -// -// fn resolve( -// &self, -// info: &(), -// selection: Option<&[::juniper::Selection<#scalar>]>, -// executor: &::juniper::Executor, -// ) -> ::juniper::ExecutionResult<#scalar> { -// #resolve -// } -// } -// } -// } -// -// /// Returns generated code implementing [`GraphQLValueAsync`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_value_async_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let generics = self.impl_generics(true); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ident#ty_gens -// #where_clause -// { -// fn resolve_async<'b>( -// &'b self, -// info: &'b Self::TypeInfo, -// selection_set: Option<&'b [::juniper::Selection<#scalar>]>, -// executor: &'b ::juniper::Executor, -// ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { -// use ::juniper::futures::future; -// let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); -// Box::pin(future::ready(v)) -// } -// } -// } -// } -// -// /// Returns generated code implementing [`InputValue`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`InputValue`]: juniper::InputValue -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_to_input_value_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let to_input_value = self.methods.expand_to_input_value(scalar); -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::ToInputValue<#scalar> for #ident#ty_gens -// #where_clause -// { -// fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { -// #to_input_value -// } -// } -// } -// } -// -// /// Returns generated code implementing [`FromInputValue`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`FromInputValue`]: juniper::FromInputValue -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_from_input_value_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let error_ty = self.methods.expand_from_input_err(scalar); -// let from_input_value = self.methods.expand_from_input(scalar); -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::FromInputValue<#scalar> for #ident#ty_gens -// #where_clause -// { -// type Error = #error_ty; -// -// fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { -// #from_input_value -// } -// } -// } -// } -// -// /// Returns generated code implementing [`ParseScalarValue`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`ParseScalarValue`]: juniper::ParseScalarValue -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_parse_scalar_value_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let from_str = self.methods.expand_parse_token(scalar); -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ident#ty_gens -// #where_clause -// { -// fn from_str( -// token: ::juniper::parser::ScalarToken<'_>, -// ) -> ::juniper::ParseScalarResult<'_, #scalar> { -// #from_str -// } -// } -// } -// } -// -// /// Returns generated code implementing [`GraphQLScalar`] trait for this -// /// [GraphQL scalar][1]. -// /// -// /// [`GraphQLScalar`]: juniper::GraphQLScalar -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_graphql_scalar_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// let to_output = self.methods.expand_to_output(scalar); -// let from_input_err = self.methods.expand_from_input_err(scalar); -// let from_input = self.methods.expand_from_input(scalar); -// let parse_token = self.methods.expand_parse_token(scalar); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::GraphQLScalar<#scalar> for #ident#ty_gens -// #where_clause -// { -// type Error = #from_input_err; -// -// fn to_output(&self) -> ::juniper::Value<#scalar> { -// #to_output -// } -// -// fn from_input( -// input: &::juniper::InputValue<#scalar> -// ) -> Result { -// #from_input -// } -// -// fn parse_token( -// token: ::juniper::ScalarToken<'_> -// ) -> ::juniper::ParseScalarResult<'_, #scalar> { -// #parse_token -// } -// } -// } -// } -// -// /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and -// /// [`WrappedType`] traits for this [GraphQL scalar][1]. -// /// -// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes -// /// [`BaseType`]: juniper::macros::reflection::BaseType -// /// [`WrappedType`]: juniper::macros::reflection::WrappedType -// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars -// fn impl_reflection_traits_tokens(&self) -> TokenStream { -// let ident = &self.ident; -// let scalar = &self.scalar; -// let name = &self.name; -// -// let generics = self.impl_generics(false); -// let (impl_gens, _, where_clause) = generics.split_for_impl(); -// let (_, ty_gens, _) = self.generics.split_for_impl(); -// -// quote! { -// #[automatically_derived] -// impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ident#ty_gens -// #where_clause -// { -// const NAME: ::juniper::macros::reflect::Type = #name; -// } -// -// #[automatically_derived] -// impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident#ty_gens -// #where_clause -// { -// const NAMES: ::juniper::macros::reflect::Types = -// &[>::NAME]; -// } -// -// #[automatically_derived] -// impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ident#ty_gens -// #where_clause -// { -// const VALUE: ::juniper::macros::reflect::WrappedValue = 1; -// } -// } -// } -// -// /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and -// /// similar) implementation of this enum. -// /// -// /// If `for_async` is `true`, then additional predicates are added to suit -// /// the [`GraphQLAsyncValue`] trait (and similar) requirements. -// /// -// /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue -// /// [`GraphQLType`]: juniper::GraphQLType -// #[must_use] -// fn impl_generics(&self, for_async: bool) -> syn::Generics { -// let mut generics = self.generics.clone(); -// -// let scalar = &self.scalar; -// if scalar.is_implicit_generic() { -// generics.params.push(parse_quote! { #scalar }); -// } -// if scalar.is_generic() { -// generics -// .make_where_clause() -// .predicates -// .push(parse_quote! { #scalar: ::juniper::ScalarValue }); -// } -// if let Some(bound) = scalar.bounds() { -// generics.make_where_clause().predicates.push(bound); -// } -// -// if for_async { -// let self_ty = if self.generics.lifetimes().next().is_some() { -// // Modify lifetime names to omit "lifetime name `'a` shadows a -// // lifetime name that is already in scope" error. -// let mut generics = self.generics.clone(); -// for lt in generics.lifetimes_mut() { -// let ident = lt.lifetime.ident.unraw(); -// lt.lifetime.ident = format_ident!("__fa__{}", ident); -// } -// -// let lifetimes = generics.lifetimes().map(|lt| <.lifetime); -// let ty = &self.ident; -// let (_, ty_generics, _) = generics.split_for_impl(); -// -// quote! { for<#( #lifetimes ),*> #ty#ty_generics } -// } else { -// quote! { Self } -// }; -// generics -// .make_where_clause() -// .predicates -// .push(parse_quote! { #self_ty: Sync }); -// -// if scalar.is_generic() { -// generics -// .make_where_clause() -// .predicates -// .push(parse_quote! { #scalar: Send + Sync }); -// } -// } -// -// generics -// } -// } diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 724db158f..bb16d116c 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -28,8 +28,10 @@ use crate::{ pub mod attr; pub mod derive; -/// Available arguments behind `#[graphql]` attribute when generating -/// code for `#[derive(GraphQLScalar)]`. +/// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when +/// generating code for [GraphQL scalar][1]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Scalars #[derive(Debug, Default)] struct Attr { /// Name of this [GraphQL scalar][1] in GraphQL schema. @@ -90,7 +92,7 @@ struct Attr { /// [`Self::parse_token`]. with: Option>, - /// TODO + /// Explicit where clause added to [`syn::WhereClause`]. where_clause: Option>>, } @@ -277,9 +279,14 @@ impl Attr { } } +/// [`syn::Type`] in case of `#[graphql_scalar]` or [`syn::Ident`] in case of +/// `#[derive(GraphQLScalar)]`. #[derive(Clone)] enum TypeOrIdent { - Type(syn::Type), + /// [`syn::Type`]. + Type(Box), + + /// [`syn::Ident`]. Ident(syn::Ident), } @@ -292,9 +299,12 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021/#sec-Scalars name: String, - /// TODO + /// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Scalars ty: TypeOrIdent, + /// Additional [`Self::generics`] [`syn::WhereClause`] predicates. where_clause: Vec, /// Generics of the Rust type that this [GraphQL scalar][1] is implemented @@ -336,7 +346,6 @@ impl ToTokens for Definition { self.impl_to_input_value_tokens().to_tokens(into); self.impl_from_input_value_tokens().to_tokens(into); self.impl_parse_scalar_value_tokens().to_tokens(into); - self.impl_graphql_scalar_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); } } @@ -512,8 +521,8 @@ impl Definition { fn impl_from_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let error_ty = self.methods.expand_from_input_err(scalar); - let from_input_value = self.methods.expand_from_input(scalar); + let error_ty = self.methods.expand_from_input_value_err(scalar); + let from_input_value = self.methods.expand_from_input_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -540,7 +549,7 @@ impl Definition { fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let from_str = self.methods.expand_parse_token(scalar); + let from_str = self.methods.expand_parse_scalar_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -559,48 +568,6 @@ impl Definition { } } - /// Returns generated code implementing [`GraphQLScalar`] trait for this - /// [GraphQL scalar][1]. - /// - /// [`GraphQLScalar`]: juniper::GraphQLScalar - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars - fn impl_graphql_scalar_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let (ty, generics) = self.impl_self_and_generics(false); - let (impl_gens, _, where_clause) = generics.split_for_impl(); - - let to_output = self.methods.expand_to_output(scalar); - let from_input_err = self.methods.expand_from_input_err(scalar); - let from_input = self.methods.expand_from_input(scalar); - let parse_token = self.methods.expand_parse_token(scalar); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::GraphQLScalar<#scalar> for #ty - #where_clause - { - type Error = #from_input_err; - - fn to_output(&self) -> ::juniper::Value<#scalar> { - #to_output - } - - fn from_input( - input: &::juniper::InputValue<#scalar> - ) -> Result { - #from_input - } - - fn parse_token( - token: ::juniper::ScalarToken<'_> - ) -> ::juniper::ParseScalarResult<'_, #scalar> { - #parse_token - } - } - } - } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL scalar][1]. /// @@ -683,8 +650,6 @@ impl Definition { if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { - // Modify lifetime names to omit "lifetime name `'a` shadows a - // lifetime name that is already in scope" error. let mut generics = self.generics.clone(); ModifyLifetimes.visit_generics_mut(&mut generics); @@ -721,6 +686,8 @@ impl Definition { } } +/// Adds `__fa__` prefix to all lifetimes to avoid "lifetime name `'a` shadows a +/// lifetime name that is already in scope" error. struct ModifyLifetimes; impl VisitMut for ModifyLifetimes { @@ -766,7 +733,7 @@ enum GraphQLScalarMethods { parse_token: Option, /// [`Field`] to resolve not provided methods. - field: Field, + field: Box, }, } @@ -796,26 +763,6 @@ impl GraphQLScalarMethods { } } - /// Expands [`GraphQLScalar::to_output`] method. - /// - /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output - fn expand_to_output(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { to_output, .. } - | Self::Delegated { - to_output: Some(to_output), - .. - } => { - quote! { #to_output(self) } - } - Self::Delegated { field, .. } => { - quote! { - ::juniper::GraphQLScalar::<#scalar>::to_output(&self.#field) - } - } - } - } - /// Expands [`ToInputValue::to_input_value`] method. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value @@ -840,7 +787,7 @@ impl GraphQLScalarMethods { /// Expands [`FromInputValue::Error`] type. /// /// [`FromInputValue::Error`]: juniper::FromInputValue::Error - fn expand_from_input_err(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { from_input: (_, err), @@ -860,7 +807,7 @@ impl GraphQLScalarMethods { /// Expands [`FromInputValue::from_input_value`][1] method. /// /// [1]: juniper::FromInputValue::from_input_value - fn expand_from_input(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { from_input: (from_input, _), @@ -886,7 +833,7 @@ impl GraphQLScalarMethods { /// Expands [`ParseScalarValue::from_str`] method. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str - fn expand_parse_token(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_parse_scalar_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { parse_token, .. } | Self::Delegated { diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index ef40c4d3e..d05636156 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -185,8 +185,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// struct UserId(String); /// ``` /// -/// All of the methods used from `newtype`'s field can be replaced with attributes -/// mirroring [`GraphQLScalar`] methods: +/// All of the methods used from `newtype`'s field can be replaced with +/// attributes: /// /// #### `#[graphql(to_output_with = )]` attribute /// @@ -200,7 +200,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// /// Increments [`Incremented`] before converting into a [`Value`]. /// fn to_output(v: &Incremented) -> Value { /// let inc = v.0 + 1; -/// inc.to_output() +/// Value::from(inc) /// } /// ``` /// @@ -239,7 +239,10 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// #### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes /// /// ```rust -/// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +/// # use juniper::{ +/// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, +/// # ScalarValue, ScalarToken, Value, +/// # }; /// # /// #[derive(GraphQLScalar)] /// #[graphql( @@ -271,8 +274,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// } /// /// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { -/// >::parse_token(value) -/// .or_else(|_| >::parse_token(value)) +/// >::from_str(value) +/// .or_else(|_| >::from_str(value)) /// } /// ``` /// > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there @@ -284,8 +287,20 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// `to_output`, `from_input`, `parse_token` functions and `Error` struct or /// type alias. /// +/// #### `#[graphql(scalar = )]` +/// +/// Custom implementation (`scalar = `[`DefaultScalarValue`]) or generic bounded +/// [`ScalarValue`] (`scalar = S: Trait`). +/// +/// #### `#[graphql(where = )]` or `#[graphql(where())]` +/// +/// Adds custom generic bounds in [scalar][1] implementation. +/// /// ```rust -/// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +/// # use juniper::{ +/// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, +/// # ScalarValue, ScalarToken, Value, +/// # }; /// # /// #[derive(GraphQLScalar)] /// #[graphql(with = string_or_int)] @@ -323,8 +338,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// where /// S: ScalarValue, /// { -/// >::parse_token(value) -/// .or_else(|_| >::parse_token(value)) +/// >::from_str(value) +/// .or_else(|_| >::from_str(value)) /// } /// } /// # @@ -375,70 +390,82 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// ``` /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars +/// [`DefaultScalarValue`]: juniper::DefaultScalarValue +/// [`ScalarValue`]: juniper::ScalarValue #[proc_macro_error] #[proc_macro_derive(GraphQLScalar, attributes(graphql))] -pub fn derive_scalar_value(input: TokenStream) -> TokenStream { +pub fn derive_scalar(input: TokenStream) -> TokenStream { graphql_scalar::derive::expand(input.into()) .unwrap_or_abort() .into() } -/// Expose GraphQL scalars -/// -/// The GraphQL language defines a number of built-in scalars: strings, numbers, and -/// booleans. This macro can be used either to define new types of scalars (e.g. -/// timestamps), or expose other types as one of the built-in scalars (e.g. bigints -/// as numbers or strings). +/// In case [`GraphQLScalar`] isn't applicable because type located in other +/// crate and you don't want to wrap it in a newtype there is +/// `#[graphql_scalar]` macro. /// -/// Since the preferred transport protocol for GraphQL responses is JSON, most -/// custom scalars will be transferred as strings. You therefore need to ensure that -/// the client library you are sending data to can parse the custom value into a -/// datatype appropriate for that platform. +/// All attributes are mirroring [`GraphQLScalar`] derive macro. /// -/// By default the trait is implemented in terms of the default scalar value -/// representation provided by juniper. If that does not fit your needs it is -/// possible to specify a custom representation. +/// > __NOTE:__ To satisfy [orphan rule] you should provide local +/// > [`ScalarValue`] implementation. /// /// ```rust -/// # use juniper::{graphql_scalar, GraphQLScalar, ParseScalarResult, ScalarToken, ScalarValue, Value}; +/// # mod date { +/// # pub struct Date; +/// # +/// # impl std::str::FromStr for Date { +/// # type Err = String; +/// # +/// # fn from_str(_value: &str) -> Result { +/// # unimplemented!() +/// # } +/// # } +/// # +/// # impl std::fmt::Display for Date { +/// # fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { +/// # unimplemented!() +/// # } +/// # } +/// # } /// # -/// // The data type -/// struct UserID(String); +/// # use juniper::DefaultScalarValue as CustomScalarValue; +/// use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; /// /// #[graphql_scalar( -/// // You can rename the type for GraphQL by specifying the name here. -/// name = "MyName", -/// // You can also specify a description here. -/// // If present, doc comments will be ignored. -/// description = "An opaque identifier, represented as a string", -/// // A specification URL. -/// specified_by_url = "https://tools.ietf.org/html/rfc4122", +/// with = date_scalar, +/// parse_token = String, +/// scalar = CustomScalarValue, +/// // ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation. /// )] -/// impl GraphQLScalar for UserID { -/// // NOTE: The Error type should implement `IntoFieldError`. -/// type Error = String; -/// -/// fn to_output(&self) -> Value { -/// Value::scalar(self.0.to_owned()) -/// } -/// -/// fn from_input(value: &juniper::InputValue) -> Result { -/// value.as_string_value() -/// .map(|s| Self(s.to_owned())) -/// .ok_or_else(|| format!("Expected `String`, found: {}", value)) +/// type Date = date::Date; +/// // ^^^^^^^^^^ Type from another crate. +/// +/// mod date_scalar { +/// use super::*; +/// +/// // Error of the `from_input_value()` method. +/// // NOTE: Should implement `IntoFieldError`. +/// pub(super) type Error = String; +/// +/// // Define how to convert your custom scalar into a primitive type. +/// pub(super) fn to_output(v: &Date) -> Value { +/// Value::scalar(v.to_string()) /// } -/// -/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { -/// >::from_str(value) +/// +/// // Define how to parse a primitive type into your custom scalar. +/// pub(super) fn from_input(v: &InputValue) -> Result { +/// v.as_string_value() +/// .ok_or_else(|| format!("Expected `String`, found: {}", v)) +/// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) /// } /// } -/// +/// # /// # fn main() { } /// ``` /// -/// In addition to implementing `GraphQLType` for the type in question, -/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type -/// usable as arguments and default values. +/// [orphan rule]: https://bit.ly/3glAGC2 +/// [`GraphQLScalar`]: juniper::GraphQLScalar +/// [`ScalarValue`]: juniper::ScalarValue #[proc_macro_error] #[proc_macro_attribute] pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { From 5b968616e56372f49844c9bd28986407ce678c5c Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 4 Feb 2022 13:36:50 +0300 Subject: [PATCH 079/122] Correction --- juniper/CHANGELOG.md | 2 +- juniper/src/integrations/bson.rs | 12 ++---------- juniper/src/integrations/chrono_tz.rs | 1 - 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index b19564da9..3216aee10 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -23,7 +23,7 @@ - Support coercion of additional nullable arguments and return sub-typing on implementer. - Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - Support generic scalars. - - Support single named field structs. + - Support structs with single named field. - Support for overriding resolvers. - Redesign `#[graphql_scalar]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - Use on type aliases only in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index a640ec8bc..28a77cfcd 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -4,11 +4,7 @@ use chrono::prelude::*; use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar( - description = "ObjectId", - with = object_id, - parse_token = String, -)] +#[graphql_scalar(with = object_id, parse_token = String)] type ObjectId = bson::oid::ObjectId; mod object_id { @@ -29,11 +25,7 @@ mod object_id { } } -#[graphql_scalar( - description = "UtcDateTime", - with = utc_date_time, - parse_token = String, -)] +#[graphql_scalar(with = utc_date_time, parse_token = String)] type UtcDateTime = bson::DateTime; mod utc_date_time { diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 1f96cb3be..79b2cf1d7 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -5,7 +5,6 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -/// Timezone #[graphql_scalar(with = tz, parse_token = String)] type Tz = chrono_tz::Tz; From 81bb650c55dabe48091984587b2002ba75d201a2 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 15 Feb 2022 07:54:16 +0300 Subject: [PATCH 080/122] WIP --- docs/book/content/types/scalars.md | 17 +--- .../implementers_duplicate_ugly.stderr | 12 +-- .../fail/scalar/impl_invalid_url.rs | 6 +- .../src/codegen/derive_scalar.rs | 16 +--- .../juniper_tests/src/codegen/impl_scalar.rs | 19 +--- .../juniper_tests/src/custom_scalar.rs | 4 +- juniper/CHANGELOG.md | 2 +- juniper/src/executor_tests/variables.rs | 4 +- juniper/src/integrations/bson.rs | 12 +-- juniper/src/integrations/chrono.rs | 30 ++---- juniper/src/integrations/chrono_tz.rs | 14 +-- juniper/src/integrations/time.rs | 40 +++----- juniper/src/integrations/url.rs | 6 +- juniper/src/integrations/uuid.rs | 6 +- juniper/src/types/containers.rs | 10 +- juniper/src/types/scalars.rs | 20 +--- juniper_codegen/src/graphql_scalar/attr.rs | 24 ++--- juniper_codegen/src/graphql_scalar/mod.rs | 94 +++++-------------- 18 files changed, 105 insertions(+), 231 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index ebe010e20..a897c3127 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -109,8 +109,7 @@ fn to_output(v: &Incremented) -> Value { # #[derive(GraphQLScalar)] #[graphql(scalar = DefaultScalarValue)] -#[graphql(from_input_with = Self::from_input, from_input_err = String)] -// Unfortunately for now there is no way to infer this ^^^^^^ +#[graphql(from_input_with = Self::from_input)] struct UserId(String); impl UserId { @@ -145,7 +144,6 @@ impl UserId { #[graphql( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, parse_token_with = parse_token, // ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)` // which tries to parse as `String` and then as `i32` @@ -197,9 +195,7 @@ enum StringOrInt { mod string_or_int { use super::*; - - pub(super) type Error = String; - + pub(super) fn to_output(v: &StringOrInt) -> Value where S: ScalarValue, @@ -240,7 +236,6 @@ Also, you can partially override `#[graphql(with)]` attribute with other custom #[derive(GraphQLScalar)] #[graphql( with = string_or_int, - from_input_err = String, parse_token(String, i32) )] enum StringOrInt { @@ -307,7 +302,7 @@ use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; #[graphql_scalar( with = date_scalar, - parse_token = String, + parse_token(String), scalar = CustomScalarValue, // ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation. )] @@ -316,14 +311,12 @@ type Date = date::Date; mod date_scalar { use super::*; - - pub(super) type Error = String; - + pub(super) fn to_output(v: &Date) -> Value { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) 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 61340f732..726e30c8d 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr @@ -1,21 +1,21 @@ -error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` --> fail/interface/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here - | conflicting implementation for `ObjA` + | conflicting implementation for `CharacterValueEnum` | - = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here - | conflicting implementation for `CharacterValueEnum` + | conflicting implementation for `ObjA` | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs index 962cec680..61b9c340e 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs @@ -5,22 +5,20 @@ struct ScalarSpecifiedByUrl; #[graphql_scalar( specified_by_url = "not an url", with = scalar, - parse_token = i32, + parse_token(i32), )] type Scalar = ScalarSpecifiedByUrl; mod scalar { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { Value::scalar(0) } pub(super) fn from_input( _: &InputValue, - ) -> Result { + ) -> Result { Ok(ScalarSpecifiedByUrl) } } diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs index 6b3609224..f8a7f66c2 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar.rs @@ -280,10 +280,9 @@ mod delegated_parse_token { #[graphql( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, )] #[graphql( - parse_token = String, + parse_token(String), specified_by_url = "https://tools.ietf.org/html/rfc3339" )] struct CustomDateTime(DateTime) @@ -366,7 +365,6 @@ mod multiple_delegated_parse_token { #[graphql( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, parse_token(String, i32), )] enum StringOrInt { @@ -429,10 +427,9 @@ mod where_attribute { #[graphql( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, )] #[graphql( - parse_token = String, + parse_token(String), where(Tz: From, Tz::Offset: fmt::Display), specified_by_url = "https://tools.ietf.org/html/rfc3339", )] @@ -513,7 +510,6 @@ mod generic_with_all_resolvers { #[graphql( to_output_with = custom_date_time::to_output, from_input_with = custom_date_time::from_input, - from_input_err = custom_date_time::Error, )] #[graphql( parse_token_with = custom_date_time::parse_token, @@ -528,8 +524,6 @@ mod generic_with_all_resolvers { mod custom_date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &CustomDateTime) -> Value where S: ScalarValue, @@ -539,7 +533,7 @@ mod generic_with_all_resolvers { Value::scalar(v.dt.to_rfc3339()) } - pub(super) fn from_input(v: &InputValue) -> Result, Error> + pub(super) fn from_input(v: &InputValue) -> Result, String> where S: ScalarValue, Tz: From + TimeZone, @@ -629,8 +623,6 @@ mod generic_with_module { mod custom_date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &CustomDateTime) -> Value where S: ScalarValue, @@ -640,7 +632,7 @@ mod generic_with_module { Value::scalar(v.dt.to_rfc3339()) } - pub(super) fn from_input(v: &InputValue) -> Result, Error> + pub(super) fn from_input(v: &InputValue) -> Result, String> where S: ScalarValue, Tz: From + TimeZone, diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs index 0761f55ae..4738a61c8 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_scalar.rs @@ -19,7 +19,6 @@ mod all_custom_resolvers { #[graphql_scalar( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, )] #[graphql_scalar( parse_token_with = parse_token, @@ -103,7 +102,6 @@ mod explicit_name { name = "Counter", to_output_with = to_output, from_input_with = from_input, - from_input_err = String, parse_token_with = parse_token, )] type CounterScalar = CustomCounter; @@ -205,8 +203,7 @@ mod delegated_parse_token { #[graphql_scalar( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, - parse_token = i32, + parse_token(i32), )] type Counter = CustomCounter; @@ -285,7 +282,6 @@ mod multiple_delegated_parse_token { #[graphql_scalar( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, parse_token(String, i32), )] type StringOrInt = StringOrIntScalar; @@ -346,8 +342,7 @@ mod where_attribute { #[graphql_scalar( to_output_with = to_output, from_input_with = from_input, - from_input_err = String, - parse_token = String, + parse_token(String), where(Tz: From + TimeZone, Tz::Offset: fmt::Display), specified_by_url = "https://tools.ietf.org/html/rfc3339", )] @@ -428,7 +423,7 @@ mod with_module { #[graphql_scalar( with = custom_date_time, - parse_token = String, + parse_token(String), where(Tz: From + TimeZone, Tz::Offset: fmt::Display), specified_by_url = "https://tools.ietf.org/html/rfc3339", )] @@ -437,8 +432,6 @@ mod with_module { mod custom_date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &CustomDateTime) -> Value where S: ScalarValue, @@ -514,7 +507,7 @@ mod description_from_doc_comment { struct CustomCounter(i32); /// Description - #[graphql_scalar(with = counter, from_input_err = String)] + #[graphql_scalar(with = counter)] type Counter = CustomCounter; mod counter { @@ -603,7 +596,6 @@ mod description_from_attribute { #[graphql_scalar( description = "Description from attribute", with = counter, - from_input_err = String )] type Counter = CustomCounter; @@ -693,7 +685,6 @@ mod custom_scalar { #[graphql_scalar( scalar = MyScalarValue, with = counter, - from_input_err = String )] type Counter = CustomCounter; @@ -783,7 +774,6 @@ mod generic_scalar { #[graphql_scalar( scalar = S: ScalarValue, with = counter, - from_input_err = String )] type Counter = CustomCounter; @@ -873,7 +863,6 @@ mod bounded_generic_scalar { #[graphql_scalar( scalar = S: ScalarValue + Clone, with = counter, - from_input_err = String )] type Counter = CustomCounter; diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index eea960af9..3e0606232 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -281,13 +281,11 @@ type Long = i64; mod long { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Long) -> Value { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value::() .copied() .ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v)) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 3216aee10..e2f7941d7 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -25,7 +25,7 @@ - Support generic scalars. - Support structs with single named field. - Support for overriding resolvers. -- Redesign `#[graphql_scalar]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) +- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) - Use on type aliases only in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). ## Features diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 9243c7d70..20e78488e 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -17,15 +17,13 @@ struct TestComplexScalar; mod test_complex_scalar { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(_: &TestComplexScalar) -> Value { graphql_value!("SerializedValue") } pub(super) fn from_input( v: &InputValue, - ) -> Result { + ) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") .map(|_| TestComplexScalar) diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 28a77cfcd..61a9efd6d 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -4,19 +4,17 @@ use chrono::prelude::*; use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar(with = object_id, parse_token = String)] +#[graphql_scalar(with = object_id, parse_token(String))] type ObjectId = bson::oid::ObjectId; mod object_id { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &ObjectId) -> Value { Value::scalar(v.to_hex()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -25,19 +23,17 @@ mod object_id { } } -#[graphql_scalar(with = utc_date_time, parse_token = String)] +#[graphql_scalar(with = utc_date_time, parse_token(String))] type UtcDateTime = bson::DateTime; mod utc_date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &UtcDateTime) -> Value { Value::scalar((*v).to_chrono().to_rfc3339()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index c2ea59fdb..ff70429f8 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -20,22 +20,20 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; #[graphql_scalar( with = date_time_fixed_offset, - parse_token = String, + parse_token(String), )] type DateTimeFixedOffset = chrono::DateTime; mod date_time_fixed_offset { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &DateTimeFixedOffset) -> Value { Value::scalar(v.to_rfc3339()) } pub(super) fn from_input( v: &InputValue, - ) -> Result { + ) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -47,20 +45,18 @@ mod date_time_fixed_offset { #[graphql_scalar( with = date_time_utc, - parse_token = String + parse_token(String) )] type DateTimeUtc = chrono::DateTime; mod date_time_utc { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &DateTimeUtc) -> Value { Value::scalar(v.to_rfc3339()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -77,20 +73,18 @@ mod date_time_utc { // `NaiveDate` instead." #[graphql_scalar( with = naive_date, - parse_token = String, + parse_token(String), )] type NaiveDate = chrono::NaiveDate; mod naive_date { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &NaiveDate) -> Value { Value::scalar(v.format("%Y-%m-%d").to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -101,20 +95,18 @@ mod naive_date { } #[cfg(feature = "scalar-naivetime")] -#[graphql_scalar(with = naive_time, parse_token = String)] +#[graphql_scalar(with = naive_time, parse_token(String))] type NaiveTime = chrono::NaiveTime; #[cfg(feature = "scalar-naivetime")] mod naive_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &NaiveTime) -> Value { Value::scalar(v.format("%H:%M:%S").to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -126,19 +118,17 @@ mod naive_time { // JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond // datetimes. Values will be truncated to microsecond resolution. -#[graphql_scalar(with = naive_date_time, parse_token = f64)] +#[graphql_scalar(with = naive_date_time, parse_token(f64))] type NaiveDateTime = chrono::NaiveDateTime; mod naive_date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &NaiveDateTime) -> Value { Value::scalar(v.timestamp() as f64) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) .and_then(|f| { diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 79b2cf1d7..d481eb341 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -5,19 +5,17 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar(with = tz, parse_token = String)] +#[graphql_scalar(with = tz, parse_token(String))] type Tz = chrono_tz::Tz; mod tz { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Tz) -> Value { Value::scalar(v.name().to_owned()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -30,19 +28,17 @@ mod tz { #[cfg(test)] mod test { mod from_input_value { - use std::ops::Deref; - use chrono_tz::Tz; - use crate::{graphql_input_value, FromInputValue, InputValue}; + use crate::{graphql_input_value, FromInputValue, InputValue, IntoFieldError}; fn tz_input_test(raw: &'static str, expected: Result) { let input: InputValue = graphql_input_value!((raw)); let parsed = FromInputValue::from_input_value(&input); assert_eq!( - parsed.as_ref().map_err(Deref::deref), - expected.as_ref().map_err(Deref::deref), + parsed.as_ref(), + expected.map_err(IntoFieldError::into_field_error).as_ref(), ); } diff --git a/juniper/src/integrations/time.rs b/juniper/src/integrations/time.rs index 50115366c..2b1e874a4 100644 --- a/juniper/src/integrations/time.rs +++ b/juniper/src/integrations/time.rs @@ -46,7 +46,7 @@ const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] /// [2]: https://docs.rs/time/*/time/struct.Date.html #[graphql_scalar( with = date, - parse_token = String, + parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", )] pub type Date = time::Date; @@ -54,8 +54,6 @@ pub type Date = time::Date; mod date { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Date) -> Value { Value::scalar( v.format(DATE_FORMAT) @@ -63,7 +61,7 @@ mod date { ) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Date::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e))) @@ -99,14 +97,12 @@ const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour] /// /// [1]: https://graphql-scalars.dev/docs/scalars/local-time /// [2]: https://docs.rs/time/*/time/struct.Time.html -#[graphql_scalar(with = local_time, parse_token = String)] +#[graphql_scalar(with = local_time, parse_token(String))] pub type LocalTime = time::Time; mod local_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &LocalTime) -> Value { Value::scalar( if v.millisecond() == 0 { @@ -118,7 +114,7 @@ mod local_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -142,14 +138,12 @@ const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] = /// See also [`time::PrimitiveDateTime`][2] for details. /// /// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html -#[graphql_scalar(with = local_date_time, parse_token = String)] +#[graphql_scalar(with = local_date_time, parse_token(String))] pub type LocalDateTime = time::PrimitiveDateTime; mod local_date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &LocalDateTime) -> Value { Value::scalar( v.format(LOCAL_DATE_TIME_FORMAT) @@ -157,7 +151,7 @@ mod local_date_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -181,7 +175,7 @@ mod local_date_time { /// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html #[graphql_scalar( with = date_time, - parse_token = String, + parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time", )] pub type DateTime = time::OffsetDateTime; @@ -189,8 +183,6 @@ pub type DateTime = time::OffsetDateTime; mod date_time { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &DateTime) -> Value { Value::scalar( v.to_offset(UtcOffset::UTC) @@ -199,7 +191,7 @@ mod date_time { ) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -226,7 +218,7 @@ const UTC_OFFSET_FORMAT: &[FormatItem<'_>] = /// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html #[graphql_scalar( with = utc_offset, - parse_token = String, + parse_token(String), specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset", )] pub type UtcOffset = time::UtcOffset; @@ -234,8 +226,6 @@ pub type UtcOffset = time::UtcOffset; mod utc_offset { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &UtcOffset) -> Value { Value::scalar( v.format(UTC_OFFSET_FORMAT) @@ -243,7 +233,7 @@ mod utc_offset { ) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { @@ -272,7 +262,7 @@ mod date_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -341,7 +331,7 @@ mod local_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -413,7 +403,7 @@ mod local_date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -501,7 +491,7 @@ mod date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -585,7 +575,7 @@ mod utc_offset_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index aa585d642..73c541871 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -2,19 +2,17 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar(with = url_scalar, parse_token = String)] +#[graphql_scalar(with = url_scalar, parse_token(String))] type Url = url::Url; mod url_scalar { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Url) -> Value { Value::scalar(v.as_str().to_owned()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e))) diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 5e7289486..cfc924ef1 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -4,19 +4,17 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar(with = uuid_scalar, parse_token = String)] +#[graphql_scalar(with = uuid_scalar, parse_token(String))] type Uuid = uuid::Uuid; mod uuid_scalar { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Uuid) -> Value { Value::scalar(v.to_string()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e))) diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index 55da456b8..ab5383a4f 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -599,7 +599,7 @@ where #[cfg(test)] mod coercion { - use crate::{graphql_input_value, FromInputValue as _, InputValue}; + use crate::{graphql_input_value, FromInputValue as _, InputValue, IntoFieldError as _}; use super::{FromInputValueArrayError, FromInputValueVecError}; @@ -685,13 +685,13 @@ mod coercion { assert_eq!( >::from_input_value(&v), Err(FromInputValueVecError::Item( - "Expected `Int`, found: null".to_owned(), + "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( >>::from_input_value(&v), Err(FromInputValueVecError::Item( - "Expected `Int`, found: null".to_owned(), + "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( @@ -795,13 +795,13 @@ mod coercion { assert_eq!( <[i32; 3]>::from_input_value(&v), Err(FromInputValueArrayError::Item( - "Expected `Int`, found: null".to_owned(), + "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( >::from_input_value(&v), Err(FromInputValueArrayError::Item( - "Expected `Int`, found: null".to_owned(), + "Expected `Int`, found: null".into_field_error(), )), ); assert_eq!( diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 0468d2808..214c4857d 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -30,13 +30,11 @@ pub struct ID(String); mod id { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &ID) -> Value { Value::scalar(v.0.clone()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .or_else(|| v.as_int_value().map(|i| i.to_string())) @@ -78,13 +76,11 @@ type String = std::string::String; mod string { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &String) -> Value { Value::scalar(v.clone()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .ok_or_else(|| format!("Expected `String`, found: {}", v)) @@ -281,13 +277,11 @@ type Boolean = bool; mod boolean { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Boolean) -> Value { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_scalar_value() .and_then(ScalarValue::as_boolean) .ok_or_else(|| format!("Expected `Boolean`, found: {}", v)) @@ -305,13 +299,11 @@ type Int = i32; mod int { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Int) -> Value { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() .ok_or_else(|| format!("Expected `Int`, found: {}", v)) } @@ -333,13 +325,11 @@ type Float = f64; mod float { use super::*; - pub(super) type Error = String; - pub(super) fn to_output(v: &Float) -> Value { Value::scalar(*v) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_float_value() .ok_or_else(|| format!("Expected `Float`, found: {}", v)) } diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index f4d422286..85679f330 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -36,34 +36,28 @@ fn expand_on_type_alias( let field = match ( attr.to_output.as_deref().cloned(), attr.from_input.as_deref().cloned(), - attr.from_input_err.as_deref().cloned(), attr.parse_token.as_deref().cloned(), attr.with.as_deref().cloned(), ) { - (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token), None) => { + (Some(to_output), Some(from_input), Some(parse_token), None) => { GraphQLScalarMethods::Custom { to_output, - from_input: (from_input, from_input_err), + from_input, parse_token, } } - (to_output, from_input, from_input_err, parse_token, Some(module)) => { - GraphQLScalarMethods::Custom { - to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), - from_input: ( - from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), - from_input_err.unwrap_or_else(|| parse_quote! { #module::Error }), - ), - parse_token: parse_token - .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), - } - } + (to_output, from_input, parse_token, Some(module)) => GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + }, _ => { return Err(ERR.custom_error( ast.span(), "all custom resolvers have to be provided via `with` or \ combination of `to_output_with`, `from_input_with`, \ - `from_input_err_with`, `parse_token_with` attributes", + `parse_token_with` attributes", )); } }; diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index bb16d116c..00cf33026 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -64,27 +64,21 @@ struct Attr { scalar: Option>, /// Explicitly specified function to be used instead of - /// [`GraphQLScalar::to_output`]. + /// [`ToInputValue::to_input_value`]. /// - /// [`GraphQLScalar::to_output`]: juniper::GraphQLScalar::to_output + /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value to_output: Option>, /// Explicitly specified function to be used instead of - /// [`GraphQLScalar::from_input`]. + /// [`FromInputValue::from_input_value`]. /// - /// [`GraphQLScalar::from_input`]: juniper::GraphQLScalar::from_input + /// [`FromInputValue::from_input_value`]: juniper::FromInputValue::from_input_value from_input: Option>, - /// Explicitly specified type to be used instead of - /// [`GraphQLScalar::Error`]. - /// - /// [`GraphQLScalar::Error`]: juniper::GraphQLScalar::Error - from_input_err: Option>, - /// Explicitly specified resolver to be used instead of - /// [`GraphQLScalar::parse_token`]. + /// [`ParseScalarValue::from_str`]. /// - /// [`GraphQLScalar::parse_token`]: juniper::GraphQLScalar::parse_token + /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str parse_token: Option>, /// Explicitly specified module with all custom resolvers for @@ -155,13 +149,6 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } - "from_input_err" => { - input.parse::()?; - let scl = input.parse::()?; - out.from_input_err - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } "parse_token_with" => { input.parse::()?; let scl = input.parse::()?; @@ -174,27 +161,20 @@ impl Parse for Attr { .none_or_else(|_| err::dup_arg(&ident))? } "parse_token" => { - let (span, parsed_types) = if input.parse::().is_ok() { - let scl = input.parse::()?; - (scl.span(), vec![scl]) - } else { - let types; - let _ = syn::parenthesized!(types in input); - let parsed_types = - types.parse_terminated::<_, token::Comma>(syn::Type::parse)?; - - if parsed_types.is_empty() { - return Err(syn::Error::new(ident.span(), "expected at least 1 type.")); - } + let types; + let _ = syn::parenthesized!(types in input); + let parsed_types = + types.parse_terminated::<_, token::Comma>(syn::Type::parse)?; - (parsed_types.span(), parsed_types.into_iter().collect()) - }; + if parsed_types.is_empty() { + return Err(syn::Error::new(ident.span(), "expected at least 1 type.")); + } out.parse_token .replace(SpanContainer::new( ident.span(), - Some(span), - ParseToken::Delegated(parsed_types), + Some(parsed_types.span()), + ParseToken::Delegated(parsed_types.into_iter().collect()), )) .none_or_else(|_| err::dup_arg(&ident))? } @@ -257,7 +237,6 @@ impl Attr { scalar: try_merge_opt!(scalar: self, another), to_output: try_merge_opt!(to_output: self, another), from_input: try_merge_opt!(from_input: self, another), - from_input_err: try_merge_opt!(from_input_err: self, another), parse_token: try_merge_opt!(parse_token: self, another), with: try_merge_opt!(with: self, another), where_clause: try_merge_opt!(where_clause: self, another), @@ -313,7 +292,7 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021/#sec-Scalars generics: syn::Generics, - /// [`GraphQLScalarDefinition`] representing [GraphQL scalar][1]. + /// [`GraphQLScalarMethods`] representing [GraphQL scalar][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars methods: GraphQLScalarMethods, @@ -521,7 +500,6 @@ impl Definition { fn impl_from_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let error_ty = self.methods.expand_from_input_value_err(scalar); let from_input_value = self.methods.expand_from_input_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); @@ -532,10 +510,11 @@ impl Definition { impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty #where_clause { - type Error = #error_ty; + type Error = ::juniper::executor::FieldError<#scalar>; fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { #from_input_value + .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error) } } } @@ -707,9 +686,8 @@ enum GraphQLScalarMethods { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: syn::ExprPath, - /// Function and return type provided with - /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. - from_input: (syn::ExprPath, syn::Type), + /// Function provided with `#[graphql(from_input_with = ...)]`. + from_input: syn::ExprPath, /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` /// or `#[graphql(parse_token(...))]`. @@ -724,9 +702,8 @@ enum GraphQLScalarMethods { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: Option, - /// Function and return type provided with - /// `#[graphql(from_input_with = ..., from_input_err = ...)]`. - from_input: Option<(syn::ExprPath, syn::Type)>, + /// Function provided with `#[graphql(from_input_with = ...)]`. + from_input: Option, /// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]` /// or `#[graphql(parse_token(...))]`. @@ -784,37 +761,14 @@ impl GraphQLScalarMethods { } } - /// Expands [`FromInputValue::Error`] type. - /// - /// [`FromInputValue::Error`]: juniper::FromInputValue::Error - fn expand_from_input_value_err(&self, scalar: &scalar::Type) -> TokenStream { - match self { - Self::Custom { - from_input: (_, err), - .. - } - | Self::Delegated { - from_input: Some((_, err)), - .. - } => quote! { #err }, - Self::Delegated { field, .. } => { - let field_ty = field.ty(); - quote! { <#field_ty as ::juniper::FromInputValue<#scalar>>::Error } - } - } - } - /// Expands [`FromInputValue::from_input_value`][1] method. /// /// [1]: juniper::FromInputValue::from_input_value fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { - Self::Custom { - from_input: (from_input, _), - .. - } + Self::Custom { from_input, .. } | Self::Delegated { - from_input: Some((from_input, _)), + from_input: Some(from_input), .. } => { quote! { #from_input(input) } From b4df74a23d9192a86a4b490cf89ebdc7eca16b83 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 15 Feb 2022 09:16:40 +0300 Subject: [PATCH 081/122] Corrections --- docs/book/content/types/scalars.md | 13 +++- .../fail/scalar/impl_with_resolver.stderr | 2 +- .../fail/scalar/impl_without_resolvers.stderr | 2 +- juniper/src/executor_tests/variables.rs | 2 +- juniper/src/types/scalars.rs | 4 +- juniper_codegen/src/common/parse/mod.rs | 8 +-- juniper_codegen/src/graphql_scalar/derive.rs | 36 +++-------- juniper_codegen/src/graphql_scalar/mod.rs | 3 +- juniper_codegen/src/lib.rs | 59 +++++++------------ juniper_codegen/src/result.rs | 2 +- 10 files changed, 53 insertions(+), 78 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index a897c3127..c9a02548b 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -102,7 +102,7 @@ fn to_output(v: &Incremented) -> Value { # fn main() {} ``` -#### `#[graphql(from_input_with = , from_input_err = )]` attributes +#### `#[graphql(from_input_with = )]` attribute ```rust # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; @@ -181,7 +181,7 @@ fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, #### `#[graphql(with = )]` attribute -Instead of providing all custom resolvers, you can provide module with `to_output`, `from_input`, `parse_token` functions and `Error` struct or type alias. +Instead of providing all custom resolvers, you can provide module with `to_output`, `from_input`, `parse_token` functions. ```rust # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarValue, ScalarToken, Value}; @@ -270,6 +270,15 @@ mod string_or_int { # fn main() {} ``` +#### `#[graphql(scalar = )]` + +Custom implementation (`scalar = DefaultScalarValue`) or generic bounded +[`ScalarValue`] (`scalar = S: Trait`). + +#### `#[graphql(where = )]` or `#[graphql(where())]` + +Adds custom generic bounds in scalar implementation. + --- ### `#[graphql_scalar]` attribute diff --git a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr index 50d3d2a7b..f130068c0 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr +++ b/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `from_input_err_with`, `parse_token_with` attributes +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes --> fail/scalar/impl_with_resolver.rs:6:1 | 6 | type CustomScalar = Scalar; diff --git a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr index ea7a9c368..441debed7 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr +++ b/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `from_input_err_with`, `parse_token_with` attributes +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes --> fail/scalar/impl_without_resolvers.rs:6:1 | 6 | type CustomScalar = Scalar; diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 20e78488e..51a8b0a37 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -11,7 +11,7 @@ use crate::{ }; #[derive(Debug, GraphQLScalar)] -#[graphql(with = test_complex_scalar, parse_token = String)] +#[graphql(with = test_complex_scalar, parse_token(String))] struct TestComplexScalar; mod test_complex_scalar { diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 214c4857d..5e04190dc 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -76,8 +76,8 @@ type String = std::string::String; mod string { use super::*; - pub(super) fn to_output(v: &String) -> Value { - Value::scalar(v.clone()) + pub(super) fn to_output(v: &str) -> Value { + Value::scalar(v.to_owned()) } pub(super) fn from_input(v: &InputValue) -> Result { diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 5f541eaa3..5781a6233 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -159,7 +159,7 @@ impl TypeExt for syn::Type { ty.lifetimes_iter_mut(func) } if let syn::ReturnType::Type(_, ty) = &mut args.output { - (&mut *ty).lifetimes_iter_mut(func) + (*ty).lifetimes_iter_mut(func) } } syn::PathArguments::None => {} @@ -172,7 +172,7 @@ impl TypeExt for syn::Type { | T::Group(syn::TypeGroup { elem, .. }) | T::Paren(syn::TypeParen { elem, .. }) | T::Ptr(syn::TypePtr { elem, .. }) - | T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_iter_mut(func), + | T::Slice(syn::TypeSlice { elem, .. }) => (*elem).lifetimes_iter_mut(func), T::Tuple(syn::TypeTuple { elems, .. }) => { for ty in elems.iter_mut() { @@ -199,7 +199,7 @@ impl TypeExt for syn::Type { if let Some(lt) = ref_ty.lifetime.as_mut() { func(lt) } - (&mut *ref_ty.elem).lifetimes_iter_mut(func) + (*ref_ty.elem).lifetimes_iter_mut(func) } T::Path(ty) => iter_path(&mut ty.path, func), @@ -228,7 +228,7 @@ impl TypeExt for syn::Type { fn topmost_ident(&self) -> Option<&syn::Ident> { match self.unparenthesized() { syn::Type::Path(p) => Some(&p.path), - syn::Type::Reference(r) => match (&*r.elem).unparenthesized() { + syn::Type::Reference(r) => match (*r.elem).unparenthesized() { syn::Type::Path(p) => Some(&p.path), syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() { syn::TypeParamBound::Trait(b) => Some(&b.path), diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index c41c14137..73c6d217a 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -20,41 +20,23 @@ pub fn expand(input: TokenStream) -> syn::Result { let field = match ( attr.to_output.as_deref().cloned(), attr.from_input.as_deref().cloned(), - attr.from_input_err.as_deref().cloned(), attr.parse_token.as_deref().cloned(), attr.with.as_deref().cloned(), ) { - (Some(to_output), Some(from_input), Some(from_input_err), Some(parse_token), None) => { + (Some(to_output), Some(from_input), Some(parse_token), None) => { GraphQLScalarMethods::Custom { to_output, - from_input: (from_input, from_input_err), + from_input, parse_token, } } - (to_output, from_input, from_input_err, parse_token, Some(module)) => { - GraphQLScalarMethods::Custom { - to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), - from_input: ( - from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), - from_input_err.unwrap_or_else(|| parse_quote! { #module::Error }), - ), - parse_token: parse_token - .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), - } - } - (to_output, from_input, from_input_err, parse_token, None) => { - let from_input = match (from_input, from_input_err) { - (Some(from_input), Some(err)) => Some((from_input, err)), - (None, None) => None, - _ => { - return Err(ERR.custom_error( - ast.span(), - "`from_input_with` attribute should be provided in \ - tandem with `from_input_err`", - )) - } - }; - + (to_output, from_input, parse_token, Some(module)) => GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + }, + (to_output, from_input, parse_token, None) => { let data = if let syn::Data::Struct(data) = &ast.data { data } else { diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 00cf33026..6907dc703 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -82,8 +82,7 @@ struct Attr { parse_token: Option>, /// Explicitly specified module with all custom resolvers for - /// [`Self::to_output`], [`Self::from_input`], [`Self::from_input_err`] and - /// [`Self::parse_token`]. + /// [`Self::to_output`], [`Self::from_input`] and [`Self::parse_token`]. with: Option>, /// Explicit where clause added to [`syn::WhereClause`]. diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index d05636156..129e3495c 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -204,15 +204,14 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// #### `#[graphql(from_input_with = , from_input_err = )]` attributes +/// #### `#[graphql(from_input_with = )]` attribute /// /// ```rust /// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; /// # /// #[derive(GraphQLScalar)] /// #[graphql(scalar = DefaultScalarValue)] -/// #[graphql(from_input_with = Self::from_input, from_input_err = String)] -/// // Unfortunately for now there is no way to infer this ^^^^^^ +/// #[graphql(from_input_with = Self::from_input)] /// struct UserId(String); /// /// impl UserId { @@ -248,7 +247,6 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// #[graphql( /// to_output_with = to_output, /// from_input_with = from_input, -/// from_input_err = String, /// parse_token_with = parse_token, /// // ^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)` /// // which tries to parse as `String` and then as `i32` @@ -284,17 +282,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// #### `#[graphql(with = )]` attribute /// /// Instead of providing all custom resolvers, you can provide module with -/// `to_output`, `from_input`, `parse_token` functions and `Error` struct or -/// type alias. -/// -/// #### `#[graphql(scalar = )]` -/// -/// Custom implementation (`scalar = `[`DefaultScalarValue`]) or generic bounded -/// [`ScalarValue`] (`scalar = S: Trait`). -/// -/// #### `#[graphql(where = )]` or `#[graphql(where())]` -/// -/// Adds custom generic bounds in [scalar][1] implementation. +/// `to_output`, `from_input`, `parse_token` functions. /// /// ```rust /// # use juniper::{ @@ -312,32 +300,21 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// mod string_or_int { /// # use super::*; /// # -/// pub(super) type Error = String; -/// -/// pub(super) fn to_output(v: &StringOrInt) -> Value -/// where -/// S: ScalarValue, -/// { +/// pub(super) fn to_output(v: &StringOrInt) -> Value { /// match v { /// StringOrInt::String(str) => Value::scalar(str.to_owned()), /// StringOrInt::Int(i) => Value::scalar(*i), /// } /// } /// -/// pub(super) fn from_input(v: &InputValue) -> Result -/// where -/// S: ScalarValue, -/// { +/// pub(super) fn from_input(v: &InputValue) -> Result { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.to_owned())) /// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) /// } /// -/// pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> -/// where -/// S: ScalarValue, -/// { +/// pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { /// >::from_str(value) /// .or_else(|_| >::from_str(value)) /// } @@ -349,12 +326,14 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// Also you can partially override `with` attribute with other custom scalars. /// /// ```rust -/// # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; +/// # use juniper::{ +/// # GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, +/// # ScalarToken, Value +/// # }; /// # /// #[derive(GraphQLScalar)] /// #[graphql( /// with = string_or_int, -/// from_input_err = String, /// parse_token(String, i32) /// )] /// enum StringOrInt { @@ -389,6 +368,15 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// # fn main() {} /// ``` /// +/// #### `#[graphql(scalar = )]` +/// +/// Custom implementation (`scalar = `[`DefaultScalarValue`]) or generic bounded +/// [`ScalarValue`] (`scalar = S: Trait`). +/// +/// #### `#[graphql(where = )]` or `#[graphql(where())]` +/// +/// Adds custom generic bounds in [scalar][1] implementation. +/// /// [1]: https://spec.graphql.org/October2021/#sec-Scalars /// [`DefaultScalarValue`]: juniper::DefaultScalarValue /// [`ScalarValue`]: juniper::ScalarValue @@ -433,7 +421,7 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// /// #[graphql_scalar( /// with = date_scalar, -/// parse_token = String, +/// parse_token(String), /// scalar = CustomScalarValue, /// // ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation. /// )] @@ -443,17 +431,14 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// mod date_scalar { /// use super::*; /// -/// // Error of the `from_input_value()` method. -/// // NOTE: Should implement `IntoFieldError`. -/// pub(super) type Error = String; -/// /// // Define how to convert your custom scalar into a primitive type. /// pub(super) fn to_output(v: &Date) -> Value { /// Value::scalar(v.to_string()) /// } /// /// // Define how to parse a primitive type into your custom scalar. -/// pub(super) fn from_input(v: &InputValue) -> Result { +/// // NOTE: The error type should implement `IntoFieldError`. +/// pub(super) fn from_input(v: &InputValue) -> Result { /// v.as_string_value() /// .ok_or_else(|| format!("Expected `String`, found: {}", v)) /// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e))) diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index f15e180ef..e9b250682 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -119,7 +119,7 @@ impl GraphQLScope { duplicates .into_iter() .for_each(|dup| { - (&dup.spanned[1..]) + let _ = &dup.spanned[1..] .iter() .for_each(|spanned| { Diagnostic::spanned( From aed11f9359dc724977c884e3d9b146e15aff1706 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 15 Feb 2022 10:17:50 +0300 Subject: [PATCH 082/122] Corrections --- juniper_codegen/src/derive_scalar_value.rs | 14 ++++++++------ juniper_codegen/src/lib.rs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 385b5d1ba..30bcec139 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -1,19 +1,19 @@ //! Code generation for `#[derive(GraphQLScalarValue)]` macro. -//! + use std::{collections::HashMap, convert::TryFrom}; use proc_macro2::{Literal, TokenStream}; -use quote::{quote, ToTokens, TokenStreamExt}; +use quote::{quote, ToTokens, TokenStreamExt as _}; use syn::{ parse::{Parse, ParseStream}, parse_quote, - spanned::Spanned, + spanned::Spanned as _, token, visit::Visit, }; use crate::{ - common::parse::{attr::err, ParseBufferExt}, + common::parse::{attr::err, ParseBufferExt as _}, util::{filter_attrs, span_container::SpanContainer}, GraphQLScope, }; @@ -205,7 +205,7 @@ impl Definition { ), ]; let methods = methods.iter().map(|(m, sig, def)| { - let arms = self.methods.get(&m).into_iter().flatten().map(|v| { + let arms = self.methods.get(m).into_iter().flatten().map(|v| { let arm = v.match_arm(); let call = v.expr.as_ref().map_or(def.clone(), |f| quote! { #f(v) }); quote! { #arm => Some(#call), } @@ -223,7 +223,7 @@ impl Definition { quote! { #[automatically_derived] impl#impl_gens ::juniper::ScalarValue for #ident#ty_gens - #where_clause + #where_clause { #(#methods)* } @@ -445,6 +445,8 @@ impl<'ast, 'gen> Visit<'ast> for IsVariantGeneric<'gen> { }); if is_generic { self.res = true; + } else { + syn::visit::visit_path(self, path); } } } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 5e20016ca..aa79f6e24 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -401,7 +401,7 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// # use std::{fmt, convert::TryInto as _}; /// # /// # use serde::{de, Deserialize, Deserializer, Serialize}; -/// # use juniper::{ScalarValue, GraphQLScalarValue}; +/// # use juniper::GraphQLScalarValue; /// # /// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] /// #[serde(untagged)] From a5cf7134d3b1ce4dcdc615333c5dc1d4eb2d2599 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 15 Feb 2022 10:23:03 +0300 Subject: [PATCH 083/122] CHANGELOG --- juniper/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index e2f7941d7..7181fc63f 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -41,6 +41,7 @@ - Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005)) +- Implement `#[derive(GraphQLScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) ## Fixes From 5cc24db64ecd9da2ec03696fef95c63c3a056702 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 15 Feb 2022 10:23:59 +0300 Subject: [PATCH 084/122] CHANGELOG --- juniper/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 7181fc63f..40231513a 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -27,6 +27,7 @@ - Support for overriding resolvers. - Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) - Use on type aliases only in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). +- Redesign `#[derive(GraphQLScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) ## Features @@ -41,7 +42,6 @@ - Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005)) -- Implement `#[derive(GraphQLScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) ## Fixes From ea6c4f77f2c8fc031d188bd5632144c9fe63fbe4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 17 Feb 2022 15:24:41 +0300 Subject: [PATCH 085/122] WIP --- .../derive_incompatible_object.stderr | 8 +- .../additional_non_nullable_argument.stderr | 7 - .../field_non_output_return_type.stderr | 7 - .../fail/interface/missing_field.stderr | 7 - .../interface/missing_field_argument.stderr | 7 - .../fail/interface/non_subtype_return.stderr | 7 - .../additional_non_nullable_argument.rs | 20 + .../additional_non_nullable_argument.stderr | 7 + .../struct/field_double_underscored.rs | 8 + .../struct/field_double_underscored.stderr | 7 + .../struct/field_non_output_return_type.rs | 13 + .../field_non_output_return_type.stderr | 5 + .../fail/interface/struct/fields_duplicate.rs | 11 + .../interface/struct/fields_duplicate.stderr | 12 + .../struct/implementers_duplicate_pretty.rs | 15 + .../implementers_duplicate_pretty.stderr | 11 + .../struct/implementers_duplicate_ugly.rs | 17 + .../struct/implementers_duplicate_ugly.stderr | 18 + .../fail/interface/struct/missing_field.rs | 15 + .../interface/struct/missing_field.stderr | 7 + .../fail/interface/struct/missing_for_attr.rs | 14 + .../interface/struct/missing_for_attr.stderr | 7 + .../interface/struct/missing_impl_attr.rs | 14 + .../interface/struct/missing_impl_attr.stderr | 7 + .../struct/name_double_underscored.rs | 8 + .../struct/name_double_underscored.stderr | 7 + .../fail/interface/struct/no_fields.rs | 6 + .../fail/interface/struct/no_fields.stderr | 7 + .../interface/struct/non_subtype_return.rs | 15 + .../struct/non_subtype_return.stderr | 7 + .../additional_non_nullable_argument.rs | 0 .../additional_non_nullable_argument.stderr | 7 + .../argument_double_underscored.rs | 0 .../argument_double_underscored.stderr | 2 +- .../{ => trait}/argument_non_input_type.rs | 0 .../argument_non_input_type.stderr | 12 +- .../argument_wrong_default_array.rs | 0 .../argument_wrong_default_array.stderr | 2 +- .../{ => trait}/field_double_underscored.rs | 0 .../field_double_underscored.stderr | 2 +- .../field_non_output_return_type.rs | 0 .../trait/field_non_output_return_type.stderr | 5 + .../interface/{ => trait}/fields_duplicate.rs | 0 .../{ => trait}/fields_duplicate.stderr | 2 +- .../implementers_duplicate_pretty.rs | 0 .../implementers_duplicate_pretty.stderr | 4 +- .../implementers_duplicate_ugly.rs | 0 .../implementers_duplicate_ugly.stderr | 13 +- .../{ => trait}/method_default_impl.rs | 0 .../{ => trait}/method_default_impl.stderr | 2 +- .../interface/{ => trait}/missing_field.rs | 0 .../fail/interface/trait/missing_field.stderr | 7 + .../{ => trait}/missing_field_argument.rs | 0 .../trait/missing_field_argument.stderr | 7 + .../interface/{ => trait}/missing_for_attr.rs | 0 .../{ => trait}/missing_for_attr.stderr | 4 +- .../{ => trait}/missing_impl_attr.rs | 0 .../{ => trait}/missing_impl_attr.stderr | 4 +- .../{ => trait}/name_double_underscored.rs | 0 .../name_double_underscored.stderr | 2 +- .../fail/interface/{ => trait}/no_fields.rs | 0 .../interface/{ => trait}/no_fields.stderr | 2 +- .../{ => trait}/non_subtype_return.rs | 0 .../interface/trait/non_subtype_return.stderr | 7 + .../{ => trait}/wrong_argument_type.rs | 0 .../trait/wrong_argument_type.stderr | 7 + .../fail/interface/wrong_argument_type.stderr | 7 - .../attr_field_non_output_return_type.stderr | 8 +- ...derive_field_non_output_return_type.stderr | 10 +- .../argument_non_input_type.stderr | 8 +- .../field_non_output_return_type.stderr | 8 +- .../src/codegen/interface_derive.rs | 2498 +++++++++++++++++ .../juniper_tests/src/codegen/mod.rs | 1 + juniper/src/lib.rs | 4 +- juniper_codegen/src/common/field/arg.rs | 7 +- juniper_codegen/src/common/field/mod.rs | 4 +- juniper_codegen/src/graphql_interface/attr.rs | 24 +- .../src/graphql_interface/derive.rs | 176 ++ juniper_codegen/src/graphql_interface/mod.rs | 134 +- juniper_codegen/src/lib.rs | 8 + juniper_codegen/src/result.rs | 5 +- juniper_codegen/src/util/mod.rs | 6 +- 82 files changed, 3117 insertions(+), 183 deletions(-) delete mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/no_fields.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/additional_non_nullable_argument.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_double_underscored.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_double_underscored.stderr (83%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_non_input_type.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_non_input_type.stderr (65%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_wrong_default_array.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/argument_wrong_default_array.stderr (90%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/field_double_underscored.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/field_double_underscored.stderr (83%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/field_non_output_return_type.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/fields_duplicate.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/fields_duplicate.stderr (84%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_pretty.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_pretty.stderr (70%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_ugly.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/implementers_duplicate_ugly.stderr (62%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/method_default_impl.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/method_default_impl.stderr (82%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_field.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_field_argument.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_for_attr.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_for_attr.stderr (82%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_impl_attr.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/missing_impl_attr.stderr (80%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/name_double_underscored.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/name_double_underscored.stderr (83%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/no_fields.rs (100%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/no_fields.stderr (79%) rename integration_tests/codegen_fail/fail/interface/{ => trait}/non_subtype_return.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr rename integration_tests/codegen_fail/fail/interface/{ => trait}/wrong_argument_type.rs (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr create mode 100644 integration_tests/juniper_tests/src/codegen/interface_derive.rs create mode 100644 juniper_codegen/src/graphql_interface/derive.rs diff --git a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr b/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr index fd21efcca..53ac2dee6 100644 --- a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr +++ b/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr @@ -1,10 +1,8 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied - --> fail/input-object/derive_incompatible_object.rs:6:10 - | -6 | #[derive(juniper::GraphQLInputObject)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` + --> fail/input-object/derive_incompatible_object.rs:8:12 | - = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) +8 | field: ObjectA, + | ^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_object.rs:6:10 diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr deleted file mode 100644 index 589ed21b5..000000000 --- a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/additional_non_nullable_argument.rs:14:1 - | -14 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr deleted file mode 100644 index 5bd7c8c9c..000000000 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/field_non_output_return_type.rs:8:1 - | -8 | #[graphql_interface] - | ^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/missing_field.stderr deleted file mode 100644 index d0efa3518..000000000 --- a/integration_tests/codegen_fail/fail/interface/missing_field.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/missing_field.rs:9:1 - | -9 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/missing_field.rs:9:1 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr deleted file mode 100644 index 719cfd1a8..000000000 --- a/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/missing_field_argument.rs:14:1 - | -14 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/missing_field_argument.rs:14:1 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr deleted file mode 100644 index ca48f8464..000000000 --- a/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/non_subtype_return.rs:9:1 - | -9 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/non_subtype_return.rs:9:1 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.rs new file mode 100644 index 000000000..5636cc177 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.rs @@ -0,0 +1,20 @@ +use juniper::{graphql_object, GraphQLInterface}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, is_present: bool) -> &str { + is_present.then(|| self.id.as_str()).unwrap_or("missing") + } +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr new file mode 100644 index 000000000..d4b80722e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/additional_non_nullable_argument.rs:17:5 + | +17 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/additional_non_nullable_argument.rs:17:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.rs new file mode 100644 index 000000000..5be56fff9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character { + __id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr new file mode 100644 index 000000000..850f63ccb --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/field_double_underscored.rs:5:5 + | +5 | __id: String, + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.rs new file mode 100644 index 000000000..6e590c9c6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.rs @@ -0,0 +1,13 @@ +use juniper::{GraphQLInputObject, GraphQLInterface}; + +#[derive(GraphQLInputObject)] +pub struct ObjB { + id: i32, +} + +#[derive(GraphQLInterface)] +struct Character { + id: ObjB, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr new file mode 100644 index 000000000..b282513cf --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/struct/field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.rs new file mode 100644 index 000000000..1080e978a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.rs @@ -0,0 +1,11 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character { + id: String, + + #[graphql(name = "id")] + id2: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr new file mode 100644 index 000000000..2f5690ec4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr @@ -0,0 +1,12 @@ +error: GraphQL interface must have a different name for each field + --> fail/interface/struct/fields_duplicate.rs:4:1 + | +4 | / struct Character { +5 | | id: String, +6 | | +7 | | #[graphql(name = "id")] +8 | | id2: String, +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.rs new file mode 100644 index 000000000..5dd70cfb6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.rs @@ -0,0 +1,15 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = [ObjA, ObjA])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..bea16ace7 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr @@ -0,0 +1,11 @@ +error: duplicated attribute argument found + --> fail/interface/struct/implementers_duplicate_pretty.rs:10:24 + | +10 | #[graphql(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/struct/implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.rs new file mode 100644 index 000000000..25f8d68c2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.rs @@ -0,0 +1,17 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +type ObjAlias = ObjA; + +#[derive(GraphQLInterface)] +#[graphql(for = [ObjA, ObjAlias])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr new file mode 100644 index 000000000..4e49ca39a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr @@ -0,0 +1,18 @@ +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/struct/implementers_duplicate_ugly.rs:12:18 + | +12 | #[graphql(for = [ObjA, ObjAlias])] + | ^^^^ -------- first implementation here + | | + | conflicting implementation for `CharacterValueEnum` + +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/struct/implementers_duplicate_ugly.rs:11:10 + | +11 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `ObjA` + | + = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_field.rs b/integration_tests/codegen_fail/fail/interface/struct/missing_field.rs new file mode 100644 index 000000000..f5993e9ab --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/missing_field.rs @@ -0,0 +1,15 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr new file mode 100644 index 000000000..49172bb82 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.rs new file mode 100644 index 000000000..536d7d6b4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.rs @@ -0,0 +1,14 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[derive(GraphQLInterface)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr new file mode 100644 index 000000000..4290fbe00 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/missing_for_attr.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/struct/missing_for_attr.rs:3:10 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.rs new file mode 100644 index 000000000..890012f7c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.rs @@ -0,0 +1,14 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr new file mode 100644 index 000000000..47003d9e5 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/missing_impl_attr.rs:8:10 + | +8 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/missing_impl_attr.rs:8:10 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.rs new file mode 100644 index 000000000..a8ce1a690 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct __Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr new file mode 100644 index 000000000..178223ff0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/name_double_underscored.rs:4:8 + | +4 | struct __Character { + | ^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/no_fields.rs b/integration_tests/codegen_fail/fail/interface/struct/no_fields.rs new file mode 100644 index 000000000..d06b8a6c0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/no_fields.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr new file mode 100644 index 000000000..f11feb2c8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface must have at least one field + --> fail/interface/struct/no_fields.rs:4:1 + | +4 | struct Character {} + | ^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.rs new file mode 100644 index 000000000..2bb1c0bf8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.rs @@ -0,0 +1,15 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr new file mode 100644 index 000000000..0b4e0966a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/non_subtype_return.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/non_subtype_return.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs rename to integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr new file mode 100644 index 000000000..f513ef68e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | +16 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr similarity index 83% rename from integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr index 0a6e79eba..817231404 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr @@ -1,5 +1,5 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/argument_double_underscored.rs:5:18 + --> fail/interface/trait/argument_double_underscored.rs:5:18 | 5 | fn id(&self, __num: i32) -> &str; | ^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs rename to integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr similarity index 65% rename from integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr rename to integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr index e20698e5b..c532bab16 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr @@ -1,13 +1,11 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:8:1 - | -8 | #[graphql_interface] - | ^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/trait/argument_non_input_type.rs:10:23 + | +10 | fn id(&self, obj: ObjA) -> &str; + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:8:1 + --> fail/interface/trait/argument_non_input_type.rs:8:1 | 8 | #[graphql_interface] | ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs rename to integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr similarity index 90% rename from integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr rename to integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr index b745a82d0..7fd044950 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:3:1 + --> fail/interface/trait/argument_wrong_default_array.rs:3:1 | 3 | #[graphql_interface] | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/field_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr similarity index 83% rename from integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr index 6c683ed51..979daae6a 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr @@ -1,5 +1,5 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/field_double_underscored.rs:5:8 + --> fail/interface/trait/field_double_underscored.rs:5:8 | 5 | fn __id(&self) -> &str; | ^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs rename to integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr new file mode 100644 index 000000000..2c6cf3a9f --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/trait/field_non_output_return_type.rs:10:21 + | +10 | fn id(&self) -> ObjB; + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/fields_duplicate.rs rename to integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr similarity index 84% rename from integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr rename to integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr index 143f85dab..e7fc50807 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr @@ -1,5 +1,5 @@ error: GraphQL interface must have a different name for each field - --> fail/interface/fields_duplicate.rs:4:1 + --> fail/interface/trait/fields_duplicate.rs:4:1 | 4 | / trait Character { 5 | | fn id(&self) -> &str; diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.rs diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr similarity index 70% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr index ce72fa2d7..5d5469027 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr @@ -1,11 +1,11 @@ error: duplicated attribute argument found - --> fail/interface/implementers_duplicate_pretty.rs:9:34 + --> fail/interface/trait/implementers_duplicate_pretty.rs:9:34 | 9 | #[graphql_interface(for = [ObjA, ObjA])] | ^^^^ error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/implementers_duplicate_pretty.rs:4:18 + --> fail/interface/trait/implementers_duplicate_pretty.rs:4:18 | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.rs diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr similarity index 62% rename from integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr rename to integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 726e30c8d..822487363 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,16 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/implementers_duplicate_ugly.rs:11:1 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:28 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | first implementation here - | conflicting implementation for `CharacterValueEnum` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^ -------- first implementation here + | | + | conflicting implementation for `CharacterValueEnum` error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` - --> fail/interface/implementers_duplicate_ugly.rs:11:1 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.rs b/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/method_default_impl.rs rename to integration_tests/codegen_fail/fail/interface/trait/method_default_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr b/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/method_default_impl.stderr rename to integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr index 887107a68..b95687d48 100644 --- a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr @@ -1,5 +1,5 @@ error: GraphQL interface trait method can't have default implementation - --> fail/interface/method_default_impl.rs:5:26 + --> fail/interface/trait/method_default_impl.rs:5:26 | 5 | fn id(&self) -> &str { | __________________________^ diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.rs b/integration_tests/codegen_fail/fail/interface/trait/missing_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_field.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_field.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr new file mode 100644 index 000000000..d76c39ff6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs b/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_field_argument.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr new file mode 100644 index 000000000..60ead166d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field_argument.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/trait/missing_field_argument.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_for_attr.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr rename to integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr index 84072d87d..da011ad9d 100644 --- a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/missing_for_attr.rs:3:10 + --> fail/interface/trait/missing_for_attr.rs:3:10 | 3 | #[derive(GraphQLObject)] - | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/missing_for_attr.rs:3:10 + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/trait/missing_for_attr.rs:3:10 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs rename to integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr rename to integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr index 767a53203..a82878d67 100644 --- a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/missing_impl_attr.rs:8:1 + --> fail/interface/trait/missing_impl_attr.rs:8:1 | 8 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/missing_impl_attr.rs:8:1 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/trait/missing_impl_attr.rs:8:1 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/name_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr similarity index 83% rename from integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr index 2cc6aa61c..1fa999a49 100644 --- a/integration_tests/codegen_fail/fail/interface/name_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr @@ -1,5 +1,5 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/name_double_underscored.rs:4:7 + --> fail/interface/trait/name_double_underscored.rs:4:7 | 4 | trait __Character { | ^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/trait/no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/no_fields.rs rename to integration_tests/codegen_fail/fail/interface/trait/no_fields.rs diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr similarity index 79% rename from integration_tests/codegen_fail/fail/interface/no_fields.stderr rename to integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr index 70ab57452..63c7d2968 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr @@ -1,5 +1,5 @@ error: GraphQL interface must have at least one field - --> fail/interface/no_fields.rs:4:1 + --> fail/interface/trait/no_fields.rs:4:1 | 4 | trait Character {} | ^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/non_subtype_return.rs rename to integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr new file mode 100644 index 000000000..ceefffb74 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/non_subtype_return.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/trait/non_subtype_return.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs b/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs rename to integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr new file mode 100644 index 000000000..2921f2538 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/wrong_argument_type.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr deleted file mode 100644 index 204bd1234..000000000 --- a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/wrong_argument_type.rs:14:1 - | -14 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/wrong_argument_type.rs:14:1 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr index 088f406e6..56c9587b8 100644 --- a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/object/attr_field_non_output_return_type.rs:10:1 + --> fail/object/attr_field_non_output_return_type.rs:12:21 | -10 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) +12 | fn id(&self) -> ObjB { + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr index 2a661f4c0..90a9be584 100644 --- a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/object/derive_field_non_output_return_type.rs:8:10 - | -8 | #[derive(GraphQLObject)] - | ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/object/derive_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr index 45c72bce6..43d9b36dd 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr @@ -1,10 +1,8 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/subscription/argument_non_input_type.rs:15:1 - | -15 | #[graphql_subscription] - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + --> fail/subscription/argument_non_input_type.rs:17:29 | - = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) +17 | async fn id(&self, obj: ObjA) -> Stream<'static, &'static str> { + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied --> fail/subscription/argument_non_input_type.rs:15:1 diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr index 090a3123a..ed30b222e 100644 --- a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/subscription/field_non_output_return_type.rs:15:1 + --> fail/subscription/field_non_output_return_type.rs:17:27 | -15 | #[graphql_subscription] - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) +17 | async fn id(&self) -> Stream<'static, ObjB> { + | ^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs new file mode 100644 index 000000000..8d586f0dd --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -0,0 +1,2498 @@ +//! Tests for `#[graphql_interface]` macro. + +#![allow(dead_code)] + +use std::marker::PhantomData; + +use juniper::{ + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, FieldError, + FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, +}; + +use crate::util::{schema, schema_with_scalar}; + +mod no_implers { + use super::*; + + #[derive(GraphQLInterface)] + struct Character { + id: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + async fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn registers_all_implementers() { + const DOC: &str = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); + + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod explicit_alias { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(enum = CharacterEnum, for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterEnum)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterEnum)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterEnum { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial_async { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + async fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn registers_all_implementers() { + const DOC: &str = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); + + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod fallible_field { + use super::*; + + struct CustomError; + + impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + } + } + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Result, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> Result { + Ok(self.id.clone()) + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn has_correct_graphql_type() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Character", + "kind": "INTERFACE", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }], + }}), + vec![], + )), + ); + } +} + +mod generic { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: String, + + #[graphql(skip)] + _phantom: PhantomData<(A, B)>, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Rust docs. + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + struct Character { + /// Rust `id` docs. + /// Long. + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + fields { + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs.\nLong."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + struct Character { + id: String, + + #[deprecated] + a: String, + + #[deprecated(note = "Use `id`.")] + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self) -> &str { + &self.id + } + + fn human_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> &'static str { + "a" + } + + fn b() -> String { + "b".to_owned() + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_fields() { + const DOC: &str = r#"{ + character { + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": null}, + {"name": "a", "deprecationReason": null}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + /// Rust docs. + #[derive(GraphQLInterface)] + #[graphql(name = "MyChar", desc = "My character.", for = Human)] + struct Character { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + id: String, + + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + a: String, + + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Human { + fn my_id(&self, #[graphql(name = "myName")] _: Option) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> String { + "a".to_owned() + } + + fn b() -> &'static str { + "b" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + myId + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + name + fields(includeDeprecated: true) { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyChar", + "fields": [ + {"name": "myId", "args": []}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + description + fields(includeDeprecated: true) { + name + description + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My character.", + "fields": [{ + "name": "myId", + "description": "My character ID.", + "args": [], + }, { + "name": "a", + "description": null, + "args": [], + }, { + "name": "b", + "description": null, + "args": [], + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": null, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": null, + }], + }}), + vec![], + )), + ); + } +} + +mod renamed_all_fields_and_args { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(rename_all = "none", for = Human)] + struct Character { + id: String, + } + + struct Human; + + #[graphql_object(rename_all = "none", impl = CharacterValue)] + impl Human { + fn id() -> &'static str { + "human-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human.into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": "human-32", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_and_args_names() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + #[graphql(scalar = DefaultScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid], scalar = MyScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema_with_scalar::(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema_with_scalar::(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema_with_scalar::(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod explicit_generic_scalar { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid], scalar = S)] + struct Character { + id: FieldResult, + } + + #[derive(GraphQLObject)] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod ignored_method { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + struct Character { + id: String, + + #[graphql(ignore)] + ignored: Option, + + #[graphql(skip)] + skipped: i32, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + +mod field_return_subtyping { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod field_return_union_subtyping { + use super::*; + + #[derive(GraphQLObject)] + struct Strength { + value: i32, + } + + #[derive(GraphQLObject)] + struct Knowledge { + value: i32, + } + + #[allow(dead_code)] + #[derive(GraphQLUnion)] + enum KeyFeature { + Strength(Strength), + Knowledge(Knowledge), + } + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Option, + key_feature: KeyFeature, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + key_feature: Knowledge, + } + + struct Droid { + id: String, + primary_function: String, + strength: i32, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + + fn key_feature(&self) -> Strength { + Strength { + value: self.strength, + } + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + key_feature: Knowledge { value: 10 }, + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + strength: 42, + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + "keyFeature": {"value": 10}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + "keyFeature": {"value": 42}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } + } + }"#; + + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_val = *expected_val; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "keyFeature": {"value": expected_val}, + }}), + vec![], + )), + ); + } + } +} + +mod nullable_argument_subtyping { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id(isPresent: true) + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "missing"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index aacc02f62..9ec883017 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -5,6 +5,7 @@ mod derive_scalar; mod derive_scalar_value; mod impl_scalar; mod interface_attr; +mod interface_derive; mod object_attr; mod object_derive; mod scalar_value_transparent; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 27fdb150c..38686776b 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -115,8 +115,8 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // functionality automatically. pub use juniper_codegen::{ graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalar, GraphQLScalarValue, - GraphQLUnion, + GraphQLEnum, GraphQLInputObject, GraphQLInterface, GraphQLObject, GraphQLScalar, + GraphQLScalarValue, GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 0534432c2..2b895add7 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -6,7 +6,7 @@ use std::mem; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, @@ -306,7 +306,7 @@ impl OnMethod { #[must_use] pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> Option { let ty = &self.as_regular()?.ty; - Some(quote! { + Some(quote_spanned! { ty.span() => <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) } @@ -329,11 +329,12 @@ impl OnMethod { .map(|desc| quote! { .description(#desc) }); let method = if let Some(val) = &arg.default { + let span = val.span(); 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) } + quote_spanned! { span => .arg_with_default::<#ty>(#name, &#val, info) } } else { quote! { .arg::<#ty>(#name, info) } }; diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index bf299b458..2efe969f6 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -6,7 +6,7 @@ pub(crate) mod arg; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::{ parse::{Parse, ParseStream}, parse_quote, @@ -280,7 +280,7 @@ impl Definition { >>::Type }; - quote! { + quote_spanned! { self.ty.span() => #( #args_marks )* <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 88f8b0d61..78e0ddbdc 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -16,7 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{Definition, TraitAttr}; +use super::{Attr, Definition}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -40,7 +40,7 @@ fn expand_on_trait( attrs: Vec, mut ast: syn::ItemTrait, ) -> syn::Result { - let attr = TraitAttr::from_attrs("graphql_interface", &attrs)?; + let attr = Attr::from_attrs("graphql_interface", &attrs)?; let trait_ident = &ast.ident; let trait_span = ast.span(); @@ -69,14 +69,18 @@ fn expand_on_trait( .copied() .unwrap_or(RenameRule::CamelCase); - let mut fields = vec![]; - for item in &mut ast.items { - if let syn::TraitItem::Method(m) = item { - if let Some(f) = parse_field(m, &renaming) { - fields.push(f) + let fields = ast + .items + .iter_mut() + .filter_map(|item| { + if let syn::TraitItem::Method(m) = item { + if let Some(f) = parse_field(m, &renaming) { + return Some(f); + } } - } - } + None + }) + .collect::>(); proc_macro_error::abort_if_dirty(); @@ -115,7 +119,7 @@ fn expand_on_trait( ); let generated_code = Definition { - trait_generics: ast.generics.clone(), + generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, enum_alias_ident, diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs new file mode 100644 index 000000000..b5ac63112 --- /dev/null +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -0,0 +1,176 @@ +//! Code generation for `#[derive(GraphQLInterface)]` macro. + +use proc_macro2::TokenStream; +use quote::{format_ident, ToTokens as _}; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; + +use crate::{ + common::{field, parse::TypeExt as _, scalar}, + result::GraphQLScope, + util::{span_container::SpanContainer, RenameRule}, +}; + +use super::{Attr, Definition}; + +/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. +const ERR: GraphQLScope = GraphQLScope::InterfaceDerive; + +/// Expands `#[graphql_interface]` macro into generated code. +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + let attr = Attr::from_attrs("graphql", &ast.attrs)?; + + let data = if let syn::Data::Struct(data) = &ast.data { + data + } else { + return Err(ERR.custom_error(ast.span(), "can only be derived for structs")); + }; + + let struct_ident = &ast.ident; + let struct_span = ast.span(); + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| struct_ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| struct_ident.span()), + ); + } + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let renaming = attr + .rename_fields + .as_deref() + .copied() + .unwrap_or(RenameRule::CamelCase); + + let fields = data + .fields + .iter() + .filter_map(|f| parse_field(f, &renaming)) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(struct_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(struct_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + + let context = attr + .context + .as_deref() + .cloned() + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments.as_ref().and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) + }) + }) + .unwrap_or_else(|| parse_quote! { () }); + + let enum_alias_ident = attr + .r#enum + .as_deref() + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", struct_ident.to_string())); + let enum_ident = attr.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", struct_ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + + Ok(Definition { + generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, + name, + description: attr.description.as_deref().cloned(), + context, + scalar, + fields, + implementers: attr + .implementers + .iter() + .map(|c| c.inner().clone()) + .collect(), + } + .into_token_stream()) +} + +/// Parses a [`field::Definition`] from the given trait method definition. +/// +/// Returns [`None`] if parsing fails, or the method field is ignored. +#[must_use] +fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { + let field_ident = field + .ident + .as_ref() + .or_else(|| err_unnamed_field(&field.span()))?; + + let attr = field::Attr::from_attrs("graphql", &field.attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.ignore.is_some() { + return None; + } + + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| field_ident.span()), + ); + return None; + } + + let mut ty = field.ty.clone(); + ty.lifetimes_anonymized(); + + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr + .deprecated + .as_deref() + .map(|d| d.as_ref().map(syn::LitStr::value)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: field_ident.clone(), + arguments: None, + has_receiver: false, + is_async: false, + }) +} + +/// Emits "expected named struct field" [`syn::Error`] pointing to the given +/// `span`. +fn err_unnamed_field(span: &S) -> Option { + ERR.emit_custom(span.span(), "expected named struct field"); + None +} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 263d1c5f7..3fda25fbc 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,11 +3,12 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; +pub mod derive; use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, @@ -31,11 +32,12 @@ use crate::{ }; /// Available arguments behind `#[graphql_interface]` attribute placed on a -/// trait definition, when generating code for [GraphQL interface][1] type. +/// trait or struct definition, when generating code for [GraphQL interface][1] +/// type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] -struct TraitAttr { +struct Attr { /// Explicitly specified name of [GraphQL interface][1] type. /// /// If [`None`], then Rust trait name is used by default. @@ -52,7 +54,7 @@ struct TraitAttr { description: Option>, /// Explicitly specified identifier of the type alias of Rust enum type - /// behind the trait, being an actual implementation of a + /// behind the trait or struct, being an actual implementation of a /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. @@ -110,7 +112,7 @@ struct TraitAttr { is_internal: bool, } -impl Parse for TraitAttr { +impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { @@ -201,7 +203,7 @@ impl Parse for TraitAttr { } } -impl TraitAttr { +impl Attr { /// Tries to merge two [`TraitAttr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { @@ -237,12 +239,14 @@ impl TraitAttr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces struct Definition { - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. + /// [`syn::Generics`] of the trait or struct describing the + /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, + generics: syn::Generics, - /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. + /// [`syn::Visibility`] of the trait or struct describing the + /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces vis: syn::Visibility, @@ -323,48 +327,42 @@ impl Definition { let enum_ident = &self.enum_ident; let alias_ident = &self.enum_alias_ident; - let variant_gens_pars = self - .implementers - .iter() - .enumerate() - .map::(|(id, _)| { - let par = format_ident!("__I{}", id); - parse_quote! { #par } - }); + let variant_gens_pars = (0..self.implementers.len()).map::(|id| { + let par = format_ident!("__I{}", id); + parse_quote! { #par } + }); let variants_idents = self .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - let trait_gens = &self.trait_generics; - let (trait_impl_gens, trait_ty_gens, trait_where_clause) = - self.trait_generics.split_for_impl(); + let interface_gens = &self.generics; + let (interface_impl_gens, interface_ty_gens, interface_where_clause) = + self.generics.split_for_impl(); - let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + let (interface_gens_lifetimes, interface_gens_tys) = interface_gens .params .clone() .into_iter() - .partition::, _>(|par| { - matches!(par, syn::GenericParam::Lifetime(_)) - }); + .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); let enum_gens = { - let mut enum_gens = trait_gens.clone(); - enum_gens.params = trait_gens_lifetimes.clone(); + let mut enum_gens = interface_gens.clone(); + enum_gens.params = interface_gens_lifetimes.clone(); enum_gens.params.extend(variant_gens_pars.clone()); - enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens.params.extend(interface_gens_tys.clone()); enum_gens }; let enum_alias_gens = { - let mut enum_alias_gens = trait_gens.clone(); + let mut enum_alias_gens = interface_gens.clone(); enum_alias_gens.move_bounds_to_where_clause(); enum_alias_gens }; let enum_to_alias_gens = { - trait_gens_lifetimes + interface_gens_lifetimes .into_iter() .map(|par| match par { syn::GenericParam::Lifetime(def) => { @@ -374,7 +372,7 @@ impl Definition { rest => quote! { #rest }, }) .chain(self.implementers.iter().map(ToTokens::to_token_stream)) - .chain(trait_gens_tys.into_iter().map(|par| match par { + .chain(interface_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; quote! { #par_ident } @@ -384,7 +382,7 @@ impl Definition { }; let phantom_variant = self.has_phantom_variant().then(|| { - let phantom_params = trait_gens.params.iter().filter_map(|p| { + let phantom_params = interface_gens.params.iter().filter_map(|p| { let ty = match p { syn::GenericParam::Type(ty) => { let ident = &ty.ident; @@ -408,11 +406,11 @@ impl Definition { .iter() .zip(variants_idents.clone()) .map(|(ty, ident)| { - quote! { + quote_spanned! { ty.span() => #[automatically_derived] - impl#trait_impl_gens ::std::convert::From<#ty> - for #alias_ident#trait_ty_gens - #trait_where_clause + impl#interface_impl_gens ::std::convert::From<#ty> + for #alias_ident#interface_ty_gens + #interface_where_clause { fn from(v: #ty) -> Self { Self::#ident(v) @@ -449,7 +447,7 @@ impl Definition { let gens = self.impl_generics(false); let (impl_generics, _, where_clause) = gens.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); let impler_tys = &self.implementers; let all_implers_unique = (impler_tys.len() > 1).then(|| { @@ -483,7 +481,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); let ty_const_generics = self.const_trait_generics(); let fields_marks = self @@ -491,7 +489,13 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter().collect::>(); + let is_output = self.implementers.iter().map(|implementer| { + quote_spanned! { implementer.span() => + <#implementer as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); + + let impler_tys = self.implementers.iter(); quote! { #[automatically_derived] @@ -501,7 +505,7 @@ impl Definition { { fn mark() { #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + #( #is_output )* ::juniper::assert_interfaces_impls!( #const_scalar, #ty#ty_const_generics, #(#impler_tys),* ); @@ -522,7 +526,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); let name = &self.name; let description = self @@ -583,7 +587,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; @@ -664,7 +668,7 @@ impl Definition { let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; @@ -732,7 +736,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); quote! { #[automatically_derived] @@ -784,7 +788,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); self.fields .iter() @@ -852,7 +856,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); self.fields .iter() @@ -863,13 +867,12 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) - .then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = + (self.implementers.is_empty() || !self.generics.params.is_empty()).then(|| { + quote! { _ => unreachable!() } + }); - quote! { + quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::macros::reflect::Field< @@ -924,7 +927,7 @@ impl Definition { let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); self.fields .iter() @@ -935,13 +938,12 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) - .then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = + (self.implementers.is_empty() || !self.generics.params.is_empty()).then(|| { + quote! { _ => unreachable!() } + }); - quote! { + quote_spanned! { field.ident.span() => #[allow(non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::macros::reflect::AsyncField< @@ -1000,7 +1002,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1034,7 +1036,7 @@ impl Definition { }) }); let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1065,7 +1067,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1112,7 +1114,7 @@ impl Definition { } let mut visitor = GenericsForConst(parse_quote!( <> )); - visitor.visit_generics(&self.trait_generics); + visitor.visit_generics(&self.generics); syn::PathArguments::AngleBracketed(visitor.0) } @@ -1126,7 +1128,7 @@ impl Definition { /// [`GraphQLType`]: juniper::GraphQLType #[must_use] fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); + let mut generics = self.generics.clone(); let scalar = &self.scalar; if scalar.is_implicit_generic() { @@ -1143,10 +1145,10 @@ impl Definition { } if for_async { - let self_ty = if self.trait_generics.lifetimes().next().is_some() { + let self_ty = if self.generics.lifetimes().next().is_some() { // Modify lifetime names to omit "lifetime name `'a` shadows a // lifetime name that is already in scope" error. - let mut generics = self.trait_generics.clone(); + let mut generics = self.generics.clone(); for lt in generics.lifetimes_mut() { let ident = lt.lifetime.ident.unraw(); lt.lifetime.ident = format_ident!("__fa__{}", ident); @@ -1180,6 +1182,6 @@ impl Definition { /// type parameters. #[must_use] fn has_phantom_variant(&self) -> bool { - !self.trait_generics.params.is_empty() + !self.generics.params.is_empty() } } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index aa79f6e24..7017a34bb 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -855,6 +855,14 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +#[proc_macro_error] +#[proc_macro_derive(GraphQLInterface, attributes(graphql))] +pub fn derive_interface(body: TokenStream) -> TokenStream { + self::graphql_interface::derive::expand(body.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index 77085d015..4e86f4101 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -11,6 +11,7 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; #[allow(unused_variables)] pub enum GraphQLScope { InterfaceAttr, + InterfaceDerive, ObjectAttr, ObjectDerive, UnionAttr, @@ -25,7 +26,7 @@ pub enum GraphQLScope { impl GraphQLScope { pub fn spec_section(&self) -> &str { match self { - Self::InterfaceAttr => "#sec-Interfaces", + Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveInputObject => "#sec-Input-Objects", @@ -39,7 +40,7 @@ impl GraphQLScope { impl fmt::Display for GraphQLScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { - Self::InterfaceAttr => "interface", + Self::InterfaceAttr | Self::InterfaceDerive => "interface", Self::ObjectAttr | Self::ObjectDerive => "object", Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveInputObject => "input object", diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 6c2a31bc6..510c3aeb2 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -7,7 +7,7 @@ use std::{collections::HashMap, convert::TryFrom, str::FromStr}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; -use quote::quote; +use quote::{quote, quote_spanned}; use span_container::SpanContainer; use syn::{ ext::IdentExt as _, @@ -1075,7 +1075,9 @@ impl GraphQLTypeDefiniton { let marks = self.fields.iter().map(|field| { let field_ty = &field._type; - quote! { <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } + quote_spanned! { field_ty.span() => + <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); + } }); let mut body = quote!( From 8cea82044f8b9553b7d05909b6e24ab7b9663059 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 18 Feb 2022 10:42:12 +0300 Subject: [PATCH 086/122] WIP --- docs/book/content/types/scalars.md | 142 ++++- .../scalar/derive_input/impl_invalid_url.rs | 24 + .../derive_input/impl_invalid_url.stderr | 26 + .../scalar/derive_input/impl_with_resolver.rs | 12 + .../derive_input/impl_with_resolver.stderr | 23 + .../derive_input/impl_without_resolvers.rs | 6 + .../impl_without_resolvers.stderr | 5 + .../{ => type_alias}/impl_invalid_url.rs | 0 .../{ => type_alias}/impl_invalid_url.stderr | 2 +- .../{ => type_alias}/impl_with_resolver.rs | 0 .../impl_with_resolver.stderr | 2 +- .../impl_without_resolvers.rs | 0 .../impl_without_resolvers.stderr | 2 +- .../juniper_tests/src/codegen/mod.rs | 5 +- ..._scalar.rs => scalar_attr_derive_input.rs} | 602 ++++++++++-------- ...pl_scalar.rs => scalar_attr_type_alias.rs} | 78 +++ .../src/executor_tests/introspection/mod.rs | 2 +- juniper/src/executor_tests/variables.rs | 14 +- juniper/src/types/scalars.rs | 14 +- juniper_codegen/src/graphql_scalar/attr.rs | 133 +++- juniper_codegen/src/graphql_scalar/derive.rs | 36 +- juniper_codegen/src/graphql_scalar/mod.rs | 7 + juniper_codegen/src/result.rs | 2 +- 23 files changed, 802 insertions(+), 335 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr rename integration_tests/codegen_fail/fail/scalar/{ => type_alias}/impl_invalid_url.rs (100%) rename integration_tests/codegen_fail/fail/scalar/{ => type_alias}/impl_invalid_url.stderr (71%) rename integration_tests/codegen_fail/fail/scalar/{ => type_alias}/impl_with_resolver.rs (100%) rename integration_tests/codegen_fail/fail/scalar/{ => type_alias}/impl_with_resolver.stderr (80%) rename integration_tests/codegen_fail/fail/scalar/{ => type_alias}/impl_without_resolvers.rs (100%) rename integration_tests/codegen_fail/fail/scalar/{ => type_alias}/impl_without_resolvers.stderr (79%) rename integration_tests/juniper_tests/src/codegen/{derive_scalar.rs => scalar_attr_derive_input.rs} (63%) rename integration_tests/juniper_tests/src/codegen/{impl_scalar.rs => scalar_attr_type_alias.rs} (92%) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index c9a02548b..371da8c51 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -42,8 +42,6 @@ crates. They are enabled via features that are on by default. ## Custom scalars -### `#[derive(GraphQLScalar)]` - Often, you might need a custom scalar that just wraps an existing type. This can be done with the newtype pattern and a custom derive, similar to how @@ -52,6 +50,7 @@ serde supports this pattern with `#[serde(transparent)]`. ```rust # extern crate juniper; #[derive(juniper::GraphQLScalar)] +#[graphql(transparent)] pub struct UserId(i32); #[derive(juniper::GraphQLObject)] @@ -62,7 +61,27 @@ struct User { # fn main() {} ``` -That's it, you can now user `UserId` in your schema. +`#[derive(GraphQLScalar)]` is mostly interchangeable with `#[graphql_scalar]` +attribute: + +```rust +# extern crate juniper; +# use juniper::graphql_scalar; +# +#[graphql_scalar(transparent)] +pub struct UserId { + value: i32, +} + +#[derive(juniper::GraphQLObject)] +struct User { + id: UserId, +} +# +# fn main() {} +``` + +That's it, you can now use `UserId` in your schema. The macro also allows for more customization: @@ -76,6 +95,7 @@ The macro also allows for more customization: // Specify a custom description. // A description in the attribute will overwrite a doc comment. description = "My user id description", + transparent, )] pub struct UserId(i32); # @@ -90,13 +110,12 @@ All the methods used from newtype's field can be replaced with attributes: # use juniper::{GraphQLScalar, ScalarValue, Value}; # #[derive(GraphQLScalar)] -#[graphql(to_output_with = to_output)] +#[graphql(to_output_with = to_output, transparent)] struct Incremented(i32); /// Increments [`Incremented`] before converting into a [`Value`]. fn to_output(v: &Incremented) -> Value { - let inc = v.0 + 1; - Value::from(inc) + Value::from(v.0 + 1) } # # fn main() {} @@ -105,17 +124,19 @@ fn to_output(v: &Incremented) -> Value { #### `#[graphql(from_input_with = )]` attribute ```rust -# use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; +# use juniper::{GraphQLScalar, InputValue, ScalarValue}; # #[derive(GraphQLScalar)] -#[graphql(scalar = DefaultScalarValue)] -#[graphql(from_input_with = Self::from_input)] +#[graphql(from_input_with = Self::from_input, transparent)] struct UserId(String); impl UserId { /// Checks whether [`InputValue`] is `String` beginning with `id: ` and /// strips it. - fn from_input(input: &InputValue) -> Result { + fn from_input(input: &InputValue) -> Result + where + S: ScalarValue + { input.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", input)) .and_then(|str| { @@ -138,7 +159,10 @@ impl UserId { #### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes ```rust -# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarValue, ScalarToken, Value}; +# use juniper::{ +# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, +# ScalarValue, ScalarToken, Value +# }; # #[derive(GraphQLScalar)] #[graphql( @@ -154,21 +178,30 @@ enum StringOrInt { Int(i32), } -fn to_output(v: &StringOrInt) -> Value { +fn to_output(v: &StringOrInt) -> Value +where + S: ScalarValue +{ match v { StringOrInt::String(str) => Value::scalar(str.to_owned()), StringOrInt::Int(i) => Value::scalar(*i), } } -fn from_input(v: &InputValue) -> Result { +fn from_input(v: &InputValue) -> Result +where + S: ScalarValue +{ v.as_string_value() .map(|s| StringOrInt::String(s.to_owned())) .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } -fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { +fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> +where + S: ScalarValue +{ >::from_str(value) .or_else(|_| >::from_str(value)) } @@ -179,12 +212,70 @@ fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there > is no need to follow `newtype` pattern. -#### `#[graphql(with = )]` attribute +#### `#[graphql(with = )]` attribute + +Instead of providing all custom resolvers, you can provide path to the `to_output`, +`from_input`, `parse_token` functions. + +Path can be simply `with = Self` (default path where macro expects resolvers to be), +in case there is an impl block with custom resolvers: + +> __NOTE:__ `with = Self` used by default in `#[derive(GraphQLScalar)]` macro, +> while `#[graphql_scalar]` expects you to specify that explicitly. +> This is the case because primary usage of `#[graphql_scalar]` is to +> [implement scalar on foreign types](#using-foreign-types-as-scalars), +> where `impl Scalar { ... }` isn't applicable. + +```rust +# use juniper::{ +# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, +# ScalarValue, ScalarToken, Value +# }; +# +#[derive(GraphQLScalar)] +// #[graphql(with = Self)] <- default behaviour +enum StringOrInt { + String(String), + Int(i32), +} + +impl StringOrInt { + fn to_output(&self) -> Value { + match self { + Self::String(str) => Value::scalar(str.to_owned()), + Self::Int(i) => Value::scalar(*i), + } + } + + fn from_input(v: &InputValue) -> Result + where + S: ScalarValue, + { + v.as_string_value() + .map(|s| Self::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| Self::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> + where + S: ScalarValue, + { + >::from_str(value) + .or_else(|_| >::from_str(value)) + } +} +# +# fn main() {} +``` -Instead of providing all custom resolvers, you can provide module with `to_output`, `from_input`, `parse_token` functions. +Or it can be path to a module, where custom resolvers are located. ```rust -# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, ScalarValue, ScalarToken, Value}; +# use juniper::{ +# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, +# ScalarValue, ScalarToken, Value +# }; # #[derive(GraphQLScalar)] #[graphql(with = string_or_int)] @@ -270,22 +361,11 @@ mod string_or_int { # fn main() {} ``` -#### `#[graphql(scalar = )]` - -Custom implementation (`scalar = DefaultScalarValue`) or generic bounded -[`ScalarValue`] (`scalar = S: Trait`). - -#### `#[graphql(where = )]` or `#[graphql(where())]` - -Adds custom generic bounds in scalar implementation. - ---- - -### `#[graphql_scalar]` attribute +### Using foreign types as scalars For implementing custom scalars on foreign types there is `#[graphql_scalar]` attribute macro. -> __NOTE:__ To satisfy [orphan rule] you should provide local [`ScalarValue`] implementation. +> __NOTE:__ To satisfy [orphan rules] you should provide local [`ScalarValue`] implementation. ```rust # extern crate juniper; @@ -335,5 +415,5 @@ mod date_scalar { # fn main() {} ``` -[orphan rule]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules +[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules [`ScalarValue`]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs new file mode 100644 index 000000000..a11200d14 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs @@ -0,0 +1,24 @@ +use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; + +#[graphql_scalar( + specified_by_url = "not an url", + with = scalar, + parse_token(i32), +)] +struct ScalarSpecifiedByUrl(i32); + +mod scalar { + use super::*; + + pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { + Value::scalar(0) + } + + pub(super) fn from_input( + _: &InputValue, + ) -> Result { + Ok(ScalarSpecifiedByUrl) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr new file mode 100644 index 000000000..59a6146dd --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr @@ -0,0 +1,26 @@ +error: Invalid URL: relative URL without a base + --> fail/scalar/derive_input/impl_invalid_url.rs:4:24 + | +4 | specified_by_url = "not an url", + | ^^^^^^^^^^^^ + +error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope + --> fail/scalar/derive_input/impl_invalid_url.rs:13:49 + | +13 | pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { + | ^^^^^^^^^^^^^^^^^^^^ not found in this scope + +error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope + --> fail/scalar/derive_input/impl_invalid_url.rs:19:17 + | +17 | pub(super) fn from_input( + | - help: you might be missing a type parameter: `, ScalarSpecifiedByUrl` +18 | _: &InputValue, +19 | ) -> Result { + | ^^^^^^^^^^^^^^^^^^^^ not found in this scope + +error[E0425]: cannot find value `ScalarSpecifiedByUrl` in this scope + --> fail/scalar/derive_input/impl_invalid_url.rs:20:12 + | +20 | Ok(ScalarSpecifiedByUrl) + | ^^^^^^^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs new file mode 100644 index 000000000..b50335380 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs @@ -0,0 +1,12 @@ +use juniper::{graphql_scalar, ScalarValue, Value}; + +#[graphql_scalar(to_output_with = Scalar::to_output)] +struct Scalar; + +impl Scalar { + fn to_output(&self) -> Value { + Value::scalar(0) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr new file mode 100644 index 000000000..2d26b0b5a --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr @@ -0,0 +1,23 @@ +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes + --> fail/scalar/derive_input/impl_with_resolver.rs:4:1 + | +4 | struct Scalar; + | ^^^^^^^^^^^^^^ + +error[E0412]: cannot find type `Scalar` in this scope + --> fail/scalar/derive_input/impl_with_resolver.rs:6:6 + | +6 | impl Scalar { + | ^^^^^^ not found in this scope + | +help: there is an enum variant `crate::Value::Scalar` and 7 others; try using the variant's enum + | +6 | impl crate::Value { + | ~~~~~~~~~~~~ +6 | impl juniper::InputValue { + | ~~~~~~~~~~~~~~~~~~~ +6 | impl juniper::LookAheadValue { + | ~~~~~~~~~~~~~~~~~~~~~~~ +6 | impl juniper::TypeKind { + | ~~~~~~~~~~~~~~~~~ + and 3 other candidates diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs new file mode 100644 index 000000000..6d3b5e338 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs @@ -0,0 +1,6 @@ +use juniper::graphql_scalar; + +#[graphql_scalar] +struct Scalar; + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr new file mode 100644 index 000000000..49fee3ef8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes + --> fail/scalar/derive_input/impl_without_resolvers.rs:4:1 + | +4 | struct Scalar; + | ^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/impl_invalid_url.rs rename to integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs diff --git a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr rename to integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr index 6b1640f73..b000693ca 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/impl_invalid_url.rs:6:24 + --> fail/scalar/type_alias/impl_invalid_url.rs:6:24 | 6 | specified_by_url = "not an url", | ^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/impl_with_resolver.rs rename to integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.rs diff --git a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr rename to integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.stderr index f130068c0..a9981aa45 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_with_resolver.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.stderr @@ -1,5 +1,5 @@ error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes - --> fail/scalar/impl_with_resolver.rs:6:1 + --> fail/scalar/type_alias/impl_with_resolver.rs:6:1 | 6 | type CustomScalar = Scalar; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.rs rename to integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs diff --git a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr similarity index 79% rename from integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr rename to integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr index 441debed7..93f6eb7ab 100644 --- a/integration_tests/codegen_fail/fail/scalar/impl_without_resolvers.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr @@ -1,5 +1,5 @@ error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes - --> fail/scalar/impl_without_resolvers.rs:6:1 + --> fail/scalar/type_alias/impl_without_resolvers.rs:6:1 | 6 | type CustomScalar = Scalar; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 6348a66c8..f87b4a3c5 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -1,12 +1,11 @@ mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; -mod derive_scalar; -mod impl_scalar; mod interface_attr; mod object_attr; mod object_derive; -mod scalar_value_transparent; +mod scalar_attr_derive_input; +mod scalar_attr_type_alias; mod subscription_attr; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs similarity index 63% rename from integration_tests/juniper_tests/src/codegen/derive_scalar.rs rename to integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs index f8a7f66c2..eb60f83fa 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs @@ -2,18 +2,37 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, GraphQLScalar, + execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; -use crate::util::{schema, schema_with_scalar}; +use crate::{ + custom_scalar::MyScalarValue, + util::{schema, schema_with_scalar}, +}; -mod trivial_unnamed { +mod trivial { use super::*; - #[derive(GraphQLScalar)] + #[graphql_scalar] struct Counter(i32); + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] @@ -68,12 +87,30 @@ mod trivial_unnamed { } } -mod trivial_named { +mod all_custom_resolvers { use super::*; - #[derive(GraphQLScalar)] - struct Counter { - value: i32, + #[graphql_scalar( + to_output_with = to_output, + from_input_with = from_input, + )] + #[graphql_scalar( + parse_token_with = parse_token, + )] + struct Counter(i32); + + fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) } struct QueryRoot; @@ -133,10 +170,25 @@ mod trivial_named { mod explicit_name { use super::*; - #[derive(GraphQLScalar)] - #[graphql(name = "Counter")] + #[graphql_scalar(name = "Counter")] struct CustomCounter(i32); + impl CustomCounter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] @@ -162,22 +214,6 @@ mod explicit_name { ); } - #[tokio::test] - async fn no_custom_counter() { - const DOC: &str = r#"{ - __type(name: "CustomCounter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!(null), vec![])), - ); - } - #[tokio::test] async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; @@ -207,23 +243,29 @@ mod explicit_name { } } -mod custom_to_output { +mod delegated_parse_token { use super::*; - #[derive(GraphQLScalar)] - #[graphql(to_output_with = to_output)] - struct Increment(i32); + #[graphql_scalar(parse_token(i32))] + struct Counter(i32); - fn to_output(val: &Increment) -> Value { - let ret = val.0 + 1; - Value::from(ret) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } } struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn increment(value: Increment) -> Increment { + fn counter(value: Counter) -> Counter { value } } @@ -231,7 +273,7 @@ mod custom_to_output { #[tokio::test] async fn is_graphql_scalar() { const DOC: &str = r#"{ - __type(name: "Increment") { + __type(name: "Counter") { kind } }"#; @@ -246,20 +288,20 @@ mod custom_to_output { #[tokio::test] async fn resolves_counter() { - const DOC: &str = r#"{ increment(value: 0) }"#; + const DOC: &str = r#"{ counter(value: 0) }"#; let schema = schema(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"increment": 1}), vec![])), + Ok((graphql_value!({"counter": 0}), vec![])), ); } #[tokio::test] async fn has_no_description() { const DOC: &str = r#"{ - __type(name: "Increment") { + __type(name: "Counter") { description } }"#; @@ -273,117 +315,29 @@ mod custom_to_output { } } -mod delegated_parse_token { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql( - to_output_with = to_output, - from_input_with = from_input, - )] - #[graphql( - parse_token(String), - specified_by_url = "https://tools.ietf.org/html/rfc3339" - )] - struct CustomDateTime(DateTime) - where - Tz: From + TimeZone, - Tz::Offset: fmt::Display; - - fn to_output(v: &CustomDateTime) -> Value - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - Value::scalar(v.0.to_rfc3339()) - } - - fn from_input(v: &InputValue) -> Result, String> - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) - }) - } - - struct QueryRoot; - - #[graphql_object(scalar = DefaultScalarValue)] - impl QueryRoot { - fn date_time(value: CustomDateTime) -> CustomDateTime { - value - } - } - - #[tokio::test] - async fn resolves_custom_date_time() { - const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), - vec![], - )), - ); - } - - #[tokio::test] - async fn has_specified_by_url() { - const DOC: &str = r#"{ - __type(name: "CustomDateTime") { - specifiedByUrl - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), - vec![], - )), - ); - } -} - mod multiple_delegated_parse_token { use super::*; - #[derive(GraphQLScalar)] - #[graphql( - to_output_with = to_output, - from_input_with = from_input, - parse_token(String, i32), - )] + #[graphql_scalar(parse_token(String, i32))] enum StringOrInt { String(String), Int(i32), } - fn to_output(v: &StringOrInt) -> Value { - match v { - StringOrInt::String(str) => Value::scalar(str.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + impl StringOrInt { + fn to_output(&self) -> Value { + match self { + StringOrInt::String(str) => Value::scalar(str.to_owned()), + StringOrInt::Int(i) => Value::scalar(*i), + } } - } - fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .map(|s| StringOrInt::String(s.to_owned())) - .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .map(|s| Self::String(s.to_owned())) + .or_else(|| v.as_int_value().map(|i| Self::Int(i))) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } } struct QueryRoot; @@ -423,12 +377,9 @@ mod multiple_delegated_parse_token { mod where_attribute { use super::*; - #[derive(GraphQLScalar)] - #[graphql( + #[graphql_scalar( to_output_with = to_output, from_input_with = from_input, - )] - #[graphql( parse_token(String), where(Tz: From, Tz::Offset: fmt::Display), specified_by_url = "https://tools.ietf.org/html/rfc3339", @@ -461,7 +412,7 @@ mod where_attribute { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -472,7 +423,7 @@ mod where_attribute { async fn resolves_custom_date_time() { const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -491,7 +442,7 @@ mod where_attribute { } }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -503,59 +454,25 @@ mod where_attribute { } } -mod generic_with_all_resolvers { +mod with_self { use super::*; - #[derive(GraphQLScalar)] - #[graphql( - to_output_with = custom_date_time::to_output, - from_input_with = custom_date_time::from_input, - )] - #[graphql( - parse_token_with = custom_date_time::parse_token, - where(Tz: From, Tz::Offset: fmt::Display), - specified_by_url = "https://tools.ietf.org/html/rfc3339", - )] - struct CustomDateTime { - dt: DateTime, - _unused: (), - } - - mod custom_date_time { - use super::*; + #[graphql_scalar(with = Self)] + struct Counter(i32); - pub(super) fn to_output(v: &CustomDateTime) -> Value - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - Value::scalar(v.dt.to_rfc3339()) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result, String> - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - v.as_string_value() + fn from_input(v: &InputValue) -> Result { + v.as_int_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime { - dt: dt.with_timezone(&Tz::from(Utc)), - _unused: (), - }) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) - }) + .map(Self) } - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> - where - S: ScalarValue, - { - >::from_str(value) + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) } } @@ -563,31 +480,44 @@ mod generic_with_all_resolvers { #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn date_time(value: CustomDateTime) -> CustomDateTime { + fn counter(value: Counter) -> Counter { value } } #[tokio::test] - async fn resolves_custom_date_time() { - const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; let schema = schema(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), - vec![], - )), + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), ); } #[tokio::test] - async fn has_specified_by_url() { + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { const DOC: &str = r#"{ - __type(name: "CustomDateTime") { - specifiedByUrl + __type(name: "Counter") { + description } }"#; @@ -595,30 +525,21 @@ mod generic_with_all_resolvers { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), - vec![], - )), + Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } } -mod generic_with_module { +mod with_module { use super::*; - #[derive(GraphQLScalar)] - #[graphql( + #[graphql_scalar( with = custom_date_time, - specified_by_url = "https://tools.ietf.org/html/rfc3339" + parse_token(String), + where(Tz: From, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", )] - struct CustomDateTime - where - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - dt: DateTime, - _unused: (), - } + struct CustomDateTime(DateTime); mod custom_date_time { use super::*; @@ -629,7 +550,7 @@ mod generic_with_module { Tz: From + TimeZone, Tz::Offset: fmt::Display, { - Value::scalar(v.dt.to_rfc3339()) + Value::scalar(v.0.to_rfc3339()) } pub(super) fn from_input(v: &InputValue) -> Result, String> @@ -642,25 +563,15 @@ mod generic_with_module { .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime { - dt: dt.with_timezone(&Tz::from(Utc)), - _unused: (), - }) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) }) } - - pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> - where - S: ScalarValue, - { - >::from_str(value) - } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -671,7 +582,7 @@ mod generic_with_module { async fn resolves_custom_date_time() { const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -690,7 +601,7 @@ mod generic_with_module { } }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -705,10 +616,30 @@ mod generic_with_module { mod description_from_doc_comment { use super::*; - /// Doc comment. - #[derive(GraphQLScalar)] + /// Description + #[graphql_scalar(with = counter)] struct Counter(i32); + mod counter { + use super::*; + + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] @@ -718,6 +649,22 @@ mod description_from_doc_comment { } } + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + #[tokio::test] async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; @@ -743,8 +690,8 @@ mod description_from_doc_comment { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"description": "Doc comment."}}), - vec![], + graphql_value!({"__type": {"description": "Description"}}), + vec![] )), ); } @@ -753,12 +700,26 @@ mod description_from_doc_comment { mod description_from_attribute { use super::*; - /// Doc comment. - #[derive(GraphQLScalar)] - #[graphql(desc = "Doc comment from attribute.")] - #[graphql(specified_by_url = "https://tools.ietf.org/html/rfc4122")] + /// Doc comment + #[graphql_scalar(description = "Description from attribute")] struct Counter(i32); + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(v.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; #[graphql_object(scalar = DefaultScalarValue)] @@ -768,6 +729,22 @@ mod description_from_attribute { } } + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + #[tokio::test] async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; @@ -781,11 +758,10 @@ mod description_from_attribute { } #[tokio::test] - async fn has_description_and_url() { + async fn has_description() { const DOC: &str = r#"{ __type(name: "Counter") { description - specifiedByUrl } }"#; @@ -794,27 +770,43 @@ mod description_from_attribute { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({ - "__type": { - "description": "Doc comment from attribute.", - "specifiedByUrl": "https://tools.ietf.org/html/rfc4122", - } - }), - vec![], + graphql_value!({"__type": {"description": "Description from attribute"}}), + vec![] )), ); } } mod custom_scalar { - use crate::custom_scalar::MyScalarValue; - use super::*; - #[derive(GraphQLScalar)] - #[graphql(scalar = MyScalarValue)] + /// Description + #[graphql_scalar( + scalar = MyScalarValue, + with = counter, + )] struct Counter(i32); + mod counter { + use super::*; + + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; #[graphql_object(scalar = MyScalarValue)] @@ -853,7 +845,7 @@ mod custom_scalar { } #[tokio::test] - async fn has_no_description() { + async fn has_description() { const DOC: &str = r#"{ __type(name: "Counter") { description @@ -864,7 +856,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), ); } } @@ -872,13 +867,36 @@ mod custom_scalar { mod generic_scalar { use super::*; - #[derive(GraphQLScalar)] - #[graphql(scalar = S: ScalarValue)] + /// Description + #[graphql_scalar( + scalar = S: ScalarValue, + with = counter, + )] struct Counter(i32); + mod counter { + use super::*; + + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; - #[graphql_object] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -893,7 +911,7 @@ mod generic_scalar { } }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -905,25 +923,60 @@ mod generic_scalar { async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"counter": 0}), vec![])), ); } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } } mod bounded_generic_scalar { use super::*; - #[derive(GraphQLScalar)] - #[graphql(scalar = S: ScalarValue + Clone)] + /// Description + #[graphql_scalar(scalar = S: ScalarValue + Clone)] struct Counter(i32); + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Clone)] + #[graphql_object(scalar = MyScalarValue)] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -938,7 +991,7 @@ mod bounded_generic_scalar { } }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, @@ -950,11 +1003,30 @@ mod bounded_generic_scalar { async fn resolves_counter() { const DOC: &str = r#"{ counter(value: 0) }"#; - let schema = schema(QueryRoot); + let schema = schema_with_scalar::(QueryRoot); assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"counter": 0}), vec![])), ); } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } } diff --git a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs similarity index 92% rename from integration_tests/juniper_tests/src/codegen/impl_scalar.rs rename to integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs index 4738a61c8..aed8cdb04 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_scalar.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs @@ -416,6 +416,84 @@ mod where_attribute { } } +mod with_self { + use super::*; + + struct CustomCounter(i32); + + #[graphql_scalar(with = Self)] + type Counter = CustomCounter; + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + mod with_module { use super::*; diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 925b2fd72..25eff8452 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -20,7 +20,7 @@ enum Sample { } #[derive(GraphQLScalar)] -#[graphql(name = "SampleScalar")] +#[graphql(name = "SampleScalar", transparent)] struct Scalar(i32); /// A sample interface diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 51a8b0a37..257f26c4e 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -11,22 +11,18 @@ use crate::{ }; #[derive(Debug, GraphQLScalar)] -#[graphql(with = test_complex_scalar, parse_token(String))] +#[graphql(parse_token(String))] struct TestComplexScalar; -mod test_complex_scalar { - use super::*; - - pub(super) fn to_output(_: &TestComplexScalar) -> Value { +impl TestComplexScalar { + fn to_output(&self) -> Value { graphql_value!("SerializedValue") } - pub(super) fn from_input( - v: &InputValue, - ) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .filter(|s| *s == "SerializedValue") - .map(|_| TestComplexScalar) + .map(|_| Self) .ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v)) } } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 5e04190dc..d75d1cc70 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -24,21 +24,19 @@ use crate::{ /// /// Represented as a string, but can be converted _to_ from an integer as well. #[derive(Clone, Debug, Eq, GraphQLScalar, PartialEq, Serialize, Deserialize)] -#[graphql(with = id, parse_token(String, i32))] +#[graphql(parse_token(String, i32))] pub struct ID(String); -mod id { - use super::*; - - pub(super) fn to_output(v: &ID) -> Value { - Value::scalar(v.0.clone()) +impl ID { + fn to_output(&self) -> Value { + Value::scalar(self.0.clone()) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) .or_else(|| v.as_int_value().map(|i| i.to_string())) - .map(ID) + .map(Self) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } } diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index 85679f330..dc55559dc 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -8,17 +8,22 @@ use crate::{ GraphQLScope, }; -use super::{Attr, Definition, GraphQLScalarMethods, ParseToken}; +use super::{Attr, Definition, Field, GraphQLScalarMethods, ParseToken}; const ERR: GraphQLScope = GraphQLScope::ImplScalar; /// Expands `#[graphql_scalar]` macro into generated code. pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body) { + if let Ok(mut ast) = syn::parse2::(body.clone()) { let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); return expand_on_type_alias(attrs, ast); } + if let Ok(mut ast) = syn::parse2::(body) { + let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); + return expand_on_derive_input(attrs, ast); + } Err(syn::Error::new( Span::call_site(), @@ -33,6 +38,13 @@ fn expand_on_type_alias( ) -> syn::Result { let attr = Attr::from_attrs("graphql_scalar", &attrs)?; + if attr.transparent { + return Err(ERR.custom_error( + ast.span(), + "`transparent` attribute isn't applicable for type aliases.", + )); + } + let field = match ( attr.to_output.as_deref().cloned(), attr.from_input.as_deref().cloned(), @@ -87,3 +99,120 @@ fn expand_on_type_alias( #def }) } + +// TODO: Add `#[graphql(transparent)]`. +/// Expands `#[graphql_scalar]` macro placed on a struct/enum/union. +fn expand_on_derive_input( + attrs: Vec, + ast: syn::DeriveInput, +) -> syn::Result { + let attr = Attr::from_attrs("graphql_scalar", &attrs)?; + + let field = match ( + attr.to_output.as_deref().cloned(), + attr.from_input.as_deref().cloned(), + attr.parse_token.as_deref().cloned(), + attr.with.as_deref().cloned(), + attr.transparent, + ) { + (Some(to_output), Some(from_input), Some(parse_token), None, false) => { + GraphQLScalarMethods::Custom { + to_output, + from_input, + parse_token, + } + } + (to_output, from_input, parse_token, Some(module), false) => GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + }, + (to_output, from_input, parse_token, None, true) => { + let data = if let syn::Data::Struct(data) = &ast.data { + data + } else { + return Err(ERR.custom_error( + ast.span(), + "expected single-field struct because of `transparent` attribute", + )); + }; + let field = match &data.fields { + syn::Fields::Unit => Err(ERR.custom_error( + ast.span(), + "expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` \ + because of `transparent` attribute", + )), + syn::Fields::Unnamed(fields) => fields + .unnamed + .first() + .and_then(|f| (fields.unnamed.len() == 1).then(|| Field::Unnamed(f.clone()))) + .ok_or_else(|| { + ERR.custom_error( + ast.span(), + "expected exactly 1 field, e.g., Test(i32) \ + because of `transparent` attribute", + ) + }), + syn::Fields::Named(fields) => fields + .named + .first() + .and_then(|f| (fields.named.len() == 1).then(|| Field::Named(f.clone()))) + .ok_or_else(|| { + ERR.custom_error( + ast.span(), + "expected exactly 1 field, e.g., Test { test: i32 } \ + because of `transparent` attribute", + ) + }), + }?; + GraphQLScalarMethods::Delegated { + to_output, + from_input, + parse_token, + field: Box::new(field), + } + } + (_, _, _, Some(module), true) => { + return Err(ERR.custom_error( + module.span(), + "`with = ` attribute can't be combined with `transparent`. \ + You can specify custom resolvers with `to_output`, `from_input`, `parse_token` \ + attributes and still use `transparent` for unspecified ones.", + )); + } + _ => { + return Err(ERR.custom_error( + ast.span(), + "all custom resolvers have to be provided via `with` or \ + combination of `to_output_with`, `from_input_with`, \ + `parse_token_with` attributes", + )); + } + }; + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + let def = Definition { + ty: TypeOrIdent::Ident(ast.ident.clone()), + where_clause: attr + .where_clause + .map_or_else(Vec::new, |cl| cl.into_inner()), + generics: ast.generics.clone(), + methods: field, + name: attr + .name + .as_deref() + .cloned() + .unwrap_or_else(|| ast.ident.to_string()), + description: attr.description.as_deref().cloned(), + specified_by_url: attr.specified_by_url.as_deref().cloned(), + scalar, + } + .to_token_stream(); + + Ok(quote! { + #ast + #def + }) +} diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index 73c6d217a..7b4a9a482 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -22,34 +22,38 @@ pub fn expand(input: TokenStream) -> syn::Result { attr.from_input.as_deref().cloned(), attr.parse_token.as_deref().cloned(), attr.with.as_deref().cloned(), + attr.transparent, ) { - (Some(to_output), Some(from_input), Some(parse_token), None) => { + (Some(to_output), Some(from_input), Some(parse_token), None, false) => { GraphQLScalarMethods::Custom { to_output, from_input, parse_token, } } - (to_output, from_input, parse_token, Some(module)) => GraphQLScalarMethods::Custom { - to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), - from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), - parse_token: parse_token - .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), - }, - (to_output, from_input, parse_token, None) => { + (to_output, from_input, parse_token, module, false) => { + let module = module.unwrap_or_else(|| parse_quote!(Self)); + GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + } + } + (to_output, from_input, parse_token, None, true) => { let data = if let syn::Data::Struct(data) = &ast.data { data } else { return Err(ERR.custom_error( ast.span(), - "expected all custom resolvers or single-field struct", + "expected single-field struct because of `transparent` attribute", )); }; let field = match &data.fields { syn::Fields::Unit => Err(ERR.custom_error( ast.span(), "expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` \ - or all custom resolvers", + because of `transparent` attribute", )), syn::Fields::Unnamed(fields) => fields .unnamed @@ -59,7 +63,7 @@ pub fn expand(input: TokenStream) -> syn::Result { ERR.custom_error( ast.span(), "expected exactly 1 field, e.g., Test(i32) \ - or all custom resolvers", + because of `transparent` attribute", ) }), syn::Fields::Named(fields) => fields @@ -70,7 +74,7 @@ pub fn expand(input: TokenStream) -> syn::Result { ERR.custom_error( ast.span(), "expected exactly 1 field, e.g., Test { test: i32 } \ - or all custom resolvers", + because of `transparent` attribute", ) }), }?; @@ -81,6 +85,14 @@ pub fn expand(input: TokenStream) -> syn::Result { field: Box::new(field), } } + (_, _, _, Some(module), true) => { + return Err(ERR.custom_error( + module.span(), + "`with = ` attribute can't be combined with `transparent`. \ + You can specify custom resolvers with `to_output`, `from_input`, `parse_token` \ + attributes and still use `transparent` for unspecified ones.", + )); + } }; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 6907dc703..44a876059 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -87,6 +87,9 @@ struct Attr { /// Explicit where clause added to [`syn::WhereClause`]. where_clause: Option>>, + + /// TODO + transparent: bool, } impl Parse for Attr { @@ -215,6 +218,9 @@ impl Parse for Attr { )) .none_or_else(|_| err::dup_arg(&ident))? } + "transparent" => { + out.transparent = true; + } name => { return Err(err::unknown_arg(&ident, name)); } @@ -239,6 +245,7 @@ impl Attr { parse_token: try_merge_opt!(parse_token: self, another), with: try_merge_opt!(with: self, another), where_clause: try_merge_opt!(where_clause: self, another), + transparent: self.transparent || another.transparent, }) } diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index e9b250682..4b3299adc 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -119,7 +119,7 @@ impl GraphQLScope { duplicates .into_iter() .for_each(|dup| { - let _ = &dup.spanned[1..] + dup.spanned[1..] .iter() .for_each(|spanned| { Diagnostic::spanned( From 9c584195c2022f95a5412d30396b285c43f5c4d3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 21 Feb 2022 08:08:59 +0300 Subject: [PATCH 087/122] Corrections --- docs/book/content/types/scalars.md | 42 +- .../scalar/derive_input/attr_invalid_url.rs | 6 + ...lid_url.stderr => attr_invalid_url.stderr} | 8 +- ...olvers.rs => attr_transparent_and_with.rs} | 2 +- .../attr_transparent_and_with.stderr | 5 + .../attr_transparent_multiple_named_fields.rs | 9 + ..._transparent_multiple_named_fields.stderr} | 4 +- ...ttr_transparent_multiple_unnamed_fields.rs | 6 + ...ransparent_multiple_unnamed_fields.stderr} | 4 +- .../attr_transparent_unit_struct.rs | 6 + .../attr_transparent_unit_struct.stderr} | 4 +- .../{ => derive_input}/derive_invalid_url.rs | 0 .../derive_invalid_url.stderr | 2 +- .../derive_transparent_and_with.rs | 7 + .../derive_transparent_and_with.stderr | 5 + ...rive_transparent_multiple_named_fields.rs} | 1 + ...e_transparent_multiple_named_fields.stderr | 9 + ...ve_transparent_multiple_unnamed_fields.rs} | 1 + ...transparent_multiple_unnamed_fields.stderr | 6 + .../derive_transparent_unit_struct.rs} | 1 + .../derive_transparent_unit_struct.stderr | 6 + .../scalar/derive_input/impl_invalid_url.rs | 24 - .../scalar/derive_input/impl_with_resolver.rs | 12 - .../derive_input/impl_with_resolver.stderr | 23 - .../impl_without_resolvers.stderr | 5 - ...ive_multiple_named_fields_with_resolver.rs | 16 - ...multiple_named_fields_with_resolver.stderr | 9 - ...e_multiple_unnamed_fields_with_resolver.rs | 13 - ...ltiple_unnamed_fields_with_resolver.stderr | 6 - .../juniper_tests/src/codegen/mod.rs | 1 + .../src/codegen/scalar_attr_derive_input.rs | 137 +- .../src/codegen/scalar_derive_derive_input.rs | 1175 +++++++++++++++++ .../src/codegen/scalar_value_transparent.rs | 5 +- juniper/CHANGELOG.md | 3 +- juniper/src/types/scalars.rs | 2 +- juniper_codegen/src/graphql_scalar/attr.rs | 26 +- juniper_codegen/src/graphql_scalar/mod.rs | 3 +- juniper_codegen/src/lib.rs | 134 +- 38 files changed, 1539 insertions(+), 189 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs rename integration_tests/codegen_fail/fail/scalar/derive_input/{impl_invalid_url.stderr => attr_invalid_url.stderr} (81%) rename integration_tests/codegen_fail/fail/scalar/derive_input/{impl_without_resolvers.rs => attr_transparent_and_with.rs} (57%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs rename integration_tests/codegen_fail/fail/scalar/{derive_multiple_named_fields.stderr => derive_input/attr_transparent_multiple_named_fields.stderr} (55%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs rename integration_tests/codegen_fail/fail/scalar/{derive_multiple_unnamed_fields.stderr => derive_input/attr_transparent_multiple_unnamed_fields.stderr} (52%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs rename integration_tests/codegen_fail/fail/scalar/{derive_unit_struct.stderr => derive_input/attr_transparent_unit_struct.stderr} (51%) rename integration_tests/codegen_fail/fail/scalar/{ => derive_input}/derive_invalid_url.rs (100%) rename integration_tests/codegen_fail/fail/scalar/{ => derive_input}/derive_invalid_url.stderr (71%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr rename integration_tests/codegen_fail/fail/scalar/{derive_multiple_named_fields.rs => derive_input/derive_transparent_multiple_named_fields.rs} (82%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr rename integration_tests/codegen_fail/fail/scalar/{derive_multiple_unnamed_fields.rs => derive_input/derive_transparent_multiple_unnamed_fields.rs} (79%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr rename integration_tests/codegen_fail/fail/scalar/{derive_unit_struct.rs => derive_input/derive_transparent_unit_struct.rs} (80%) create mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.rs delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs delete mode 100644 integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr create mode 100644 integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 371da8c51..cc032b8a7 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -42,6 +42,8 @@ crates. They are enabled via features that are on by default. ## Custom scalars +#### `#[graphql(transparent)]` attribute + Often, you might need a custom scalar that just wraps an existing type. This can be done with the newtype pattern and a custom derive, similar to how @@ -49,6 +51,7 @@ serde supports this pattern with `#[serde(transparent)]`. ```rust # extern crate juniper; +# #[derive(juniper::GraphQLScalar)] #[graphql(transparent)] pub struct UserId(i32); @@ -90,12 +93,12 @@ The macro also allows for more customization: /// You can use a doc comment to specify a description. #[derive(juniper::GraphQLScalar)] #[graphql( + transparent, // Overwrite the GraphQL type name. name = "MyUserId", // Specify a custom description. // A description in the attribute will overwrite a doc comment. description = "My user id description", - transparent, )] pub struct UserId(i32); # @@ -154,7 +157,7 @@ impl UserId { } # # fn main() {} - ``` +``` #### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes @@ -218,13 +221,7 @@ Instead of providing all custom resolvers, you can provide path to the `to_outpu `from_input`, `parse_token` functions. Path can be simply `with = Self` (default path where macro expects resolvers to be), -in case there is an impl block with custom resolvers: - -> __NOTE:__ `with = Self` used by default in `#[derive(GraphQLScalar)]` macro, -> while `#[graphql_scalar]` expects you to specify that explicitly. -> This is the case because primary usage of `#[graphql_scalar]` is to -> [implement scalar on foreign types](#using-foreign-types-as-scalars), -> where `impl Scalar { ... }` isn't applicable. +in case there is an impl block with custom resolvers: ```rust # use juniper::{ @@ -253,7 +250,7 @@ impl StringOrInt { { v.as_string_value() .map(|s| Self::String(s.to_owned())) - .or_else(|| v.as_int_value().map(|i| Self::Int(i))) + .or_else(|| v.as_int_value().map(Self::Int)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } @@ -303,7 +300,7 @@ mod string_or_int { { v.as_string_value() .map(|s| StringOrInt::String(s.to_owned())) - .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .or_else(|| v.as_int_value().map(StringOrInt::Int)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } @@ -325,35 +322,30 @@ Also, you can partially override `#[graphql(with)]` attribute with other custom # use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value}; # #[derive(GraphQLScalar)] -#[graphql( - with = string_or_int, - parse_token(String, i32) -)] +#[graphql(parse_token(String, i32))] enum StringOrInt { String(String), Int(i32), } -mod string_or_int { - use super::*; - - pub(super) fn to_output(v: &StringOrInt) -> Value +impl StringOrInt { + fn to_output(&self) -> Value where S: ScalarValue, { - match v { - StringOrInt::String(str) => Value::scalar(str.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + match self { + Self::String(str) => Value::scalar(str.to_owned()), + Self::Int(i) => Value::scalar(*i), } } - pub(super) fn from_input(v: &InputValue) -> Result + fn from_input(v: &InputValue) -> Result where S: ScalarValue, { v.as_string_value() - .map(|s| StringOrInt::String(s.to_owned())) - .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) + .map(|s| Self::String(s.to_owned())) + .or_else(|| v.as_int_value().map(Self::Int)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } } diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs new file mode 100644 index 000000000..d7256348a --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs @@ -0,0 +1,6 @@ +use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; + +#[graphql_scalar(specified_by_url = "not an url")] +struct ScalarSpecifiedByUrl(i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr similarity index 81% rename from integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr index 59a6146dd..f80472b2d 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr @@ -1,17 +1,17 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/derive_input/impl_invalid_url.rs:4:24 + --> fail/scalar/derive_input/attr_invalid_url.rs:4:24 | 4 | specified_by_url = "not an url", | ^^^^^^^^^^^^ error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope - --> fail/scalar/derive_input/impl_invalid_url.rs:13:49 + --> fail/scalar/derive_input/attr_invalid_url.rs:13:49 | 13 | pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { | ^^^^^^^^^^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope - --> fail/scalar/derive_input/impl_invalid_url.rs:19:17 + --> fail/scalar/derive_input/attr_invalid_url.rs:19:17 | 17 | pub(super) fn from_input( | - help: you might be missing a type parameter: `, ScalarSpecifiedByUrl` @@ -20,7 +20,7 @@ error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope | ^^^^^^^^^^^^^^^^^^^^ not found in this scope error[E0425]: cannot find value `ScalarSpecifiedByUrl` in this scope - --> fail/scalar/derive_input/impl_invalid_url.rs:20:12 + --> fail/scalar/derive_input/attr_invalid_url.rs:20:12 | 20 | Ok(ScalarSpecifiedByUrl) | ^^^^^^^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs similarity index 57% rename from integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs rename to integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs index 6d3b5e338..3b3190840 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs @@ -1,6 +1,6 @@ use juniper::graphql_scalar; -#[graphql_scalar] +#[graphql_scalar(with = Self, transparent)] struct Scalar; fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr new file mode 100644 index 000000000..829c42c75 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar `with = ` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones. + --> fail/scalar/derive_input/attr_transparent_and_with.rs:3:25 + | +3 | #[graphql_scalar(with = Self, transparent)] + | ^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs new file mode 100644 index 000000000..56658b548 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs @@ -0,0 +1,9 @@ +use juniper::graphql_scalar; + +#[graphql_scalar(transparent)] +struct Scalar { + id: i32, + another: i32, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr similarity index 55% rename from integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr index 8a27aaf9e..5a6f94063 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr @@ -1,5 +1,5 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } or all custom resolvers - --> fail/scalar/derive_multiple_named_fields.rs:4:1 +error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute + --> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:1 | 4 | / struct Scalar { 5 | | id: i32, diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs new file mode 100644 index 000000000..bbf969364 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs @@ -0,0 +1,6 @@ +use juniper::graphql_scalar; + +#[graphql_scalar(transparent)] +struct Scalar(i32, i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr similarity index 52% rename from integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr index c848368cb..dfdcff7c3 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr @@ -1,5 +1,5 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) or all custom resolvers - --> fail/scalar/derive_multiple_unnamed_fields.rs:4:1 +error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute + --> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:1 | 4 | struct Scalar(i32, i32); | ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs new file mode 100644 index 000000000..9f8d7568d --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs @@ -0,0 +1,6 @@ +use juniper::graphql_scalar; + +#[graphql_scalar(transparent)] +struct ScalarSpecifiedByUrl; + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr similarity index 51% rename from integration_tests/codegen_fail/fail/scalar/derive_unit_struct.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr index 8b678d2a0..11b661f98 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr @@ -1,5 +1,5 @@ -error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` or all custom resolvers - --> fail/scalar/derive_unit_struct.rs:4:1 +error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute + --> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:1 | 4 | struct ScalarSpecifiedByUrl; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_invalid_url.rs rename to integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/scalar/derive_invalid_url.stderr rename to integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr index 9a0d5afde..b1e7a948b 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/derive_invalid_url.rs:4:30 + --> fail/scalar/derive_input/derive_invalid_url.rs:4:30 | 4 | #[graphql(specified_by_url = "not an url")] | ^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs new file mode 100644 index 000000000..494ff6686 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs @@ -0,0 +1,7 @@ +use juniper::GraphQLScalar; + +#[derive(GraphQLScalar)] +#[graphql(with = Self, transparent)] +struct Scalar; + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr new file mode 100644 index 000000000..860d5391f --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr @@ -0,0 +1,5 @@ +error: GraphQL scalar `with = ` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones. + --> fail/scalar/derive_input/derive_transparent_and_with.rs:4:18 + | +4 | #[graphql(with = Self, transparent)] + | ^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs similarity index 82% rename from integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.rs rename to integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs index 038fd656b..3388e1fc6 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs @@ -1,6 +1,7 @@ use juniper::GraphQLScalar; #[derive(GraphQLScalar)] +#[graphql(transparent)] struct Scalar { id: i32, another: i32, diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr new file mode 100644 index 000000000..d5258cefc --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr @@ -0,0 +1,9 @@ +error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute + --> fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs:4:1 + | +4 | / #[graphql(transparent)] +5 | | struct Scalar { +6 | | id: i32, +7 | | another: i32, +8 | | } + | |_^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs similarity index 79% rename from integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.rs rename to integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs index 855d2351f..d7710fc83 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs @@ -1,6 +1,7 @@ use juniper::GraphQLScalar; #[derive(GraphQLScalar)] +#[graphql(transparent)] struct Scalar(i32, i32); fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr new file mode 100644 index 000000000..a832801b4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr @@ -0,0 +1,6 @@ +error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute + --> fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs:4:1 + | +4 | / #[graphql(transparent)] +5 | | struct Scalar(i32, i32); + | |________________________^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs similarity index 80% rename from integration_tests/codegen_fail/fail/scalar/derive_unit_struct.rs rename to integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs index 53964886e..09712baa2 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_unit_struct.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs @@ -1,6 +1,7 @@ use juniper::GraphQLScalar; #[derive(GraphQLScalar)] +#[graphql(transparent)] struct ScalarSpecifiedByUrl; fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr new file mode 100644 index 000000000..3fc185ed9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr @@ -0,0 +1,6 @@ +error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute + --> fail/scalar/derive_input/derive_transparent_unit_struct.rs:4:1 + | +4 | / #[graphql(transparent)] +5 | | struct ScalarSpecifiedByUrl; + | |____________________________^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs deleted file mode 100644 index a11200d14..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_invalid_url.rs +++ /dev/null @@ -1,24 +0,0 @@ -use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; - -#[graphql_scalar( - specified_by_url = "not an url", - with = scalar, - parse_token(i32), -)] -struct ScalarSpecifiedByUrl(i32); - -mod scalar { - use super::*; - - pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { - Value::scalar(0) - } - - pub(super) fn from_input( - _: &InputValue, - ) -> Result { - Ok(ScalarSpecifiedByUrl) - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs deleted file mode 100644 index b50335380..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.rs +++ /dev/null @@ -1,12 +0,0 @@ -use juniper::{graphql_scalar, ScalarValue, Value}; - -#[graphql_scalar(to_output_with = Scalar::to_output)] -struct Scalar; - -impl Scalar { - fn to_output(&self) -> Value { - Value::scalar(0) - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr deleted file mode 100644 index 2d26b0b5a..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_with_resolver.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes - --> fail/scalar/derive_input/impl_with_resolver.rs:4:1 - | -4 | struct Scalar; - | ^^^^^^^^^^^^^^ - -error[E0412]: cannot find type `Scalar` in this scope - --> fail/scalar/derive_input/impl_with_resolver.rs:6:6 - | -6 | impl Scalar { - | ^^^^^^ not found in this scope - | -help: there is an enum variant `crate::Value::Scalar` and 7 others; try using the variant's enum - | -6 | impl crate::Value { - | ~~~~~~~~~~~~ -6 | impl juniper::InputValue { - | ~~~~~~~~~~~~~~~~~~~ -6 | impl juniper::LookAheadValue { - | ~~~~~~~~~~~~~~~~~~~~~~~ -6 | impl juniper::TypeKind { - | ~~~~~~~~~~~~~~~~~ - and 3 other candidates diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr deleted file mode 100644 index 49fee3ef8..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/impl_without_resolvers.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes - --> fail/scalar/derive_input/impl_without_resolvers.rs:4:1 - | -4 | struct Scalar; - | ^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.rs deleted file mode 100644 index 4dad2d4bd..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.rs +++ /dev/null @@ -1,16 +0,0 @@ -use juniper::{GraphQLScalar, Value}; - -#[derive(GraphQLScalar)] -#[graphql(to_output_with = Self::to_output)] -struct Scalar { - id: i32, - another: i32, -} - -impl Scalar { - fn to_output(&self) -> Value { - Value::scalar(self.id) - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr deleted file mode 100644 index 4604ddc5c..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_named_fields_with_resolver.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } or all custom resolvers - --> fail/scalar/derive_multiple_named_fields_with_resolver.rs:4:1 - | -4 | / #[graphql(to_output_with = Self::to_output)] -5 | | struct Scalar { -6 | | id: i32, -7 | | another: i32, -8 | | } - | |_^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs deleted file mode 100644 index ebe38d052..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs +++ /dev/null @@ -1,13 +0,0 @@ -use juniper::{GraphQLScalar, Value}; - -#[derive(GraphQLScalar)] -#[graphql(to_output_with = Self::to_output)] -struct Scalar(i32, i32); - -impl Scalar { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr deleted file mode 100644 index 97bb0b871..000000000 --- a/integration_tests/codegen_fail/fail/scalar/derive_multiple_unnamed_fields_with_resolver.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) or all custom resolvers - --> fail/scalar/derive_multiple_unnamed_fields_with_resolver.rs:4:1 - | -4 | / #[graphql(to_output_with = Self::to_output)] -5 | | struct Scalar(i32, i32); - | |________________________^ diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index f87b4a3c5..ed6a74acb 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -6,6 +6,7 @@ mod object_attr; mod object_derive; mod scalar_attr_derive_input; mod scalar_attr_type_alias; +mod scalar_derive_derive_input; mod subscription_attr; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs index eb60f83fa..7571fbf94 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs @@ -87,6 +87,135 @@ mod trivial { } } +mod transparent { + use super::*; + + #[graphql_scalar(transparent)] + struct Counter(i32); + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod transparent_with_resolver { + use super::*; + + #[graphql_scalar( + transparent, + to_output_with = Self::to_output, + )] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0 + 1) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 1}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + mod all_custom_resolvers { use super::*; @@ -327,15 +456,15 @@ mod multiple_delegated_parse_token { impl StringOrInt { fn to_output(&self) -> Value { match self { - StringOrInt::String(str) => Value::scalar(str.to_owned()), - StringOrInt::Int(i) => Value::scalar(*i), + Self::String(str) => Value::scalar(str.to_owned()), + Self::Int(i) => Value::scalar(*i), } } fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(|s| Self::String(s.to_owned())) - .or_else(|| v.as_int_value().map(|i| Self::Int(i))) + .or_else(|| v.as_int_value().map(Self::Int)) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } } @@ -706,7 +835,7 @@ mod description_from_attribute { impl Counter { fn to_output(&self) -> Value { - Value::scalar(v.0) + Value::scalar(self.0) } fn from_input(v: &InputValue) -> Result { diff --git a/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs new file mode 100644 index 000000000..f9c4bfde9 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs @@ -0,0 +1,1175 @@ +use std::fmt; + +use chrono::{DateTime, TimeZone, Utc}; +use juniper::{ + execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, GraphQLScalar, + InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, +}; + +use crate::{ + custom_scalar::MyScalarValue, + util::{schema, schema_with_scalar}, +}; + +mod trivial { + use super::*; + + #[derive(GraphQLScalar)] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod transparent { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(transparent)] + struct Counter(i32); + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod transparent_with_resolver { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + transparent, + to_output_with = Self::to_output, + )] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0 + 1) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 1}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod all_custom_resolvers { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + to_output_with = to_output, + from_input_with = from_input, + )] + #[graphql( + parse_token_with = parse_token, + )] + struct Counter(i32); + + fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod explicit_name { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(name = "Counter")] + struct CustomCounter(i32); + + impl CustomCounter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: CustomCounter) -> CustomCounter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod delegated_parse_token { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(parse_token(i32))] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod multiple_delegated_parse_token { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(parse_token(String, i32))] + enum StringOrInt { + String(String), + Int(i32), + } + + impl StringOrInt { + fn to_output(&self) -> Value { + match self { + Self::String(str) => Value::scalar(str.to_owned()), + Self::Int(i) => Value::scalar(*i), + } + } + + fn from_input(v: &InputValue) -> Result { + v.as_string_value() + .map(|s| Self::String(s.to_owned())) + .or_else(|| v.as_int_value().map(Self::Int)) + .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn string_or_int(value: StringOrInt) -> StringOrInt { + value + } + } + + #[tokio::test] + async fn resolves_string() { + const DOC: &str = r#"{ stringOrInt(value: "test") }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"stringOrInt": "test"}), vec![],)), + ); + } + + #[tokio::test] + async fn resolves_int() { + const DOC: &str = r#"{ stringOrInt(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"stringOrInt": 0}), vec![],)), + ); + } +} + +mod where_attribute { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + to_output_with = to_output, + from_input_with = from_input, + parse_token(String), + where(Tz: From, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", + )] + struct CustomDateTime(DateTime); + + fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.0.to_rfc3339()) + } + + fn from_input(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } + } + + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } +} + +mod with_self { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql(with = Self)] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod with_module { + use super::*; + + #[derive(GraphQLScalar)] + #[graphql( + with = custom_date_time, + parse_token(String), + where(Tz: From, Tz::Offset: fmt::Display), + specified_by_url = "https://tools.ietf.org/html/rfc3339", + )] + struct CustomDateTime(DateTime); + + mod custom_date_time { + use super::*; + + pub(super) fn to_output(v: &CustomDateTime) -> Value + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.0.to_rfc3339()) + } + + pub(super) fn from_input(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: From + TimeZone, + Tz::Offset: fmt::Display, + { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + DateTime::parse_from_rfc3339(s) + .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) + .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + }) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn date_time(value: CustomDateTime) -> CustomDateTime { + value + } + } + + #[tokio::test] + async fn resolves_custom_date_time() { + const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_specified_by_url() { + const DOC: &str = r#"{ + __type(name: "CustomDateTime") { + specifiedByUrl + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Description + #[derive(GraphQLScalar)] + #[graphql(with = counter)] + struct Counter(i32); + + mod counter { + use super::*; + + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } +} + +mod description_from_attribute { + use super::*; + + /// Doc comment + #[derive(GraphQLScalar)] + #[graphql(description = "Description from attribute")] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description from attribute"}}), + vec![] + )), + ); + } +} + +mod custom_scalar { + use super::*; + + /// Description + #[derive(GraphQLScalar)] + #[graphql( + scalar = MyScalarValue, + with = counter, + )] + struct Counter(i32); + + mod counter { + use super::*; + + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } +} + +mod generic_scalar { + use super::*; + + /// Description + #[derive(GraphQLScalar)] + #[graphql( + scalar = S: ScalarValue, + with = counter, + )] + struct Counter(i32); + + mod counter { + use super::*; + + pub(super) fn to_output(v: &Counter) -> Value { + Value::scalar(v.0) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Counter) + } + + pub(super) fn parse_token( + value: ScalarToken<'_>, + ) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + /// Description + #[derive(GraphQLScalar)] + #[graphql(scalar = S: ScalarValue + Clone)] + struct Counter(i32); + + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) + } + + fn from_input(v: &InputValue) -> Result { + v.as_int_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .map(Self) + } + + fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(value) + } + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn counter(value: Counter) -> Counter { + value + } + } + + #[tokio::test] + async fn is_graphql_scalar() { + const DOC: &str = r#"{ + __type(name: "Counter") { + kind + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_counter() { + const DOC: &str = r#"{ counter(value: 0) }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"counter": 0}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Counter") { + description + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Description"}}), + vec![] + )), + ); + } +} 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 f5d0c877c..55259d435 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs @@ -5,15 +5,16 @@ use juniper::{ }; #[derive(GraphQLScalar, Debug, Eq, PartialEq)] +#[graphql(transparent)] struct UserId(String); #[derive(GraphQLScalar, Debug, Eq, PartialEq)] -#[graphql(name = "MyUserId", description = "custom description...")] +#[graphql(name = "MyUserId", description = "custom description...", transparent)] struct CustomUserId(String); /// The doc comment... #[derive(GraphQLScalar, Debug, Eq, PartialEq)] -#[graphql(specified_by_url = "https://tools.ietf.org/html/rfc4122")] +#[graphql(specified_by_url = "https://tools.ietf.org/html/rfc4122", transparent)] struct IdWithDocComment(i32); #[derive(GraphQLObject)] diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index e2f7941d7..f0c8b1ad0 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -26,7 +26,8 @@ - Support structs with single named field. - Support for overriding resolvers. - Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) - - Use on type aliases only in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rule](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). + - Mirror `#[derive(GraphQLScalar)]` macro. + - Support usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). ## Features diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index d75d1cc70..71924c91b 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -35,7 +35,7 @@ impl ID { fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) - .or_else(|| v.as_int_value().map(|i| i.to_string())) + .or_else(|| v.as_int_value().map(ToString::to_string)) .map(Self) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index dc55559dc..2255f6fc5 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -1,3 +1,5 @@ +//! Code generation for `#[graphql_scalar]` macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{parse_quote, spanned::Spanned}; @@ -100,7 +102,6 @@ fn expand_on_type_alias( }) } -// TODO: Add `#[graphql(transparent)]`. /// Expands `#[graphql_scalar]` macro placed on a struct/enum/union. fn expand_on_derive_input( attrs: Vec, @@ -122,12 +123,15 @@ fn expand_on_derive_input( parse_token, } } - (to_output, from_input, parse_token, Some(module), false) => GraphQLScalarMethods::Custom { - to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), - from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), - parse_token: parse_token - .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), - }, + (to_output, from_input, parse_token, module, false) => { + let module = module.unwrap_or_else(|| parse_quote! { Self }); + GraphQLScalarMethods::Custom { + to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), + from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), + parse_token: parse_token + .unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })), + } + } (to_output, from_input, parse_token, None, true) => { let data = if let syn::Data::Struct(data) = &ast.data { data @@ -181,14 +185,6 @@ fn expand_on_derive_input( attributes and still use `transparent` for unspecified ones.", )); } - _ => { - return Err(ERR.custom_error( - ast.span(), - "all custom resolvers have to be provided via `with` or \ - combination of `to_output_with`, `from_input_with`, \ - `parse_token_with` attributes", - )); - } }; let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 44a876059..bf6619197 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -88,7 +88,8 @@ struct Attr { /// Explicit where clause added to [`syn::WhereClause`]. where_clause: Option>>, - /// TODO + /// Flag for single-field structs that allows to use that field for + /// non-provided resolvers. transparent: bool, } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 129e3495c..106423fde 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -145,6 +145,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][1] /// implementation. /// +/// #### `#[graphql(transparent)]` attribute +/// /// Sometimes, you want to create a custom [GraphQL scalar][1] type by wrapping /// an existing type. In Rust, this is often called the `newtype` pattern. /// Thanks to this custom derive, this becomes really easy: @@ -152,9 +154,11 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// ```rust /// // Deriving GraphQLScalar is all that is required. /// #[derive(juniper::GraphQLScalar)] +/// #[graphql(transparent)] /// struct UserId(String); /// /// #[derive(juniper::GraphQLScalar)] +/// #[graphql(transparent)] /// struct DroidId { /// value: String, /// } @@ -172,15 +176,16 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// /// Doc comments are used for the GraphQL type description. /// #[derive(juniper::GraphQLScalar)] /// #[graphql( -/// // Set a custom GraphQL name. -/// name = "MyUserId", -/// // A description can also specified in the attribute. -/// // This will the doc comment, if one exists. -/// description = "...", -/// // A specification URL. -/// specified_by_url = "https://tools.ietf.org/html/rfc4122", -/// // Explicit generic scalar. -/// scalar = S: juniper::ScalarValue, +/// // Set a custom GraphQL name. +/// name = "MyUserId", +/// // A description can also specified in the attribute. +/// // This will the doc comment, if one exists. +/// description = "...", +/// // A specification URL. +/// specified_by_url = "https://tools.ietf.org/html/rfc4122", +/// // Explicit generic scalar. +/// scalar = S: juniper::ScalarValue, +/// transparent, /// )] /// struct UserId(String); /// ``` @@ -194,7 +199,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// # use juniper::{GraphQLScalar, ScalarValue, Value}; /// # /// #[derive(GraphQLScalar)] -/// #[graphql(to_output_with = to_output)] +/// #[graphql(to_output_with = to_output, transparent)] /// struct Incremented(i32); /// /// /// Increments [`Incremented`] before converting into a [`Value`]. @@ -210,7 +215,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// # use juniper::{DefaultScalarValue, GraphQLScalar, InputValue, ScalarValue}; /// # /// #[derive(GraphQLScalar)] -/// #[graphql(scalar = DefaultScalarValue)] +/// #[graphql(scalar = DefaultScalarValue, transparent)] /// #[graphql(from_input_with = Self::from_input)] /// struct UserId(String); /// @@ -267,7 +272,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// fn from_input(v: &InputValue) -> Result { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.to_owned())) -/// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) +/// .or_else(|| v.as_int_value().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) /// } /// @@ -279,7 +284,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there /// > is no need to follow `newtype` pattern. /// -/// #### `#[graphql(with = )]` attribute +/// #### `#[graphql(with = )]` attribute /// /// Instead of providing all custom resolvers, you can provide module with /// `to_output`, `from_input`, `parse_token` functions. @@ -291,6 +296,52 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// # }; /// # /// #[derive(GraphQLScalar)] +/// // #[graphql(with = string_or_int)] <- default behaviour +/// enum StringOrInt { +/// String(String), +/// Int(i32), +/// } +/// +/// impl StringOrInt { +/// fn to_output(&self) -> Value { +/// match self { +/// Self::String(str) => Value::scalar(str.to_owned()), +/// Self::Int(i) => Value::scalar(*i), +/// } +/// } +/// +/// fn from_input(v: &InputValue) -> Result +/// where +/// S: ScalarValue +/// { +/// v.as_string_value() +/// .map(|s| Self::String(s.to_owned())) +/// .or_else(|| v.as_int_value().map(Self::Int)) +/// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) +/// } +/// +/// fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> +/// where +/// S: ScalarValue +/// { +/// >::from_str(value) +/// .or_else(|_| >::from_str(value)) +/// } +/// } +/// # +/// # fn main() {} +/// ``` +/// +/// `#[graphql(with = )]` can specify path to a module, where all +/// resolvers are located. +/// +/// ```rust +/// # use juniper::{ +/// # GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue, +/// # ScalarValue, ScalarToken, Value, +/// # }; +/// # +/// #[derive(GraphQLScalar)] /// #[graphql(with = string_or_int)] /// enum StringOrInt { /// String(String), @@ -298,8 +349,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// } /// /// mod string_or_int { -/// # use super::*; -/// # +/// use super::*; +/// /// pub(super) fn to_output(v: &StringOrInt) -> Value { /// match v { /// StringOrInt::String(str) => Value::scalar(str.to_owned()), @@ -363,6 +414,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) /// } +/// +/// // No `parse_token` resolver. /// } /// # /// # fn main() {} @@ -388,13 +441,54 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { .into() } -/// In case [`GraphQLScalar`] isn't applicable because type located in other -/// crate and you don't want to wrap it in a newtype there is -/// `#[graphql_scalar]` macro. +/// `#[graphql_scalar]` is interchangeable with `#[derive(`[`GraphQLScalar`]`)]` +/// macro: +/// +/// ```rust +/// /// Doc comments are used for the GraphQL type description. +/// #[derive(juniper::GraphQLScalar)] +/// #[graphql( +/// // Set a custom GraphQL name. +/// name = "MyUserId", +/// // A description can also specified in the attribute. +/// // This will the doc comment, if one exists. +/// description = "...", +/// // A specification URL. +/// specified_by_url = "https://tools.ietf.org/html/rfc4122", +/// // Explicit generic scalar. +/// scalar = S: juniper::ScalarValue, +/// transparent, +/// )] +/// struct UserId(String); +/// ``` +/// +/// Is transformed into: +/// +/// ```rust +/// /// Doc comments are used for the GraphQL type description. +/// #[juniper::graphql_scalar( +/// // Set a custom GraphQL name. +/// name = "MyUserId", +/// // A description can also specified in the attribute. +/// // This will the doc comment, if one exists. +/// description = "...", +/// // A specification URL. +/// specified_by_url = "https://tools.ietf.org/html/rfc4122", +/// // Explicit generic scalar. +/// scalar = S: juniper::ScalarValue, +/// transparent, +/// )] +/// struct UserId(String); +/// ``` +/// +/// In addition to that `#[graphql_scalar]` can be used in case +/// [`GraphQLScalar`] isn't applicable because type located in other crate and +/// you don't want to wrap it in a newtype. This is done by placing +/// `#[graphql_scalar]` on a type alias. /// /// All attributes are mirroring [`GraphQLScalar`] derive macro. /// -/// > __NOTE:__ To satisfy [orphan rule] you should provide local +/// > __NOTE:__ To satisfy [orphan rules] you should provide local /// > [`ScalarValue`] implementation. /// /// ```rust @@ -448,7 +542,7 @@ pub fn derive_scalar(input: TokenStream) -> TokenStream { /// # fn main() { } /// ``` /// -/// [orphan rule]: https://bit.ly/3glAGC2 +/// [orphan rules]: https://bit.ly/3glAGC2 /// [`GraphQLScalar`]: juniper::GraphQLScalar /// [`ScalarValue`]: juniper::ScalarValue #[proc_macro_error] From baad84b2680a325dbcf6699877a65bc562da7312 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 21 Feb 2022 09:01:18 +0300 Subject: [PATCH 088/122] Corrections --- .../scalar/derive_input/attr_invalid_url.rs | 2 +- .../derive_input/attr_invalid_url.stderr | 27 +++---------------- juniper/src/types/scalars.rs | 2 +- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs index d7256348a..70ad2aaf2 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs @@ -1,4 +1,4 @@ -use juniper::{graphql_scalar, InputValue, ScalarValue, Value}; +use juniper::graphql_scalar; #[graphql_scalar(specified_by_url = "not an url")] struct ScalarSpecifiedByUrl(i32); diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr index f80472b2d..8a1fcffed 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr @@ -1,26 +1,5 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/derive_input/attr_invalid_url.rs:4:24 + --> fail/scalar/derive_input/attr_invalid_url.rs:3:37 | -4 | specified_by_url = "not an url", - | ^^^^^^^^^^^^ - -error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope - --> fail/scalar/derive_input/attr_invalid_url.rs:13:49 - | -13 | pub(super) fn to_output(_: &ScalarSpecifiedByUrl) -> Value { - | ^^^^^^^^^^^^^^^^^^^^ not found in this scope - -error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope - --> fail/scalar/derive_input/attr_invalid_url.rs:19:17 - | -17 | pub(super) fn from_input( - | - help: you might be missing a type parameter: `, ScalarSpecifiedByUrl` -18 | _: &InputValue, -19 | ) -> Result { - | ^^^^^^^^^^^^^^^^^^^^ not found in this scope - -error[E0425]: cannot find value `ScalarSpecifiedByUrl` in this scope - --> fail/scalar/derive_input/attr_invalid_url.rs:20:12 - | -20 | Ok(ScalarSpecifiedByUrl) - | ^^^^^^^^^^^^^^^^^^^^ not found in this scope +3 | #[graphql_scalar(specified_by_url = "not an url")] + | ^^^^^^^^^^^^ diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 71924c91b..a39c989c3 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -35,7 +35,7 @@ impl ID { fn from_input(v: &InputValue) -> Result { v.as_string_value() .map(str::to_owned) - .or_else(|| v.as_int_value().map(ToString::to_string)) + .or_else(|| v.as_int_value().as_ref().map(ToString::to_string)) .map(Self) .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) } From a2ecb447eccfff0cbd55bce7ab2d4b28ad5fe430 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 21 Feb 2022 11:42:19 +0300 Subject: [PATCH 089/122] Corrections --- .../juniper_tests/src/codegen/mod.rs | 2 -- juniper/src/value/scalar.rs | 9 +++--- juniper_codegen/src/derive_scalar_value.rs | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index ce92ee6e9..375765b53 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -1,9 +1,7 @@ mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; -mod derive_scalar; mod derive_scalar_value; -mod impl_scalar; mod interface_attr; mod object_attr; mod object_derive; diff --git a/juniper/src/value/scalar.rs b/juniper/src/value/scalar.rs index 899b9507a..51878f384 100644 --- a/juniper/src/value/scalar.rs +++ b/juniper/src/value/scalar.rs @@ -22,9 +22,9 @@ pub trait ParseScalarValue { /// The main objective of this abstraction is to allow other libraries to /// replace the default representation with something that better fits their /// needs. -/// There is a custom derive (`#[derive(juniper::`[`GraphQLScalarValue`]`)]`) -/// available that implements most of the required traits automatically for a -/// enum representing a scalar value. However, [`Serialize`] and [`Deserialize`] +/// There is a custom derive (`#[derive(`[`GraphQLScalarValue`]`)]`) available +/// that implements most of the required traits automatically for a enum +/// representing a scalar value. However, [`Serialize`] and [`Deserialize`] /// implementations are expected to be provided. /// /// # Implementing a new scalar value representation @@ -38,7 +38,7 @@ pub trait ParseScalarValue { /// # use std::{fmt, convert::TryInto as _}; /// # /// # use serde::{de, Deserialize, Deserializer, Serialize}; -/// # use juniper::{ScalarValue, GraphQLScalarValue}; +/// # use juniper::GraphQLScalarValue; /// # /// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)] /// #[serde(untagged)] @@ -121,6 +121,7 @@ pub trait ParseScalarValue { /// ``` /// /// [`Deserialize`]: trait@serde::Deserialize +/// [`GraphQLScalarValue`]: juniper::GraphQLScalarValue /// [`Serialize`]: trait@serde::Serialize pub trait ScalarValue: fmt::Debug diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 30bcec139..0ac4cd542 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -161,6 +161,7 @@ impl ToTokens for Definition { self.impl_scalar_value_tokens().to_tokens(into); self.impl_from_tokens().to_tokens(into); self.impl_display_tokens().to_tokens(into); + self.emit_warnings_tokens().to_tokens(into); } } @@ -343,6 +344,34 @@ impl Definition { } } } + + // TODO: replace with proper warning, once `proc_macro_diagnostics` is + // stabilized. + // https://github.com/rust-lang/rust/issues/54140 + /// Emits warnings for missing [`Method`]s. + fn emit_warnings_tokens(&self) -> TokenStream { + [ + (Method::AsInt, "missing `as_int` attribute"), + (Method::AsFloat, "missing `as_float` attribute"), + (Method::AsStr, "missing `as_str` attribute"), + (Method::AsString, "missing `as_string` attribute"), + (Method::IntoString, "missing `into_string` attribute"), + (Method::AsBoolean, "missing `as_boolean` attribute"), + ] + .iter() + .filter_map(|(method, err)| (!self.methods.contains_key(method)).then(|| err)) + .map(|err| { + quote! { + #[warn(deprecated)] + const _: () = { + #[deprecated(note = #err)] + const JUNIPER_DERIVE_SCALAR_VALUE_WARNING: () = (); + JUNIPER_DERIVE_SCALAR_VALUE_WARNING + }; + } + }) + .collect() + } } /// Single-[`Field`] enum variant. From 943526f746ba8dceb63ca45c15494347e0290058 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 21 Feb 2022 13:07:49 +0300 Subject: [PATCH 090/122] Corrections --- .../attr_additional_non_nullable_argument.rs | 19 + ...tr_additional_non_nullable_argument.stderr | 7 + .../struct/attr_field_double_underscored.rs | 8 + ...r => attr_field_double_underscored.stderr} | 2 +- .../attr_field_non_output_return_type.rs | 13 + ... attr_field_non_output_return_type.stderr} | 2 +- .../interface/struct/attr_fields_duplicate.rs | 11 + ...te.stderr => attr_fields_duplicate.stderr} | 2 +- .../attr_implementers_duplicate_pretty.rs | 14 + .../attr_implementers_duplicate_pretty.stderr | 11 + .../attr_implementers_duplicate_ugly.rs | 16 + .../attr_implementers_duplicate_ugly.stderr | 18 + .../interface/struct/attr_missing_field.rs | 14 + .../struct/attr_missing_field.stderr | 7 + .../interface/struct/attr_missing_for_attr.rs | 14 + ...tr.stderr => attr_missing_for_attr.stderr} | 4 +- .../struct/attr_missing_impl_attr.rs | 13 + .../struct/attr_missing_impl_attr.stderr | 7 + .../struct/attr_name_double_underscored.rs | 8 + ...rr => attr_name_double_underscored.stderr} | 2 +- .../fail/interface/struct/attr_no_fields.rs | 6 + ...no_fields.stderr => attr_no_fields.stderr} | 2 +- .../struct/attr_non_subtype_return.rs | 14 + .../struct/attr_non_subtype_return.stderr | 7 + ...erive_additional_non_nullable_argument.rs} | 0 ...e_additional_non_nullable_argument.stderr} | 4 +- ....rs => derive_field_double_underscored.rs} | 0 .../derive_field_double_underscored.stderr | 7 + ...=> derive_field_non_output_return_type.rs} | 0 ...derive_field_non_output_return_type.stderr | 5 + ...uplicate.rs => derive_fields_duplicate.rs} | 0 .../struct/derive_fields_duplicate.stderr | 12 + ...> derive_implementers_duplicate_pretty.rs} | 0 ...rive_implementers_duplicate_pretty.stderr} | 4 +- ... => derive_implementers_duplicate_ugly.rs} | 0 ...derive_implementers_duplicate_ugly.stderr} | 8 +- ...ssing_field.rs => derive_missing_field.rs} | 0 ...eld.stderr => derive_missing_field.stderr} | 4 +- ...for_attr.rs => derive_missing_for_attr.rs} | 0 .../struct/derive_missing_for_attr.stderr | 7 + ...pl_attr.rs => derive_missing_impl_attr.rs} | 0 ...stderr => derive_missing_impl_attr.stderr} | 4 +- ...d.rs => derive_name_double_underscored.rs} | 0 .../derive_name_double_underscored.stderr | 7 + .../{no_fields.rs => derive_no_fields.rs} | 0 .../interface/struct/derive_no_fields.stderr | 7 + ...return.rs => derive_non_subtype_return.rs} | 0 ...tderr => derive_non_subtype_return.stderr} | 4 +- .../trait/implementers_duplicate_ugly.stderr | 6 +- .../fail/interface/wrong_item_enum.stderr | 10 +- .../interface/wrong_item_impl_block.stderr | 2 +- .../object/argument_non_input_type.stderr | 8 +- .../src/codegen/interface_attr_struct.rs | 2479 +++++++++++++++++ ...erface_attr.rs => interface_attr_trait.rs} | 0 .../juniper_tests/src/codegen/mod.rs | 3 +- juniper_codegen/src/common/field/arg.rs | 3 +- juniper_codegen/src/graphql_interface/attr.rs | 182 +- .../src/graphql_interface/derive.rs | 4 +- 58 files changed, 2946 insertions(+), 45 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs rename integration_tests/codegen_fail/fail/interface/struct/{field_double_underscored.stderr => attr_field_double_underscored.stderr} (81%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs rename integration_tests/codegen_fail/fail/interface/struct/{field_non_output_return_type.stderr => attr_field_non_output_return_type.stderr} (71%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs rename integration_tests/codegen_fail/fail/interface/struct/{fields_duplicate.stderr => attr_fields_duplicate.stderr} (82%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs rename integration_tests/codegen_fail/fail/interface/struct/{missing_for_attr.stderr => attr_missing_for_attr.stderr} (80%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs rename integration_tests/codegen_fail/fail/interface/struct/{name_double_underscored.stderr => attr_name_double_underscored.stderr} (82%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs rename integration_tests/codegen_fail/fail/interface/struct/{no_fields.stderr => attr_no_fields.stderr} (77%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr rename integration_tests/codegen_fail/fail/interface/struct/{additional_non_nullable_argument.rs => derive_additional_non_nullable_argument.rs} (100%) rename integration_tests/codegen_fail/fail/interface/struct/{additional_non_nullable_argument.stderr => derive_additional_non_nullable_argument.stderr} (70%) rename integration_tests/codegen_fail/fail/interface/struct/{field_double_underscored.rs => derive_field_double_underscored.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr rename integration_tests/codegen_fail/fail/interface/struct/{field_non_output_return_type.rs => derive_field_non_output_return_type.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr rename integration_tests/codegen_fail/fail/interface/struct/{fields_duplicate.rs => derive_fields_duplicate.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr rename integration_tests/codegen_fail/fail/interface/struct/{implementers_duplicate_pretty.rs => derive_implementers_duplicate_pretty.rs} (100%) rename integration_tests/codegen_fail/fail/interface/struct/{implementers_duplicate_pretty.stderr => derive_implementers_duplicate_pretty.stderr} (65%) rename integration_tests/codegen_fail/fail/interface/struct/{implementers_duplicate_ugly.rs => derive_implementers_duplicate_ugly.rs} (100%) rename integration_tests/codegen_fail/fail/interface/struct/{implementers_duplicate_ugly.stderr => derive_implementers_duplicate_ugly.stderr} (70%) rename integration_tests/codegen_fail/fail/interface/struct/{missing_field.rs => derive_missing_field.rs} (100%) rename integration_tests/codegen_fail/fail/interface/struct/{missing_field.stderr => derive_missing_field.stderr} (73%) rename integration_tests/codegen_fail/fail/interface/struct/{missing_for_attr.rs => derive_missing_for_attr.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr rename integration_tests/codegen_fail/fail/interface/struct/{missing_impl_attr.rs => derive_missing_impl_attr.rs} (100%) rename integration_tests/codegen_fail/fail/interface/struct/{missing_impl_attr.stderr => derive_missing_impl_attr.stderr} (79%) rename integration_tests/codegen_fail/fail/interface/struct/{name_double_underscored.rs => derive_name_double_underscored.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr rename integration_tests/codegen_fail/fail/interface/struct/{no_fields.rs => derive_no_fields.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr rename integration_tests/codegen_fail/fail/interface/struct/{non_subtype_return.rs => derive_non_subtype_return.rs} (100%) rename integration_tests/codegen_fail/fail/interface/struct/{non_subtype_return.stderr => derive_non_subtype_return.stderr} (82%) create mode 100644 integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs rename integration_tests/juniper_tests/src/codegen/{interface_attr.rs => interface_attr_trait.rs} (100%) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs new file mode 100644 index 000000000..dfe2d7b55 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, is_present: bool) -> &str { + is_present.then(|| self.id.as_str()).unwrap_or("missing") + } +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr new file mode 100644 index 000000000..60a8f73cd --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | +16 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs new file mode 100644 index 000000000..0fb8bf91e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character { + __id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr similarity index 81% rename from integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr index 850f63ccb..31648f4e1 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr @@ -1,5 +1,5 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/struct/field_double_underscored.rs:5:5 + --> fail/interface/struct/attr_field_double_underscored.rs:5:5 | 5 | __id: String, | ^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs new file mode 100644 index 000000000..29be71aa0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs @@ -0,0 +1,13 @@ +use juniper::{graphql_interface, GraphQLInputObject}; + +#[derive(GraphQLInputObject)] +pub struct ObjB { + id: i32, +} + +#[graphql_interface] +struct Character { + id: ObjB, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr rename to integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr index b282513cf..ecd251f1a 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/struct/field_non_output_return_type.rs:10:9 + --> fail/interface/struct/attr_field_non_output_return_type.rs:10:9 | 10 | id: ObjB, | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs new file mode 100644 index 000000000..f5cee2669 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs @@ -0,0 +1,11 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character { + id: String, + + #[graphql(name = "id")] + id2: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr rename to integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr index 2f5690ec4..b6a25f246 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr @@ -1,5 +1,5 @@ error: GraphQL interface must have a different name for each field - --> fail/interface/struct/fields_duplicate.rs:4:1 + --> fail/interface/struct/attr_fields_duplicate.rs:4:1 | 4 | / struct Character { 5 | | id: String, diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs new file mode 100644 index 000000000..fd367d499 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = [ObjA, ObjA])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..c87ad2e06 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr @@ -0,0 +1,11 @@ +error: duplicated attribute argument found + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs new file mode 100644 index 000000000..f119eeaf4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs @@ -0,0 +1,16 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +type ObjAlias = ObjA; + +#[graphql_interface(for = [ObjA, ObjAlias])] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr new file mode 100644 index 000000000..3f8332882 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr @@ -0,0 +1,18 @@ +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:34 + | +11 | #[graphql_interface(for = [ObjA, ObjAlias])] + | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` + | | + | first implementation here + +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 + | +11 | #[graphql_interface(for = [ObjA, ObjAlias])] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `ObjA` + | + = note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs new file mode 100644 index 000000000..b835e486b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr new file mode 100644 index 000000000..47856d5d4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs new file mode 100644 index 000000000..b7480d649 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[graphql_interface] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr rename to integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr index 4290fbe00..b7008d176 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/struct/missing_for_attr.rs:3:10 + --> fail/interface/struct/attr_missing_for_attr.rs:3:10 | 3 | #[derive(GraphQLObject)] - | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/struct/missing_for_attr.rs:3:10 + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/struct/attr_missing_for_attr.rs:3:10 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs new file mode 100644 index 000000000..5d822f716 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs @@ -0,0 +1,13 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr new file mode 100644 index 000000000..f03491d39 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_impl_attr.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/attr_missing_impl_attr.rs:8:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs new file mode 100644 index 000000000..ac0613b97 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct __Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr rename to integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr index 178223ff0..15db17f68 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr @@ -1,5 +1,5 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/struct/name_double_underscored.rs:4:8 + --> fail/interface/struct/attr_name_double_underscored.rs:4:8 | 4 | struct __Character { | ^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs new file mode 100644 index 000000000..7dd3ab857 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs @@ -0,0 +1,6 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr similarity index 77% rename from integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr rename to integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr index f11feb2c8..6920674e2 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr @@ -1,5 +1,5 @@ error: GraphQL interface must have at least one field - --> fail/interface/struct/no_fields.rs:4:1 + --> fail/interface/struct/attr_no_fields.rs:4:1 | 4 | struct Character {} | ^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs new file mode 100644 index 000000000..8a931d1d4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr new file mode 100644 index 000000000..213ecb20d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_non_subtype_return.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/attr_non_subtype_return.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr similarity index 70% rename from integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr rename to integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr index d4b80722e..cfe81a29b 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/additional_non_nullable_argument.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/struct/additional_non_nullable_argument.rs:17:5 + --> fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 | 17 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/additional_non_nullable_argument.rs:17:5 + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/field_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr new file mode 100644 index 000000000..37b88b689 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/derive_field_double_underscored.rs:5:5 + | +5 | __id: String, + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/field_non_output_return_type.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr new file mode 100644 index 000000000..fc418f480 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/struct/derive_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/fields_duplicate.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr new file mode 100644 index 000000000..a00789895 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr @@ -0,0 +1,12 @@ +error: GraphQL interface must have a different name for each field + --> fail/interface/struct/derive_fields_duplicate.rs:4:1 + | +4 | / struct Character { +5 | | id: String, +6 | | +7 | | #[graphql(name = "id")] +8 | | id2: String, +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr similarity index 65% rename from integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr rename to integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr index bea16ace7..a4a803789 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_pretty.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr @@ -1,11 +1,11 @@ error: duplicated attribute argument found - --> fail/interface/struct/implementers_duplicate_pretty.rs:10:24 + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:10:24 | 10 | #[graphql(for = [ObjA, ObjA])] | ^^^^ error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/struct/implementers_duplicate_pretty.rs:4:18 + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:4:18 | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr similarity index 70% rename from integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr rename to integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr index 4e49ca39a..26f6669be 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr @@ -1,13 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/struct/implementers_duplicate_ugly.rs:12:18 + --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:12:24 | 12 | #[graphql(for = [ObjA, ObjAlias])] - | ^^^^ -------- first implementation here + | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` | | - | conflicting implementation for `CharacterValueEnum` + | first implementation here error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` - --> fail/interface/struct/implementers_duplicate_ugly.rs:11:10 + --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 | 11 | #[derive(GraphQLInterface)] | ^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_field.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/missing_field.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr similarity index 73% rename from integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr rename to integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr index 49172bb82..a8570a6cf 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/missing_field.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/struct/missing_field.rs:12:5 + --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/missing_field.rs:12:5 + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/missing_for_attr.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr new file mode 100644 index 000000000..9f394b974 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_for_attr.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/struct/derive_missing_for_attr.rs:3:10 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr similarity index 79% rename from integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr rename to integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr index 47003d9e5..e092494a0 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/missing_impl_attr.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/struct/missing_impl_attr.rs:8:10 + --> fail/interface/struct/derive_missing_impl_attr.rs:8:10 | 8 | #[derive(GraphQLInterface)] - | ^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/missing_impl_attr.rs:8:10 + | ^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/struct/derive_missing_impl_attr.rs:8:10 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/name_double_underscored.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr new file mode 100644 index 000000000..0996adac9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/interface/struct/derive_name_double_underscored.rs:4:8 + | +4 | struct __Character { + | ^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/struct/no_fields.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/no_fields.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr new file mode 100644 index 000000000..d0718a6de --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface must have at least one field + --> fail/interface/struct/derive_no_fields.rs:4:1 + | +4 | struct Character {} + | ^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.rs rename to integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr similarity index 82% rename from integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr rename to integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr index 0b4e0966a..46c9de9de 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/non_subtype_return.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr @@ -1,7 +1,7 @@ error[E0080]: evaluation of constant value failed - --> fail/interface/struct/non_subtype_return.rs:12:5 + --> fail/interface/struct/derive_non_subtype_return.rs:12:5 | 12 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/non_subtype_return.rs:12:5 + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/derive_non_subtype_return.rs:12:5 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 822487363..97399003d 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,10 +1,10 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/trait/implementers_duplicate_ugly.rs:11:28 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:34 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ^^^^ -------- first implementation here + | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` | | - | conflicting implementation for `CharacterValueEnum` + | first implementation here error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr index 9c2607dd4..0c579b777 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr @@ -1,7 +1,5 @@ -error: #[graphql_interface] attribute is applicable to trait definitions only - --> fail/interface/wrong_item_enum.rs:8:1 +error: GraphQL interface can only be derived on structs + --> fail/interface/wrong_item_enum.rs:9:1 | -8 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) +9 | enum Character {} + | ^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr index 712206c0b..e5d3d2a98 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr @@ -1,4 +1,4 @@ -error: #[graphql_interface] attribute is applicable to trait definitions only +error: #[graphql_interface] attribute is applicable to trait and struct definitions only --> fail/interface/wrong_item_impl_block.rs:8:1 | 8 | #[graphql_interface(for = ObjA)] diff --git a/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr index 44e21e0a1..d4a3b19a8 100644 --- a/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr @@ -1,10 +1,8 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/object/argument_non_input_type.rs:10:1 - | -10 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + --> fail/object/argument_non_input_type.rs:12:23 | - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) +12 | fn id(&self, obj: ObjA) -> &str { + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied --> fail/object/argument_non_input_type.rs:10:1 diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs new file mode 100644 index 000000000..9b4d3a7cd --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs @@ -0,0 +1,2479 @@ +//! Tests for `#[graphql_interface]` macro. + +use std::marker::PhantomData; + +use juniper::{ + execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, + FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, +}; + +use crate::util::{schema, schema_with_scalar}; + +mod no_implers { + use super::*; + + #[graphql_interface] + struct Character { + id: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + async fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn registers_all_implementers() { + const DOC: &str = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); + + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod explicit_alias { + use super::*; + + #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterEnum)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterEnum)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterEnum { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod trivial_async { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + async fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn is_graphql_interface() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + + #[tokio::test] + async fn registers_all_implementers() { + const DOC: &str = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); + + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + object, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn uses_trait_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } +} + +mod 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])] + struct Character { + id: Result, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> Result { + Ok(self.id.clone()) + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn has_correct_graphql_type() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Character", + "kind": "INTERFACE", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }], + }}), + vec![], + )), + ); + } +} + +mod generic { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: String, + + #[graphql(skip)] + _phantom: PhantomData<(A, B)>, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Rust docs. + #[graphql_interface(for = Human)] + struct Character { + /// Rust `id` docs. + /// Long. + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + fields { + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs.\nLong."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + #[graphql_interface(for = Human)] + struct Character { + id: String, + + #[deprecated] + a: String, + + #[deprecated(note = "Use `id`.")] + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self) -> &str { + &self.id + } + + fn human_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> &'static str { + "a" + } + + fn b() -> String { + "b".to_owned() + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_fields() { + const DOC: &str = r#"{ + character { + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": null}, + {"name": "a", "deprecationReason": null}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + /// Rust docs. + #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] + struct Character { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + id: String, + + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + a: String, + + b: String, + } + + struct Human { + id: String, + home_planet: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Human { + fn my_id(&self, #[graphql(name = "myName")] _: Option) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> String { + "a".to_owned() + } + + fn b() -> &'static str { + "b" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + myId + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + name + fields(includeDeprecated: true) { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyChar", + "fields": [ + {"name": "myId", "args": []}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + description + fields(includeDeprecated: true) { + name + description + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My character.", + "fields": [{ + "name": "myId", + "description": "My character ID.", + "args": [], + }, { + "name": "a", + "description": null, + "args": [], + }, { + "name": "b", + "description": null, + "args": [], + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyChar") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": null, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": null, + }], + }}), + vec![], + )), + ); + } +} + +mod renamed_all_fields_and_args { + use super::*; + + #[graphql_interface(rename_all = "none", for = Human)] + struct Character { + id: String, + } + + struct Human; + + #[graphql_object(rename_all = "none", impl = CharacterValue)] + impl Human { + fn id() -> &'static str { + "human-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human.into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": "human-32", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_and_args_names() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + #[graphql_interface(scalar = DefaultScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema_with_scalar::(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema_with_scalar::(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema_with_scalar::(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod explicit_generic_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = S)] + struct Character { + id: FieldResult, + } + + #[derive(GraphQLObject)] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod bounded_generic_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod ignored_method { + use super::*; + + #[graphql_interface(for = Human)] + struct Character { + id: String, + + #[graphql(ignore)] + ignored: Option, + + #[graphql(skip)] + skipped: i32, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into() + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Character") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + +mod field_return_subtyping { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod field_return_union_subtyping { + use super::*; + + #[derive(GraphQLObject)] + struct Strength { + value: i32, + } + + #[derive(GraphQLObject)] + struct Knowledge { + value: i32, + } + + #[allow(dead_code)] + #[derive(GraphQLUnion)] + enum KeyFeature { + Strength(Strength), + Knowledge(Knowledge), + } + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: Option, + key_feature: KeyFeature, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + key_feature: Knowledge, + } + + struct Droid { + id: String, + primary_function: String, + strength: i32, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + + fn key_feature(&self) -> Strength { + Strength { + value: self.strength, + } + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + key_feature: Knowledge { value: 10 }, + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + strength: 42, + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + "keyFeature": {"value": 10}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + "keyFeature": {"value": 42}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } + } + }"#; + + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_val = *expected_val; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "keyFeature": {"value": expected_val}, + }}), + vec![], + )), + ); + } + } +} + +mod nullable_argument_subtyping { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + struct Character { + id: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id(isPresent: true) + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "missing"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/interface_attr.rs rename to integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 64b365fb5..ba776c7ea 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -2,7 +2,8 @@ mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; mod derive_scalar_value; -mod interface_attr; +mod interface_attr_struct; +mod interface_attr_trait; mod interface_derive; mod object_attr; mod object_derive; diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 2b895add7..b0f9cdccc 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -329,12 +329,11 @@ impl OnMethod { .map(|desc| quote! { .description(#desc) }); let method = if let Some(val) = &arg.default { - let span = val.span(); let val = val .as_ref() .map(|v| quote! { (#v).into() }) .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote_spanned! { span => .arg_with_default::<#ty>(#name, &#val, info) } + quote_spanned! { val.span() => .arg_with_default::<#ty>(#name, &#val, info) } } else { quote! { .arg::<#ty>(#name, info) } }; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 78e0ddbdc..41e082812 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -23,15 +23,20 @@ const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body) { + if let Ok(mut ast) = syn::parse2::(body.clone()) { let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); return expand_on_trait(trait_attrs, ast); } + if let Ok(mut ast) = syn::parse2::(body) { + let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); + return expand_on_derive_input(trait_attrs, ast); + } Err(syn::Error::new( Span::call_site(), - "#[graphql_interface] attribute is applicable to trait definitions only", + "#[graphql_interface] attribute is applicable to trait and struct definitions only", )) } @@ -74,7 +79,7 @@ fn expand_on_trait( .iter_mut() .filter_map(|item| { if let syn::TraitItem::Method(m) = item { - if let Some(f) = parse_field(m, &renaming) { + if let Some(f) = parse_trait_method(m, &renaming) { return Some(f); } } @@ -145,7 +150,7 @@ fn expand_on_trait( /// /// Returns [`None`] if parsing fails, or the method field is ignored. #[must_use] -fn parse_field( +fn parse_trait_method( method: &mut syn::TraitItemMethod, renaming: &RenameRule, ) -> Option { @@ -237,6 +242,175 @@ fn parse_field( }) } +/// Expands `#[graphql_interface]` macro placed on trait definition. +fn expand_on_derive_input( + attrs: Vec, + mut ast: syn::DeriveInput, +) -> syn::Result { + let attr = Attr::from_attrs("graphql_interface", &attrs)?; + + let trait_ident = &ast.ident; + let trait_span = ast.span(); + + let data = match &mut ast.data { + syn::Data::Struct(data) => data, + syn::Data::Enum(_) | syn::Data::Union(_) => { + return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); + } + }; + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| trait_ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| trait_ident.span()), + ); + } + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let renaming = attr + .rename_fields + .as_deref() + .copied() + .unwrap_or(RenameRule::CamelCase); + + let fields = data + .fields + .iter_mut() + .filter_map(|f| parse_struct_field(f, &renaming)) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(trait_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(trait_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + + let context = attr + .context + .as_deref() + .cloned() + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments.as_ref().and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) + }) + }) + .unwrap_or_else(|| parse_quote! { () }); + + let enum_alias_ident = attr + .r#enum + .as_deref() + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + let enum_ident = attr.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", trait_ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + + let generated_code = Definition { + generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, + name, + description: attr.description.as_deref().cloned(), + context, + scalar, + fields, + implementers: attr + .implementers + .iter() + .map(|c| c.inner().clone()) + .collect(), + }; + + Ok(quote! { + #[allow(dead_code)] + #ast + #generated_code + }) +} + +/// Parses a [`field::Definition`] from the given trait method definition. +/// +/// Returns [`None`] if parsing fails, or the method field is ignored. +#[must_use] +fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option { + let field_ident = field + .ident + .as_ref() + .ok_or_else(|| proc_macro_error::emit_error!(field.span(), "expected named field")) + .ok()?; + let field_attrs = field.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + field.attrs = mem::take(&mut field.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); + + let attr = field::Attr::from_attrs("graphql", &field_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.ignore.is_some() { + return None; + } + + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&field_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| field_ident.span()), + ); + return None; + } + + let mut ty = field.ty.clone(); + ty.lifetimes_anonymized(); + + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr + .deprecated + .as_deref() + .map(|d| d.as_ref().map(syn::LitStr::value)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: field_ident.clone(), + arguments: None, + has_receiver: false, + is_async: false, + }) +} + /// Emits "trait method can't have default implementation" [`syn::Error`] /// pointing to the given `span`. fn err_default_impl_block(span: &S) -> Option { diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index b5ac63112..847dd54d1 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -12,10 +12,10 @@ use crate::{ use super::{Attr, Definition}; -/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. +/// [`GraphQLScope`] of errors for `#[derive(GraphQLInterface)]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceDerive; -/// Expands `#[graphql_interface]` macro into generated code. +/// Expands `#[derive(GraphQLInterface)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; let attr = Attr::from_attrs("graphql", &ast.attrs)?; From 9144d022059b827f42d1be294fb59a4ee171d361 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 21 Feb 2022 14:19:46 +0300 Subject: [PATCH 091/122] Corrections --- .../fail/scalar_value/missing_methods.rs | 13 +++ .../fail/scalar_value/missing_methods.stderr | 11 ++ .../src/codegen/derive_scalar_value.rs | 61 +++++++--- juniper_codegen/src/derive_scalar_value.rs | 104 +++++++++++++----- 4 files changed, 145 insertions(+), 44 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/scalar_value/missing_methods.rs create mode 100644 integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.rs b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.rs new file mode 100644 index 000000000..556d92eb4 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.rs @@ -0,0 +1,13 @@ +use juniper::GraphQLScalarValue; + +#[derive(Clone, Debug, GraphQLScalarValue, PartialEq)] +pub enum DefaultScalarValue { + Int(i32), + Float(f64), + #[graphql(as_str, as_string, into_string)] + String(String), + #[graphql(as_boolean)] + Boolean(bool), +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr new file mode 100644 index 000000000..dd3c337e2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr @@ -0,0 +1,11 @@ +error: GraphQL built-in scalars missing `as_int`, `as_float` attributes. In case you are sure that it\'s ok, use `#[graphql(allow_unused_methods)]` to suppress this error. + --> fail/scalar_value/missing_methods.rs:4:1 + | +4 | / pub enum DefaultScalarValue { +5 | | Int(i32), +6 | | Float(f64), +7 | | #[graphql(as_str, as_string, into_string)] +... | +10 | | Boolean(bool), +11 | | } + | |_^ diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs index 17a810fc7..0d97349db 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs @@ -6,7 +6,7 @@ mod trivial { #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] #[serde(untagged)] - pub enum ScalarValue { + pub enum CustomScalarValue { #[graphql(as_int, as_float)] Int(i32), #[graphql(as_float)] @@ -19,16 +19,16 @@ mod trivial { #[test] fn into_another() { - assert!(ScalarValue::from(5) + assert!(CustomScalarValue::from(5) .into_another::() .is_type::()); - assert!(ScalarValue::from(0.5_f64) + assert!(CustomScalarValue::from(0.5_f64) .into_another::() .is_type::()); - assert!(ScalarValue::from("str".to_owned()) + assert!(CustomScalarValue::from("str".to_owned()) .into_another::() .is_type::()); - assert!(ScalarValue::from(true) + assert!(CustomScalarValue::from(true) .into_another::() .is_type::()); } @@ -39,7 +39,7 @@ mod named_fields { #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] #[serde(untagged)] - pub enum ScalarValue { + pub enum CustomScalarValue { #[graphql(as_int, as_float)] Int { int: i32 }, #[graphql(as_float)] @@ -52,16 +52,16 @@ mod named_fields { #[test] fn into_another() { - assert!(ScalarValue::from(5) + assert!(CustomScalarValue::from(5) .into_another::() .is_type::()); - assert!(ScalarValue::from(0.5_f64) + assert!(CustomScalarValue::from(0.5_f64) .into_another::() .is_type::()); - assert!(ScalarValue::from("str".to_owned()) + assert!(CustomScalarValue::from("str".to_owned()) .into_another::() .is_type::()); - assert!(ScalarValue::from(true) + assert!(CustomScalarValue::from(true) .into_another::() .is_type::()); } @@ -72,7 +72,7 @@ mod custom_fn { #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] #[serde(untagged)] - pub enum ScalarValue { + pub enum CustomScalarValue { #[graphql(as_int, as_float)] Int(i32), #[graphql(as_float)] @@ -89,16 +89,47 @@ mod custom_fn { #[test] fn into_another() { - assert!(ScalarValue::from(5) + assert!(CustomScalarValue::from(5) .into_another::() .is_type::()); - assert!(ScalarValue::from(0.5_f64) + assert!(CustomScalarValue::from(0.5_f64) .into_another::() .is_type::()); - assert!(ScalarValue::from("str".to_owned()) + assert!(CustomScalarValue::from("str".to_owned()) .into_another::() .is_type::()); - assert!(ScalarValue::from(true) + assert!(CustomScalarValue::from(true) + .into_another::() + .is_type::()); + } +} + +mod allow_missing_methods { + use super::*; + + #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] + #[graphql(allow_missing_methods)] + #[serde(untagged)] + pub enum CustomScalarValue { + Int(i32), + #[graphql(as_float)] + Float(f64), + #[graphql(as_str, as_string, into_string)] + String(String), + #[graphql(as_boolean)] + Boolean(bool), + } + + #[test] + fn into_another() { + assert!(CustomScalarValue::Int(5).as_int().is_none()); + assert!(CustomScalarValue::from(0.5_f64) + .into_another::() + .is_type::()); + assert!(CustomScalarValue::from("str".to_owned()) + .into_another::() + .is_type::()); + assert!(CustomScalarValue::from(true) .into_another::() .is_type::()); } diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 0ac4cd542..09a075ab6 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -24,12 +24,15 @@ const ERR: GraphQLScope = GraphQLScope::DeriveScalarValue; /// Expands `#[derive(GraphQLScalarValue)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input)?; + let span = ast.span(); let data_enum = match ast.data { syn::Data::Enum(e) => e, _ => return Err(ERR.custom_error(ast.span(), "can only be derived for enums")), }; + let attr = Attr::from_attrs("graphql", &ast.attrs)?; + let mut methods = HashMap::>::new(); for var in data_enum.variants.clone() { let (ident, field) = (var.ident, Field::try_from(var.fields)?); @@ -43,6 +46,34 @@ pub fn expand(input: TokenStream) -> syn::Result { } } + let missing_methods = [ + (Method::AsInt, "`#[graphql(as_int)]`"), + (Method::AsFloat, "`#[graphql(as_float)]`"), + (Method::AsStr, "`#[graphql(as_str)]`"), + (Method::AsString, "`#[graphql(as_string)]`"), + (Method::IntoString, "`#[graphql(into_string)]`"), + (Method::AsBoolean, "`#[graphql(as_boolean)]`"), + ] + .iter() + .filter_map(|(method, err)| (!methods.contains_key(method)).then(|| err)) + .fold(None, |acc, &method| { + Some( + acc.map(|acc| format!("{}, {}", acc, method)) + .unwrap_or_else(|| method.to_owned()), + ) + }) + .filter(|_| !attr.allow_missing_methods); + if let Some(missing_methods) = missing_methods { + return Err(ERR.custom_error( + span, + format!( + "missing {} attributes. In case you are sure that it's ok, \ + use `#[graphql(allow_missing_methods)]` to suppress this error.", + missing_methods, + ), + )); + } + Ok(Definition { ident: ast.ident, generics: ast.generics, @@ -52,6 +83,50 @@ pub fn expand(input: TokenStream) -> syn::Result { .into_token_stream()) } +/// Available arguments behind `#[graphql]` attribute when generating code for +/// enum container. +#[derive(Default)] +struct Attr { + /// Allows missing [`Method`]s. + allow_missing_methods: bool, +} + +impl Parse for Attr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Attr::default(); + while !input.is_empty() { + let ident = input.parse::()?; + match ident.to_string().as_str() { + "allow_missing_methods" => { + out.allow_missing_methods = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + }; + input.try_parse::()?; + } + Ok(out) + } +} + +impl Attr { + /// Tries to merge two [`Attr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(mut self, another: Self) -> syn::Result { + self.allow_missing_methods |= another.allow_missing_methods; + Ok(self) + } + + /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a enum variant. + 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?)) + } +} + /// Possible attribute names of the `#[derive(GraphQLScalarValue)]`. #[derive(Eq, Hash, PartialEq)] enum Method { @@ -161,7 +236,6 @@ impl ToTokens for Definition { self.impl_scalar_value_tokens().to_tokens(into); self.impl_from_tokens().to_tokens(into); self.impl_display_tokens().to_tokens(into); - self.emit_warnings_tokens().to_tokens(into); } } @@ -344,34 +418,6 @@ impl Definition { } } } - - // TODO: replace with proper warning, once `proc_macro_diagnostics` is - // stabilized. - // https://github.com/rust-lang/rust/issues/54140 - /// Emits warnings for missing [`Method`]s. - fn emit_warnings_tokens(&self) -> TokenStream { - [ - (Method::AsInt, "missing `as_int` attribute"), - (Method::AsFloat, "missing `as_float` attribute"), - (Method::AsStr, "missing `as_str` attribute"), - (Method::AsString, "missing `as_string` attribute"), - (Method::IntoString, "missing `into_string` attribute"), - (Method::AsBoolean, "missing `as_boolean` attribute"), - ] - .iter() - .filter_map(|(method, err)| (!self.methods.contains_key(method)).then(|| err)) - .map(|err| { - quote! { - #[warn(deprecated)] - const _: () = { - #[deprecated(note = #err)] - const JUNIPER_DERIVE_SCALAR_VALUE_WARNING: () = (); - JUNIPER_DERIVE_SCALAR_VALUE_WARNING - }; - } - }) - .collect() - } } /// Single-[`Field`] enum variant. From 7fab2ce803183260921197aec24a7c2a60ce1382 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 21 Feb 2022 15:27:12 +0300 Subject: [PATCH 092/122] WIP --- ...g_item_enum.rs => attr_wrong_item_enum.rs} | 0 .../interface/attr_wrong_item_enum.stderr | 5 ++ ...block.rs => attr_wrong_item_impl_block.rs} | 0 ...derr => attr_wrong_item_impl_block.stderr} | 2 +- .../fail/interface/derive_wrong_item_enum.rs | 12 ++++ .../interface/derive_wrong_item_enum.stderr | 6 ++ .../fail/interface/wrong_item_enum.stderr | 5 -- .../fail/scalar_value/missing_methods.stderr | 2 +- .../src/codegen/interface_derive.rs | 2 +- juniper_codegen/src/graphql_interface/attr.rs | 19 ++++-- .../src/graphql_interface/derive.rs | 2 +- juniper_codegen/src/lib.rs | 58 ++++++++++++++++++- 12 files changed, 96 insertions(+), 17 deletions(-) rename integration_tests/codegen_fail/fail/interface/{wrong_item_enum.rs => attr_wrong_item_enum.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr rename integration_tests/codegen_fail/fail/interface/{wrong_item_impl_block.rs => attr_wrong_item_impl_block.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{wrong_item_impl_block.stderr => attr_wrong_item_impl_block.stderr} (85%) create mode 100644 integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.rs create mode 100644 integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs rename to integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.rs diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr new file mode 100644 index 000000000..f11e4b406 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr @@ -0,0 +1,5 @@ +error: GraphQL interface #[graphql_interface] attribute is applicable to trait and struct definitions only + --> fail/interface/attr_wrong_item_enum.rs:9:1 + | +9 | enum Character {} + | ^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs rename to integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.rs diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr similarity index 85% rename from integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr rename to integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr index e5d3d2a98..b221c278b 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr +++ b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr @@ -1,5 +1,5 @@ error: #[graphql_interface] attribute is applicable to trait and struct definitions only - --> fail/interface/wrong_item_impl_block.rs:8:1 + --> fail/interface/attr_wrong_item_impl_block.rs:8:1 | 8 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.rs b/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.rs new file mode 100644 index 000000000..2ba9b2923 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.rs @@ -0,0 +1,12 @@ +use juniper::{GraphQLInterface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + test: String, +} + +#[derive(GraphQLInterface)] +#[graphql(for = ObjA)] +enum Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr b/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr new file mode 100644 index 000000000..c3889563b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr @@ -0,0 +1,6 @@ +error: GraphQL interface can only be derived on structs + --> fail/interface/derive_wrong_item_enum.rs:9:1 + | +9 | / #[graphql(for = ObjA)] +10 | | enum Character {} + | |_________________^ diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr deleted file mode 100644 index 0c579b777..000000000 --- a/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: GraphQL interface can only be derived on structs - --> fail/interface/wrong_item_enum.rs:9:1 - | -9 | enum Character {} - | ^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr index dd3c337e2..b44c19f18 100644 --- a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr +++ b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr @@ -1,4 +1,4 @@ -error: GraphQL built-in scalars missing `as_int`, `as_float` attributes. In case you are sure that it\'s ok, use `#[graphql(allow_unused_methods)]` to suppress this error. +error: GraphQL built-in scalars missing `#[graphql(as_int)]`, `#[graphql(as_float)]` attributes. In case you are sure that it\'s ok, use `#[graphql(allow_missing_methods)]` to suppress this error. --> fail/scalar_value/missing_methods.rs:4:1 | 4 | / pub enum DefaultScalarValue { diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs index 8d586f0dd..ee0065352 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -1,4 +1,4 @@ -//! Tests for `#[graphql_interface]` macro. +//! Tests for `#[derive(GraphQLInterface)]` macro. #![allow(dead_code)] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 41e082812..9c26fe25c 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -255,7 +255,11 @@ fn expand_on_derive_input( let data = match &mut ast.data { syn::Data::Struct(data) => data, syn::Data::Enum(_) | syn::Data::Union(_) => { - return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); + return Err(ERR.custom_error( + ast.span(), + "#[graphql_interface] attribute is applicable \ + to trait and struct definitions only", + )); } }; @@ -354,11 +358,7 @@ fn expand_on_derive_input( /// Returns [`None`] if parsing fails, or the method field is ignored. #[must_use] fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option { - let field_ident = field - .ident - .as_ref() - .ok_or_else(|| proc_macro_error::emit_error!(field.span(), "expected named field")) - .ok()?; + let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; let field_attrs = field.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. @@ -440,3 +440,10 @@ fn err_no_method_receiver(span: &S) -> Option { ); None } + +/// Emits "expected named struct field" [`syn::Error`] pointing to the given +/// `span`. +fn err_unnamed_field(span: &S) -> Option { + ERR.emit_custom(span.span(), "expected named struct field"); + None +} diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 847dd54d1..18f83f851 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -23,7 +23,7 @@ pub fn expand(input: TokenStream) -> syn::Result { let data = if let syn::Data::Struct(data) = &ast.data { data } else { - return Err(ERR.custom_error(ast.span(), "can only be derived for structs")); + return Err(ERR.custom_error(ast.span(), "can only be derived on structs")); }; let struct_ident = &ast.ident; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 5f39ab111..1fc22ed17 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -671,8 +671,7 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// [GraphQL interfaces][1] are more like structurally-typed interfaces, while /// Rust's traits are more like type classes. Using `impl Trait` isn't an /// option, so you have to cover all trait's methods with type's fields or -/// impl block. But no one is stopping you from additionally implementing trait -/// manually. +/// impl block. /// /// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to @@ -682,6 +681,29 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// Macro uses Rust enums only to represent a value type of a /// [GraphQL interface][1]. /// +/// [GraphQL interface][1] can be represented with struct in case methods don't +/// have any arguments: +/// +/// ``` +/// use juniper::{graphql_interface, GraphQLObject}; +/// +/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this +/// // GraphQL interface. +/// #[graphql_interface(for = Human)] // enumerating all implementers is mandatory +/// struct Character { +/// id: String, +/// } +/// +/// #[derive(GraphQLObject)] +/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name +/// struct Human { +/// id: String, // this field is used to resolve Character::id +/// home_planet: String, +/// } +/// ``` +/// +/// Also [GraphQL interface][1] can be represented with trait: +/// /// ``` /// use juniper::{graphql_interface, GraphQLObject}; /// @@ -700,6 +722,10 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream { /// } /// ``` /// +/// > __NOTE:__ Struct or trait representing interface acts as a blueprint and +/// > isn't actually used. But no-one is stopping you from +/// > implementing trait manually for your own usage. +/// /// # Custom name, description, deprecation and argument defaults /// /// The name of [GraphQL interface][1], its field, or a field argument may be overridden with a @@ -949,6 +975,34 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +/// `#[derive(GraphQLInterface)]` macro for generating a [GraphQL interface][1] +/// implementation for traits and its implementers. +/// +/// This macro is applicable only to structs and useful in case [interface][1] +/// methods doesn't have any arguments: +/// +/// ``` +/// use juniper::{GraphQLInterface, GraphQLObject}; +/// +/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this +/// // GraphQL interface. +/// #[derive(GraphQLInterface)] +/// #[graphql(for = Human)] // enumerating all implementers is mandatory +/// struct Character { +/// id: String, +/// } +/// +/// #[derive(GraphQLObject)] +/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name +/// struct Human { +/// id: String, // this field is used to resolve Character::id +/// home_planet: String, +/// } +/// ``` +/// +/// For more info and possibilities see `#[`[`graphql_interface`]`]` macro. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[proc_macro_error] #[proc_macro_derive(GraphQLInterface, attributes(graphql))] pub fn derive_interface(body: TokenStream) -> TokenStream { From 91fd9e43b2a7cb27023f2c87ff2c021dc8ecda30 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 22 Feb 2022 15:32:04 +0300 Subject: [PATCH 093/122] WIP --- .../fail/interface/struct/attr_cyclic_impl.rs | 13 + .../interface/struct/attr_cyclic_impl.stderr | 26 + .../interface/struct/derive_cyclic_impl.rs | 21 + .../struct/derive_cyclic_impl.stderr | 26 + .../src/codegen/interface_attr_struct.rs | 538 +++++++++++++++++- .../juniper_tests/src/codegen/mod.rs | 1 + juniper/src/macros/reflect.rs | 2 +- juniper/src/schema/meta.rs | 15 + juniper/src/schema/model.rs | 4 + juniper/src/schema/schema.rs | 14 +- .../src/schema/translate/graphql_parser.rs | 7 +- juniper_codegen/src/graphql_interface/attr.rs | 10 +- .../src/graphql_interface/derive.rs | 5 +- juniper_codegen/src/graphql_interface/mod.rs | 166 ++++-- 14 files changed, 785 insertions(+), 63 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.rs new file mode 100644 index 000000000..7681972ae --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.rs @@ -0,0 +1,13 @@ +use juniper::graphql_interface; + +#[graphql_interface(impl = Node2Value, for = Node2Value)] +struct Node1 { + id: String, +} + +#[graphql_interface(impl = Node1Value, for = Node1Value)] +struct Node2 { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.stderr new file mode 100644 index 000000000..ed809a5a0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.stderr @@ -0,0 +1,26 @@ +error[E0391]: cycle detected when expanding type alias `Node1Value` + --> fail/interface/struct/attr_cyclic_impl.rs:3:46 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ + | +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/struct/attr_cyclic_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^ + = note: ...which again requires expanding type alias `Node1Value`, completing the cycle + = note: type aliases cannot be recursive + = help: consider using a struct, enum, or union instead to break the cycle + = help: see for more information +note: cycle used when collecting item types in top-level module + --> fail/interface/struct/attr_cyclic_impl.rs:1:1 + | +1 | / use juniper::graphql_interface; +2 | | +3 | | #[graphql_interface(impl = Node2Value, for = Node2Value)] +4 | | struct Node1 { +... | +12 | | +13 | | fn main() {} + | |____________^ diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs new file mode 100644 index 000000000..45f9059e2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs @@ -0,0 +1,21 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +#[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] +struct Node1 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = Node1Value, for = Node3Value)] +struct Node2 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = [Node1Value, Node2Value], for = Node1Value)] +struct Node3 { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr new file mode 100644 index 000000000..687b0df37 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr @@ -0,0 +1,26 @@ +error[E0391]: cycle detected when expanding type alias `Node1Value` + --> fail/interface/struct/derive_cyclic_impl.rs:4:49 + | +4 | #[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] + | ^^^^^^^^^^ + | +note: ...which requires expanding type alias `Node3Value`... + --> fail/interface/struct/derive_cyclic_impl.rs:16:50 + | +16 | #[graphql(impl = [Node1Value, Node2Value], for = Node1Value)] + | ^^^^^^^^^^ + = note: ...which again requires expanding type alias `Node1Value`, completing the cycle + = note: type aliases cannot be recursive + = help: consider using a struct, enum, or union instead to break the cycle + = help: see for more information +note: cycle used when collecting item types in top-level module + --> fail/interface/struct/derive_cyclic_impl.rs:1:1 + | +1 | / use juniper::GraphQLInterface; +2 | | +3 | | #[derive(GraphQLInterface)] +4 | | #[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] +... | +20 | | +21 | | fn main() {} + | |____________^ diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs index 9b4d3a7cd..0ad84b461 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, - FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, + FieldError, FieldResult, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, ID, }; use crate::util::{schema, schema_with_scalar}; @@ -2477,3 +2477,539 @@ mod nullable_argument_subtyping { } } } + +mod simple_inheritance { + use super::*; + + #[graphql_interface(for = [ResourceValue, Endpoint])] + struct Node { + id: Option, + } + + #[graphql_interface(impl = NodeValue, for = Endpoint)] + struct Resource { + id: ID, + url: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [ResourceValue, NodeValue])] + struct Endpoint { + id: ID, + url: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn node() -> NodeValue { + Endpoint { + id: ID::from("1".to_owned()), + url: "2".to_owned(), + } + .into() + } + + fn resource() -> ResourceValue { + Endpoint { + id: ID::from("3".to_owned()), + url: "4".to_owned(), + } + .into() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_node() { + const DOC: &str = r#"{ + node { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_resource() { + const DOC: &str = r#"{ + node { + ... on Resource { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_endpoint() { + const DOC: &str = r#"{ + node { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource() { + const DOC: &str = r#"{ + resource { + id + url + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource_on_endpoint() { + const DOC: &str = r#"{ + resource { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_possible_types() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + possibleTypes {{ + kind + name + }} + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Endpoint"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn registers_interfaces() { + let schema = schema(QueryRoot); + + for (name, interfaces) in [ + ("Node", graphql_value!([])), + ( + "Resource", + graphql_value!([{"kind": "INTERFACE", "name": "Node"}]), + ), + ( + "Endpoint", + graphql_value!([ + {"kind": "INTERFACE", "name": "Node"}, + {"kind": "INTERFACE", "name": "Resource"}, + ]), + ), + ] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + name, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": interfaces}}), + vec![], + )), + ); + } + } +} + +mod branching_inheritance { + use super::*; + + #[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])] + struct Node { + id: ID, + } + + #[graphql_interface(for = [HumanConnection, DroidConnection])] + struct Connection { + nodes: Vec, + } + + #[graphql_interface(impl = NodeValue, for = Luke)] + struct Human { + id: ID, + home_planet: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct HumanConnection { + nodes: Vec, + } + + #[graphql_interface(impl = NodeValue, for = R2D2)] + struct Droid { + id: ID, + primary_function: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct DroidConnection { + nodes: Vec, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [HumanValue, NodeValue])] + struct Luke { + id: ID, + home_planet: String, + father: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [DroidValue, NodeValue])] + struct R2D2 { + id: ID, + primary_function: String, + charge: f64, + } + + enum QueryRoot { + Luke, + R2D2, + } + + #[graphql_object] + impl QueryRoot { + fn crew(&self) -> ConnectionValue { + match self { + Self::Luke => HumanConnection { + nodes: vec![Luke { + id: ID::new("1"), + home_planet: "earth".to_owned(), + father: "SPOILER".to_owned(), + } + .into()], + } + .into(), + Self::R2D2 => DroidConnection { + nodes: vec![R2D2 { + id: ID::new("2"), + primary_function: "roll".to_owned(), + charge: 146.0, + } + .into()], + } + .into(), + } + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Connection", "Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_human_connection() { + const DOC: &str = r#"{ + crew { + ... on HumanConnection { + nodes { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Human { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_luke() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Luke { + id + homePlanet + father + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + "father": "SPOILER", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid_connection() { + const DOC: &str = r#"{ + crew { + ... on DroidConnection { + nodes { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Droid { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_r2d2() { + const DOC: &str = r#"{ + crew { + nodes { + ... on R2D2 { + id + primaryFunction + charge + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + "charge": 146.0, + }], + }}), + vec![], + )), + ); + } +} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index ba776c7ea..1aeb47dcf 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -5,6 +5,7 @@ mod derive_scalar_value; mod interface_attr_struct; mod interface_attr_trait; mod interface_derive; +mod interface_inheritance; mod object_attr; mod object_derive; mod scalar_attr_derive_input; diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 64b04c2c2..4cba097f8 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -194,7 +194,7 @@ pub type WrappedValue = u128; /// /// const TYPE_STR: Type = >> as BaseType>::NAME; /// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::VALUE; -/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// assert_eq!(format_type!(TYPE_STR, WRAP_VAL_STR), "[String]"); /// ``` /// /// [`VALUE`]: Self::VALUE diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index ccd43a99a..51692d5bb 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -104,6 +104,8 @@ pub struct InterfaceMeta<'a, S> { pub description: Option, #[doc(hidden)] pub fields: Vec>, + #[doc(hidden)] + pub interface_names: Vec, } /// Union type metadata @@ -583,6 +585,7 @@ impl<'a, S> InterfaceMeta<'a, S> { name, description: None, fields: fields.to_vec(), + interface_names: Vec::new(), } } @@ -595,6 +598,18 @@ impl<'a, S> InterfaceMeta<'a, S> { self } + /// Set the `interfaces` this [`InterfaceMeta`] interface implements. + /// + /// Overwrites any previously set list of interfaces. + #[must_use] + pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { + self.interface_names = interfaces + .iter() + .map(|t| t.innermost_name().to_owned()) + .collect(); + self + } + /// Wraps this [`InterfaceMeta`] type into a generic [`MetaType`]. pub fn into_meta(self) -> MetaType<'a, S> { MetaType::Interface(self) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 0576174e5..52add94fe 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -415,6 +415,10 @@ impl<'a, S> SchemaType<'a, S> { MetaType::Object(ObjectMeta { ref interface_names, .. + }) + | MetaType::Interface(InterfaceMeta { + ref interface_names, + .. }) => interface_names.iter().any(|iname| iname == name), _ => false, }) diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 30906478f..825bb43aa 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -244,10 +244,16 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option>> { match self { - TypeType::Concrete(&MetaType::Object(ObjectMeta { - ref interface_names, - .. - })) => Some( + TypeType::Concrete( + &MetaType::Object(ObjectMeta { + ref interface_names, + .. + }) + | &MetaType::Interface(InterfaceMeta { + ref interface_names, + .. + }), + ) => Some( interface_names .iter() .filter_map(|n| context.type_by_name(n)) diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index 859bf942e..ff762bd94 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -190,8 +190,11 @@ impl GraphQLParserTranslator { position: Pos::default(), description: x.description.as_ref().map(|s| From::from(s.as_str())), name: From::from(x.name.as_ref()), - // TODO: Support this with GraphQL October 2021 Edition. - implements_interfaces: vec![], + implements_interfaces: x + .interface_names + .iter() + .map(|s| From::from(s.as_str())) + .collect(), directives: vec![], fields: x .fields diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 9c26fe25c..e2946973d 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -133,11 +133,12 @@ fn expand_on_trait( context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), + implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), }; Ok(quote! { @@ -339,11 +340,12 @@ fn expand_on_derive_input( context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), + implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), }; Ok(quote! { diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 18f83f851..456cf72bc 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -105,11 +105,12 @@ pub fn expand(input: TokenStream) -> syn::Result { context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), + implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), } .into_token_stream()) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 3fda25fbc..007723d2f 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -62,12 +62,19 @@ struct Attr { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces r#enum: Option>, - /// Explicitly specified Rust types of [GraphQL objects][2] implementing - /// this [GraphQL interface][1] type. + /// Explicitly specified Rust types of [GraphQL objects][2] or + /// [interfaces][1] implementing this [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - implementers: HashSet>, + implemented_for: HashSet>, + + /// Explicitly specified [GraphQL interfaces][2] this [interface][1] type + /// implements. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces + implements: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -161,11 +168,23 @@ impl Parse for Attr { >()? { let impler_span = impler.span(); out - .implementers + .implemented_for .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) .none_or_else(|_| err::dup_arg(impler_span))?; } } + "impl" | "implements" | "interfaces" => { + input.parse::()?; + for iface in input.parse_maybe_wrapped_and_punctuated::< + syn::Type, token::Bracket, token::Comma, + >()? { + let iface_span = iface.span(); + out + .implements + .replace(SpanContainer::new(ident.span(), Some(iface_span), iface)) + .none_or_else(|_| err::dup_arg(iface_span))?; + } + } "enum" => { input.parse::()?; let alias = input.parse::()?; @@ -212,7 +231,8 @@ impl Attr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), - implementers: try_merge_hashset!(implementers: self, another => span_joined), + implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), + implements: try_merge_hashset!(implements: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), @@ -299,7 +319,12 @@ struct Definition { /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + implemented_for: Vec, + + /// Defined [`Implementer`]s of this [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + implements: Vec, } impl ToTokens for Definition { @@ -327,13 +352,13 @@ impl Definition { let enum_ident = &self.enum_ident; let alias_ident = &self.enum_alias_ident; - let variant_gens_pars = (0..self.implementers.len()).map::(|id| { + let variant_gens_pars = (0..self.implemented_for.len()).map::(|id| { let par = format_ident!("__I{}", id); parse_quote! { #par } }); let variants_idents = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); @@ -371,7 +396,7 @@ impl Definition { } rest => quote! { #rest }, }) - .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(self.implemented_for.iter().map(ToTokens::to_token_stream)) .chain(interface_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; @@ -401,23 +426,23 @@ impl Definition { quote! { __Phantom(#(#phantom_params),*) } }); - let from_impls = - self.implementers - .iter() - .zip(variants_idents.clone()) - .map(|(ty, ident)| { - quote_spanned! { ty.span() => - #[automatically_derived] - impl#interface_impl_gens ::std::convert::From<#ty> - for #alias_ident#interface_ty_gens - #interface_where_clause - { - fn from(v: #ty) -> Self { - Self::#ident(v) - } + let from_impls = self + .implemented_for + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote_spanned! { ty.span() => + #[automatically_derived] + impl#interface_impl_gens ::std::convert::From<#ty> + for #alias_ident#interface_ty_gens + #interface_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) } } - }); + } + }); quote! { #[automatically_derived] @@ -449,7 +474,7 @@ impl Definition { let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); - let impler_tys = &self.implementers; + let impler_tys = &self.implemented_for; let all_implers_unique = (impler_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); @@ -462,7 +487,7 @@ impl Definition { { fn mark() { #all_implers_unique - #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + // #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } } @@ -489,13 +514,25 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let is_output = self.implementers.iter().map(|implementer| { + let is_output = self.implemented_for.iter().map(|implementer| { quote_spanned! { implementer.span() => <#implementer as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); - let impler_tys = self.implementers.iter(); + let impler_tys = &self.implemented_for; + let const_implements = self.implements.clone().into_iter().map(|mut ty| { + generics.replace_type_with_defaults(&mut ty); + ty + }); + + let transitive_check = const_implements.clone().map(|const_impl| { + quote! { + ::juniper::assert_interfaces_impls!( + #const_scalar, #const_impl, #(#impler_tys),* + ); + } + }); quote! { #[automatically_derived] @@ -509,6 +546,10 @@ impl Definition { ::juniper::assert_interfaces_impls!( #const_scalar, #ty#ty_const_generics, #(#impler_tys),* ); + ::juniper::assert_implemented_for!( + #const_scalar, #ty#ty_const_generics, #(#const_implements),* + ); + #(#transitive_check)* } } } @@ -535,12 +576,26 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.implementers.clone(); + let mut impler_tys = self.implemented_for.clone(); impler_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); + // Sorting is required to preserve/guarantee the order of interfaces registered in schema. + let mut implements_tys: Vec<_> = self.implements.iter().collect(); + implements_tys.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); + a.cmp(&b) + }); + let interfaces = (!implements_tys.is_empty()).then(|| { + quote! { + .interfaces(&[ + #( registry.get_type::<#implements_tys>(info), )* + ]) + } + }); + let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None)); quote! { @@ -567,6 +622,7 @@ impl Definition { ]; registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description + #interfaces .into_meta() } } @@ -729,7 +785,8 @@ impl Definition { #[must_use] fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; - let implementers = &self.implementers; + let implemented_for = &self.implemented_for; + let implements = &self.implements; let scalar = &self.scalar; let name = &self.name; let fields = self.fields.iter().map(|f| &f.name); @@ -754,10 +811,19 @@ impl Definition { { const NAMES: ::juniper::macros::reflect::Types = &[ >::NAME, - #(<#implementers as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* + #(<#implemented_for as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* ]; } + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::Implements<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[#(<#implements as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*]; + } + #[automatically_derived] impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty#ty_generics @@ -847,9 +913,9 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implementers.iter().collect::>(); + let impl_tys = self.implemented_for.iter().collect::>(); let impl_idents = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); @@ -867,10 +933,11 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = - (self.implementers.is_empty() || !self.generics.params.is_empty()).then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = (self.implemented_for.is_empty() + || !self.generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); quote_spanned! { field.ident.span() => #[allow(non_snake_case)] @@ -918,9 +985,9 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implementers.iter().collect::>(); + let impl_tys = self.implemented_for.iter().collect::>(); let impl_idents = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); @@ -938,10 +1005,11 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = - (self.implementers.is_empty() || !self.generics.params.is_empty()).then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = (self.implemented_for.is_empty() + || !self.generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); quote_spanned! { field.ident.span() => #[allow(non_snake_case)] @@ -990,7 +1058,7 @@ impl Definition { let scalar = &self.scalar; let match_arms = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) .map(|(ident, ty)| { @@ -1002,7 +1070,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1025,7 +1093,7 @@ impl Definition { fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - let match_arms = self.implementers.iter().filter_map(|ty| { + let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(v) => { @@ -1036,7 +1104,7 @@ impl Definition { }) }); let non_exhaustive_match_arm = - (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1058,7 +1126,7 @@ impl Definition { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.implementers.iter().filter_map(|ty| { + let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(res) => #resolving_code, @@ -1067,7 +1135,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); From da857c7d0f55ae3ffa8d982c3e9f7382a0c34439 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 23 Feb 2022 08:13:02 +0300 Subject: [PATCH 094/122] Fix introspection tests --- juniper/src/executor_tests/introspection/mod.rs | 2 +- juniper/src/tests/schema_introspection.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 25eff8452..ed59efc79 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -247,7 +247,7 @@ async fn interface_introspection() { ); assert_eq!( type_info.get_field_value("interfaces"), - Some(&graphql_value!(null)), + Some(&graphql_value!([])), ); assert_eq!( type_info.get_field_value("enumValues"), diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index 1e1b03e0a..d2e4643e5 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -1173,7 +1173,7 @@ pub(crate) fn schema_introspection_result() -> Value { } ], "inputFields": null, - "interfaces": null, + "interfaces": [], "enumValues": null, "possibleTypes": [ { @@ -2500,7 +2500,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { } ], "inputFields": null, - "interfaces": null, + "interfaces": [], "enumValues": null, "possibleTypes": [ { From fb2f43c53482bfbe0e7f5e8659461f0ef7e352bc Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 23 Feb 2022 08:30:55 +0300 Subject: [PATCH 095/122] Correction --- integration_tests/juniper_tests/src/codegen/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 1aeb47dcf..ba776c7ea 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -5,7 +5,6 @@ mod derive_scalar_value; mod interface_attr_struct; mod interface_attr_trait; mod interface_derive; -mod interface_inheritance; mod object_attr; mod object_derive; mod scalar_attr_derive_input; From eb8bbbef42bb66671ee0d02a06feac77eaef5cdc Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 23 Feb 2022 11:07:23 +0300 Subject: [PATCH 096/122] Fix fragment spread tests --- .../struct/derive_cyclic_impl.stderr | 9 ++- .../fail/interface/trait/cyclic_impl.rs | 13 +++++ .../fail/interface/trait/cyclic_impl.stderr | 56 +++++++++++++++++++ juniper/src/schema/model.rs | 4 -- .../rules/possible_fragment_spreads.rs | 56 +++++++++++++++++++ juniper/src/validation/test_harness.rs | 38 +++++++++++++ 6 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr index 687b0df37..9b82afa09 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr @@ -1,9 +1,14 @@ error[E0391]: cycle detected when expanding type alias `Node1Value` - --> fail/interface/struct/derive_cyclic_impl.rs:4:49 + --> fail/interface/struct/derive_cyclic_impl.rs:4:37 | 4 | #[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] - | ^^^^^^^^^^ + | ^^^^^^^^^^ | +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/struct/derive_cyclic_impl.rs:10:36 + | +10 | #[graphql(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ note: ...which requires expanding type alias `Node3Value`... --> fail/interface/struct/derive_cyclic_impl.rs:16:50 | diff --git a/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.rs b/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.rs new file mode 100644 index 000000000..6f5b79471 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.rs @@ -0,0 +1,13 @@ +use juniper::graphql_interface; + +#[graphql_interface(impl = Node2Value, for = Node2Value)] +trait Node1 { + fn id(&self) -> &str; +} + +#[graphql_interface(impl = Node1Value, for = Node1Value)] +trait Node2 { + fn id() -> String; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr new file mode 100644 index 000000000..5b40d1a8c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr @@ -0,0 +1,56 @@ +error: GraphQL interface trait method should have a shared reference receiver `&self` + --> fail/interface/trait/cyclic_impl.rs:8:1 + | +8 | #[graphql_interface(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0412]: cannot find type `Node2Value` in this scope + --> fail/interface/trait/cyclic_impl.rs:3:46 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ not found in this scope + | +help: there is an enum variant `crate::Node1ValueEnum::Node2Value`; try using the variant's enum + | +3 | #[graphql_interface(impl = Node2Value, for = crate::Node1ValueEnum)] + | ~~~~~~~~~~~~~~~~~~~~~ + +error[E0412]: cannot find type `Node2Value` in this scope + --> fail/interface/trait/cyclic_impl.rs:3:46 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ not found in this scope + | +help: there is an enum variant `crate::Node1ValueEnum::Node2Value`; try using the variant's enum + | +3 | #[graphql_interface(impl = Node2Value, for = crate::Node1ValueEnum)] + | ~~~~~~~~~~~~~~~~~~~~~ +help: you might be missing a type parameter + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ++++++++++++ + +error[E0412]: cannot find type `Node2Value` in this scope + --> fail/interface/trait/cyclic_impl.rs:3:28 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ not found in this scope + | +help: there is an enum variant `crate::Node1ValueEnum::Node2Value`; try using the variant's enum + | +3 | #[graphql_interface(impl = crate::Node1ValueEnum, for = Node2Value)] + | ~~~~~~~~~~~~~~~~~~~~~ + +error[E0599]: no method named `ok_or_else` found for enum `std::option::Option<&str>` in the current scope + --> fail/interface/trait/cyclic_impl.rs:3:1 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `std::option::Option<&str>` + | + = note: the method was found for + - `std::option::Option` + = note: `#[graphql_interface(impl = Node2Value, for = Node2Value)]` is a function, perhaps you wish to call it + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 52add94fe..0576174e5 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -415,10 +415,6 @@ impl<'a, S> SchemaType<'a, S> { MetaType::Object(ObjectMeta { ref interface_names, .. - }) - | MetaType::Interface(InterfaceMeta { - ref interface_names, - .. }) => interface_names.iter().any(|iname| iname == name), _ => false, }) diff --git a/juniper/src/validation/rules/possible_fragment_spreads.rs b/juniper/src/validation/rules/possible_fragment_spreads.rs index ceb308c91..3edca6caf 100644 --- a/juniper/src/validation/rules/possible_fragment_spreads.rs +++ b/juniper/src/validation/rules/possible_fragment_spreads.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use crate::{ ast::{Definition, Document, FragmentSpread, InlineFragment}, + meta::InterfaceMeta, parser::Spanning, schema::meta::MetaType, validation::{ValidatorContext, Visitor}, @@ -45,6 +46,23 @@ where .as_ref() .and_then(|s| ctx.schema.concrete_type_by_name(s.item)), ) { + // Even if there is no object type in the overlap of interfaces + // implementers, it's ok to spread in case `frag_type` implements + // `parent_type`. + // https://bit.ly/3t3JqTh + if let MetaType::Interface(InterfaceMeta { + interface_names, .. + }) = frag_type + { + let implements_parent = parent_type + .name() + .map(|parent| interface_names.iter().any(|i| i == parent)) + .unwrap_or_default(); + if implements_parent { + return; + } + } + if !ctx.schema.type_overlap(parent_type, frag_type) { ctx.report_error( &error_message( @@ -67,6 +85,23 @@ where ctx.parent_type(), self.fragment_types.get(spread.item.name.item), ) { + // Even if there is no object type in the overlap of interfaces + // implementers, it's ok to spread in case `frag_type` implements + // `parent_type`. + // https://bit.ly/3t3JqTh + if let MetaType::Interface(InterfaceMeta { + interface_names, .. + }) = frag_type + { + let implements_parent = parent_type + .name() + .map(|parent| interface_names.iter().any(|i| i == parent)) + .unwrap_or_default(); + if implements_parent { + return; + } + } + if !ctx.schema.type_overlap(parent_type, frag_type) { ctx.report_error( &error_message( @@ -226,6 +261,27 @@ mod tests { ); } + #[test] + fn no_object_overlap_but_implements_parent() { + expect_passes_rule::<_, _, DefaultScalarValue>( + factory, + r#" + fragment beingFragment on Being { ...unpopulatedFragment } + fragment unpopulatedFragment on Unpopulated { name } + "#, + ); + } + + #[test] + fn no_object_overlap_but_implements_parent_inline() { + expect_passes_rule::<_, _, DefaultScalarValue>( + factory, + r#" + fragment beingFragment on Being { ...on Unpopulated { name } } + "#, + ); + } + #[test] fn different_object_into_object() { expect_fails_rule::<_, _, DefaultScalarValue>( diff --git a/juniper/src/validation/test_harness.rs b/juniper/src/validation/test_harness.rs index 5bba0a73b..1772b639e 100644 --- a/juniper/src/validation/test_harness.rs +++ b/juniper/src/validation/test_harness.rs @@ -20,6 +20,7 @@ use crate::{ struct Being; struct Pet; struct Canine; +struct Unpopulated; struct Dog; struct Cat; @@ -167,6 +168,41 @@ where } } +impl GraphQLType for Unpopulated +where + S: ScalarValue, +{ + fn name(_: &()) -> Option<&'static str> { + Some("Unpopulated") + } + + fn meta<'r>(i: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S> + where + S: 'r, + { + let fields = &[registry + .field::>("name", i) + .argument(registry.arg::>("surname", i))]; + + registry + .build_interface_type::(i, fields) + .interfaces(&[registry.get_type::(i)]) + .into_meta() + } +} + +impl GraphQLValue for Unpopulated +where + S: ScalarValue, +{ + type Context = (); + type TypeInfo = (); + + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { + ::name(info) + } +} + impl GraphQLType for DogCommand where S: ScalarValue, @@ -777,6 +813,8 @@ where where S: 'r, { + let _ = registry.get_type::(i); + let fields = [registry.field::("testInput", i).argument( registry.arg_with_default::( "input", From d5b75fd7f085dc49afcd769d7ba2a49a3b692330 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 23 Feb 2022 16:12:06 +0300 Subject: [PATCH 097/122] Corrections --- .../attr_implementers_duplicate_ugly.stderr | 6 +- .../struct/attr_missing_transitive_impl.rs | 18 ++ .../attr_missing_transitive_impl.stderr | 7 + .../struct/derive_cyclic_impl.stderr | 9 +- .../struct/derive_missing_transitive_impl.rs | 21 +++ .../derive_missing_transitive_impl.stderr | 7 + .../trait/implementers_duplicate_ugly.stderr | 6 +- .../trait/missing_transitive_impl.rs | 18 ++ .../trait/missing_transitive_impl.stderr | 60 +++++++ juniper/src/macros/reflect.rs | 32 ++++ juniper/src/types/marker.rs | 3 +- juniper_codegen/src/common/parse/mod.rs | 77 +++++---- juniper_codegen/src/graphql_interface/mod.rs | 160 +++++++++++++----- 13 files changed, 333 insertions(+), 91 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr index 3f8332882..de660ced8 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr @@ -1,10 +1,10 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:34 + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:28 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` + | ^^^^ -------- first implementation here | | - | first implementation here + | conflicting implementation for `CharacterValueEnum` error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.rs new file mode 100644 index 000000000..590d1517e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.rs @@ -0,0 +1,18 @@ +use juniper::graphql_interface; + +#[graphql_interface(for = Node2Value)] +struct Node1 { + id: String, +} + +#[graphql_interface(impl = Node1Value, for = Node3Value)] +struct Node2 { + id: String, +} + +#[graphql_interface(impl = Node2Value)] +struct Node3 { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.stderr new file mode 100644 index 000000000..389334dc0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_transitive_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/struct/attr_missing_transitive_impl.rs:8:46 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr index 9b82afa09..687b0df37 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr @@ -1,14 +1,9 @@ error[E0391]: cycle detected when expanding type alias `Node1Value` - --> fail/interface/struct/derive_cyclic_impl.rs:4:37 + --> fail/interface/struct/derive_cyclic_impl.rs:4:49 | 4 | #[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] - | ^^^^^^^^^^ + | ^^^^^^^^^^ | -note: ...which requires expanding type alias `Node2Value`... - --> fail/interface/struct/derive_cyclic_impl.rs:10:36 - | -10 | #[graphql(impl = Node1Value, for = Node3Value)] - | ^^^^^^^^^^ note: ...which requires expanding type alias `Node3Value`... --> fail/interface/struct/derive_cyclic_impl.rs:16:50 | diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.rs new file mode 100644 index 000000000..446aae214 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.rs @@ -0,0 +1,21 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +#[graphql(for = Node2Value)] +struct Node1 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = Node1Value, for = Node3Value)] +struct Node2 { + id: String, +} + +#[derive(GraphQLInterface)] +#[graphql(impl = Node2Value)] +struct Node3 { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.stderr new file mode 100644 index 000000000..278b725f2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_transitive_impl.rs:10:36 + | +10 | #[graphql(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/struct/derive_missing_transitive_impl.rs:10:36 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 97399003d..822487363 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,10 +1,10 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/trait/implementers_duplicate_ugly.rs:11:34 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:28 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` + | ^^^^ -------- first implementation here | | - | first implementation here + | conflicting implementation for `CharacterValueEnum` error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.rs b/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.rs new file mode 100644 index 000000000..2bc08fb0a --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.rs @@ -0,0 +1,18 @@ +use juniper::graphql_interface; + +#[graphql_interface(for = Node2Value)] +trait Node1 { + fn id() -> String; +} + +#[graphql_interface(impl = Node1Value, for = Node3Value)] +trait Node2 { + fn id(&self) -> &str; +} + +#[graphql_interface(impl = Node2Value)] +trait Node3 { + fn id() -> &'static str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr new file mode 100644 index 000000000..522727b91 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr @@ -0,0 +1,60 @@ +error: GraphQL interface trait method should have a shared reference receiver `&self` + --> fail/interface/trait/missing_transitive_impl.rs:3:1 + | +3 | #[graphql_interface(for = Node2Value)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: GraphQL interface trait method should have a shared reference receiver `&self` + --> fail/interface/trait/missing_transitive_impl.rs:13:1 + | +13 | #[graphql_interface(impl = Node2Value)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0412]: cannot find type `Node3Value` in this scope + --> fail/interface/trait/missing_transitive_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ not found in this scope + | +help: there is an enum variant `crate::Node2ValueEnum::Node3Value`; try using the variant's enum + | +8 | #[graphql_interface(impl = Node1Value, for = crate::Node2ValueEnum)] + | ~~~~~~~~~~~~~~~~~~~~~ + +error[E0412]: cannot find type `Node3Value` in this scope + --> fail/interface/trait/missing_transitive_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ not found in this scope + | +help: there is an enum variant `crate::Node2ValueEnum::Node3Value`; try using the variant's enum + | +8 | #[graphql_interface(impl = Node1Value, for = crate::Node2ValueEnum)] + | ~~~~~~~~~~~~~~~~~~~~~ +help: you might be missing a type parameter + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ++++++++++++ + +error[E0412]: cannot find type `Node1Value` in this scope + --> fail/interface/trait/missing_transitive_impl.rs:8:28 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^ not found in this scope + +error[E0599]: no method named `ok_or_else` found for enum `std::option::Option<&str>` in the current scope + --> fail/interface/trait/missing_transitive_impl.rs:8:1 + | +8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `std::option::Option<&str>` + | + = note: the method was found for + - `std::option::Option` + = note: `#[graphql_interface(impl = Node1Value, for = Node3Value)]` is a function, perhaps you wish to call it + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 4cba097f8..724239866 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -551,6 +551,38 @@ macro_rules! assert_interfaces_impls { }; } +/// Asserts that all `transitive` interfaces that are implemented by `interface` +/// are also implemented by `implementor`. See [spec] for more info. +/// +/// [spec]: https://spec.graphql.org/October2021/#sel-FAHbhBHCAACGB35P +#[macro_export] +macro_rules! assert_transitive_implementations { + ($scalar: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => { + const _: () = { + $({ + let is_present = $crate::macros::reflect::str_exists_in_arr( + <$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME, + <$transitive as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, + ); + if !is_present { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "` on `", + <$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "`: missing `impl = ` for transitive interface `", + <$transitive as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "` on `", + <$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME, + "`." + ); + ::std::panic!("{}", MSG); + } + })* + }; + }; +} + /// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. /// /// This assertion is a combination of [`assert_subtype`] and diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index b04a41906..2dd22789e 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -186,8 +186,7 @@ where /// types. Each type which can be used as an output type should /// implement this trait. The specification defines enum, scalar, /// object, union, and interface as output types. -// TODO: Re-enable GraphQLType requirement in #682 -pub trait IsOutputType /*: GraphQLType*/ { +pub trait IsOutputType: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 5781a6233..e6f469aa7 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -256,6 +256,10 @@ pub(crate) trait GenericsExt { /// Replaces generic parameters in the given [`syn::Type`] with default /// ones, provided by these [`syn::Generics`]. fn replace_type_with_defaults(&self, ty: &mut syn::Type); + + /// Replaces generic parameters in the given [`syn::TypePath`] with default + /// ones, provided by these [`syn::Generics`]. + fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath); } impl GenericsExt for syn::Generics { @@ -307,40 +311,51 @@ impl GenericsExt for syn::Generics { } fn replace_type_with_defaults(&self, ty: &mut syn::Type) { - struct Replace<'a>(&'a syn::Generics); + ReplaceWithDefaults(self).visit_type_mut(ty) + } - impl<'a> VisitMut for Replace<'a> { - fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { - match arg { - syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote! { 'static }; - } - syn::GenericArgument::Type(ty) => { - let is_generic = self - .0 - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .any(|par| { - let par = quote! { #par }.to_string(); - let ty = quote! { #ty }.to_string(); - par == ty - }); - - if is_generic { - // Replace with `DefaultScalarValue` instead of `()` - // because generic parameter may be scalar. - *ty = parse_quote!(::juniper::DefaultScalarValue); - } - } - _ => {} + fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath) { + ReplaceWithDefaults(self).visit_type_path_mut(ty) + } +} + +/// Replaces [`Generics`] with default values: +/// - `'static` for [`Lifetime`]s +/// - `::juniper::DefaultScalarValue` for [`Type`]s. +/// +/// [`Generics`]: syn::Generics +/// [`Lifetime`]: syn::Lifetime +/// [`Type`]: syn::Type +struct ReplaceWithDefaults<'a>(&'a syn::Generics); + +impl<'a> VisitMut for ReplaceWithDefaults<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote! { 'static }; + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + // Replace with `DefaultScalarValue` instead of `()` + // because generic parameter may be scalar. + *ty = parse_quote!(::juniper::DefaultScalarValue); } } + _ => {} } - - Replace(self).visit_type_mut(ty) } } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 007723d2f..6c7d8a457 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -74,7 +74,7 @@ struct Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces - implements: HashSet>, + implements: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -176,7 +176,7 @@ impl Parse for Attr { "impl" | "implements" | "interfaces" => { input.parse::()?; for iface in input.parse_maybe_wrapped_and_punctuated::< - syn::Type, token::Bracket, token::Comma, + syn::TypePath, token::Bracket, token::Comma, >()? { let iface_span = iface.span(); out @@ -316,15 +316,17 @@ struct Definition { /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields fields: Vec, - /// Defined [`Implementer`]s of this [GraphQL interface][1]. + /// Specified [GraphQL objects][2] or [interfaces][1] this + /// [GraphQL interface][1] is implemented for type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Objects implemented_for: Vec, - /// Defined [`Implementer`]s of this [GraphQL interface][1]. + /// Specified [GraphQL interfaces][1] this [interface][1] type implements. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implements: Vec, + implements: Vec, } impl ToTokens for Definition { @@ -474,9 +476,47 @@ impl Definition { let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); - let impler_tys = &self.implemented_for; - let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } + let implemented_for = &self.implemented_for; + let all_impled_for_unique = (implemented_for.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } + }); + + let mark_object_or_interface = self.implemented_for.iter().map(|impl_for| { + quote_spanned! { impl_for.span() => + trait GraphQLObjectOrInterface { + fn mark(); + } + + { + struct Object; + + impl GraphQLObjectOrInterface for T + where + S: juniper::ScalarValue, + T: juniper::marker::GraphQLObject, + { + fn mark() { + >::mark() + } + } + } + + { + struct Interface; + + impl GraphQLObjectOrInterface for T + where + S: juniper::ScalarValue, + T: juniper::marker::GraphQLInterface, + { + fn mark() { + >::mark() + } + } + } + + <#impl_for as GraphQLObjectOrInterface<#scalar, _>>::mark(); + } }); quote! { @@ -486,8 +526,8 @@ impl Definition { #where_clause { fn mark() { - #all_implers_unique - // #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + #all_impled_for_unique + #({ #mark_object_or_interface })* } } } @@ -520,16 +560,26 @@ impl Definition { } }); - let impler_tys = &self.implemented_for; - let const_implements = self.implements.clone().into_iter().map(|mut ty| { - generics.replace_type_with_defaults(&mut ty); + let const_impl_for = self.implemented_for.iter().cloned().map(|mut ty| { + generics.replace_type_path_with_defaults(&mut ty); ty }); - - let transitive_check = const_implements.clone().map(|const_impl| { - quote! { - ::juniper::assert_interfaces_impls!( - #const_scalar, #const_impl, #(#impler_tys),* + let const_implements = self + .implements + .clone() + .into_iter() + .map(|mut ty| { + generics.replace_type_path_with_defaults(&mut ty); + ty + }) + .collect::>(); + let transitive_checks = const_impl_for.clone().map(|const_impl_for| { + quote_spanned! { const_impl_for.span() => + juniper::assert_transitive_implementations!( + #const_scalar, + #ty#ty_const_generics, + #const_impl_for, + #(#const_implements),* ); } }); @@ -544,12 +594,16 @@ impl Definition { #( #fields_marks )* #( #is_output )* ::juniper::assert_interfaces_impls!( - #const_scalar, #ty#ty_const_generics, #(#impler_tys),* + #const_scalar, + #ty#ty_const_generics, + #(#const_impl_for),* ); ::juniper::assert_implemented_for!( - #const_scalar, #ty#ty_const_generics, #(#const_implements),* + #const_scalar, + #ty#ty_const_generics, + #(#const_implements),* ); - #(#transitive_check)* + #(#transitive_checks)* } } } @@ -576,22 +630,22 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.implemented_for.clone(); - impler_tys.sort_unstable_by(|a, b| { + let mut implemented_for = self.implemented_for.clone(); + implemented_for.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. - let mut implements_tys: Vec<_> = self.implements.iter().collect(); - implements_tys.sort_unstable_by(|a, b| { + let mut implements: Vec<_> = self.implements.iter().collect(); + implements.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); - let interfaces = (!implements_tys.is_empty()).then(|| { + let impl_interfaces = (!implements.is_empty()).then(|| { quote! { .interfaces(&[ - #( registry.get_type::<#implements_tys>(info), )* + #( registry.get_type::<#implements>(info), )* ]) } }); @@ -615,14 +669,14 @@ impl Definition { where #scalar: 'r, { // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_tys>(info); )* + #( let _ = registry.get_type::<#implemented_for>(info); )* let fields = [ #( #fields_meta, )* ]; registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description - #interfaces + #impl_interfaces .into_meta() } } @@ -913,17 +967,25 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implemented_for.iter().collect::>(); - let impl_idents = self + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let const_implemented_for = self + .implemented_for + .iter() + .cloned() + .map(|mut impl_for| { + generics.replace_type_path_with_defaults(&mut impl_for); + impl_for + }) + .collect::>(); + let implemented_for_idents = self .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.generics.split_for_impl(); - self.fields .iter() .map(|field| { @@ -953,10 +1015,10 @@ impl Definition { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { match self { - #(#ty::#impl_idents(v) => { + #(#ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, - #impl_tys, + #const_implemented_for, #const_scalar, #field_name, ); @@ -985,17 +1047,25 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implemented_for.iter().collect::>(); - let impl_idents = self + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let const_implemented_for = self + .implemented_for + .iter() + .cloned() + .map(|mut impl_for| { + generics.replace_type_path_with_defaults(&mut impl_for); + impl_for + }) + .collect::>(); + let implemented_for_idents = self .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); - let generics = self.impl_generics(true); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.generics.split_for_impl(); - self.fields .iter() .map(|field| { @@ -1025,10 +1095,10 @@ impl Definition { executor: &'b ::juniper::Executor, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match self { - #(#ty::#impl_idents(v) => { + #(#ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, - #impl_tys, + #const_implemented_for, #const_scalar, #field_name, ); From d29806bd80b58518bbd7c6fbf54c3c227e856be2 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 24 Feb 2022 09:48:24 +0300 Subject: [PATCH 098/122] CHANGELOG --- .../attr_implementers_duplicate_ugly.stderr | 11 +- .../interface/struct/derive_cyclic_impl.rs | 10 +- .../struct/derive_cyclic_impl.stderr | 20 +- .../derive_implementers_duplicate_ugly.stderr | 13 +- .../fail/interface/trait/cyclic_impl.stderr | 82 +-- .../trait/implementers_duplicate_ugly.stderr | 11 +- .../trait/missing_transitive_impl.stderr | 59 +- .../src/codegen/interface_attr_trait.rs | 538 ++++++++++++++++- .../src/codegen/interface_derive.rs | 544 +++++++++++++++++- juniper/CHANGELOG.md | 1 + .../rules/possible_fragment_spreads.rs | 4 +- juniper_codegen/src/graphql_interface/attr.rs | 56 +- juniper_codegen/src/graphql_interface/mod.rs | 4 +- 13 files changed, 1157 insertions(+), 196 deletions(-) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr index de660ced8..da24362cf 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr @@ -1,10 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:28 + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ^^^^ -------- first implementation here - | | - | conflicting implementation for `CharacterValueEnum` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs index 45f9059e2..56d77cc62 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs @@ -1,21 +1,15 @@ use juniper::GraphQLInterface; #[derive(GraphQLInterface)] -#[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] +#[graphql(impl = Node2Value, for = Node2Value)] struct Node1 { id: String, } #[derive(GraphQLInterface)] -#[graphql(impl = Node1Value, for = Node3Value)] +#[graphql(impl = Node1Value, for = Node1Value)] struct Node2 { id: String, } -#[derive(GraphQLInterface)] -#[graphql(impl = [Node1Value, Node2Value], for = Node1Value)] -struct Node3 { - id: String, -} - fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr index 687b0df37..28f960ebe 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr @@ -1,14 +1,14 @@ error[E0391]: cycle detected when expanding type alias `Node1Value` - --> fail/interface/struct/derive_cyclic_impl.rs:4:49 + --> fail/interface/struct/derive_cyclic_impl.rs:4:36 | -4 | #[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] - | ^^^^^^^^^^ +4 | #[graphql(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ | -note: ...which requires expanding type alias `Node3Value`... - --> fail/interface/struct/derive_cyclic_impl.rs:16:50 +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/struct/derive_cyclic_impl.rs:10:36 | -16 | #[graphql(impl = [Node1Value, Node2Value], for = Node1Value)] - | ^^^^^^^^^^ +10 | #[graphql(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^ = note: ...which again requires expanding type alias `Node1Value`, completing the cycle = note: type aliases cannot be recursive = help: consider using a struct, enum, or union instead to break the cycle @@ -19,8 +19,8 @@ note: cycle used when collecting item types in top-level module 1 | / use juniper::GraphQLInterface; 2 | | 3 | | #[derive(GraphQLInterface)] -4 | | #[graphql(impl = Node1Value, for = [Node2Value, Node3Value])] +4 | | #[graphql(impl = Node2Value, for = Node2Value)] ... | -20 | | -21 | | fn main() {} +14 | | +15 | | fn main() {} | |____________^ diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr index 26f6669be..fde533c1b 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr @@ -1,10 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:12:24 + --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 + | +11 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` | -12 | #[graphql(for = [ObjA, ObjAlias])] - | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` - | | - | first implementation here + = note: this error originates in the derive macro `GraphQLInterface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 diff --git a/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr b/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr index 5b40d1a8c..9865861fd 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr @@ -1,56 +1,26 @@ -error: GraphQL interface trait method should have a shared reference receiver `&self` - --> fail/interface/trait/cyclic_impl.rs:8:1 - | -8 | #[graphql_interface(impl = Node1Value, for = Node1Value)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0412]: cannot find type `Node2Value` in this scope - --> fail/interface/trait/cyclic_impl.rs:3:46 - | -3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] - | ^^^^^^^^^^ not found in this scope - | -help: there is an enum variant `crate::Node1ValueEnum::Node2Value`; try using the variant's enum - | -3 | #[graphql_interface(impl = Node2Value, for = crate::Node1ValueEnum)] - | ~~~~~~~~~~~~~~~~~~~~~ - -error[E0412]: cannot find type `Node2Value` in this scope - --> fail/interface/trait/cyclic_impl.rs:3:46 - | -3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] - | ^^^^^^^^^^ not found in this scope - | -help: there is an enum variant `crate::Node1ValueEnum::Node2Value`; try using the variant's enum - | -3 | #[graphql_interface(impl = Node2Value, for = crate::Node1ValueEnum)] - | ~~~~~~~~~~~~~~~~~~~~~ -help: you might be missing a type parameter - | -3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] - | ++++++++++++ - -error[E0412]: cannot find type `Node2Value` in this scope - --> fail/interface/trait/cyclic_impl.rs:3:28 - | -3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] - | ^^^^^^^^^^ not found in this scope - | -help: there is an enum variant `crate::Node1ValueEnum::Node2Value`; try using the variant's enum - | -3 | #[graphql_interface(impl = crate::Node1ValueEnum, for = Node2Value)] - | ~~~~~~~~~~~~~~~~~~~~~ - -error[E0599]: no method named `ok_or_else` found for enum `std::option::Option<&str>` in the current scope - --> fail/interface/trait/cyclic_impl.rs:3:1 - | -3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `std::option::Option<&str>` - | - = note: the method was found for - - `std::option::Option` - = note: `#[graphql_interface(impl = Node2Value, for = Node2Value)]` is a function, perhaps you wish to call it - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0391]: cycle detected when expanding type alias `Node1Value` + --> fail/interface/trait/cyclic_impl.rs:3:46 + | +3 | #[graphql_interface(impl = Node2Value, for = Node2Value)] + | ^^^^^^^^^^ + | +note: ...which requires expanding type alias `Node2Value`... + --> fail/interface/trait/cyclic_impl.rs:8:46 + | +8 | #[graphql_interface(impl = Node1Value, for = Node1Value)] + | ^^^^^^^^^^ + = note: ...which again requires expanding type alias `Node1Value`, completing the cycle + = note: type aliases cannot be recursive + = help: consider using a struct, enum, or union instead to break the cycle + = help: see for more information +note: cycle used when collecting item types in top-level module + --> fail/interface/trait/cyclic_impl.rs:1:1 + | +1 | / use juniper::graphql_interface; +2 | | +3 | | #[graphql_interface(impl = Node2Value, for = Node2Value)] +4 | | trait Node1 { +... | +12 | | +13 | | fn main() {} + | |____________^ diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 822487363..74b695d8f 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,10 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/trait/implementers_duplicate_ugly.rs:11:28 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ^^^^ -------- first implementation here - | | - | conflicting implementation for `CharacterValueEnum` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr index 522727b91..f120ac360 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr @@ -1,60 +1,7 @@ -error: GraphQL interface trait method should have a shared reference receiver `&self` - --> fail/interface/trait/missing_transitive_impl.rs:3:1 - | -3 | #[graphql_interface(for = Node2Value)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: GraphQL interface trait method should have a shared reference receiver `&self` - --> fail/interface/trait/missing_transitive_impl.rs:13:1 - | -13 | #[graphql_interface(impl = Node2Value)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0412]: cannot find type `Node3Value` in this scope - --> fail/interface/trait/missing_transitive_impl.rs:8:46 - | -8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] - | ^^^^^^^^^^ not found in this scope - | -help: there is an enum variant `crate::Node2ValueEnum::Node3Value`; try using the variant's enum - | -8 | #[graphql_interface(impl = Node1Value, for = crate::Node2ValueEnum)] - | ~~~~~~~~~~~~~~~~~~~~~ - -error[E0412]: cannot find type `Node3Value` in this scope +error[E0080]: evaluation of constant value failed --> fail/interface/trait/missing_transitive_impl.rs:8:46 | 8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] - | ^^^^^^^^^^ not found in this scope - | -help: there is an enum variant `crate::Node2ValueEnum::Node3Value`; try using the variant's enum - | -8 | #[graphql_interface(impl = Node1Value, for = crate::Node2ValueEnum)] - | ~~~~~~~~~~~~~~~~~~~~~ -help: you might be missing a type parameter - | -8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] - | ++++++++++++ - -error[E0412]: cannot find type `Node1Value` in this scope - --> fail/interface/trait/missing_transitive_impl.rs:8:28 - | -8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] - | ^^^^^^^^^^ not found in this scope - -error[E0599]: no method named `ok_or_else` found for enum `std::option::Option<&str>` in the current scope - --> fail/interface/trait/missing_transitive_impl.rs:8:1 - | -8 | #[graphql_interface(impl = Node1Value, for = Node3Value)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `std::option::Option<&str>` + | ^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Node2` on `Node3`: missing `impl = ` for transitive interface `Node1` on `Node3`.', $DIR/fail/interface/trait/missing_transitive_impl.rs:8:46 | - = note: the method was found for - - `std::option::Option` - = note: `#[graphql_interface(impl = Node1Value, for = Node3Value)]` is a function, perhaps you wish to call it - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs index e5bdc4fce..eaf95c5df 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs @@ -3,7 +3,7 @@ use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion, - IntoFieldError, ScalarValue, + IntoFieldError, ScalarValue, ID, }; use crate::util::{schema, schema_with_scalar}; @@ -3295,3 +3295,539 @@ mod nullable_argument_subtyping { } } } + +mod simple_inheritance { + use super::*; + + #[graphql_interface(for = [ResourceValue, Endpoint])] + trait Node { + fn id() -> Option; + } + + #[graphql_interface(impl = NodeValue, for = Endpoint)] + trait Resource { + fn id(&self) -> &ID; + fn url(&self) -> Option<&str>; + } + + #[derive(GraphQLObject)] + #[graphql(impl = [ResourceValue, NodeValue])] + struct Endpoint { + id: ID, + url: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn node() -> NodeValue { + Endpoint { + id: ID::from("1".to_owned()), + url: "2".to_owned(), + } + .into() + } + + fn resource() -> ResourceValue { + Endpoint { + id: ID::from("3".to_owned()), + url: "4".to_owned(), + } + .into() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_node() { + const DOC: &str = r#"{ + node { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_resource() { + const DOC: &str = r#"{ + node { + ... on Resource { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_endpoint() { + const DOC: &str = r#"{ + node { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource() { + const DOC: &str = r#"{ + resource { + id + url + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource_on_endpoint() { + const DOC: &str = r#"{ + resource { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_possible_types() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + possibleTypes {{ + kind + name + }} + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Endpoint"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn registers_interfaces() { + let schema = schema(QueryRoot); + + for (name, interfaces) in [ + ("Node", graphql_value!([])), + ( + "Resource", + graphql_value!([{"kind": "INTERFACE", "name": "Node"}]), + ), + ( + "Endpoint", + graphql_value!([ + {"kind": "INTERFACE", "name": "Node"}, + {"kind": "INTERFACE", "name": "Resource"}, + ]), + ), + ] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + name, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": interfaces}}), + vec![], + )), + ); + } + } +} + +mod branching_inheritance { + use super::*; + + #[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])] + trait Node { + fn id() -> ID; + } + + #[graphql_interface(for = [HumanConnection, DroidConnection])] + trait Connection { + fn nodes(&self) -> &[NodeValue]; + } + + #[graphql_interface(impl = NodeValue, for = Luke)] + trait Human { + fn id(&self) -> &ID; + fn home_planet(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct HumanConnection { + nodes: Vec, + } + + #[graphql_interface(impl = NodeValue, for = R2D2)] + trait Droid { + fn id() -> ID; + fn primary_function() -> String; + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct DroidConnection { + nodes: Vec, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [HumanValue, NodeValue])] + struct Luke { + id: ID, + home_planet: String, + father: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [DroidValue, NodeValue])] + struct R2D2 { + id: ID, + primary_function: String, + charge: f64, + } + + enum QueryRoot { + Luke, + R2D2, + } + + #[graphql_object] + impl QueryRoot { + fn crew(&self) -> ConnectionValue { + match self { + Self::Luke => HumanConnection { + nodes: vec![Luke { + id: ID::new("1"), + home_planet: "earth".to_owned(), + father: "SPOILER".to_owned(), + } + .into()], + } + .into(), + Self::R2D2 => DroidConnection { + nodes: vec![R2D2 { + id: ID::new("2"), + primary_function: "roll".to_owned(), + charge: 146.0, + } + .into()], + } + .into(), + } + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Connection", "Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_human_connection() { + const DOC: &str = r#"{ + crew { + ... on HumanConnection { + nodes { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Human { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_luke() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Luke { + id + homePlanet + father + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + "father": "SPOILER", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid_connection() { + const DOC: &str = r#"{ + crew { + ... on DroidConnection { + nodes { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Droid { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_r2d2() { + const DOC: &str = r#"{ + crew { + nodes { + ... on R2D2 { + id + primaryFunction + charge + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + "charge": 146.0, + }], + }}), + vec![], + )), + ); + } +} diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs index ee0065352..fc04bc5d3 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use juniper::{ execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, FieldError, - FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, + FieldResult, GraphQLInterface, GraphQLObject, GraphQLUnion, IntoFieldError, ScalarValue, ID, }; use crate::util::{schema, schema_with_scalar}; @@ -2496,3 +2496,545 @@ mod nullable_argument_subtyping { } } } + +mod simple_inheritance { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [ResourceValue, Endpoint])] + struct Node { + id: Option, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = Endpoint)] + struct Resource { + id: ID, + url: Option, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [ResourceValue, NodeValue])] + struct Endpoint { + id: ID, + url: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn node() -> NodeValue { + Endpoint { + id: ID::from("1".to_owned()), + url: "2".to_owned(), + } + .into() + } + + fn resource() -> ResourceValue { + Endpoint { + id: ID::from("3".to_owned()), + url: "4".to_owned(), + } + .into() + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_node() { + const DOC: &str = r#"{ + node { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_resource() { + const DOC: &str = r#"{ + node { + ... on Resource { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_node_on_endpoint() { + const DOC: &str = r#"{ + node { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"node": { + "id": "1", + "url": "2", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource() { + const DOC: &str = r#"{ + resource { + id + url + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_resource_on_endpoint() { + const DOC: &str = r#"{ + resource { + ... on Endpoint { + id + url + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"resource": { + "id": "3", + "url": "4", + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn registers_possible_types() { + for name in ["Node", "Resource"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + possibleTypes {{ + kind + name + }} + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Endpoint"}, + ]}}), + vec![], + )), + ); + } + } + + #[tokio::test] + async fn registers_interfaces() { + let schema = schema(QueryRoot); + + for (name, interfaces) in [ + ("Node", graphql_value!([])), + ( + "Resource", + graphql_value!([{"kind": "INTERFACE", "name": "Node"}]), + ), + ( + "Endpoint", + graphql_value!([ + {"kind": "INTERFACE", "name": "Node"}, + {"kind": "INTERFACE", "name": "Resource"}, + ]), + ), + ] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} + }}"#, + name, + ); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"interfaces": interfaces}}), + vec![], + )), + ); + } + } +} + +mod branching_inheritance { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = [HumanValue, DroidValue, Luke, R2D2])] + struct Node { + id: ID, + } + + #[derive(GraphQLInterface)] + #[graphql(for = [HumanConnection, DroidConnection])] + struct Connection { + nodes: Vec, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = Luke)] + struct Human { + id: ID, + home_planet: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct HumanConnection { + nodes: Vec, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = R2D2)] + struct Droid { + id: ID, + primary_function: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = ConnectionValue)] + struct DroidConnection { + nodes: Vec, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [HumanValue, NodeValue])] + struct Luke { + id: ID, + home_planet: String, + father: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = [DroidValue, NodeValue])] + struct R2D2 { + id: ID, + primary_function: String, + charge: f64, + } + + enum QueryRoot { + Luke, + R2D2, + } + + #[graphql_object] + impl QueryRoot { + fn crew(&self) -> ConnectionValue { + match self { + Self::Luke => HumanConnection { + nodes: vec![Luke { + id: ID::new("1"), + home_planet: "earth".to_owned(), + father: "SPOILER".to_owned(), + } + .into()], + } + .into(), + Self::R2D2 => DroidConnection { + nodes: vec![R2D2 { + id: ID::new("2"), + primary_function: "roll".to_owned(), + charge: 146.0, + } + .into()], + } + .into(), + } + } + } + + #[tokio::test] + async fn is_graphql_interface() { + for name in ["Node", "Connection", "Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + kind + }} + }}"#, + name, + ); + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_human_connection() { + const DOC: &str = r#"{ + crew { + ... on HumanConnection { + nodes { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Human { + id + homePlanet + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_luke() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Luke { + id + homePlanet + father + } + } + } + }"#; + + let schema = schema(QueryRoot::Luke); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "1", + "homePlanet": "earth", + "father": "SPOILER", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid_connection() { + const DOC: &str = r#"{ + crew { + ... on DroidConnection { + nodes { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + const DOC: &str = r#"{ + crew { + nodes { + ... on Droid { + id + primaryFunction + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_r2d2() { + const DOC: &str = r#"{ + crew { + nodes { + ... on R2D2 { + id + primaryFunction + charge + } + } + } + }"#; + + let schema = schema(QueryRoot::R2D2); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"crew": { + "nodes": [{ + "id": "2", + "primaryFunction": "roll", + "charge": 146.0, + }], + }}), + vec![], + )), + ); + } +} diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index d395e8c39..72a0b070d 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -21,6 +21,7 @@ - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). - Forbid default impls on non-ignored trait methods. - Support coercion of additional nullable arguments and return sub-typing on implementer. + - Support interfaces implementing other interfaces ([#1028](https://github.com/graphql-rust/juniper/pull/1028)) - Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - Support generic scalars. - Support structs with single named field. diff --git a/juniper/src/validation/rules/possible_fragment_spreads.rs b/juniper/src/validation/rules/possible_fragment_spreads.rs index 3edca6caf..cddf5e4e5 100644 --- a/juniper/src/validation/rules/possible_fragment_spreads.rs +++ b/juniper/src/validation/rules/possible_fragment_spreads.rs @@ -49,7 +49,7 @@ where // Even if there is no object type in the overlap of interfaces // implementers, it's ok to spread in case `frag_type` implements // `parent_type`. - // https://bit.ly/3t3JqTh + // https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { interface_names, .. }) = frag_type @@ -88,7 +88,7 @@ where // Even if there is no object type in the overlap of interfaces // implementers, it's ok to spread in case `frag_type` implements // `parent_type`. - // https://bit.ly/3t3JqTh + // https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { interface_names, .. }) = frag_type diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index e2946973d..8c6d9aac1 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -191,33 +191,15 @@ fn parse_trait_method( return None; } - let arguments = { - if method.sig.inputs.is_empty() { - return err_no_method_receiver(&method.sig.inputs); - } - let mut args_iter = method.sig.inputs.iter_mut(); - match args_iter.next().unwrap() { - syn::FnArg::Receiver(rcv) => { - if rcv.reference.is_none() || rcv.mutability.is_some() { - return err_invalid_method_receiver(rcv); - } - } - syn::FnArg::Typed(arg) => { - if let syn::Pat::Ident(a) = &*arg.pat { - if a.ident.to_string().as_str() != "self" { - return err_invalid_method_receiver(arg); - } - } - return err_no_method_receiver(arg); - } - }; - args_iter - .filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), - }) - .collect() - }; + let arguments = method + .sig + .inputs + .iter_mut() + .filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), + }) + .collect(); let mut ty = match &method.sig.output { syn::ReturnType::Default => parse_quote! { () }, @@ -423,26 +405,6 @@ fn err_default_impl_block(span: &S) -> Option { None } -/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given -/// `span`. -fn err_invalid_method_receiver(span: &S) -> Option { - ERR.emit_custom( - span.span(), - "trait method receiver can only be a shared reference `&self`", - ); - None -} - -/// Emits "no trait method receiver" [`syn::Error`] pointing to the given -/// `span`. -fn err_no_method_receiver(span: &S) -> Option { - ERR.emit_custom( - span.span(), - "trait method should have a shared reference receiver `&self`", - ); - None -} - /// Emits "expected named struct field" [`syn::Error`] pointing to the given /// `span`. fn err_unnamed_field(span: &S) -> Option { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 6c7d8a457..235ee7cd9 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -173,7 +173,7 @@ impl Parse for Attr { .none_or_else(|_| err::dup_arg(impler_span))?; } } - "impl" | "implements" | "interfaces" => { + "impl" | "implements" => { input.parse::()?; for iface in input.parse_maybe_wrapped_and_punctuated::< syn::TypePath, token::Bracket, token::Comma, @@ -433,7 +433,7 @@ impl Definition { .iter() .zip(variants_idents.clone()) .map(|(ty, ident)| { - quote_spanned! { ty.span() => + quote! { #[automatically_derived] impl#interface_impl_gens ::std::convert::From<#ty> for #alias_ident#interface_ty_gens From 173cddbccb457d4c3fcd2741d2272e77bb123716 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 24 Feb 2022 15:31:59 +0300 Subject: [PATCH 099/122] WIP --- juniper/Cargo.toml | 1 + juniper/src/integrations/chrono.rs | 443 +++++++++++++++++--------- juniper/src/integrations/chrono_tz.rs | 18 +- 3 files changed, 308 insertions(+), 154 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index e28f1e2d4..f25d61274 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -28,6 +28,7 @@ default = [ expose-test-schema = ["anyhow", "serde_json"] graphql-parser-integration = ["graphql-parser"] schema-language = ["graphql-parser-integration"] +chrono-clock = ["chrono", "chrono/clock"] [dependencies] juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 3b293107b..c889d7414 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -18,61 +18,55 @@ //! [s2]: https://graphql-scalars.dev/docs/scalars/local-time //! [s4]: https://graphql-scalars.dev/docs/scalars/date-time -use chrono::{SecondsFormat, Timelike as _}; +use std::fmt; -use crate::{ - graphql_scalar, - parser::{ParseError, ScalarToken, Token}, - value::ParseScalarResult, - Value, -}; +use chrono::{FixedOffset, SecondsFormat, TimeZone, Timelike as _}; -pub use chrono::{ - DateTime, FixedOffset as UtcOffset, NaiveDate as Date, NaiveDateTime as LocalDateTime, - NaiveTime as LocalTime, Utc, -}; +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; /// Format of a [`Date` scalar][1]. /// /// [1]: https://graphql-scalars.dev/docs/scalars/date const DATE_FORMAT: &str = "%Y-%m-%d"; +/// Date in the proleptic Gregorian calendar (without time zone). +/// +/// Represents a description of the date (as used for birthdays, for example). +/// It cannot represent an instant on the time-line. +/// +/// [`Date` scalar][1] compliant. +/// +/// See also [`chrono::NaiveDate`][2] for details. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/date +/// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html #[graphql_scalar( - description = "Date in the proleptic Gregorian calendar (without time \ - zone).\ - \n\n\ - Represents a description of the date (as used for birthdays, - for example). It cannot represent an instant on the \ - time-line.\ - \n\n\ - [`Date` scalar][1] compliant.\ - \n\n\ - See also [`chrono::NaiveDate`][2] for details.\ - \n\n\ - [1]: https://graphql-scalars.dev/docs/scalars/date\n\ - [2]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date" + with = date, + parse_token(String), + specified_by_url = "https://graphql-scalars.dev/docs/scalars/date", )] -impl GraphQLScalar for Date { - fn resolve(&self) -> Value { - Value::scalar(self.format(DATE_FORMAT).to_string()) +pub type Date = chrono::NaiveDate; + +mod date { + use super::*; + + pub(super) fn to_output(v: &Date) -> Value + where + S: ScalarValue, + { + Value::scalar(v.format(DATE_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result + where + S: ScalarValue, + { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse_from_str(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)) + Date::parse_from_str(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)) }) } - - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } /// Full format of a [`LocalTime` scalar][1]. @@ -90,134 +84,233 @@ const LOCAL_TIME_FORMAT_NO_MILLIS: &str = "%H:%M:%S"; /// [1]: https://graphql-scalars.dev/docs/scalars/local-time const LOCAL_TIME_FORMAT_NO_SECS: &str = "%H:%M"; +/// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]` +/// format. +/// +/// All minutes are assumed to have exactly 60 seconds; no attempt is made to +/// handle leap seconds (either positive or negative). +/// +/// [`LocalTime` scalar][1] compliant. +/// +/// See also [`chrono::NaiveTime`][2] for details. +/// +/// [1]: https://graphql-scalars.dev/docs/scalars/local-time +/// [2]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html #[graphql_scalar( - description = "Clock time within a given date (without time zone) in \ - `HH:mm[:ss[.SSS]]` format.\ - \n\n\ - All minutes are assumed to have exactly 60 seconds; no \ - attempt is made to handle leap seconds (either positive or \ - negative).\ - \n\n\ - [`LocalTime` scalar][1] compliant.\ - \n\n\ - See also [`chrono::NaiveTime`][2] for details.\ - \n\n\ - [1]: https://graphql-scalars.dev/docs/scalars/local-time\n\ - [2]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveTime.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time" + with = local_time, + parse_token(String), + specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time", )] -impl GraphQLScalar for LocalTime { - fn resolve(&self) -> Value { +pub type LocalTime = chrono::NaiveTime; + +mod local_time { + use super::*; + + pub(super) fn to_output(v: &LocalTime) -> Value + where + S: ScalarValue, + { Value::scalar( - if self.nanosecond() == 0 { - self.format(LOCAL_TIME_FORMAT_NO_MILLIS) + if v.nanosecond() == 0 { + v.format(LOCAL_TIME_FORMAT_NO_MILLIS) } else { - self.format(LOCAL_TIME_FORMAT) + v.format(LOCAL_TIME_FORMAT) } .to_string(), ) } - fn from_input_value(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result + where + S: ScalarValue, + { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { // First, try to parse the most used format. // At the end, try to parse the full format for the parsing // error to be most informative. - Self::parse_from_str(s, LOCAL_TIME_FORMAT_NO_MILLIS) - .or_else(|_| Self::parse_from_str(s, LOCAL_TIME_FORMAT_NO_SECS)) - .or_else(|_| Self::parse_from_str(s, LOCAL_TIME_FORMAT)) + LocalTime::parse_from_str(s, LOCAL_TIME_FORMAT_NO_MILLIS) + .or_else(|_| LocalTime::parse_from_str(s, LOCAL_TIME_FORMAT_NO_SECS)) + .or_else(|_| LocalTime::parse_from_str(s, LOCAL_TIME_FORMAT)) .map_err(|e| format!("Invalid `LocalTime`: {}", e)) }) } - - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } /// Format of a `LocalDateTime` scalar. const LOCAL_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; -#[graphql_scalar( - description = "Combined date and time (without time zone) in `yyyy-MM-dd \ - HH:mm:ss` format.\ - \n\n\ - See also [`chrono::NaiveDateTime`][1] for details.\ - \n\n\ - [1]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDateTime.html" -)] -impl GraphQLScalar for LocalDateTime { - fn resolve(&self) -> Value { - Value::scalar(self.format(LOCAL_DATE_TIME_FORMAT).to_string()) +/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format. +/// +/// See also [`chrono::NaiveDateTime`][1] for details. +/// +/// [1]: https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html +#[graphql_scalar(with = local_date_time, parse_token(String))] +pub type LocalDateTime = chrono::NaiveDateTime; + +mod local_date_time { + use super::*; + + pub(super) fn to_output(v: &LocalDateTime) -> Value + where + S: ScalarValue, + { + Value::scalar(v.format(LOCAL_DATE_TIME_FORMAT).to_string()) } - fn from_input_value(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result + where + S: ScalarValue, + { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse_from_str(s, LOCAL_DATE_TIME_FORMAT) + LocalDateTime::parse_from_str(s, LOCAL_DATE_TIME_FORMAT) .map_err(|e| format!("Invalid `LocalDateTime`: {}", e)) }) } - - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } - } } -// TODO: Make generic over `chrono::TimeZone` once `#[graphql_scalar]` macro -// supports generics. +/// Combined date and time (with time zone) in [RFC 3339][0] format. +/// +/// Represents a description of an exact instant on the time-line (such as the +/// instant that a user account was created). +/// +/// [`DateTime` scalar][1] compliant. +/// +/// See also [`chrono::DateTime`][2] and [`chrono::FixedOffset`][3]` for +/// details. To implement custom [`TimeZone`]s see [`FromFixedOffset`]. +/// +/// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5 +/// [1]: https://graphql-scalars.dev/docs/scalars/date-time +/// [2]: https://docs.rs/chrono/latest/chrono/struct.DateTime.html +/// [3]: https://docs.rs/chrono/latest/chrono/offset/struct.FixedOffset.html +/// [`TimeZone`]: chrono::TimeZone #[graphql_scalar( - description = "Combined date and time (with time zone) in [RFC 3339][0] \ - format.\ - \n\n\ - Represents a description of an exact instant on the \ - time-line (such as the instant that a user account was \ - created).\ - \n\n\ - [`DateTime` scalar][1] compliant.\ - \n\n\ - See also [`chrono::DateTime`][2]` and \ - [`chrono::FixedOffset`][3]` for details.\ - \n\n\ - [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\ - [1]: https://graphql-scalars.dev/docs/scalars/date-time\n\ - [2]: https://docs.rs/chrono/*/chrono/struct.DateTime.html\n\", - [3]: https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html", - specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time" + with = date_time, + parse_token(String), + where( + Tz: TimeZone + FromFixedOffset, + Tz::Offset: fmt::Display, + ) )] -impl GraphQLScalar for DateTime { - fn resolve(&self) -> Value { - Value::scalar( - self.with_timezone(&Utc) - .to_rfc3339_opts(SecondsFormat::AutoSi, true), - ) +pub type DateTime = chrono::DateTime; + +mod date_time { + use super::*; + + pub(super) fn to_output(v: &DateTime) -> Value + where + S: ScalarValue, + Tz: chrono::TimeZone, + Tz::Offset: fmt::Display, + { + Value::scalar(v.to_rfc3339_opts(SecondsFormat::AutoSi, true)) } - fn from_input_value(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result, String> + where + S: ScalarValue, + Tz: TimeZone + FromFixedOffset, + { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - Self::parse_from_rfc3339(s).map_err(|e| format!("Invalid `DateTime`: {}", e)) + DateTime::::parse_from_rfc3339(s) + .map(FromFixedOffset::from_fixed_offset) + .map_err(|e| format!("Invalid `DateTime`: {}", e)) }) } +} - fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { - if let ScalarToken::String(s) = value { - Ok(S::from(s.to_owned())) - } else { - Err(ParseError::UnexpectedToken(Token::Scalar(value))) - } +/// This trait allows to implement custom [`TimeZone`]s compatible with +/// GraphQL [`DateTime`] type. +/// +/// # Example +/// +/// Creating custom [CET] [`TimeZone`] using [`chrono-tz`] crate. This is +/// required because [`chrono-tz`] uses enum to represent all [`TimeZone`]s, so +/// we have no knowledge of concrete underlying [`TimeZone`]. +/// +/// ```rust +/// # use chrono::{FixedOffset, TimeZone}; +/// # use juniper::{ +/// # integrations::chrono::{FromFixedOffset, DateTime}, +/// # graphql_object, +/// # }; +/// # +/// #[derive(Clone, Copy)] +/// struct CET; +/// +/// impl TimeZone for CET { +/// type Offset = ::Offset; +/// +/// fn from_offset(_: &Self::Offset) -> Self { +/// CET +/// } +/// +/// fn offset_from_local_date( +/// &self, +/// local: &chrono::NaiveDate, +/// ) -> chrono::LocalResult { +/// chrono_tz::CET.offset_from_local_date(local) +/// } +/// +/// fn offset_from_local_datetime( +/// &self, +/// local: &chrono::NaiveDateTime, +/// ) -> chrono::LocalResult { +/// chrono_tz::CET.offset_from_local_datetime(local) +/// } +/// +/// fn offset_from_utc_date(&self, utc: &chrono::NaiveDate) -> Self::Offset { +/// chrono_tz::CET.offset_from_utc_date(utc) +/// } +/// +/// fn offset_from_utc_datetime(&self, utc: &chrono::NaiveDateTime) -> Self::Offset { +/// chrono_tz::CET.offset_from_utc_datetime(utc) +/// } +/// } +/// +/// impl FromFixedOffset for CET { +/// fn from_fixed_offset(dt: DateTime) -> DateTime { +/// dt.with_timezone(&CET) +/// } +/// } +/// +/// struct Root; +/// +/// #[graphql_object] +/// impl Root { +/// fn pass_date_time(dt: DateTime) -> DateTime { +/// dt +/// } +/// } +/// ``` +/// +/// [CET]: https://en.wikipedia.org/wiki/Central_European_Time +pub trait FromFixedOffset: TimeZone { + /// Converts [`DateTime`]`<`[`FixedOffset`]`>` into [`DateTime`]``. + fn from_fixed_offset(dt: DateTime) -> DateTime; +} + +impl FromFixedOffset for FixedOffset { + fn from_fixed_offset(dt: DateTime) -> DateTime { + dt + } +} + +impl FromFixedOffset for chrono::Utc { + fn from_fixed_offset(dt: DateTime) -> DateTime { + dt.into() + } +} + +#[cfg(feature = "chrono-clock")] +impl FromFixedOffset for chrono::Local { + fn from_fixed_offset(dt: DateTime) -> DateTime { + dt.into() } } @@ -238,7 +331,7 @@ mod date_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -313,7 +406,7 @@ mod local_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -406,7 +499,7 @@ mod local_date_time_test { assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -470,62 +563,65 @@ mod local_date_time_test { #[cfg(test)] mod date_time_test { - use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime}; + use chrono::{ + naive::{NaiveDate, NaiveDateTime, NaiveTime}, + FixedOffset, + }; use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; - use super::{DateTime, UtcOffset}; + use super::DateTime; #[test] fn parses_correct_input() { for (raw, expected) in [ ( "2014-11-28T21:00:09+09:00", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms(12, 0, 9), ), - UtcOffset::east(9 * 3600), + FixedOffset::east(9 * 3600), ), ), ( "2014-11-28T21:00:09Z", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms(21, 0, 9), ), - UtcOffset::east(0), + FixedOffset::east(0), ), ), ( "2014-11-28T21:00:09+00:00", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms(21, 0, 9), ), - UtcOffset::east(0), + FixedOffset::east(0), ), ), ( "2014-11-28T21:00:09.05+09:00", - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(2014, 11, 28), NaiveTime::from_hms_milli(12, 0, 9, 50), ), - UtcOffset::east(0), + FixedOffset::east(0), ), ), ] { let input: InputValue = graphql_input_value!((raw)); - let parsed = DateTime::::from_input_value(&input); + let parsed = DateTime::::from_input_value(&input); assert!( parsed.is_ok(), - "failed to parse `{}`: {}", + "failed to parse `{}`: {:?}", raw, parsed.unwrap_err(), ); @@ -561,7 +657,7 @@ mod date_time_test { graphql_input_value!(false), ] { let input: InputValue = input; - let parsed = DateTime::::from_input_value(&input); + let parsed = DateTime::::from_input_value(&input); assert!(parsed.is_err(), "allows input: {:?}", input); } @@ -571,24 +667,24 @@ mod date_time_test { fn formats_correctly() { for (val, expected) in [ ( - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(1996, 12, 19), NaiveTime::from_hms(0, 0, 0), ), - UtcOffset::east(0), + FixedOffset::east(0), ), graphql_input_value!("1996-12-19T00:00:00Z"), ), ( - DateTime::::from_utc( + DateTime::::from_utc( NaiveDateTime::new( NaiveDate::from_ymd(1564, 1, 30), NaiveTime::from_hms_milli(5, 0, 0, 123), ), - UtcOffset::east(9 * 3600), + FixedOffset::east(9 * 3600), ), - graphql_input_value!("1564-01-30T05:00:00.123Z"), + graphql_input_value!("1564-01-30T14:00:00.123+09:00"), ), ] { let actual: InputValue = val.to_input_value(); @@ -606,10 +702,49 @@ mod integration_test { types::scalars::{EmptyMutation, EmptySubscription}, }; - use super::{Date, DateTime, LocalDateTime, LocalTime, UtcOffset}; + use super::{Date, DateTime, FixedOffset, FromFixedOffset, LocalDateTime, LocalTime, TimeZone}; #[tokio::test] async fn serializes() { + #[derive(Clone, Copy)] + struct CET; + + impl TimeZone for CET { + type Offset = ::Offset; + + fn from_offset(_: &Self::Offset) -> Self { + CET + } + + fn offset_from_local_date( + &self, + local: &chrono::NaiveDate, + ) -> chrono::LocalResult { + chrono_tz::CET.offset_from_local_date(local) + } + + fn offset_from_local_datetime( + &self, + local: &chrono::NaiveDateTime, + ) -> chrono::LocalResult { + chrono_tz::CET.offset_from_local_datetime(local) + } + + fn offset_from_utc_date(&self, utc: &chrono::NaiveDate) -> Self::Offset { + chrono_tz::CET.offset_from_utc_date(utc) + } + + fn offset_from_utc_datetime(&self, utc: &chrono::NaiveDateTime) -> Self::Offset { + chrono_tz::CET.offset_from_utc_datetime(utc) + } + } + + impl FromFixedOffset for CET { + fn from_fixed_offset(dt: DateTime) -> DateTime { + dt.with_timezone(&CET) + } + } + struct Root; #[graphql_object] @@ -626,15 +761,23 @@ mod integration_test { LocalDateTime::new(Date::from_ymd(2016, 7, 8), LocalTime::from_hms(9, 10, 11)) } - fn date_time() -> DateTime { - DateTime::::from_utc( + fn date_time() -> DateTime { + DateTime::from_utc( LocalDateTime::new( Date::from_ymd(1996, 12, 20), LocalTime::from_hms(0, 39, 57), ), - UtcOffset::west(8 * 3600), + chrono::Utc, ) } + + fn pass_date_time(dt: DateTime) -> DateTime { + dt + } + + fn transform_date_time(dt: DateTime) -> DateTime { + dt.with_timezone(&chrono::Utc) + } } const DOC: &str = r#"{ @@ -642,6 +785,8 @@ mod integration_test { localTime localDateTime dateTime, + passDateTime(dt: "2014-11-28T21:00:09+09:00") + transformDateTime(dt: "2014-11-28T21:00:09+09:00") }"#; let schema = RootNode::new( @@ -658,6 +803,8 @@ mod integration_test { "localTime": "16:07:08", "localDateTime": "2016-07-08 09:10:11", "dateTime": "1996-12-20T00:39:57Z", + "passDateTime": "2014-11-28T13:00:09+01:00", + "transformDateTime": "2014-11-28T12:00:09Z", }), vec![], )), diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index d481eb341..b00898c68 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -5,21 +5,25 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; +/// See [List of tz database time zones][1] `TZ database name` column for +/// available names. +/// +/// [1]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones #[graphql_scalar(with = tz, parse_token(String))] -type Tz = chrono_tz::Tz; +pub type TimeZone = chrono_tz::Tz; mod tz { use super::*; - pub(super) fn to_output(v: &Tz) -> Value { + pub(super) fn to_output(v: &TimeZone) -> Value { Value::scalar(v.name().to_owned()) } - pub(super) fn from_input(v: &InputValue) -> Result { + pub(super) fn from_input(v: &InputValue) -> Result { v.as_string_value() .ok_or_else(|| format!("Expected `String`, found: {}", v)) .and_then(|s| { - s.parse::() + s.parse::() .map_err(|e| format!("Failed to parse `Tz`: {}", e)) }) } @@ -27,12 +31,14 @@ mod tz { #[cfg(test)] mod test { + use super::TimeZone; + mod from_input_value { - use chrono_tz::Tz; + use super::TimeZone; use crate::{graphql_input_value, FromInputValue, InputValue, IntoFieldError}; - fn tz_input_test(raw: &'static str, expected: Result) { + fn tz_input_test(raw: &'static str, expected: Result) { let input: InputValue = graphql_input_value!((raw)); let parsed = FromInputValue::from_input_value(&input); From a7d4b286bd988cae8570eb9fa10a34cd2a52f356 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 25 Feb 2022 14:29:54 +0300 Subject: [PATCH 100/122] Corrections --- docs/book/content/types/scalars.md | 13 +- .../scalar/derive_input/attr_invalid_url.rs | 2 +- .../derive_input/attr_invalid_url.stderr | 2 +- .../scalar/derive_input/derive_invalid_url.rs | 2 +- .../derive_input/derive_invalid_url.stderr | 2 +- ...mpl_invalid_url.rs => attr_invalid_url.rs} | 0 ...lid_url.stderr => attr_invalid_url.stderr} | 2 +- ...lver.rs => attr_with_not_all_resolvers.rs} | 0 ...err => attr_with_not_all_resolvers.stderr} | 4 +- ...resolvers.rs => attr_without_resolvers.rs} | 0 ...r.stderr => attr_without_resolvers.stderr} | 4 +- integration_tests/juniper_tests/Cargo.toml | 2 +- .../src/codegen/scalar_attr_derive_input.rs | 170 ++++++------------ .../src/codegen/scalar_attr_type_alias.rs | 106 +++++------ .../src/codegen/scalar_derive_derive_input.rs | 160 ++++++----------- juniper/CHANGELOG.md | 2 +- juniper/src/integrations/bson.rs | 4 +- juniper/src/integrations/chrono.rs | 33 ++-- juniper/src/integrations/chrono_tz.rs | 2 +- juniper/src/integrations/url.rs | 2 +- juniper/src/integrations/uuid.rs | 2 +- juniper/src/types/scalars.rs | 18 +- juniper_codegen/src/derive_scalar_value.rs | 10 +- juniper_codegen/src/graphql_scalar/attr.rs | 18 +- juniper_codegen/src/graphql_scalar/derive.rs | 12 +- juniper_codegen/src/graphql_scalar/mod.rs | 76 ++++---- juniper_codegen/src/lib.rs | 26 +-- juniper_codegen/src/result.rs | 16 +- 28 files changed, 272 insertions(+), 418 deletions(-) rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_invalid_url.rs => attr_invalid_url.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_invalid_url.stderr => attr_invalid_url.stderr} (71%) rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_with_resolver.rs => attr_with_not_all_resolvers.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_without_resolvers.stderr => attr_with_not_all_resolvers.stderr} (65%) rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_without_resolvers.rs => attr_without_resolvers.rs} (100%) rename integration_tests/codegen_fail/fail/scalar/type_alias/{impl_with_resolver.stderr => attr_without_resolvers.stderr} (66%) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index cc032b8a7..ccd35a6e0 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -40,9 +40,12 @@ crates. They are enabled via features that are on by default. * url::Url * bson::oid::ObjectId + + + ## Custom scalars -#### `#[graphql(transparent)]` attribute +### `#[graphql(transparent)]` attribute Often, you might need a custom scalar that just wraps an existing type. @@ -107,7 +110,7 @@ pub struct UserId(i32); All the methods used from newtype's field can be replaced with attributes: -#### `#[graphql(to_output_with = )]` attribute +### `#[graphql(to_output_with = )]` attribute ```rust # use juniper::{GraphQLScalar, ScalarValue, Value}; @@ -124,7 +127,7 @@ fn to_output(v: &Incremented) -> Value { # fn main() {} ``` -#### `#[graphql(from_input_with = )]` attribute +### `#[graphql(from_input_with = )]` attribute ```rust # use juniper::{GraphQLScalar, InputValue, ScalarValue}; @@ -159,7 +162,7 @@ impl UserId { # fn main() {} ``` -#### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes +### `#[graphql(parse_token_with = ]` or `#[graphql(parse_token()]` attributes ```rust # use juniper::{ @@ -215,7 +218,7 @@ where > __NOTE:__ As you can see, once you provide all 3 custom resolvers, there > is no need to follow `newtype` pattern. -#### `#[graphql(with = )]` attribute +### `#[graphql(with = )]` attribute Instead of providing all custom resolvers, you can provide path to the `to_output`, `from_input`, `parse_token` functions. diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs index 70ad2aaf2..b28cb1fc3 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs @@ -1,6 +1,6 @@ use juniper::graphql_scalar; -#[graphql_scalar(specified_by_url = "not an url")] +#[graphql_scalar(specified_by_url = "not an url", transparent)] struct ScalarSpecifiedByUrl(i32); fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr index 8a1fcffed..b1bd12105 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base --> fail/scalar/derive_input/attr_invalid_url.rs:3:37 | -3 | #[graphql_scalar(specified_by_url = "not an url")] +3 | #[graphql_scalar(specified_by_url = "not an url", transparent)] | ^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs index ea56e8e8b..ae85bfd9d 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs @@ -1,7 +1,7 @@ use juniper::GraphQLScalar; #[derive(GraphQLScalar)] -#[graphql(specified_by_url = "not an url")] +#[graphql(specified_by_url = "not an url", transparent)] struct ScalarSpecifiedByUrl(i64); fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr index b1e7a948b..ee830d6a6 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base --> fail/scalar/derive_input/derive_invalid_url.rs:4:30 | -4 | #[graphql(specified_by_url = "not an url")] +4 | #[graphql(specified_by_url = "not an url", transparent)] | ^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.rs rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.rs diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr index b000693ca..4ecefa21a 100644 --- a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_invalid_url.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr @@ -1,5 +1,5 @@ error: Invalid URL: relative URL without a base - --> fail/scalar/type_alias/impl_invalid_url.rs:6:24 + --> fail/scalar/type_alias/attr_invalid_url.rs:6:24 | 6 | specified_by_url = "not an url", | ^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.rs rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.rs diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr similarity index 65% rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr index 93f6eb7ab..2bb56a2b2 100644 --- a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr @@ -1,5 +1,5 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes - --> fail/scalar/type_alias/impl_without_resolvers.rs:6:1 +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments + --> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:1 | 6 | type CustomScalar = Scalar; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_without_resolvers.rs rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.rs diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr similarity index 66% rename from integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.stderr rename to integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr index a9981aa45..626f34a3e 100644 --- a/integration_tests/codegen_fail/fail/scalar/type_alias/impl_with_resolver.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr @@ -1,5 +1,5 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes - --> fail/scalar/type_alias/impl_with_resolver.rs:6:1 +error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments + --> fail/scalar/type_alias/attr_without_resolvers.rs:6:1 | 6 | type CustomScalar = Scalar; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/juniper_tests/Cargo.toml b/integration_tests/juniper_tests/Cargo.toml index 44cb39aa2..d037598b5 100644 --- a/integration_tests/juniper_tests/Cargo.toml +++ b/integration_tests/juniper_tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" publish = false [dependencies] -chrono = "0.4.19" +chrono = "0.4" derive_more = "0.99" futures = "0.3" juniper = { path = "../../juniper" } diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs index 7571fbf94..172dd7ead 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs @@ -2,8 +2,8 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, - InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, + execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, InputValue, + ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; use crate::{ @@ -24,18 +24,18 @@ mod trivial { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(t) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -95,7 +95,7 @@ mod transparent { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -164,7 +164,7 @@ mod transparent_with_resolver { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -223,9 +223,7 @@ mod all_custom_resolvers { to_output_with = to_output, from_input_with = from_input, )] - #[graphql_scalar( - parse_token_with = parse_token, - )] + #[graphql_scalar(parse_token_with = parse_token)] struct Counter(i32); fn to_output(v: &Counter) -> Value { @@ -234,8 +232,8 @@ mod all_custom_resolvers { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Counter) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -244,7 +242,7 @@ mod all_custom_resolvers { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -309,8 +307,8 @@ mod explicit_name { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -320,7 +318,7 @@ mod explicit_name { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: CustomCounter) -> CustomCounter { value @@ -385,14 +383,14 @@ mod delegated_parse_token { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -486,7 +484,7 @@ mod multiple_delegated_parse_token { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": "test"}), vec![],)), + Ok((graphql_value!({"stringOrInt": "test"}), vec![])), ); } @@ -498,7 +496,7 @@ mod multiple_delegated_parse_token { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": 0}), vec![],)), + Ok((graphql_value!({"stringOrInt": 0}), vec![])), ); } } @@ -535,13 +533,13 @@ mod where_attribute { .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) }) } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -596,8 +594,8 @@ mod with_self { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -607,7 +605,7 @@ mod with_self { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -693,14 +691,14 @@ mod with_module { .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) }) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -746,32 +744,24 @@ mod description_from_doc_comment { use super::*; /// Description - #[graphql_scalar(with = counter)] + #[graphql_scalar(parse_token(i32))] struct Counter(i32); - mod counter { - use super::*; - - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .map(Counter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -820,7 +810,7 @@ mod description_from_doc_comment { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -830,7 +820,7 @@ mod description_from_attribute { use super::*; /// Doc comment - #[graphql_scalar(description = "Description from attribute")] + #[graphql_scalar(description = "Description from attribute", parse_token(i32))] struct Counter(i32); impl Counter { @@ -840,18 +830,14 @@ mod description_from_attribute { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -900,7 +886,7 @@ mod description_from_attribute { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description from attribute"}}), - vec![] + vec![], )), ); } @@ -910,29 +896,18 @@ mod custom_scalar { use super::*; /// Description - #[graphql_scalar( - scalar = MyScalarValue, - with = counter, - )] + #[graphql_scalar(scalar = MyScalarValue, parse_token(i32))] struct Counter(i32); - mod counter { - use super::*; - - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .map(Counter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } @@ -987,7 +962,7 @@ mod custom_scalar { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -997,35 +972,24 @@ mod generic_scalar { use super::*; /// Description - #[graphql_scalar( - scalar = S: ScalarValue, - with = counter, - )] + #[graphql_scalar(scalar = S: ScalarValue, parse_token(i32))] struct Counter(i32); - mod counter { - use super::*; - - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .map(Counter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -1083,8 +1047,7 @@ mod generic_scalar { mod bounded_generic_scalar { use super::*; - /// Description - #[graphql_scalar(scalar = S: ScalarValue + Clone)] + #[graphql_scalar(scalar = S: ScalarValue + Clone, parse_token(i32))] struct Counter(i32); impl Counter { @@ -1094,18 +1057,14 @@ mod bounded_generic_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -1139,23 +1098,4 @@ mod bounded_generic_scalar { Ok((graphql_value!({"counter": 0}), vec![])), ); } - - #[tokio::test] - async fn has_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"description": "Description"}}), - vec![] - )), - ); - } } diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs index aed8cdb04..3f8854c3a 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs @@ -2,8 +2,8 @@ use std::fmt; use chrono::{DateTime, TimeZone, Utc}; use juniper::{ - execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue, - InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, + execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, InputValue, + ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, }; use crate::{ @@ -31,8 +31,8 @@ mod all_custom_resolvers { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -41,7 +41,7 @@ mod all_custom_resolvers { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -112,8 +112,8 @@ mod explicit_name { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -122,7 +122,7 @@ mod explicit_name { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: CounterScalar) -> CounterScalar { value @@ -150,11 +150,11 @@ mod explicit_name { for name in ["CustomCounter", "CustomScalar"] { let doc = format!( r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - name + __type(name: "{}") {{ + kind + }} + }}"#, + name, ); let schema = schema(QueryRoot); @@ -213,13 +213,13 @@ mod delegated_parse_token { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -317,7 +317,7 @@ mod multiple_delegated_parse_token { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": "test"}), vec![],)), + Ok((graphql_value!({"stringOrInt": "test"}), vec![])), ); } @@ -329,7 +329,7 @@ mod multiple_delegated_parse_token { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": 0}), vec![],)), + Ok((graphql_value!({"stringOrInt": 0}), vec![])), ); } } @@ -368,13 +368,13 @@ mod where_attribute { .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) }) } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -431,8 +431,8 @@ mod with_self { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -442,7 +442,7 @@ mod with_self { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -530,14 +530,14 @@ mod with_module { .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) }) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -585,7 +585,7 @@ mod description_from_doc_comment { struct CustomCounter(i32); /// Description - #[graphql_scalar(with = counter)] + #[graphql_scalar(with = counter, parse_token(i32))] type Counter = CustomCounter; mod counter { @@ -597,20 +597,14 @@ mod description_from_doc_comment { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -659,7 +653,7 @@ mod description_from_doc_comment { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -674,6 +668,7 @@ mod description_from_attribute { #[graphql_scalar( description = "Description from attribute", with = counter, + parse_token(i32), )] type Counter = CustomCounter; @@ -686,20 +681,14 @@ mod description_from_attribute { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -748,7 +737,7 @@ mod description_from_attribute { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description from attribute"}}), - vec![] + vec![], )), ); } @@ -763,6 +752,7 @@ mod custom_scalar { #[graphql_scalar( scalar = MyScalarValue, with = counter, + parse_token(i32), )] type Counter = CustomCounter; @@ -775,14 +765,8 @@ mod custom_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } @@ -837,7 +821,7 @@ mod custom_scalar { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -852,6 +836,7 @@ mod generic_scalar { #[graphql_scalar( scalar = S: ScalarValue, with = counter, + parse_token(i32), )] type Counter = CustomCounter; @@ -864,20 +849,14 @@ mod generic_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -926,7 +905,7 @@ mod generic_scalar { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -941,6 +920,7 @@ mod bounded_generic_scalar { #[graphql_scalar( scalar = S: ScalarValue + Clone, with = counter, + parse_token(i32), )] type Counter = CustomCounter; @@ -953,20 +933,14 @@ mod bounded_generic_scalar { pub(super) fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(CustomCounter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -1015,7 +989,7 @@ mod bounded_generic_scalar { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } diff --git a/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs index f9c4bfde9..d11ef8811 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs @@ -24,18 +24,18 @@ mod trivial { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { + >::from_str(t) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -226,9 +226,7 @@ mod all_custom_resolvers { to_output_with = to_output, from_input_with = from_input, )] - #[graphql( - parse_token_with = parse_token, - )] + #[graphql(parse_token_with = parse_token)] struct Counter(i32); fn to_output(v: &Counter) -> Value { @@ -237,8 +235,8 @@ mod all_custom_resolvers { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Counter) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -247,7 +245,7 @@ mod all_custom_resolvers { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -313,8 +311,8 @@ mod explicit_name { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -324,7 +322,7 @@ mod explicit_name { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: CustomCounter) -> CustomCounter { value @@ -390,14 +388,14 @@ mod delegated_parse_token { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -492,7 +490,7 @@ mod multiple_delegated_parse_token { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": "test"}), vec![],)), + Ok((graphql_value!({"stringOrInt": "test"}), vec![])), ); } @@ -504,7 +502,7 @@ mod multiple_delegated_parse_token { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": 0}), vec![],)), + Ok((graphql_value!({"stringOrInt": 0}), vec![])), ); } } @@ -542,13 +540,13 @@ mod where_attribute { .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) }) } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -604,8 +602,8 @@ mod with_self { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { @@ -615,7 +613,7 @@ mod with_self { struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -702,14 +700,14 @@ mod with_module { .and_then(|s| { DateTime::parse_from_rfc3339(s) .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse CustomDateTime: {}", e)) + .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) }) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn date_time(value: CustomDateTime) -> CustomDateTime { value @@ -756,26 +754,18 @@ mod description_from_doc_comment { /// Description #[derive(GraphQLScalar)] - #[graphql(with = counter)] + #[graphql(parse_token(i32))] struct Counter(i32); - mod counter { - use super::*; - - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .map(Counter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } @@ -830,7 +820,7 @@ mod description_from_doc_comment { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -841,7 +831,7 @@ mod description_from_attribute { /// Doc comment #[derive(GraphQLScalar)] - #[graphql(description = "Description from attribute")] + #[graphql(description = "Description from attribute", parse_token(i32))] struct Counter(i32); impl Counter { @@ -851,18 +841,14 @@ mod description_from_attribute { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = DefaultScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -911,7 +897,7 @@ mod description_from_attribute { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description from attribute"}}), - vec![] + vec![], )), ); } @@ -922,29 +908,18 @@ mod custom_scalar { /// Description #[derive(GraphQLScalar)] - #[graphql( - scalar = MyScalarValue, - with = counter, - )] + #[graphql(scalar = MyScalarValue, parse_token(i32))] struct Counter(i32); - mod counter { - use super::*; - - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .map(Counter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } @@ -999,7 +974,7 @@ mod custom_scalar { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -1010,35 +985,24 @@ mod generic_scalar { /// Description #[derive(GraphQLScalar)] - #[graphql( - scalar = S: ScalarValue, - with = counter, - )] + #[graphql(scalar = S: ScalarValue, parse_token(i32))] struct Counter(i32); - mod counter { - use super::*; - - pub(super) fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) + impl Counter { + fn to_output(&self) -> Value { + Value::scalar(self.0) } - pub(super) fn from_input(v: &InputValue) -> Result { + fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .map(Counter) - } - - pub(super) fn parse_token( - value: ScalarToken<'_>, - ) -> ParseScalarResult<'_, S> { - >::from_str(value) + .map(Self) + .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) } } struct QueryRoot; - #[graphql_object(scalar = MyScalarValue)] + #[graphql_object] impl QueryRoot { fn counter(value: Counter) -> Counter { value @@ -1087,7 +1051,7 @@ mod generic_scalar { execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"description": "Description"}}), - vec![] + vec![], )), ); } @@ -1096,9 +1060,8 @@ mod generic_scalar { mod bounded_generic_scalar { use super::*; - /// Description #[derive(GraphQLScalar)] - #[graphql(scalar = S: ScalarValue + Clone)] + #[graphql(scalar = S: ScalarValue + Clone, parse_token(i32))] struct Counter(i32); impl Counter { @@ -1108,12 +1071,8 @@ mod bounded_generic_scalar { fn from_input(v: &InputValue) -> Result { v.as_int_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) .map(Self) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) + .ok_or_else(|| format!("Expected `String`, found: {}", v)) } } @@ -1153,23 +1112,4 @@ mod bounded_generic_scalar { Ok((graphql_value!({"counter": 0}), vec![])), ); } - - #[tokio::test] - async fn has_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"description": "Description"}}), - vec![] - )), - ); - } } diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index d395e8c39..5859a72d6 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -24,7 +24,7 @@ - Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - Support generic scalars. - Support structs with single named field. - - Support for overriding resolvers. + - Support overriding resolvers. - Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) - Mirror `#[derive(GraphQLScalar)]` macro. - Support usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 61a9efd6d..c9af35fb6 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -52,7 +52,7 @@ mod test { use crate::{graphql_input_value, FromInputValue, InputValue}; #[test] - fn objectid_from_input_value() { + fn objectid_from_input() { let raw = "53e37d08776f724e42000000"; let input: InputValue = graphql_input_value!((raw)); @@ -63,7 +63,7 @@ mod test { } #[test] - fn utcdatetime_from_input_value() { + fn utcdatetime_from_input() { let raw = "2020-03-23T17:38:32.446+00:00"; let input: InputValue = graphql_input_value!((raw)); diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index ff70429f8..6c0f8fc2a 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -18,10 +18,7 @@ #![allow(clippy::needless_lifetimes)] use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -#[graphql_scalar( - with = date_time_fixed_offset, - parse_token(String), -)] +#[graphql_scalar(with = date_time_fixed_offset, parse_token(String))] type DateTimeFixedOffset = chrono::DateTime; mod date_time_fixed_offset { @@ -43,10 +40,7 @@ mod date_time_fixed_offset { } } -#[graphql_scalar( - with = date_time_utc, - parse_token(String) -)] +#[graphql_scalar(with = date_time_utc, parse_token(String))] type DateTimeUtc = chrono::DateTime; mod date_time_utc { @@ -71,10 +65,7 @@ mod date_time_utc { // inherent lack of precision required for the time zone resolution. // For serialization and deserialization uses, it is best to use // `NaiveDate` instead." -#[graphql_scalar( - with = naive_date, - parse_token(String), -)] +#[graphql_scalar(with = naive_date, parse_token(String))] type NaiveDate = chrono::NaiveDate; mod naive_date { @@ -155,17 +146,17 @@ mod test { } #[test] - fn datetime_fixedoffset_from_input_value() { + fn datetime_fixedoffset_from_input() { datetime_fixedoffset_test("2014-11-28T21:00:09+09:00"); } #[test] - fn datetime_fixedoffset_from_input_value_with_z_timezone() { + fn datetime_fixedoffset_from_input_with_z_timezone() { datetime_fixedoffset_test("2014-11-28T21:00:09Z"); } #[test] - fn datetime_fixedoffset_from_input_value_with_fractional_seconds() { + fn datetime_fixedoffset_from_input_with_fractional_seconds() { datetime_fixedoffset_test("2014-11-28T21:00:09.05+09:00"); } @@ -181,22 +172,22 @@ mod test { } #[test] - fn datetime_utc_from_input_value() { + fn datetime_utc_from_input() { datetime_utc_test("2014-11-28T21:00:09+09:00") } #[test] - fn datetime_utc_from_input_value_with_z_timezone() { + fn datetime_utc_from_input_with_z_timezone() { datetime_utc_test("2014-11-28T21:00:09Z") } #[test] - fn datetime_utc_from_input_value_with_fractional_seconds() { + fn datetime_utc_from_input_with_fractional_seconds() { datetime_utc_test("2014-11-28T21:00:09.005+09:00"); } #[test] - fn naivedate_from_input_value() { + fn naivedate_from_input() { let input: InputValue = graphql_input_value!("1996-12-19"); let y = 1996; let m = 12; @@ -214,7 +205,7 @@ mod test { #[test] #[cfg(feature = "scalar-naivetime")] - fn naivetime_from_input_value() { + fn naivetime_from_input() { let input: InputValue = graphql_input_value!("21:12:19"); let [h, m, s] = [21, 12, 19]; let parsed: NaiveTime = FromInputValue::from_input_value(&input).unwrap(); @@ -226,7 +217,7 @@ mod test { } #[test] - fn naivedatetime_from_input_value() { + fn naivedatetime_from_input() { let raw = 1_000_000_000_f64; let input: InputValue = graphql_input_value!((raw)); diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index d481eb341..cf7621392 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -27,7 +27,7 @@ mod tz { #[cfg(test)] mod test { - mod from_input_value { + mod from_input { use chrono_tz::Tz; use crate::{graphql_input_value, FromInputValue, InputValue, IntoFieldError}; diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 73c541871..e3764c6bc 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -26,7 +26,7 @@ mod test { use crate::{graphql_input_value, InputValue}; #[test] - fn url_from_input_value() { + fn url_from_input() { let raw = "https://example.net/"; let input: InputValue = graphql_input_value!((raw)); diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index cfc924ef1..37f710424 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -28,7 +28,7 @@ mod test { use crate::{graphql_input_value, FromInputValue, InputValue}; #[test] - fn uuid_from_input_value() { + fn uuid_from_input() { let raw = "123e4567-e89b-12d3-a456-426655440000"; let input: InputValue = graphql_input_value!((raw)); diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index a39c989c3..3a61b9b58 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -68,10 +68,10 @@ impl fmt::Display for ID { } } -#[graphql_scalar(with = string)] +#[graphql_scalar(with = impl_string_scalar)] type String = std::string::String; -mod string { +mod impl_string_scalar { use super::*; pub(super) fn to_output(v: &str) -> Value { @@ -269,10 +269,10 @@ where } } -#[graphql_scalar(with = boolean)] +#[graphql_scalar(with = impl_boolean_scalar)] type Boolean = bool; -mod boolean { +mod impl_boolean_scalar { use super::*; pub(super) fn to_output(v: &Boolean) -> Value { @@ -286,15 +286,15 @@ mod boolean { } pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - // Bools are parsed separately - they shouldn't reach this code path + // `Boolean`s are parsed separately, they shouldn't reach this code path. Err(ParseError::UnexpectedToken(Token::Scalar(value))) } } -#[graphql_scalar(with = int)] +#[graphql_scalar(with = impl_int_scalar)] type Int = i32; -mod int { +mod impl_int_scalar { use super::*; pub(super) fn to_output(v: &Int) -> Value { @@ -317,10 +317,10 @@ mod int { } } -#[graphql_scalar(with = float)] +#[graphql_scalar(with = impl_float_scalar)] type Float = f64; -mod float { +mod impl_float_scalar { use super::*; pub(super) fn to_output(v: &Float) -> Value { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 09a075ab6..4e676e08a 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -19,7 +19,7 @@ use crate::{ }; /// [`GraphQLScope`] of errors for `#[derive(GraphQLScalarValue)]` macro. -const ERR: GraphQLScope = GraphQLScope::DeriveScalarValue; +const ERR: GraphQLScope = GraphQLScope::ScalarValueDerive; /// Expands `#[derive(GraphQLScalarValue)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { @@ -456,8 +456,8 @@ enum Field { impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { match self { - Field::Named(f) => f.ident.to_tokens(tokens), - Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + Self::Named(f) => f.ident.to_tokens(tokens), + Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), } } } @@ -482,8 +482,8 @@ impl Field { /// Returns [`Field`] for constructing or matching over [`Variant`]. fn match_arg(&self) -> TokenStream { match self { - Field::Named(_) => quote! { { #self: v } }, - Field::Unnamed(_) => quote! { (v) }, + Self::Named(_) => quote! { { #self: v } }, + Self::Unnamed(_) => quote! { (v) }, } } } diff --git a/juniper_codegen/src/graphql_scalar/attr.rs b/juniper_codegen/src/graphql_scalar/attr.rs index 2255f6fc5..e9ff22dfd 100644 --- a/juniper_codegen/src/graphql_scalar/attr.rs +++ b/juniper_codegen/src/graphql_scalar/attr.rs @@ -12,7 +12,7 @@ use crate::{ use super::{Attr, Definition, Field, GraphQLScalarMethods, ParseToken}; -const ERR: GraphQLScope = GraphQLScope::ImplScalar; +const ERR: GraphQLScope = GraphQLScope::ScalarAttr; /// Expands `#[graphql_scalar]` macro into generated code. pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { @@ -20,8 +20,7 @@ pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result(body) { + } else if let Ok(mut ast) = syn::parse2::(body) { let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs); return expand_on_derive_input(attrs, ast); @@ -29,7 +28,8 @@ pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result fields .unnamed .first() - .and_then(|f| (fields.unnamed.len() == 1).then(|| Field::Unnamed(f.clone()))) + .filter(|_| fields.unnamed.len() == 1) + .cloned() + .map(Field::Unnamed) .ok_or_else(|| { ERR.custom_error( ast.span(), @@ -161,7 +163,9 @@ fn expand_on_derive_input( syn::Fields::Named(fields) => fields .named .first() - .and_then(|f| (fields.named.len() == 1).then(|| Field::Named(f.clone()))) + .filter(|_| fields.named.len() == 1) + .cloned() + .map(Field::Named) .ok_or_else(|| { ERR.custom_error( ast.span(), diff --git a/juniper_codegen/src/graphql_scalar/derive.rs b/juniper_codegen/src/graphql_scalar/derive.rs index 7b4a9a482..101f89a55 100644 --- a/juniper_codegen/src/graphql_scalar/derive.rs +++ b/juniper_codegen/src/graphql_scalar/derive.rs @@ -9,7 +9,7 @@ use crate::{common::scalar, result::GraphQLScope}; use super::{Attr, Definition, Field, GraphQLScalarMethods, ParseToken, TypeOrIdent}; /// [`GraphQLScope`] of errors for `#[derive(GraphQLScalar)]` macro. -const ERR: GraphQLScope = GraphQLScope::DeriveScalar; +const ERR: GraphQLScope = GraphQLScope::ScalarDerive; /// Expands `#[derive(GraphQLScalar)]` macro into generated code. pub fn expand(input: TokenStream) -> syn::Result { @@ -32,7 +32,7 @@ pub fn expand(input: TokenStream) -> syn::Result { } } (to_output, from_input, parse_token, module, false) => { - let module = module.unwrap_or_else(|| parse_quote!(Self)); + let module = module.unwrap_or_else(|| parse_quote! { Self }); GraphQLScalarMethods::Custom { to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }), from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }), @@ -58,7 +58,9 @@ pub fn expand(input: TokenStream) -> syn::Result { syn::Fields::Unnamed(fields) => fields .unnamed .first() - .and_then(|f| (fields.unnamed.len() == 1).then(|| Field::Unnamed(f.clone()))) + .filter(|_| fields.unnamed.len() == 1) + .cloned() + .map(Field::Unnamed) .ok_or_else(|| { ERR.custom_error( ast.span(), @@ -69,7 +71,9 @@ pub fn expand(input: TokenStream) -> syn::Result { syn::Fields::Named(fields) => fields .named .first() - .and_then(|f| (fields.named.len() == 1).then(|| Field::Named(f.clone()))) + .filter(|_| fields.named.len() == 1) + .cloned() + .map(Field::Named) .ok_or_else(|| { ERR.custom_error( ast.span(), diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index bf6619197..7cc52e5e5 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -1,6 +1,6 @@ //! Code generation for [GraphQL scalar][1]. //! -//! [1]: https://spec.graphql.org/October2021/#sec-Scalars +//! [1]: https://spec.graphql.org/October2021#sec-Scalars use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; @@ -31,22 +31,22 @@ pub mod derive; /// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when /// generating code for [GraphQL scalar][1]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +/// [1]: https://spec.graphql.org/October2021#sec-Scalars #[derive(Debug, Default)] struct Attr { /// Name of this [GraphQL scalar][1] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars name: Option>, /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars description: Option>, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars specified_by_url: Option>, /// Explicitly specified type (or type parameter with its bounds) of @@ -60,23 +60,23 @@ struct Attr { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars scalar: Option>, - /// Explicitly specified function to be used instead of - /// [`ToInputValue::to_input_value`]. + /// Explicitly specified function to be used as + /// [`ToInputValue::to_input_value`] implementation. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value to_output: Option>, - /// Explicitly specified function to be used instead of - /// [`FromInputValue::from_input_value`]. + /// Explicitly specified function to be used as + /// [`FromInputValue::from_input_value`] implementation. /// /// [`FromInputValue::from_input_value`]: juniper::FromInputValue::from_input_value from_input: Option>, - /// Explicitly specified resolver to be used instead of - /// [`ParseScalarValue::from_str`]. + /// Explicitly specified resolver to be used as + /// [`ParseScalarValue::from_str`] implementation. /// /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str parse_token: Option>, @@ -278,16 +278,16 @@ enum TypeOrIdent { /// Definition of [GraphQL scalar][1] for code generation. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +/// [1]: https://spec.graphql.org/October2021#sec-Scalars struct Definition { /// Name of this [GraphQL scalar][1] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars name: String, /// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars ty: TypeOrIdent, /// Additional [`Self::generics`] [`syn::WhereClause`] predicates. @@ -296,22 +296,22 @@ struct Definition { /// Generics of the Rust type that this [GraphQL scalar][1] is implemented /// for. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars generics: syn::Generics, /// [`GraphQLScalarMethods`] representing [GraphQL scalar][1]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars methods: GraphQLScalarMethods, /// Description of this [GraphQL scalar][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars description: Option, /// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars specified_by_url: Option, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] @@ -319,7 +319,7 @@ struct Definition { /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars scalar: scalar::Type, } @@ -342,7 +342,7 @@ impl Definition { /// /// [`marker::IsInputType`]: juniper::marker::IsInputType /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars #[must_use] fn impl_output_and_input_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -365,7 +365,7 @@ impl Definition { /// [GraphQL scalar][1]. /// /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; @@ -411,7 +411,7 @@ impl Definition { /// [GraphQL scalar][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -448,7 +448,7 @@ impl Definition { /// [GraphQL scalar][1]. /// /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -478,7 +478,7 @@ impl Definition { /// [GraphQL scalar][1]. /// /// [`InputValue`]: juniper::InputValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_to_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -503,7 +503,7 @@ impl Definition { /// [GraphQL scalar][1]. /// /// [`FromInputValue`]: juniper::FromInputValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_from_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -520,7 +520,7 @@ impl Definition { type Error = ::juniper::executor::FieldError<#scalar>; fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result { - #from_input_value + #from_input_value .map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error) } } @@ -531,7 +531,7 @@ impl Definition { /// [GraphQL scalar][1]. /// /// [`ParseScalarValue`]: juniper::ParseScalarValue - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_parse_scalar_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -560,7 +560,7 @@ impl Definition { /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes /// [`BaseType`]: juniper::macros::reflection::BaseType /// [`WrappedType`]: juniper::macros::reflection::WrappedType - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; @@ -684,11 +684,11 @@ impl VisitMut for ModifyLifetimes { /// Methods representing [GraphQL scalar][1]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Scalars +/// [1]: https://spec.graphql.org/October2021#sec-Scalars enum GraphQLScalarMethods { /// [GraphQL scalar][1] represented with only custom resolvers. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars Custom { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: syn::ExprPath, @@ -704,7 +704,7 @@ enum GraphQLScalarMethods { /// [GraphQL scalar][1] maybe partially represented with custom resolver. /// Other methods are used from [`Field`]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars Delegated { /// Function provided with `#[graphql(to_output_with = ...)]`. to_output: Option, @@ -833,10 +833,10 @@ impl ParseToken { /// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream { match self { - ParseToken::Custom(parse_token) => { + Self::Custom(parse_token) => { quote! { #parse_token(token) } } - ParseToken::Delegated(delegated) => delegated + Self::Delegated(delegated) => delegated .iter() .fold(None, |acc, ty| { acc.map_or_else( @@ -867,8 +867,8 @@ enum Field { impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { match self { - Field::Named(f) => f.ident.to_tokens(tokens), - Field::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), + Self::Named(f) => f.ident.to_tokens(tokens), + Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), } } } @@ -877,13 +877,13 @@ impl Field { /// [`syn::Type`] of this [`Field`]. fn ty(&self) -> &syn::Type { match self { - Field::Named(f) | Field::Unnamed(f) => &f.ty, + Self::Named(f) | Self::Unnamed(f) => &f.ty, } } /// Closure to construct [GraphQL scalar][1] struct from [`Field`]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Scalars + /// [1]: https://spec.graphql.org/October2021#sec-Scalars fn closure_constructor(&self) -> TokenStream { match self { Field::Named(syn::Field { ident, .. }) => { diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 6618d93cf..8ca9afcc9 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -108,12 +108,12 @@ macro_rules! try_merge_hashset { mod derive_enum; mod derive_input_object; -mod graphql_scalar; mod common; mod derive_scalar_value; mod graphql_interface; mod graphql_object; +mod graphql_scalar; mod graphql_subscription; mod graphql_union; @@ -153,7 +153,6 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// Thanks to this custom derive, this becomes really easy: /// /// ```rust -/// // Deriving GraphQLScalar is all that is required. /// #[derive(juniper::GraphQLScalar)] /// #[graphql(transparent)] /// struct UserId(String); @@ -223,7 +222,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// impl UserId { /// /// Checks whether [`InputValue`] is `String` beginning with `id: ` and /// /// strips it. -/// fn from_input(input: &InputValue) -> Result { +/// fn from_input(input: &InputValue) -> Result { +/// // Must implement `IntoFieldError` ^^^^^^ /// input.as_string_value() /// .ok_or_else(|| format!("Expected `String`, found: {}", input)) /// .and_then(|str| { @@ -297,7 +297,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// # }; /// # /// #[derive(GraphQLScalar)] -/// // #[graphql(with = string_or_int)] <- default behaviour +/// // #[graphql(with = Self)] <- default behaviour, so can be omitted /// enum StringOrInt { /// String(String), /// Int(i32), @@ -362,13 +362,13 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// pub(super) fn from_input(v: &InputValue) -> Result { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.to_owned())) -/// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) +/// .or_else(|| v.as_int_value().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) /// } /// -/// pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { -/// >::from_str(value) -/// .or_else(|_| >::from_str(value)) +/// pub(super) fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { +/// >::from_str(t) +/// .or_else(|_| >::from_str(t)) /// } /// } /// # @@ -394,8 +394,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// } /// /// mod string_or_int { -/// # use super::*; -/// # +/// use super::*; +/// /// pub(super) fn to_output(v: &StringOrInt) -> Value /// where /// S: ScalarValue, @@ -412,7 +412,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { /// { /// v.as_string_value() /// .map(|s| StringOrInt::String(s.to_owned())) -/// .or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i))) +/// .or_else(|| v.as_int_value().map(StringOrInt::Int)) /// .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) /// } /// @@ -633,12 +633,12 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// /// mod date_scalar { /// use super::*; -/// +/// /// // Define how to convert your custom scalar into a primitive type. /// pub(super) fn to_output(v: &Date) -> Value { /// Value::scalar(v.to_string()) /// } -/// +/// /// // Define how to parse a primitive type into your custom scalar. /// // NOTE: The error type should implement `IntoFieldError`. /// pub(super) fn from_input(v: &InputValue) -> Result { diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index 5e78112a4..f3a06f426 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -8,18 +8,17 @@ use std::fmt; /// URL of the GraphQL specification (June 2018 Edition). pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; -#[allow(unused_variables)] pub enum GraphQLScope { InterfaceAttr, ObjectAttr, ObjectDerive, + ScalarAttr, + ScalarDerive, + ScalarValueDerive, UnionAttr, UnionDerive, DeriveInputObject, DeriveEnum, - DeriveScalarValue, - DeriveScalar, - ImplScalar, } impl GraphQLScope { @@ -27,11 +26,11 @@ impl GraphQLScope { match self { Self::InterfaceAttr => "#sec-Interfaces", Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", + Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars", + Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars", Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveInputObject => "#sec-Input-Objects", Self::DeriveEnum => "#sec-Enums", - Self::DeriveScalarValue => "#sec-Scalars.Built-in-Scalars", - Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars", } } } @@ -41,13 +40,12 @@ impl fmt::Display for GraphQLScope { let name = match self { Self::InterfaceAttr => "interface", Self::ObjectAttr | Self::ObjectDerive => "object", + Self::ScalarAttr | Self::ScalarDerive => "scalar", + Self::ScalarValueDerive => "built-in scalars", Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveInputObject => "input object", Self::DeriveEnum => "enum", - Self::DeriveScalarValue => "built-in scalars", - Self::DeriveScalar | Self::ImplScalar => "scalar", }; - write!(f, "GraphQL {}", name) } } From aa8a345acd70b70831e1c9e1f023c9883b99ef90 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 1 Mar 2022 11:55:51 +0300 Subject: [PATCH 101/122] Corrections --- .../argument_wrong_default_array.stderr | 3 + .../argument_wrong_default_array.stderr | 3 + .../attr_transparent_and_with.stderr | 2 +- ...r_transparent_multiple_named_fields.stderr | 2 +- ...transparent_multiple_unnamed_fields.stderr | 2 +- .../attr_transparent_unit_struct.stderr | 2 +- .../derive_transparent_and_with.stderr | 2 +- ...e_transparent_multiple_named_fields.stderr | 2 +- ...transparent_multiple_unnamed_fields.stderr | 2 +- .../derive_transparent_unit_struct.stderr | 2 +- .../attr_with_not_all_resolvers.stderr | 2 +- .../type_alias/attr_without_resolvers.stderr | 2 +- ...ssing_methods.rs => missing_attributes.rs} | 0 .../scalar_value/missing_attributes.stderr | 11 + .../fail/scalar_value/missing_methods.stderr | 11 - .../argument_wrong_default_array.stderr | 3 + .../src/codegen/derive_scalar_value.rs | 4 +- .../src/codegen/scalar_derive_derive_input.rs | 1115 ----------------- juniper_codegen/src/derive_scalar_value.rs | 26 +- 19 files changed, 45 insertions(+), 1151 deletions(-) rename integration_tests/codegen_fail/fail/scalar_value/{missing_methods.rs => missing_attributes.rs} (100%) create mode 100644 integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr delete mode 100644 integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr delete mode 100644 integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr index b745a82d0..c134913f8 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr @@ -5,6 +5,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | = help: the following implementations were found: + <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> <[T; LANES] as From>> <[bool; LANES] as From>> = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr index 27332cfeb..6992876c5 100644 --- a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr @@ -5,6 +5,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | = help: the following implementations were found: + <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> <[T; LANES] as From>> <[bool; LANES] as From>> = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr index 829c42c75..ce9f808d6 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar `with = ` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones. +error: GraphQL scalar `with = ` attribute argument cannot be combined with `transparent`. You can specify custom resolvers with `to_output_with`, `from_input_with`, `parse_token`/`parse_token_with` attribute arguments and still use `transparent` for unspecified ones. --> fail/scalar/derive_input/attr_transparent_and_with.rs:3:25 | 3 | #[graphql_scalar(with = Self, transparent)] diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr index 5a6f94063..a101fdb60 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute +error: GraphQL scalar `transparent` attribute argument requires exactly 1 field --> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:1 | 4 | / struct Scalar { diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr index dfdcff7c3..6ff2eb84f 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute +error: GraphQL scalar `transparent` attribute argument requires exactly 1 field --> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:1 | 4 | struct Scalar(i32, i32); diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr index 11b661f98..8882861fb 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute +error: GraphQL scalar `transparent` attribute argument requires exactly 1 field --> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:1 | 4 | struct ScalarSpecifiedByUrl; diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr index 860d5391f..74a2fd969 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar `with = ` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones. +error: GraphQL scalar `with = ` attribute argument cannot be combined with `transparent`. You can specify custom resolvers with `to_output_with`, `from_input_with`, `parse_token`/`parse_token_with` attribute arguments and still use `transparent` for unspecified ones. --> fail/scalar/derive_input/derive_transparent_and_with.rs:4:18 | 4 | #[graphql(with = Self, transparent)] diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr index d5258cefc..1d42b4c1f 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute +error: GraphQL scalar `transparent` attribute argument requires exactly 1 field --> fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs:4:1 | 4 | / #[graphql(transparent)] diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr index a832801b4..e7c5f5a86 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute +error: GraphQL scalar `transparent` attribute argument requires exactly 1 field --> fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs:4:1 | 4 | / #[graphql(transparent)] diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr index 3fc185ed9..21156b67f 100644 --- a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr +++ b/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute +error: GraphQL scalar `transparent` attribute argument requires exactly 1 field --> fail/scalar/derive_input/derive_transparent_unit_struct.rs:4:1 | 4 | / #[graphql(transparent)] diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr index 2bb56a2b2..879dfbd04 100644 --- a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments +error: GraphQL scalar all the resolvers have to be provided via `with` attribute argument or a combination of `to_output_with`, `from_input_with`, `parse_token_with`/`parse_token` attribute arguments --> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:1 | 6 | type CustomScalar = Scalar; diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr index 626f34a3e..6f77be791 100644 --- a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr +++ b/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr @@ -1,4 +1,4 @@ -error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments +error: GraphQL scalar all the resolvers have to be provided via `with` attribute argument or a combination of `to_output_with`, `from_input_with`, `parse_token_with`/`parse_token` attribute arguments --> fail/scalar/type_alias/attr_without_resolvers.rs:6:1 | 6 | type CustomScalar = Scalar; diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.rs b/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/missing_methods.rs rename to integration_tests/codegen_fail/fail/scalar_value/missing_attributes.rs diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr b/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr new file mode 100644 index 000000000..61ade2eea --- /dev/null +++ b/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr @@ -0,0 +1,11 @@ +error: GraphQL built-in scalars missing `#[graphql(as_int, as_float)]` attributes. In case you are sure that it is ok, use `#[graphql(allow_missing_attributes)]` to suppress this error. + --> fail/scalar_value/missing_attributes.rs:4:1 + | +4 | / pub enum DefaultScalarValue { +5 | | Int(i32), +6 | | Float(f64), +7 | | #[graphql(as_str, as_string, into_string)] +... | +10 | | Boolean(bool), +11 | | } + | |_^ diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr b/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr deleted file mode 100644 index dd3c337e2..000000000 --- a/integration_tests/codegen_fail/fail/scalar_value/missing_methods.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: GraphQL built-in scalars missing `as_int`, `as_float` attributes. In case you are sure that it\'s ok, use `#[graphql(allow_unused_methods)]` to suppress this error. - --> fail/scalar_value/missing_methods.rs:4:1 - | -4 | / pub enum DefaultScalarValue { -5 | | Int(i32), -6 | | Float(f64), -7 | | #[graphql(as_str, as_string, into_string)] -... | -10 | | Boolean(bool), -11 | | } - | |_^ diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr index 4d71e80d9..d6416583b 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr @@ -5,6 +5,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | = help: the following implementations were found: + <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> <[T; LANES] as From>> <[bool; LANES] as From>> = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs index 0d97349db..00f3d50c1 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs @@ -104,11 +104,11 @@ mod custom_fn { } } -mod allow_missing_methods { +mod allow_missing_attributes { use super::*; #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] - #[graphql(allow_missing_methods)] + #[graphql(allow_missing_attributes)] #[serde(untagged)] pub enum CustomScalarValue { Int(i32), diff --git a/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs b/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs deleted file mode 100644 index d11ef8811..000000000 --- a/integration_tests/juniper_tests/src/codegen/scalar_derive_derive_input.rs +++ /dev/null @@ -1,1115 +0,0 @@ -use std::fmt; - -use chrono::{DateTime, TimeZone, Utc}; -use juniper::{ - execute, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, GraphQLScalar, - InputValue, ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value, -}; - -use crate::{ - custom_scalar::MyScalarValue, - util::{schema, schema_with_scalar}, -}; - -mod trivial { - use super::*; - - #[derive(GraphQLScalar)] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - - fn parse_token(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(t) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod transparent { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql(transparent)] - struct Counter(i32); - - struct QueryRoot; - - #[graphql_object(scalar = DefaultScalarValue)] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod transparent_with_resolver { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql( - transparent, - to_output_with = Self::to_output, - )] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0 + 1) - } - } - - struct QueryRoot; - - #[graphql_object(scalar = DefaultScalarValue)] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 1}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod all_custom_resolvers { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql( - to_output_with = to_output, - from_input_with = from_input, - )] - #[graphql(parse_token_with = parse_token)] - struct Counter(i32); - - fn to_output(v: &Counter) -> Value { - Value::scalar(v.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Counter) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod explicit_name { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql(name = "Counter")] - struct CustomCounter(i32); - - impl CustomCounter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: CustomCounter) -> CustomCounter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod delegated_parse_token { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql(parse_token(i32))] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod multiple_delegated_parse_token { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql(parse_token(String, i32))] - enum StringOrInt { - String(String), - Int(i32), - } - - impl StringOrInt { - fn to_output(&self) -> Value { - match self { - Self::String(str) => Value::scalar(str.to_owned()), - Self::Int(i) => Value::scalar(*i), - } - } - - fn from_input(v: &InputValue) -> Result { - v.as_string_value() - .map(|s| Self::String(s.to_owned())) - .or_else(|| v.as_int_value().map(Self::Int)) - .ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn string_or_int(value: StringOrInt) -> StringOrInt { - value - } - } - - #[tokio::test] - async fn resolves_string() { - const DOC: &str = r#"{ stringOrInt(value: "test") }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": "test"}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_int() { - const DOC: &str = r#"{ stringOrInt(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"stringOrInt": 0}), vec![])), - ); - } -} - -mod where_attribute { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql( - to_output_with = to_output, - from_input_with = from_input, - parse_token(String), - where(Tz: From, Tz::Offset: fmt::Display), - specified_by_url = "https://tools.ietf.org/html/rfc3339", - )] - struct CustomDateTime(DateTime); - - fn to_output(v: &CustomDateTime) -> Value - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - Value::scalar(v.0.to_rfc3339()) - } - - fn from_input(v: &InputValue) -> Result, String> - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) - }) - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn date_time(value: CustomDateTime) -> CustomDateTime { - value - } - } - - #[tokio::test] - async fn resolves_custom_date_time() { - const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), - vec![], - )), - ); - } - - #[tokio::test] - async fn has_specified_by_url() { - const DOC: &str = r#"{ - __type(name: "CustomDateTime") { - specifiedByUrl - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), - vec![], - )), - ); - } -} - -mod with_self { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql(with = Self)] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - - fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> { - >::from_str(value) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod with_module { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql( - with = custom_date_time, - parse_token(String), - where(Tz: From, Tz::Offset: fmt::Display), - specified_by_url = "https://tools.ietf.org/html/rfc3339", - )] - struct CustomDateTime(DateTime); - - mod custom_date_time { - use super::*; - - pub(super) fn to_output(v: &CustomDateTime) -> Value - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - Value::scalar(v.0.to_rfc3339()) - } - - pub(super) fn from_input(v: &InputValue) -> Result, String> - where - S: ScalarValue, - Tz: From + TimeZone, - Tz::Offset: fmt::Display, - { - v.as_string_value() - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - .and_then(|s| { - DateTime::parse_from_rfc3339(s) - .map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc)))) - .map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e)) - }) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn date_time(value: CustomDateTime) -> CustomDateTime { - value - } - } - - #[tokio::test] - async fn resolves_custom_date_time() { - const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}), - vec![], - )), - ); - } - - #[tokio::test] - async fn has_specified_by_url() { - const DOC: &str = r#"{ - __type(name: "CustomDateTime") { - specifiedByUrl - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), - vec![], - )), - ); - } -} - -mod description_from_doc_comment { - use super::*; - - /// Description - #[derive(GraphQLScalar)] - #[graphql(parse_token(i32))] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object(scalar = DefaultScalarValue)] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"description": "Description"}}), - vec![], - )), - ); - } -} - -mod description_from_attribute { - use super::*; - - /// Doc comment - #[derive(GraphQLScalar)] - #[graphql(description = "Description from attribute", parse_token(i32))] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"description": "Description from attribute"}}), - vec![], - )), - ); - } -} - -mod custom_scalar { - use super::*; - - /// Description - #[derive(GraphQLScalar)] - #[graphql(scalar = MyScalarValue, parse_token(i32))] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object(scalar = MyScalarValue)] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"description": "Description"}}), - vec![], - )), - ); - } -} - -mod generic_scalar { - use super::*; - - /// Description - #[derive(GraphQLScalar)] - #[graphql(scalar = S: ScalarValue, parse_token(i32))] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `Counter`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } - - #[tokio::test] - async fn has_description() { - const DOC: &str = r#"{ - __type(name: "Counter") { - description - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"description": "Description"}}), - vec![], - )), - ); - } -} - -mod bounded_generic_scalar { - use super::*; - - #[derive(GraphQLScalar)] - #[graphql(scalar = S: ScalarValue + Clone, parse_token(i32))] - struct Counter(i32); - - impl Counter { - fn to_output(&self) -> Value { - Value::scalar(self.0) - } - - fn from_input(v: &InputValue) -> Result { - v.as_int_value() - .map(Self) - .ok_or_else(|| format!("Expected `String`, found: {}", v)) - } - } - - struct QueryRoot; - - #[graphql_object(scalar = MyScalarValue)] - impl QueryRoot { - fn counter(value: Counter) -> Counter { - value - } - } - - #[tokio::test] - async fn is_graphql_scalar() { - const DOC: &str = r#"{ - __type(name: "Counter") { - kind - } - }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])), - ); - } - - #[tokio::test] - async fn resolves_counter() { - const DOC: &str = r#"{ counter(value: 0) }"#; - - let schema = schema_with_scalar::(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"counter": 0}), vec![])), - ); - } -} diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 4e676e08a..75802a164 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -47,12 +47,12 @@ pub fn expand(input: TokenStream) -> syn::Result { } let missing_methods = [ - (Method::AsInt, "`#[graphql(as_int)]`"), - (Method::AsFloat, "`#[graphql(as_float)]`"), - (Method::AsStr, "`#[graphql(as_str)]`"), - (Method::AsString, "`#[graphql(as_string)]`"), - (Method::IntoString, "`#[graphql(into_string)]`"), - (Method::AsBoolean, "`#[graphql(as_boolean)]`"), + (Method::AsInt, "as_int"), + (Method::AsFloat, "as_float"), + (Method::AsStr, "as_str"), + (Method::AsString, "as_string"), + (Method::IntoString, "into_string"), + (Method::AsBoolean, "as_boolean"), ] .iter() .filter_map(|(method, err)| (!methods.contains_key(method)).then(|| err)) @@ -62,13 +62,13 @@ pub fn expand(input: TokenStream) -> syn::Result { .unwrap_or_else(|| method.to_owned()), ) }) - .filter(|_| !attr.allow_missing_methods); + .filter(|_| !attr.allow_missing_attrs); if let Some(missing_methods) = missing_methods { return Err(ERR.custom_error( span, format!( - "missing {} attributes. In case you are sure that it's ok, \ - use `#[graphql(allow_missing_methods)]` to suppress this error.", + "missing `#[graphql({})]` attributes. In case you are sure that it is ok, \ + use `#[graphql(allow_missing_attributes)]` to suppress this error.", missing_methods, ), )); @@ -88,7 +88,7 @@ pub fn expand(input: TokenStream) -> syn::Result { #[derive(Default)] struct Attr { /// Allows missing [`Method`]s. - allow_missing_methods: bool, + allow_missing_attrs: bool, } impl Parse for Attr { @@ -97,8 +97,8 @@ impl Parse for Attr { while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { - "allow_missing_methods" => { - out.allow_missing_methods = true; + "allow_missing_attributes" => { + out.allow_missing_attrs = true; } name => { return Err(err::unknown_arg(&ident, name)); @@ -114,7 +114,7 @@ impl Attr { /// Tries to merge two [`Attr`]s into a single one, reporting about /// duplicates, if any. fn try_merge(mut self, another: Self) -> syn::Result { - self.allow_missing_methods |= another.allow_missing_methods; + self.allow_missing_attrs |= another.allow_missing_attrs; Ok(self) } From 641860b2e868c9928d157cf6c3a4b74f28f587d4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 1 Mar 2022 12:33:46 +0300 Subject: [PATCH 102/122] Corrections --- docs/book/content/types/scalars.md | 1 + juniper/Cargo.toml | 3 ++- juniper/src/integrations/chrono_tz.rs | 7 +++++-- juniper/src/lib.rs | 2 ++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/book/content/types/scalars.md b/docs/book/content/types/scalars.md index 56eb1a4e0..a17e07a2c 100644 --- a/docs/book/content/types/scalars.md +++ b/docs/book/content/types/scalars.md @@ -36,6 +36,7 @@ crates. They are enabled via features that are on by default. * uuid::Uuid * chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime} +* chrono_tz::Tz; * time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset} * url::Url * bson::oid::ObjectId diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index 415b4f7fa..31d976424 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -21,14 +21,15 @@ travis-ci = { repository = "graphql-rust/juniper" } [features] default = [ "bson", + "chrono", "schema-language", "url", "uuid", ] +chrono-clock = ["chrono", "chrono/clock"] expose-test-schema = ["anyhow", "serde_json"] graphql-parser-integration = ["graphql-parser"] schema-language = ["graphql-parser-integration"] -chrono-clock = ["chrono", "chrono/clock"] [dependencies] juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" } diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index b00898c68..71d04449e 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -5,10 +5,13 @@ use crate::{graphql_scalar, InputValue, ScalarValue, Value}; -/// See [List of tz database time zones][1] `TZ database name` column for +/// Timezone based on [`IANA` database][1]. +/// +/// See [List of tz database time zones][2] `TZ database name` column for /// available names. /// -/// [1]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +/// [1]: https://www.iana.org/time-zones +/// [2]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones #[graphql_scalar(with = tz, parse_token(String))] pub type TimeZone = chrono_tz::Tz; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 27fdb150c..69a861f0a 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -59,6 +59,7 @@ your Schemas automatically. * [uuid][uuid] * [url][url] * [chrono][chrono] +* [chrono-tz][chrono-tz] * [time][time] * [bson][bson] @@ -88,6 +89,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [uuid]: https://crates.io/crates/uuid [url]: https://crates.io/crates/url [chrono]: https://crates.io/crates/chrono +[chrono-tz]: https://crates.io/crates/chrono-tz [time]: https://crates.io/crates/time [bson]: https://crates.io/crates/bson From b4a73702911dd8ab11b556868e6015962356b166 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 1 Mar 2022 13:46:34 +0300 Subject: [PATCH 103/122] WIP --- .../fail/interface/trait/implementers_duplicate_ugly.stderr | 6 +++--- .../juniper_tests/src/codegen/interface_attr_struct.rs | 2 +- .../juniper_tests/src/codegen/interface_attr_trait.rs | 2 +- juniper_codegen/src/graphql_interface/attr.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 97399003d..822487363 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,10 +1,10 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/trait/implementers_duplicate_ugly.rs:11:34 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:28 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` + | ^^^^ -------- first implementation here | | - | first implementation here + | conflicting implementation for `CharacterValueEnum` error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs index 9b4d3a7cd..f44e4eb47 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs @@ -1,4 +1,4 @@ -//! Tests for `#[graphql_interface]` macro. +//! Tests for `#[graphql_interface]` macro placed on a struct. use std::marker::PhantomData; diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs index e5bdc4fce..5d792095c 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs @@ -1,4 +1,4 @@ -//! Tests for `#[graphql_interface]` macro. +//! Tests for `#[graphql_interface]` macro placed on a trait. use juniper::{ execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 9c26fe25c..c4f9e0f34 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -242,7 +242,7 @@ fn parse_trait_method( }) } -/// Expands `#[graphql_interface]` macro placed on trait definition. +/// Expands `#[graphql_interface]` macro placed on struct. fn expand_on_derive_input( attrs: Vec, mut ast: syn::DeriveInput, From d4431903f045b73303e968c46bd55db1487b1216 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 1 Mar 2022 14:57:09 +0300 Subject: [PATCH 104/122] Corrections --- .../attr_implementers_duplicate_ugly.stderr | 11 +- .../derive_implementers_duplicate_ugly.stderr | 13 +- .../trait/implementers_duplicate_ugly.stderr | 11 +- juniper/src/macros/reflect.rs | 2 +- juniper/src/types/marker.rs | 3 +- juniper_codegen/src/common/parse/mod.rs | 77 +++++---- juniper_codegen/src/graphql_interface/attr.rs | 68 ++------ .../src/graphql_interface/derive.rs | 4 +- juniper_codegen/src/graphql_interface/mod.rs | 157 ++++++++++-------- 9 files changed, 176 insertions(+), 170 deletions(-) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr index 3f8332882..da24362cf 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr @@ -1,10 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:34 + --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` - | | - | first implementation here + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/struct/attr_implementers_duplicate_ugly.rs:11:1 diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr index 26f6669be..fde533c1b 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr @@ -1,10 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:12:24 + --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 + | +11 | #[derive(GraphQLInterface)] + | ^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` | -12 | #[graphql(for = [ObjA, ObjAlias])] - | ---- ^^^^^^^^ conflicting implementation for `CharacterValueEnum` - | | - | first implementation here + = note: this error originates in the derive macro `GraphQLInterface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/struct/derive_implementers_duplicate_ugly.rs:11:10 diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr index 822487363..74b695d8f 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr @@ -1,10 +1,13 @@ error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` - --> fail/interface/trait/implementers_duplicate_ugly.rs:11:28 + --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 | 11 | #[graphql_interface(for = [ObjA, ObjAlias])] - | ^^^^ -------- first implementation here - | | - | conflicting implementation for `CharacterValueEnum` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `CharacterValueEnum` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` --> fail/interface/trait/implementers_duplicate_ugly.rs:11:1 diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 64b04c2c2..4cba097f8 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -194,7 +194,7 @@ pub type WrappedValue = u128; /// /// const TYPE_STR: Type = >> as BaseType>::NAME; /// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::VALUE; -/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// assert_eq!(format_type!(TYPE_STR, WRAP_VAL_STR), "[String]"); /// ``` /// /// [`VALUE`]: Self::VALUE diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index b04a41906..2dd22789e 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -186,8 +186,7 @@ where /// types. Each type which can be used as an output type should /// implement this trait. The specification defines enum, scalar, /// object, union, and interface as output types. -// TODO: Re-enable GraphQLType requirement in #682 -pub trait IsOutputType /*: GraphQLType*/ { +pub trait IsOutputType: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 5781a6233..e6f469aa7 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -256,6 +256,10 @@ pub(crate) trait GenericsExt { /// Replaces generic parameters in the given [`syn::Type`] with default /// ones, provided by these [`syn::Generics`]. fn replace_type_with_defaults(&self, ty: &mut syn::Type); + + /// Replaces generic parameters in the given [`syn::TypePath`] with default + /// ones, provided by these [`syn::Generics`]. + fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath); } impl GenericsExt for syn::Generics { @@ -307,40 +311,51 @@ impl GenericsExt for syn::Generics { } fn replace_type_with_defaults(&self, ty: &mut syn::Type) { - struct Replace<'a>(&'a syn::Generics); + ReplaceWithDefaults(self).visit_type_mut(ty) + } - impl<'a> VisitMut for Replace<'a> { - fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { - match arg { - syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote! { 'static }; - } - syn::GenericArgument::Type(ty) => { - let is_generic = self - .0 - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .any(|par| { - let par = quote! { #par }.to_string(); - let ty = quote! { #ty }.to_string(); - par == ty - }); - - if is_generic { - // Replace with `DefaultScalarValue` instead of `()` - // because generic parameter may be scalar. - *ty = parse_quote!(::juniper::DefaultScalarValue); - } - } - _ => {} + fn replace_type_path_with_defaults(&self, ty: &mut syn::TypePath) { + ReplaceWithDefaults(self).visit_type_path_mut(ty) + } +} + +/// Replaces [`Generics`] with default values: +/// - `'static` for [`Lifetime`]s +/// - `::juniper::DefaultScalarValue` for [`Type`]s. +/// +/// [`Generics`]: syn::Generics +/// [`Lifetime`]: syn::Lifetime +/// [`Type`]: syn::Type +struct ReplaceWithDefaults<'a>(&'a syn::Generics); + +impl<'a> VisitMut for ReplaceWithDefaults<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote! { 'static }; + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + // Replace with `DefaultScalarValue` instead of `()` + // because generic parameter may be scalar. + *ty = parse_quote!(::juniper::DefaultScalarValue); } } + _ => {} } - - Replace(self).visit_type_mut(ty) } } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c4f9e0f34..4510c128c 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -79,9 +79,7 @@ fn expand_on_trait( .iter_mut() .filter_map(|item| { if let syn::TraitItem::Method(m) = item { - if let Some(f) = parse_trait_method(m, &renaming) { - return Some(f); - } + return parse_trait_method(m, &renaming); } None }) @@ -133,8 +131,8 @@ fn expand_on_trait( context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), @@ -190,33 +188,15 @@ fn parse_trait_method( return None; } - let arguments = { - if method.sig.inputs.is_empty() { - return err_no_method_receiver(&method.sig.inputs); - } - let mut args_iter = method.sig.inputs.iter_mut(); - match args_iter.next().unwrap() { - syn::FnArg::Receiver(rcv) => { - if rcv.reference.is_none() || rcv.mutability.is_some() { - return err_invalid_method_receiver(rcv); - } - } - syn::FnArg::Typed(arg) => { - if let syn::Pat::Ident(a) = &*arg.pat { - if a.ident.to_string().as_str() != "self" { - return err_invalid_method_receiver(arg); - } - } - return err_no_method_receiver(arg); - } - }; - args_iter - .filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), - }) - .collect() - }; + let arguments = method + .sig + .inputs + .iter_mut() + .filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), + }) + .collect(); let mut ty = match &method.sig.output { syn::ReturnType::Default => parse_quote! { () }, @@ -339,8 +319,8 @@ fn expand_on_derive_input( context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), @@ -421,26 +401,6 @@ fn err_default_impl_block(span: &S) -> Option { None } -/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given -/// `span`. -fn err_invalid_method_receiver(span: &S) -> Option { - ERR.emit_custom( - span.span(), - "trait method receiver can only be a shared reference `&self`", - ); - None -} - -/// Emits "no trait method receiver" [`syn::Error`] pointing to the given -/// `span`. -fn err_no_method_receiver(span: &S) -> Option { - ERR.emit_custom( - span.span(), - "trait method should have a shared reference receiver `&self`", - ); - None -} - /// Emits "expected named struct field" [`syn::Error`] pointing to the given /// `span`. fn err_unnamed_field(span: &S) -> Option { diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 18f83f851..263368991 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -105,8 +105,8 @@ pub fn expand(input: TokenStream) -> syn::Result { context, scalar, fields, - implementers: attr - .implementers + implemented_for: attr + .implemented_for .iter() .map(|c| c.inner().clone()) .collect(), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 3fda25fbc..20ceb133b 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -67,7 +67,7 @@ struct Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - implementers: HashSet>, + implemented_for: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -161,7 +161,7 @@ impl Parse for Attr { >()? { let impler_span = impler.span(); out - .implementers + .implemented_for .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) .none_or_else(|_| err::dup_arg(impler_span))?; } @@ -212,7 +212,7 @@ impl Attr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), - implementers: try_merge_hashset!(implementers: self, another => span_joined), + implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), @@ -299,7 +299,7 @@ struct Definition { /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + implemented_for: Vec, } impl ToTokens for Definition { @@ -327,13 +327,13 @@ impl Definition { let enum_ident = &self.enum_ident; let alias_ident = &self.enum_alias_ident; - let variant_gens_pars = (0..self.implementers.len()).map::(|id| { + let variant_gens_pars = (0..self.implemented_for.len()).map::(|id| { let par = format_ident!("__I{}", id); parse_quote! { #par } }); let variants_idents = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); @@ -371,7 +371,7 @@ impl Definition { } rest => quote! { #rest }, }) - .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(self.implemented_for.iter().map(ToTokens::to_token_stream)) .chain(interface_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; @@ -401,23 +401,23 @@ impl Definition { quote! { __Phantom(#(#phantom_params),*) } }); - let from_impls = - self.implementers - .iter() - .zip(variants_idents.clone()) - .map(|(ty, ident)| { - quote_spanned! { ty.span() => - #[automatically_derived] - impl#interface_impl_gens ::std::convert::From<#ty> - for #alias_ident#interface_ty_gens - #interface_where_clause - { - fn from(v: #ty) -> Self { - Self::#ident(v) - } + let from_impls = self + .implemented_for + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote! { + #[automatically_derived] + impl#interface_impl_gens ::std::convert::From<#ty> + for #alias_ident#interface_ty_gens + #interface_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) } } - }); + } + }); quote! { #[automatically_derived] @@ -449,9 +449,9 @@ impl Definition { let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); - let impler_tys = &self.implementers; - let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } + let implemented_for = &self.implemented_for; + let all_impled_for_unique = (implemented_for.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } }); quote! { @@ -461,8 +461,8 @@ impl Definition { #where_clause { fn mark() { - #all_implers_unique - #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + #all_impled_for_unique + #( <#implemented_for as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } } @@ -489,13 +489,16 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let is_output = self.implementers.iter().map(|implementer| { + let is_output = self.implemented_for.iter().map(|implementer| { quote_spanned! { implementer.span() => <#implementer as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); - let impler_tys = self.implementers.iter(); + let const_impl_for = self.implemented_for.iter().cloned().map(|mut ty| { + generics.replace_type_path_with_defaults(&mut ty); + ty + }); quote! { #[automatically_derived] @@ -507,7 +510,9 @@ impl Definition { #( #fields_marks )* #( #is_output )* ::juniper::assert_interfaces_impls!( - #const_scalar, #ty#ty_const_generics, #(#impler_tys),* + #const_scalar, + #ty#ty_const_generics, + #(#const_impl_for),* ); } } @@ -535,8 +540,8 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.implementers.clone(); - impler_tys.sort_unstable_by(|a, b| { + let mut implemented_for = self.implemented_for.clone(); + implemented_for.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) }); @@ -560,7 +565,7 @@ impl Definition { where #scalar: 'r, { // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_tys>(info); )* + #( let _ = registry.get_type::<#implemented_for>(info); )* let fields = [ #( #fields_meta, )* @@ -729,7 +734,7 @@ impl Definition { #[must_use] fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; - let implementers = &self.implementers; + let implemented_for = &self.implemented_for; let scalar = &self.scalar; let name = &self.name; let fields = self.fields.iter().map(|f| &f.name); @@ -754,7 +759,7 @@ impl Definition { { const NAMES: ::juniper::macros::reflect::Types = &[ >::NAME, - #(<#implementers as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* + #(<#implemented_for as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* ]; } @@ -847,17 +852,25 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) - .collect::>(); - let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); + let const_implemented_for = self + .implemented_for + .iter() + .cloned() + .map(|mut impl_for| { + generics.replace_type_path_with_defaults(&mut impl_for); + impl_for + }) + .collect::>(); + let implemented_for_idents = self + .implemented_for + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + self.fields .iter() .map(|field| { @@ -867,10 +880,11 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = - (self.implementers.is_empty() || !self.generics.params.is_empty()).then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = (self.implemented_for.is_empty() + || !self.generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); quote_spanned! { field.ident.span() => #[allow(non_snake_case)] @@ -886,10 +900,10 @@ impl Definition { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { match self { - #(#ty::#impl_idents(v) => { + #(#ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, - #impl_tys, + #const_implemented_for, #const_scalar, #field_name, ); @@ -918,17 +932,25 @@ impl Definition { let scalar = &self.scalar; let const_scalar = self.scalar.default_ty(); - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) - .collect::>(); - let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); + let const_implemented_for = self + .implemented_for + .iter() + .cloned() + .map(|mut impl_for| { + generics.replace_type_path_with_defaults(&mut impl_for); + impl_for + }) + .collect::>(); + let implemented_for_idents = self + .implemented_for + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + self.fields .iter() .map(|field| { @@ -938,10 +960,11 @@ impl Definition { let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = - (self.implementers.is_empty() || !self.generics.params.is_empty()).then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = (self.implemented_for.is_empty() + || !self.generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); quote_spanned! { field.ident.span() => #[allow(non_snake_case)] @@ -957,10 +980,10 @@ impl Definition { executor: &'b ::juniper::Executor, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match self { - #(#ty::#impl_idents(v) => { + #(#ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, - #impl_tys, + #const_implemented_for, #const_scalar, #field_name, ); @@ -990,7 +1013,7 @@ impl Definition { let scalar = &self.scalar; let match_arms = self - .implementers + .implemented_for .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) .map(|(ident, ty)| { @@ -1002,7 +1025,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1025,7 +1048,7 @@ impl Definition { fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - let match_arms = self.implementers.iter().filter_map(|ty| { + let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(v) => { @@ -1036,7 +1059,7 @@ impl Definition { }) }); let non_exhaustive_match_arm = - (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); @@ -1058,7 +1081,7 @@ impl Definition { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.implementers.iter().filter_map(|ty| { + let match_arms = self.implemented_for.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(res) => #resolving_code, @@ -1067,7 +1090,7 @@ impl Definition { }); let non_exhaustive_match_arm = - (!self.generics.params.is_empty() || self.implementers.is_empty()).then(|| { + (!self.generics.params.is_empty() || self.implemented_for.is_empty()).then(|| { quote! { _ => unreachable!(), } }); From 42337997074845870bf05976ffc8789c17f47735 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 1 Mar 2022 15:18:46 +0300 Subject: [PATCH 105/122] Corrections --- .../juniper_tests/src/codegen/interface_attr_struct.rs | 7 +------ .../juniper_tests/src/codegen/interface_attr_trait.rs | 7 +------ .../juniper_tests/src/codegen/interface_derive.rs | 7 +------ juniper_codegen/src/graphql_interface/attr.rs | 4 +--- juniper_codegen/src/graphql_interface/mod.rs | 4 +--- 5 files changed, 5 insertions(+), 24 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs index d3d104ed5..5b057f91d 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs @@ -2553,12 +2553,7 @@ mod simple_inheritance { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"node": { - "id": "1", - }}), - vec![], - )), + Ok((graphql_value!({"node": {"id": "1"}}), vec![])), ); } diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs index 704f409f4..37ac34f17 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs @@ -3371,12 +3371,7 @@ mod simple_inheritance { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"node": { - "id": "1", - }}), - vec![], - )), + Ok((graphql_value!({"node": {"id": "1"}}), vec![])), ); } diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs index fc04bc5d3..fc43df748 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -2574,12 +2574,7 @@ mod simple_inheritance { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"node": { - "id": "1", - }}), - vec![], - )), + Ok((graphql_value!({"node": {"id": "1"}}), vec![])), ); } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 5cb7b68a7..74f17f58d 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -79,9 +79,7 @@ fn expand_on_trait( .iter_mut() .filter_map(|item| { if let syn::TraitItem::Method(m) = item { - if let Some(f) = parse_trait_method(m, &renaming) { - return Some(f); - } + return parse_trait_method(m, &renaming); } None }) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 235ee7cd9..bba71a7a0 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -316,11 +316,9 @@ struct Definition { /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields fields: Vec, - /// Specified [GraphQL objects][2] or [interfaces][1] this - /// [GraphQL interface][1] is implemented for type. + /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects implemented_for: Vec, /// Specified [GraphQL interfaces][1] this [interface][1] type implements. From f25488e16bd6ec7a9001de274f7e2c0aac6850ce Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 10 Mar 2022 15:07:15 +0300 Subject: [PATCH 106/122] Corrections --- ..._wrong_item_enum.rs => attr_wrong_item.rs} | 0 ...tem_enum.stderr => attr_wrong_item.stderr} | 2 +- .../interface/attr_wrong_item_impl_block.rs | 11 - .../attr_wrong_item_impl_block.stderr | 7 - ...rong_item_enum.rs => derive_wrong_item.rs} | 0 ...m_enum.stderr => derive_wrong_item.stderr} | 2 +- .../trait/argument_wrong_default_array.stderr | 4 +- .../argument_wrong_default_array.stderr | 4 +- .../fail/scalar_value/derive_on_struct.rs | 4 - .../fail/scalar_value/derive_on_struct.stderr | 5 - .../argument_wrong_default_array.stderr | 4 +- .../src/codegen/derive_scalar_value.rs | 136 ----- .../juniper_tests/src/codegen/mod.rs | 1 - juniper/src/lib.rs | 3 +- juniper_codegen/src/derive_scalar_value.rs | 528 ------------------ juniper_codegen/src/lib.rs | 6 +- 16 files changed, 12 insertions(+), 705 deletions(-) rename integration_tests/codegen_fail/fail/interface/{attr_wrong_item_enum.rs => attr_wrong_item.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{attr_wrong_item_enum.stderr => attr_wrong_item.stderr} (76%) delete mode 100644 integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr rename integration_tests/codegen_fail/fail/interface/{derive_wrong_item_enum.rs => derive_wrong_item.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{derive_wrong_item_enum.stderr => derive_wrong_item.stderr} (73%) delete mode 100644 integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs delete mode 100644 integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr delete mode 100644 integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs delete mode 100644 juniper_codegen/src/derive_scalar_value.rs diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.rs b/integration_tests/codegen_fail/fail/interface/attr_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.rs rename to integration_tests/codegen_fail/fail/interface/attr_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr b/integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr similarity index 76% rename from integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr rename to integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr index f11e4b406..410f72bd2 100644 --- a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_enum.stderr +++ b/integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr @@ -1,5 +1,5 @@ error: GraphQL interface #[graphql_interface] attribute is applicable to trait and struct definitions only - --> fail/interface/attr_wrong_item_enum.rs:9:1 + --> fail/interface/attr_wrong_item.rs:9:1 | 9 | enum Character {} | ^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.rs b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.rs deleted file mode 100644 index 61e142410..000000000 --- a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.rs +++ /dev/null @@ -1,11 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -pub struct ObjA { - test: String, -} - -#[graphql_interface(for = ObjA)] -impl ObjA {} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr deleted file mode 100644 index b221c278b..000000000 --- a/integration_tests/codegen_fail/fail/interface/attr_wrong_item_impl_block.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: #[graphql_interface] attribute is applicable to trait and struct definitions only - --> fail/interface/attr_wrong_item_impl_block.rs:8:1 - | -8 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.rs b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.rs rename to integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr similarity index 73% rename from integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr rename to integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr index c3889563b..9b9be446f 100644 --- a/integration_tests/codegen_fail/fail/interface/derive_wrong_item_enum.stderr +++ b/integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr @@ -1,5 +1,5 @@ error: GraphQL interface can only be derived on structs - --> fail/interface/derive_wrong_item_enum.rs:9:1 + --> fail/interface/derive_wrong_item.rs:9:1 | 9 | / #[graphql(for = ObjA)] 10 | | enum Character {} diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr index 02596aaef..d609ce029 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr @@ -6,9 +6,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | = help: the following implementations were found: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - <[T; LANES] as From>> - <[bool; LANES] as From>> + and 11 others = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr index 6992876c5..1e65f81c4 100644 --- a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr @@ -6,9 +6,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | = help: the following implementations were found: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - <[T; LANES] as From>> - <[bool; LANES] as From>> + and 11 others = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs b/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs deleted file mode 100644 index 68b63b706..000000000 --- a/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(juniper::GraphQLScalarValue)] -struct ScalarValue; - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr b/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr deleted file mode 100644 index 8d2e85510..000000000 --- a/integration_tests/codegen_fail/fail/scalar_value/derive_on_struct.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: GraphQL built-in scalars can only be derived for enums - --> fail/scalar_value/derive_on_struct.rs:2:1 - | -2 | struct ScalarValue; - | ^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr index d6416583b..eafad0546 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr @@ -6,9 +6,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied | = help: the following implementations were found: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> + <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - <[T; LANES] as From>> - <[bool; LANES] as From>> + and 11 others = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs b/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs deleted file mode 100644 index 00f3d50c1..000000000 --- a/integration_tests/juniper_tests/src/codegen/derive_scalar_value.rs +++ /dev/null @@ -1,136 +0,0 @@ -use juniper::{DefaultScalarValue, GraphQLScalarValue, ScalarValue as _}; -use serde::{Deserialize, Serialize}; - -mod trivial { - use super::*; - - #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] - #[serde(untagged)] - pub enum CustomScalarValue { - #[graphql(as_int, as_float)] - Int(i32), - #[graphql(as_float)] - Float(f64), - #[graphql(as_str, as_string, into_string)] - String(String), - #[graphql(as_boolean)] - Boolean(bool), - } - - #[test] - fn into_another() { - assert!(CustomScalarValue::from(5) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(0.5_f64) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from("str".to_owned()) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(true) - .into_another::() - .is_type::()); - } -} - -mod named_fields { - use super::*; - - #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] - #[serde(untagged)] - pub enum CustomScalarValue { - #[graphql(as_int, as_float)] - Int { int: i32 }, - #[graphql(as_float)] - Float(f64), - #[graphql(as_str, as_string, into_string)] - String(String), - #[graphql(as_boolean)] - Boolean { v: bool }, - } - - #[test] - fn into_another() { - assert!(CustomScalarValue::from(5) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(0.5_f64) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from("str".to_owned()) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(true) - .into_another::() - .is_type::()); - } -} - -mod custom_fn { - use super::*; - - #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] - #[serde(untagged)] - pub enum CustomScalarValue { - #[graphql(as_int, as_float)] - Int(i32), - #[graphql(as_float)] - Float(f64), - #[graphql( - as_str, - as_string = str::to_owned, - into_string = std::convert::identity, - )] - String(String), - #[graphql(as_boolean)] - Boolean(bool), - } - - #[test] - fn into_another() { - assert!(CustomScalarValue::from(5) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(0.5_f64) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from("str".to_owned()) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(true) - .into_another::() - .is_type::()); - } -} - -mod allow_missing_attributes { - use super::*; - - #[derive(Clone, Debug, Deserialize, GraphQLScalarValue, PartialEq, Serialize)] - #[graphql(allow_missing_attributes)] - #[serde(untagged)] - pub enum CustomScalarValue { - Int(i32), - #[graphql(as_float)] - Float(f64), - #[graphql(as_str, as_string, into_string)] - String(String), - #[graphql(as_boolean)] - Boolean(bool), - } - - #[test] - fn into_another() { - assert!(CustomScalarValue::Int(5).as_int().is_none()); - assert!(CustomScalarValue::from(0.5_f64) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from("str".to_owned()) - .into_another::() - .is_type::()); - assert!(CustomScalarValue::from(true) - .into_another::() - .is_type::()); - } -} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 2fef869d2..3a709c66a 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -1,7 +1,6 @@ mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; -mod derive_scalar_value; mod interface_attr_struct; mod interface_attr_trait; mod interface_derive; diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 13dacaec9..817206181 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -117,8 +117,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // functionality automatically. pub use juniper_codegen::{ graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLInterface, GraphQLObject, GraphQLScalar, - GraphQLScalarValue, GraphQLUnion, + GraphQLEnum, GraphQLInputObject, GraphQLInterface, GraphQLObject, GraphQLScalar, GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs deleted file mode 100644 index 75802a164..000000000 --- a/juniper_codegen/src/derive_scalar_value.rs +++ /dev/null @@ -1,528 +0,0 @@ -//! Code generation for `#[derive(GraphQLScalarValue)]` macro. - -use std::{collections::HashMap, convert::TryFrom}; - -use proc_macro2::{Literal, TokenStream}; -use quote::{quote, ToTokens, TokenStreamExt as _}; -use syn::{ - parse::{Parse, ParseStream}, - parse_quote, - spanned::Spanned as _, - token, - visit::Visit, -}; - -use crate::{ - common::parse::{attr::err, ParseBufferExt as _}, - util::{filter_attrs, span_container::SpanContainer}, - GraphQLScope, -}; - -/// [`GraphQLScope`] of errors for `#[derive(GraphQLScalarValue)]` macro. -const ERR: GraphQLScope = GraphQLScope::ScalarValueDerive; - -/// Expands `#[derive(GraphQLScalarValue)]` macro into generated code. -pub fn expand(input: TokenStream) -> syn::Result { - let ast = syn::parse2::(input)?; - let span = ast.span(); - - let data_enum = match ast.data { - syn::Data::Enum(e) => e, - _ => return Err(ERR.custom_error(ast.span(), "can only be derived for enums")), - }; - - let attr = Attr::from_attrs("graphql", &ast.attrs)?; - - let mut methods = HashMap::>::new(); - for var in data_enum.variants.clone() { - let (ident, field) = (var.ident, Field::try_from(var.fields)?); - for attr in VariantAttr::from_attrs("graphql", &var.attrs)?.0 { - let (method, expr) = attr.into_inner(); - methods.entry(method).or_default().push(Variant { - ident: ident.clone(), - field: field.clone(), - expr, - }); - } - } - - let missing_methods = [ - (Method::AsInt, "as_int"), - (Method::AsFloat, "as_float"), - (Method::AsStr, "as_str"), - (Method::AsString, "as_string"), - (Method::IntoString, "into_string"), - (Method::AsBoolean, "as_boolean"), - ] - .iter() - .filter_map(|(method, err)| (!methods.contains_key(method)).then(|| err)) - .fold(None, |acc, &method| { - Some( - acc.map(|acc| format!("{}, {}", acc, method)) - .unwrap_or_else(|| method.to_owned()), - ) - }) - .filter(|_| !attr.allow_missing_attrs); - if let Some(missing_methods) = missing_methods { - return Err(ERR.custom_error( - span, - format!( - "missing `#[graphql({})]` attributes. In case you are sure that it is ok, \ - use `#[graphql(allow_missing_attributes)]` to suppress this error.", - missing_methods, - ), - )); - } - - Ok(Definition { - ident: ast.ident, - generics: ast.generics, - variants: data_enum.variants.into_iter().collect(), - methods, - } - .into_token_stream()) -} - -/// Available arguments behind `#[graphql]` attribute when generating code for -/// enum container. -#[derive(Default)] -struct Attr { - /// Allows missing [`Method`]s. - allow_missing_attrs: bool, -} - -impl Parse for Attr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Attr::default(); - while !input.is_empty() { - let ident = input.parse::()?; - match ident.to_string().as_str() { - "allow_missing_attributes" => { - out.allow_missing_attrs = true; - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - }; - input.try_parse::()?; - } - Ok(out) - } -} - -impl Attr { - /// Tries to merge two [`Attr`]s into a single one, reporting about - /// duplicates, if any. - fn try_merge(mut self, another: Self) -> syn::Result { - self.allow_missing_attrs |= another.allow_missing_attrs; - Ok(self) - } - - /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a enum variant. - 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?)) - } -} - -/// Possible attribute names of the `#[derive(GraphQLScalarValue)]`. -#[derive(Eq, Hash, PartialEq)] -enum Method { - /// `#[graphql(as_int)]`. - AsInt, - - /// `#[graphql(as_float)]`. - AsFloat, - - /// `#[graphql(as_str)]`. - AsStr, - - /// `#[graphql(as_string)]`. - AsString, - - /// `#[graphql(into_string)]`. - IntoString, - - /// `#[graphql(as_boolean)]`. - AsBoolean, -} - -/// Available arguments behind `#[graphql]` attribute when generating code for -/// enum variant. -#[derive(Default)] -struct VariantAttr(Vec)>>); - -impl Parse for VariantAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Vec::new(); - while !input.is_empty() { - let ident = input.parse::()?; - let method = match ident.to_string().as_str() { - "as_int" => Method::AsInt, - "as_float" => Method::AsFloat, - "as_str" => Method::AsStr, - "as_string" => Method::AsString, - "into_string" => Method::IntoString, - "as_bool" | "as_boolean" => Method::AsBoolean, - name => { - return Err(err::unknown_arg(&ident, name)); - } - }; - let expr = input - .parse::() - .ok() - .map(|_| input.parse::()) - .transpose()?; - out.push(SpanContainer::new( - ident.span(), - expr.as_ref().map(|e| e.span()), - (method, expr), - )); - input.try_parse::()?; - } - Ok(VariantAttr(out)) - } -} - -impl VariantAttr { - /// Tries to merge two [`VariantAttr`]s into a single one, reporting about - /// duplicates, if any. - fn try_merge(mut self, mut another: Self) -> syn::Result { - let dup = another.0.iter().find(|m| self.0.contains(m)); - if let Some(dup) = dup { - Err(err::dup_arg(dup.span_ident())) - } else { - self.0.append(&mut another.0); - Ok(self) - } - } - - /// Parses [`VariantAttr`] from the given multiple `name`d - /// [`syn::Attribute`]s placed on a enum variant. - 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 the [`ScalarValue`]. -/// -/// [`ScalarValue`]: juniper::ScalarValue -struct Definition { - /// [`syn::Ident`] of the enum representing [`ScalarValue`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - ident: syn::Ident, - - /// [`syn::Generics`] of the enum representing [`ScalarValue`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - generics: syn::Generics, - - /// [`syn::Variant`]s of the enum representing [`ScalarValue`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - variants: Vec, - - /// [`Variant`]s marked with [`Method`] attribute. - methods: HashMap>, -} - -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - self.impl_scalar_value_tokens().to_tokens(into); - self.impl_from_tokens().to_tokens(into); - self.impl_display_tokens().to_tokens(into); - } -} - -impl Definition { - /// Returns generated code implementing [`ScalarValue`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - fn impl_scalar_value_tokens(&self) -> TokenStream { - let ident = &self.ident; - let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); - - let methods = [ - ( - Method::AsInt, - quote! { fn as_int(&self) -> Option }, - quote! { i32::from(*v) }, - ), - ( - Method::AsFloat, - quote! { fn as_float(&self) -> Option }, - quote! { f64::from(*v) }, - ), - ( - Method::AsStr, - quote! { fn as_str(&self) -> Option<&str> }, - quote! { std::convert::AsRef::as_ref(v) }, - ), - ( - Method::AsString, - quote! { fn as_string(&self) -> Option }, - quote! { std::string::ToString::to_string(v) }, - ), - ( - Method::IntoString, - quote! { fn into_string(self) -> Option }, - quote! { std::string::String::from(v) }, - ), - ( - Method::AsBoolean, - quote! { fn as_boolean(&self) -> Option }, - quote! { bool::from(*v) }, - ), - ]; - let methods = methods.iter().map(|(m, sig, def)| { - let arms = self.methods.get(m).into_iter().flatten().map(|v| { - let arm = v.match_arm(); - let call = v.expr.as_ref().map_or(def.clone(), |f| quote! { #f(v) }); - quote! { #arm => Some(#call), } - }); - quote! { - #sig { - match self { - #(#arms)* - _ => None, - } - } - } - }); - - quote! { - #[automatically_derived] - impl#impl_gens ::juniper::ScalarValue for #ident#ty_gens - #where_clause - { - #(#methods)* - } - } - } - - /// Returns generated code implementing: - /// - [`From`] each variant into enum itself. - /// - [`From`] enum into [`Option`] of each variant. - /// - [`From`] enum reference into [`Option`] of each variant reference. - fn impl_from_tokens(&self) -> TokenStream { - let ty_ident = &self.ident; - let (impl_gen, ty_gen, where_clause) = self.generics.split_for_impl(); - - // We don't impose additional bounds on generic parameters, because - // `ScalarValue` itself has `'static` bound. - let mut generics = self.generics.clone(); - generics.params.push(parse_quote! { '___a }); - let (lf_impl_gen, _, _) = generics.split_for_impl(); - - self.variants - .iter() - .map(|v| { - let var_ident = &v.ident; - let field = v.fields.iter().next().unwrap(); - let var_ty = &field.ty; - let var_field = field - .ident - .as_ref() - .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); - - quote! { - #[automatically_derived] - impl#impl_gen std::convert::From<#var_ty> for #ty_ident#ty_gen - #where_clause - { - fn from(v: #var_ty) -> Self { - Self::#var_ident#var_field - } - } - - #[automatically_derived] - impl#impl_gen std::convert::From<#ty_ident#ty_gen> for Option<#var_ty> - #where_clause - { - fn from(ty: #ty_ident#ty_gen) -> Self { - if let #ty_ident::#var_ident#var_field = ty { - Some(v) - } else { - None - } - } - } - - #[automatically_derived] - impl#lf_impl_gen std::convert::From<&'___a #ty_ident#ty_gen> for - Option<&'___a #var_ty> - #where_clause - { - fn from(ty: &'___a #ty_ident#ty_gen) -> Self { - if let #ty_ident::#var_ident#var_field = ty { - Some(v) - } else { - None - } - } - } - } - }) - .collect() - } - - /// Returns generated code implementing [`Display`] by matching over each - /// enum variant. - /// - /// [`Display`]: std::fmt::Display - fn impl_display_tokens(&self) -> TokenStream { - let ident = &self.ident; - - let mut generics = self.generics.clone(); - generics.make_where_clause(); - for var in &self.variants { - let var_ty = &var.fields.iter().next().unwrap().ty; - let mut check = IsVariantGeneric::new(&self.generics); - check.visit_type(var_ty); - if check.res { - generics - .where_clause - .as_mut() - .unwrap() - .predicates - .push(parse_quote! { #var_ty: std::fmt::Display }); - } - } - let (impl_gen, ty_gen, where_clause) = generics.split_for_impl(); - - let arms = self.variants.iter().map(|v| { - let var_ident = &v.ident; - let field = v.fields.iter().next().unwrap(); - let var_field = field - .ident - .as_ref() - .map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } }); - - quote! { Self::#var_ident#var_field => std::fmt::Display::fmt(v, f), } - }); - - quote! { - impl#impl_gen std::fmt::Display for #ident#ty_gen - #where_clause - { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - #(#arms)* - } - } - } - } - } -} - -/// Single-[`Field`] enum variant. -#[derive(Clone)] -struct Variant { - /// [`Variant`] [`syn::Ident`]. - ident: syn::Ident, - - /// Single [`Variant`] [`Field`]. - field: Field, - - /// Optional resolver provided by [`VariantAttr`]. - expr: Option, -} - -impl Variant { - /// Returns generated code for matching over this [`Variant`]. - fn match_arm(&self) -> TokenStream { - let (ident, field) = (&self.ident, &self.field.match_arg()); - quote! { - Self::#ident#field - } - } -} - -/// Enum [`Variant`] field. -#[derive(Clone)] -enum Field { - /// Named [`Field`]. - Named(syn::Field), - - /// Unnamed [`Field`]. - Unnamed(syn::Field), -} - -impl ToTokens for Field { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Named(f) => f.ident.to_tokens(tokens), - Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)), - } - } -} - -impl TryFrom for Field { - type Error = syn::Error; - - fn try_from(value: syn::Fields) -> Result { - match value { - syn::Fields::Named(mut f) if f.named.len() == 1 => { - Ok(Self::Named(f.named.pop().unwrap().into_value())) - } - syn::Fields::Unnamed(mut f) if f.unnamed.len() == 1 => { - Ok(Self::Unnamed(f.unnamed.pop().unwrap().into_value())) - } - _ => Err(ERR.custom_error(value.span(), "expected exactly 1 field")), - } - } -} - -impl Field { - /// Returns [`Field`] for constructing or matching over [`Variant`]. - fn match_arg(&self) -> TokenStream { - match self { - Self::Named(_) => quote! { { #self: v } }, - Self::Unnamed(_) => quote! { (v) }, - } - } -} - -/// [`Visit`]or to check whether [`Variant`] [`Field`] contains generic -/// parameters. -struct IsVariantGeneric<'a> { - /// Indicates whether [`Variant`] [`Field`] contains generic parameters. - res: bool, - - /// [`syn::Generics`] to search parameters. - generics: &'a syn::Generics, -} - -impl<'a> IsVariantGeneric<'a> { - /// Construct a new [`IsVariantGeneric`]. - fn new(generics: &'a syn::Generics) -> Self { - Self { - res: false, - generics, - } - } -} - -impl<'ast, 'gen> Visit<'ast> for IsVariantGeneric<'gen> { - fn visit_path(&mut self, path: &'ast syn::Path) { - if let Some(ident) = path.get_ident() { - let is_generic = self.generics.params.iter().any(|par| { - if let syn::GenericParam::Type(ty) = par { - ty.ident == *ident - } else { - false - } - }); - if is_generic { - self.res = true; - } else { - syn::visit::visit_path(self, path); - } - } - } -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index aebbc69c1..abf31143b 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -110,7 +110,6 @@ mod derive_enum; mod derive_input_object; mod common; -mod derive_scalar_value; mod graphql_interface; mod graphql_object; mod graphql_scalar; @@ -719,8 +718,9 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// > __NOTE:__ Struct or trait representing interface acts as a blueprint and -/// > isn't actually used. But no-one is stopping you from +/// > __NOTE:__ Struct or trait representing interface acts only as a blueprint +/// > for names of methods, their arguments and return type. So isn't +/// > actually used at a runtime. But no-one is stopping you from /// > implementing trait manually for your own usage. /// /// # Custom name, description, deprecation and argument defaults From f4bbdafc3d76634a64be92ce67289718a49eaadd Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 10 Mar 2022 15:10:24 +0300 Subject: [PATCH 107/122] CHANGELOG --- juniper/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 700175119..e8604ddf9 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -47,6 +47,7 @@ - Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005)) - Implement `#[derive(ScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) +- Implement `#[derive(GraphQLInterface)]` macro to use structs as GraphQL interfaces. ([#1026](https://github.com/graphql-rust/juniper/pull/1026)) ## Fixes From c77fa9a891e2947bffc4f3429df4d95e0a48509d Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 11 Mar 2022 18:18:12 +0200 Subject: [PATCH 108/122] Corrections [skip ci] --- .../src/codegen/interface_attr_struct.rs | 82 +++++++++---- .../src/codegen/interface_attr_trait.rs | 2 +- .../src/codegen/interface_derive.rs | 110 +++++++++++++----- juniper_codegen/src/common/field/arg.rs | 4 +- juniper_codegen/src/graphql_interface/attr.rs | 17 +-- .../src/graphql_interface/derive.rs | 4 +- juniper_codegen/src/graphql_interface/mod.rs | 10 +- 7 files changed, 165 insertions(+), 64 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs index f44e4eb47..d651079c1 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs @@ -43,7 +43,7 @@ mod no_implers { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -273,7 +273,7 @@ mod trivial { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -449,7 +449,7 @@ mod explicit_alias { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -673,7 +673,7 @@ mod trivial_async { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -712,7 +712,7 @@ mod fallible_field { impl IntoFieldError for CustomError { fn into_field_error(self) -> FieldError { - juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + FieldError::new("Whatever", graphql_value!({"code": "some"})) } } @@ -784,7 +784,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -996,7 +999,7 @@ mod generic { } #[tokio::test] - async fn uses_trait_name_without_type_params() { + async fn uses_struct_name_without_type_params() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -1539,7 +1542,10 @@ mod explicit_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1561,7 +1567,10 @@ mod explicit_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1663,7 +1672,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1685,7 +1697,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1785,7 +1800,10 @@ mod explicit_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1807,7 +1825,10 @@ mod explicit_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1907,7 +1928,10 @@ mod bounded_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1929,7 +1953,10 @@ mod bounded_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2008,7 +2035,10 @@ mod ignored_method { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2123,7 +2153,10 @@ mod field_return_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2145,7 +2178,10 @@ mod field_return_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2427,7 +2463,10 @@ mod nullable_argument_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2449,7 +2488,10 @@ mod nullable_argument_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs index 5d792095c..8ae247138 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs @@ -711,7 +711,7 @@ mod fallible_field { impl IntoFieldError for CustomError { fn into_field_error(self) -> FieldError { - juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + FieldError::new("Whatever", graphql_value!({"code": "some"})) } } diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs index ee0065352..aee81c69c 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -1,7 +1,5 @@ //! Tests for `#[derive(GraphQLInterface)]` macro. -#![allow(dead_code)] - use std::marker::PhantomData; use juniper::{ @@ -45,7 +43,7 @@ mod no_implers { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -276,7 +274,7 @@ mod trivial { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -453,7 +451,7 @@ mod explicit_alias { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -557,7 +555,10 @@ mod trivial_async { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -579,7 +580,10 @@ mod trivial_async { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -678,7 +682,7 @@ mod trivial_async { } #[tokio::test] - async fn uses_trait_name() { + async fn uses_struct_name() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -717,7 +721,7 @@ mod fallible_field { impl IntoFieldError for CustomError { fn into_field_error(self) -> FieldError { - juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + FieldError::new("Whatever", graphql_value!({"code": "some"})) } } @@ -790,7 +794,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -812,7 +819,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -952,7 +962,10 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -974,7 +987,10 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1003,7 +1019,7 @@ mod generic { } #[tokio::test] - async fn uses_trait_name_without_type_params() { + async fn uses_struct_name_without_type_params() { const DOC: &str = r#"{ __type(name: "Character") { name @@ -1286,7 +1302,11 @@ mod explicit_name_description_and_deprecation { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + graphql_value!({"character": { + "myId": "human-32", + "a": "a", + "b": "b", + }}), vec![], )), ); @@ -1573,7 +1593,10 @@ mod explicit_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1676,7 +1699,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1698,7 +1724,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1799,7 +1828,10 @@ mod explicit_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1821,7 +1853,10 @@ mod explicit_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1922,7 +1957,10 @@ mod bounded_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1944,7 +1982,10 @@ mod bounded_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2024,7 +2065,10 @@ mod ignored_method { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2140,7 +2184,10 @@ mod field_return_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2162,7 +2209,10 @@ mod field_return_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2446,7 +2496,10 @@ mod nullable_argument_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2468,7 +2521,10 @@ mod nullable_argument_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index b0f9cdccc..fdd8f6480 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -333,7 +333,9 @@ impl OnMethod { .as_ref() .map(|v| quote! { (#v).into() }) .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote_spanned! { val.span() => .arg_with_default::<#ty>(#name, &#val, info) } + quote_spanned! { val.span() => + .arg_with_default::<#ty>(#name, &#val, info) + } } else { quote! { .arg::<#ty>(#name, info) } }; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 4510c128c..15d48c201 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -36,11 +36,12 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, mut ast: syn::ItemTrait, @@ -146,7 +147,7 @@ fn expand_on_trait( /// Parses a [`field::Definition`] from the given trait method definition. /// -/// Returns [`None`] if parsing fails, or the method field is ignored. +/// Returns [`None`] if the parsing fails, or the method field is ignored. #[must_use] fn parse_trait_method( method: &mut syn::TraitItemMethod, @@ -222,7 +223,7 @@ fn parse_trait_method( }) } -/// Expands `#[graphql_interface]` macro placed on struct. +/// Expands `#[graphql_interface]` macro placed on the given struct. fn expand_on_derive_input( attrs: Vec, mut ast: syn::DeriveInput, @@ -237,8 +238,8 @@ fn expand_on_derive_input( syn::Data::Enum(_) | syn::Data::Union(_) => { return Err(ERR.custom_error( ast.span(), - "#[graphql_interface] attribute is applicable \ - to trait and struct definitions only", + "#[graphql_interface] attribute is applicable to trait and \ + struct definitions only", )); } }; @@ -333,9 +334,9 @@ fn expand_on_derive_input( }) } -/// Parses a [`field::Definition`] from the given trait method definition. +/// Parses a [`field::Definition`] from the given struct field definition. /// -/// Returns [`None`] if parsing fails, or the method field is ignored. +/// Returns [`None`] if the parsing fails, or the struct field is ignored. #[must_use] fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option { let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 263368991..9774ffde7 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -114,9 +114,9 @@ pub fn expand(input: TokenStream) -> syn::Result { .into_token_stream()) } -/// Parses a [`field::Definition`] from the given trait method definition. +/// Parses a [`field::Definition`] from the given struct field definition. /// -/// Returns [`None`] if parsing fails, or the method field is ignored. +/// Returns [`None`] if the parsing fails, or the struct field is ignored. #[must_use] fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { let field_ident = field diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 20ceb133b..98b855299 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -489,9 +489,9 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let is_output = self.implemented_for.iter().map(|implementer| { - quote_spanned! { implementer.span() => - <#implementer as ::juniper::marker::IsOutputType<#scalar>>::mark(); + let is_output = self.implemented_for.iter().map(|impler| { + quote_spanned! { impler.span() => + <#impler as ::juniper::marker::IsOutputType<#scalar>>::mark(); } }); @@ -512,7 +512,7 @@ impl Definition { ::juniper::assert_interfaces_impls!( #const_scalar, #ty#ty_const_generics, - #(#const_impl_for),* + #( #const_impl_for ),* ); } } @@ -759,7 +759,7 @@ impl Definition { { const NAMES: ::juniper::macros::reflect::Types = &[ >::NAME, - #(<#implemented_for as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* + #( <#implemented_for as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),* ]; } From 6aa9b0be135d3327dc11098ae7290742585924f8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 24 Mar 2022 15:24:57 +0300 Subject: [PATCH 109/122] Corrections --- .../interface/struct/attr_unnamed_field.rs | 6 +++ .../struct/attr_unnamed_field.stderr | 0 .../interface/struct/derive_unnamed_field.rs | 6 +++ .../struct/derive_unnamed_field.stderr | 0 .../src/codegen/interface_derive.rs | 2 - juniper_codegen/src/graphql_interface/attr.rs | 27 +++--------- .../src/graphql_interface/derive.rs | 25 ++--------- juniper_codegen/src/graphql_interface/mod.rs | 44 ++++++++++++++++++- 8 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs new file mode 100644 index 000000000..479efd00c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs @@ -0,0 +1,6 @@ +use juniper::graphql_interface; + +#[graphql_interface] +struct Character(i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs new file mode 100644 index 000000000..4ecd78c23 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLInterface; + +#[derive(GraphQLInterface)] +struct Character(i32); + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/integration_tests/juniper_tests/src/codegen/interface_derive.rs index ee0065352..dd68c40e2 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_derive.rs @@ -1,7 +1,5 @@ //! Tests for `#[derive(GraphQLInterface)]` macro. -#![allow(dead_code)] - use std::marker::PhantomData; use juniper::{ diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 4510c128c..1bd8d9af3 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -16,7 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{Attr, Definition}; +use super::{enum_idents, Attr, Definition}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -111,15 +111,7 @@ fn expand_on_trait( }) .unwrap_or_else(|| parse_quote! { () }); - let enum_alias_ident = attr - .r#enum - .as_deref() - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); - let enum_ident = attr.r#enum.as_ref().map_or_else( - || format_ident!("{}ValueEnum", trait_ident.to_string()), - |c| format_ident!("{}Enum", c.inner().to_string()), - ); + let (enum_ident, enum_alias_ident) = enum_idents(trait_ident, attr.r#enum.as_deref()); let generated_code = Definition { generics: ast.generics.clone(), @@ -136,6 +128,7 @@ fn expand_on_trait( .iter() .map(|c| c.inner().clone()) .collect(), + suppress_dead_code: None, }; Ok(quote! { @@ -299,16 +292,7 @@ fn expand_on_derive_input( }) .unwrap_or_else(|| parse_quote! { () }); - let enum_alias_ident = attr - .r#enum - .as_deref() - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); - let enum_ident = attr.r#enum.as_ref().map_or_else( - || format_ident!("{}ValueEnum", trait_ident.to_string()), - |c| format_ident!("{}Enum", c.inner().to_string()), - ); - + let (enum_ident, enum_alias_ident) = enum_idents(trait_ident, attr.r#enum.as_deref()); let generated_code = Definition { generics: ast.generics.clone(), vis: ast.vis.clone(), @@ -324,6 +308,7 @@ fn expand_on_derive_input( .iter() .map(|c| c.inner().clone()) .collect(), + suppress_dead_code: None, }; Ok(quote! { @@ -403,7 +388,7 @@ fn err_default_impl_block(span: &S) -> Option { /// Emits "expected named struct field" [`syn::Error`] pointing to the given /// `span`. -fn err_unnamed_field(span: &S) -> Option { +pub(crate) fn err_unnamed_field(span: &S) -> Option { ERR.emit_custom(span.span(), "expected named struct field"); None } diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 263368991..eaa0a25f5 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -10,7 +10,7 @@ use crate::{ util::{span_container::SpanContainer, RenameRule}, }; -use super::{Attr, Definition}; +use super::{attr::err_unnamed_field, enum_idents, Attr, Definition}; /// [`GraphQLScope`] of errors for `#[derive(GraphQLInterface)]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceDerive; @@ -85,15 +85,7 @@ pub fn expand(input: TokenStream) -> syn::Result { }) .unwrap_or_else(|| parse_quote! { () }); - let enum_alias_ident = attr - .r#enum - .as_deref() - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", struct_ident.to_string())); - let enum_ident = attr.r#enum.as_ref().map_or_else( - || format_ident!("{}ValueEnum", struct_ident.to_string()), - |c| format_ident!("{}Enum", c.inner().to_string()), - ); + let (enum_ident, enum_alias_ident) = enum_idents(struct_ident, attr.r#enum.as_deref()); Ok(Definition { generics: ast.generics.clone(), @@ -110,6 +102,7 @@ pub fn expand(input: TokenStream) -> syn::Result { .iter() .map(|c| c.inner().clone()) .collect(), + suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())), } .into_token_stream()) } @@ -119,10 +112,7 @@ pub fn expand(input: TokenStream) -> syn::Result { /// Returns [`None`] if parsing fails, or the method field is ignored. #[must_use] fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { - let field_ident = field - .ident - .as_ref() - .or_else(|| err_unnamed_field(&field.span()))?; + let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?; let attr = field::Attr::from_attrs("graphql", &field.attrs) .map_err(|e| proc_macro_error::emit_error!(e)) @@ -167,10 +157,3 @@ fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option(span: &S) -> Option { - ERR.emit_custom(span.span(), "expected named struct field"); - None -} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 20ceb133b..56e631c39 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -14,7 +14,7 @@ use syn::{ parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, - spanned::Spanned as _, + spanned::Spanned, token, visit::Visit, }; @@ -29,8 +29,29 @@ use crate::{ scalar, }, util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, + GraphQLScope, }; +/// Returns [`Ident`]s for generic enum deriving [`Clone`] and [`Copy`] on it +/// and enum alias which generic arguments are filled with +/// [GraphQL interface][1] implementers. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [`Ident`]: syn::Ident +fn enum_idents( + trait_ident: &syn::Ident, + alias_ident: Option<&syn::Ident>, +) -> (syn::Ident, syn::Ident) { + let enum_alias_ident = alias_ident + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + let enum_ident = alias_ident.map_or_else( + || format_ident!("{}ValueEnum", trait_ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + (enum_ident, enum_alias_ident) +} + /// Available arguments behind `#[graphql_interface]` attribute placed on a /// trait or struct definition, when generating code for [GraphQL interface][1] /// type. @@ -300,6 +321,8 @@ struct Definition { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces implemented_for: Vec, + + suppress_dead_code: Option<(syn::Ident, syn::Fields)>, } impl ToTokens for Definition { @@ -454,6 +477,24 @@ impl Definition { quote! { ::juniper::sa::assert_type_ne_all!(#( #implemented_for ),*); } }); + let suppress_dead_code = self.suppress_dead_code.as_ref().map(|(ident, fields)| { + let const_gens = self.const_trait_generics(); + let fields = fields.iter().map(|f| &f.ident); + + quote! {{ + const SUPPRESS_DEAD_CODE: () = { + let none = Option::<#ident#const_gens>::None; + match none { + Some(unreachable) => { + #(let _ = unreachable.#fields;)* + } + None => {} + } + }; + let _ = SUPPRESS_DEAD_CODE; + }} + }); + quote! { #[automatically_derived] impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> @@ -461,6 +502,7 @@ impl Definition { #where_clause { fn mark() { + #suppress_dead_code #all_impled_for_unique #( <#implemented_for as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } From e9961ec2bbbf4fa99a3c66b603642e95b40494aa Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 25 Mar 2022 09:10:47 +0300 Subject: [PATCH 110/122] Corrections --- .../fail/interface/struct/attr_unnamed_field.stderr | 7 +++++++ .../fail/interface/struct/derive_unnamed_field.stderr | 7 +++++++ juniper_codegen/src/graphql_interface/attr.rs | 2 +- juniper_codegen/src/graphql_interface/derive.rs | 2 +- juniper_codegen/src/graphql_interface/mod.rs | 9 +++++++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr index e69de29bb..6fa918e25 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface expected named struct field + --> fail/interface/struct/attr_unnamed_field.rs:4:18 + | +4 | struct Character(i32); + | ^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr index e69de29bb..8a826c10c 100644 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr +++ b/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr @@ -0,0 +1,7 @@ +error: GraphQL interface expected named struct field + --> fail/interface/struct/derive_unnamed_field.rs:4:18 + | +4 | struct Character(i32); + | ^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 1bd8d9af3..8befadcce 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -3,7 +3,7 @@ use std::mem; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::quote; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index eaa0a25f5..30b7810b8 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -1,7 +1,7 @@ //! Code generation for `#[derive(GraphQLInterface)]` macro. use proc_macro2::TokenStream; -use quote::{format_ident, ToTokens as _}; +use quote::ToTokens as _; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 56e631c39..b1c572007 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -29,7 +29,6 @@ use crate::{ scalar, }, util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, - GraphQLScope, }; /// Returns [`Ident`]s for generic enum deriving [`Clone`] and [`Copy`] on it @@ -47,7 +46,7 @@ fn enum_idents( .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); let enum_ident = alias_ident.map_or_else( || format_ident!("{}ValueEnum", trait_ident.to_string()), - |c| format_ident!("{}Enum", c.inner().to_string()), + |c| format_ident!("{}Enum", c.to_string()), ); (enum_ident, enum_alias_ident) } @@ -322,6 +321,12 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces implemented_for: Vec, + /// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't + /// append `#[allow(dead_code)]` to the unused struct, representing + /// [GraphQL interface][1]. We generate hacky `const` which doesn't actually + /// use it, but suppresses this warning. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces suppress_dead_code: Option<(syn::Ident, syn::Fields)>, } From 678d00a4763c72428620c08d189f1b450ef22535 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 28 Mar 2022 13:57:46 +0300 Subject: [PATCH 111/122] Corrections --- juniper_codegen/src/graphql_interface/mod.rs | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index b849c291f..0d1bf13f9 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -527,7 +527,7 @@ impl Definition { let mark_object_or_interface = self.implemented_for.iter().map(|impl_for| { quote_spanned! { impl_for.span() => - trait GraphQLObjectOrInterface { + trait GraphQLObjectOrInterface { fn mark(); } @@ -536,11 +536,11 @@ impl Definition { impl GraphQLObjectOrInterface for T where - S: juniper::ScalarValue, - T: juniper::marker::GraphQLObject, + S: ::juniper::ScalarValue, + T: ::juniper::marker::GraphQLObject, { fn mark() { - >::mark() + >::mark() } } } @@ -550,11 +550,11 @@ impl Definition { impl GraphQLObjectOrInterface for T where - S: juniper::ScalarValue, - T: juniper::marker::GraphQLInterface, + S: ::juniper::ScalarValue, + T: ::juniper::marker::GraphQLInterface, { fn mark() { - >::mark() + >::mark() } } } @@ -572,7 +572,7 @@ impl Definition { fn mark() { #suppress_dead_code #all_impled_for_unique - #({ #mark_object_or_interface })* + #( { #mark_object_or_interface } )* } } } @@ -611,8 +611,8 @@ impl Definition { }); let const_implements = self .implements - .clone() - .into_iter() + .iter() + .cloned() .map(|mut ty| { generics.replace_type_path_with_defaults(&mut ty); ty @@ -620,7 +620,7 @@ impl Definition { .collect::>(); let transitive_checks = const_impl_for.clone().map(|const_impl_for| { quote_spanned! { const_impl_for.span() => - juniper::assert_transitive_implementations!( + ::juniper::assert_transitive_implementations!( #const_scalar, #ty#ty_const_generics, #const_impl_for, @@ -648,7 +648,7 @@ impl Definition { #ty#ty_const_generics, #(#const_implements),* ); - #(#transitive_checks)* + #( #transitive_checks )* } } } @@ -682,7 +682,7 @@ impl Definition { }); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. - let mut implements: Vec<_> = self.implements.iter().collect(); + let mut implements = self.implements.clone(); implements.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) From 5e38cd76f3d13eb4a18a1a2c29ef1321627385ae Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 28 Mar 2022 14:05:46 +0300 Subject: [PATCH 112/122] Reference issue in CHANGELOG --- juniper/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 6374ce71c..d5e25043c 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -21,7 +21,7 @@ - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). - Forbid default impls on non-ignored trait methods. - Support coercion of additional nullable arguments and return sub-typing on implementer. - - Support interfaces implementing other interfaces ([#1028](https://github.com/graphql-rust/juniper/pull/1028)) + - Support interfaces implementing other interfaces ([#1028](https://github.com/graphql-rust/juniper/pull/1028), [#1000](https://github.com/graphql-rust/juniper/issues/1000)) - Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - Support generic scalars. - Support structs with single named field. From f969976e4650b51235cd5390459d7998e97a7670 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 28 Mar 2022 15:47:06 +0300 Subject: [PATCH 113/122] Add docs section --- docs/book/content/types/interfaces.md | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index aa9c543e7..945f8c177 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -76,6 +76,70 @@ struct Human { # fn main() {} ``` +### Interfaces implementing other interfaces + +GraphQL allows implementing interfaces on other interfaces in addition to objects. Note that every interface has to specify all other interfaces/objects it implements or implemented for. Missing one of `for = ` or `impl = ` attributes is a compile time error, as with cyclic references and all other interface features, so if it builds, you won't get runtime errors or panics. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, graphql_object, ID}; + +#[graphql_interface(for = [HumanValue, Luke])] +struct Node { + id: ID, +} + +#[graphql_interface(for = HumanConnection)] +struct Connection { + nodes: Vec, +} + +#[graphql_interface(impl = NodeValue, for = Luke)] +struct Human { + id: ID, + home_planet: String, +} + +#[derive(GraphQLObject)] +#[graphql(impl = ConnectionValue)] +struct HumanConnection { + nodes: Vec, + // ^^^^^^^^^^ notice not `NodeValue` + // GraphQL allows us to return `subtypes` on implementers. + // This can happen, because every `Human` is a `Node` too, so we are just + // imposing additional bounds, which still can be resolved with + // `... on Connection { nodes }`. + // https://spec.graphql.org/October2021/#IsValidImplementation() +} + +struct Luke { + id: ID, +} + +#[graphql_object(impl = [HumanValue, NodeValue])] +impl Luke { + fn id(&self) -> &ID { + &self.id + } + + // As `String` and `&str` aren't distinguished by GraphQL + // spec, you can use them interchangeably. ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ + fn home_planet(language: Option) -> &'static str { + // ^^^^^^^^^^^^^^ + // Notice additional nullable field, which is missing on `Human`. + // GraphQL allows that and on resolving `...on Human { homePlanet }` + // will provide `None` for this argument. + match language.as_deref() { + None | Some("en") => "Tatooine", + Some("ko") => "타투ì¸", + _ => todo!(), + } + } +} +# +# fn main() {} +``` + ### Ignoring trait methods From fb44c8435af1f867c2c4e9e4226bbe1736e2bd13 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 29 Mar 2022 08:59:06 +0300 Subject: [PATCH 114/122] Add docs section --- docs/book/content/types/interfaces.md | 136 ++++++++++++++++++++++++-- juniper/src/types/scalars.rs | 76 +++++++++++++- juniper_codegen/src/lib.rs | 120 +++++++++++++++++++++++ 3 files changed, 322 insertions(+), 10 deletions(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 945f8c177..26e2be0dc 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -78,7 +78,82 @@ struct Human { ### Interfaces implementing other interfaces -GraphQL allows implementing interfaces on other interfaces in addition to objects. Note that every interface has to specify all other interfaces/objects it implements or implemented for. Missing one of `for = ` or `impl = ` attributes is a compile time error, as with cyclic references and all other interface features, so if it builds, you won't get runtime errors or panics. +GraphQL allows implementing interfaces on other interfaces in addition to objects. + +```rust +# extern crate juniper; +use juniper::{graphql_interface, graphql_object, ID}; + +#[graphql_interface(for = [HumanValue, Luke])] +struct Node { + id: ID, +} + +#[graphql_interface(impl = NodeValue, for = Luke)] +struct Human { + id: ID, + home_planet: String, +} + +struct Luke { + id: ID, +} + +#[graphql_object(impl = [HumanValue, NodeValue])] +impl Luke { + fn id(&self) -> &ID { + &self.id + } + + // As `String` and `&str` aren't distinguished by + // GraphQL spec, you can use them interchangeably. + // Same is applied for `Cow<'a, str>`. + // ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ + fn home_planet() -> &'static str { + "Tatooine" + } +} +# +# fn main() {} +``` + +> __NOTE:__ Every interface has to specify all other interfaces/objects it implements or implemented for. Missing one of `for = ` or `impl = ` attributes is a compile-time error. + +```compile_fail +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = ObjA)] +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at +// 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.' +struct Character { + id: String, +} + +fn main() {} +``` + +### GraphQL subtyping and additional nullable fields + +GraphQL allows implementers (both objects and other interfaces) to return "subtypes" instead of an original value. Basically this allows you to impose additional bounds on implementation. + +Valid "subtypes": +- interface implementer instead of an interface itself + - `I implements T` in place of a T + - `Vec` in place of a `Vec` + - ... +- non-null value in place of a nullable: + - `T` in place of a `Option` + - `Vec` in place of a `Vec>` + - ... +Those rules are recursively applied, so `Vec>` is a valid "subtype" of a `Option>>>>`. + +Also, GraphQL allows implementers to add nullable fields, which aren't present on an original interface. ```rust # extern crate juniper; @@ -100,16 +175,13 @@ struct Human { home_planet: String, } -#[derive(GraphQLObject)] -#[graphql(impl = ConnectionValue)] +#[graphql_interface(impl = ConnectionValue)] struct HumanConnection { nodes: Vec, // ^^^^^^^^^^ notice not `NodeValue` - // GraphQL allows us to return `subtypes` on implementers. // This can happen, because every `Human` is a `Node` too, so we are just // imposing additional bounds, which still can be resolved with // `... on Connection { nodes }`. - // https://spec.graphql.org/October2021/#IsValidImplementation() } struct Luke { @@ -122,13 +194,11 @@ impl Luke { &self.id } - // As `String` and `&str` aren't distinguished by GraphQL - // spec, you can use them interchangeably. ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ fn home_planet(language: Option) -> &'static str { // ^^^^^^^^^^^^^^ // Notice additional nullable field, which is missing on `Human`. - // GraphQL allows that and on resolving `...on Human { homePlanet }` - // will provide `None` for this argument. + // Resolving `...on Human { homePlanet }` will provide `None` for this + // argument. match language.as_deref() { None | Some("en") => "Tatooine", Some("ko") => "타투ì¸", @@ -140,6 +210,54 @@ impl Luke { # fn main() {} ``` +Violating GraphQL "subtyping" or additional nullable field rules is a compile-time error. + +```compile_fail +# extern crate juniper; +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, is_present: bool) -> &str { +// ^^ the evaluated program panicked at +// 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` +// isn't present on the interface and so has to be nullable.' + is_present.then(|| self.id.as_str()).unwrap_or("missing") + } +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} +# +# fn main() {} +``` + +```compile_fail +# extern crate juniper; +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +// ^^ the evaluated program panicked at +// 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of +// interface's return object: `[String!]!` is not a subtype of `String!`.' +} + +#[graphql_interface(for = ObjA)] +struct Character { + id: String, +} +# +# fn main() {} +``` ### Ignoring trait methods diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 66e26e74f..84a9947ee 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -1,5 +1,6 @@ use std::{ - char, convert::From, fmt, marker::PhantomData, ops::Deref, rc::Rc, thread::JoinHandle, u32, + borrow::Cow, char, convert::From, fmt, marker::PhantomData, ops::Deref, rc::Rc, + thread::JoinHandle, u32, }; use serde::{Deserialize, Serialize}; @@ -269,6 +270,79 @@ where } } +impl<'s, S> reflect::WrappedType for Cow<'s, str> { + const VALUE: reflect::WrappedValue = 1; +} + +impl<'s, S> reflect::BaseType for Cow<'s, str> { + const NAME: reflect::Type = "String"; +} + +impl<'s, S> reflect::BaseSubTypes for Cow<'s, str> { + const NAMES: reflect::Types = &[>::NAME]; +} + +impl<'s, S> GraphQLType for Cow<'s, str> +where + S: ScalarValue, +{ + fn name(_: &()) -> 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() + } +} + +impl<'s, S> GraphQLValue for Cow<'s, str> +where + S: ScalarValue, +{ + type Context = (); + type TypeInfo = (); + + fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { + >::name(info) + } + + fn resolve( + &self, + _: &(), + _: Option<&[Selection]>, + _: &Executor, + ) -> ExecutionResult { + Ok(Value::scalar(self.to_string())) + } +} + +impl<'s, S> GraphQLValueAsync for Cow<'s, str> +where + S: ScalarValue + Send + Sync, +{ + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [Selection]>, + executor: &'a Executor, + ) -> crate::BoxFuture<'a, crate::ExecutionResult> { + use futures::future; + Box::pin(future::ready(self.resolve(info, selection_set, executor))) + } +} + +impl<'s, S> ToInputValue for Cow<'s, str> +where + S: ScalarValue, +{ + fn to_input_value(&self) -> InputValue { + InputValue::scalar(self.to_string()) + } +} + #[graphql_scalar(with = impl_boolean_scalar)] type Boolean = bool; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index abf31143b..a3eee6048 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -763,6 +763,126 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// } /// ``` /// +/// # Interfaces implementing other interfaces +/// +/// GraphQL allows implementing interfaces on other interfaces in addition to +/// objects. +/// +/// > __NOTE:__ Every interface has to specify all other interfaces/objects it +/// > implements or implemented for. Missing one of `for = ` or +/// > `impl = ` attributes is an understandable compile-time error. +/// +/// ```rust +/// # extern crate juniper; +/// use juniper::{graphql_interface, graphql_object, ID}; +/// +/// #[graphql_interface(for = [HumanValue, Luke])] +/// struct Node { +/// id: ID, +/// } +/// +/// #[graphql_interface(impl = NodeValue, for = Luke)] +/// struct Human { +/// id: ID, +/// home_planet: String, +/// } +/// +/// struct Luke { +/// id: ID, +/// } +/// +/// #[graphql_object(impl = [HumanValue, NodeValue])] +/// impl Luke { +/// fn id(&self) -> &ID { +/// &self.id +/// } +/// +/// // As `String` and `&str` aren't distinguished by +/// // GraphQL spec, you can use them interchangeably. +/// // Same is applied for `Cow<'a, str>`. +/// // ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄ +/// fn home_planet() -> &'static str { +/// "Tatooine" +/// } +/// } +/// ``` +/// +/// # GraphQL subtyping and additional nullable fields +/// +/// GraphQL allows implementers (both objects and other interfaces) to return +/// "subtypes" instead of an original value. Basically this allows you to impose +/// additional bounds on implementation. +/// +/// Valid "subtypes": +/// - interface implementer instead of an interface itself +/// - `I implements T` in place of a T +/// - `Vec` in place of a `Vec` +/// - ... +/// - non-null value in place of a nullable: +/// - `T` in place of a `Option` +/// - `Vec` in place of a `Vec>` +/// - ... +/// Those rules are recursively applied, so `Vec>` is a +/// valid "subtype" of a `Option>>>>`. +/// +/// Also, GraphQL allows implementers to add nullable fields, which aren't +/// present on an original interface. +/// +/// ```rust +/// # extern crate juniper; +/// use juniper::{graphql_interface, graphql_object, ID}; +/// +/// #[graphql_interface(for = [HumanValue, Luke])] +/// struct Node { +/// id: ID, +/// } +/// +/// #[graphql_interface(for = HumanConnection)] +/// struct Connection { +/// nodes: Vec, +/// } +/// +/// #[graphql_interface(impl = NodeValue, for = Luke)] +/// struct Human { +/// id: ID, +/// home_planet: String, +/// } +/// +/// #[graphql_interface(impl = ConnectionValue)] +/// struct HumanConnection { +/// nodes: Vec, +/// // ^^^^^^^^^^ notice not `NodeValue` +/// // This can happen, because every `Human` is a `Node` too, so we are +/// // just imposing additional bounds, which still can be resolved with +/// // `... on Connection { nodes }`. +/// } +/// +/// struct Luke { +/// id: ID, +/// } +/// +/// #[graphql_object(impl = [HumanValue, NodeValue])] +/// impl Luke { +/// fn id(&self) -> &ID { +/// &self.id +/// } +/// +/// fn home_planet(language: Option) -> &'static str { +/// // ^^^^^^^^^^^^^^ +/// // Notice additional nullable field, which is missing on `Human`. +/// // Resolving `...on Human { homePlanet }` will provide `None` for +/// // this argument. +/// match language.as_deref() { +/// None | Some("en") => "Tatooine", +/// Some("ko") => "타투ì¸", +/// _ => todo!(), +/// } +/// } +/// } +/// # +/// # fn main() {} +/// ``` +/// /// # Renaming policy /// /// By default, all [GraphQL interface][1] fields and their arguments are renamed From 07995280ddb1ec74adf4c2a372d98e0d34efb72e Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 29 Mar 2022 09:04:09 +0300 Subject: [PATCH 115/122] Add docs section --- docs/book/content/types/interfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 26e2be0dc..72d15e07c 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -164,7 +164,7 @@ struct Node { id: ID, } -#[graphql_interface(for = HumanConnection)] +#[graphql_interface(for = HumanConnectionValue)] struct Connection { nodes: Vec, } From b43e7113523c2c82174f97b5d7e6edb337377be7 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 29 Mar 2022 09:35:00 +0300 Subject: [PATCH 116/122] Corrections --- juniper_codegen/src/common/parse/mod.rs | 11 ++++------- juniper_codegen/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index e6f469aa7..0165f4c05 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -207,13 +207,10 @@ impl TypeExt for syn::Type { // These types unlikely will be used as GraphQL types. T::BareFn(_) | T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) => {} - // Following the syn idiom for exhaustive matching on Type: - // https://github.com/dtolnay/syn/blob/master/src/ty.rs#L66-L88 - #[cfg(test)] - T::__TestExhaustive(_) => unimplemented!(), - - #[cfg(not(test))] - _ => {} + // TODO: uncomment this, once lint is stabilized. + // https://github.com/rust-lang/rust/issues/89554 + // #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!(), } } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index a3eee6048..3bffe9751 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -837,7 +837,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// id: ID, /// } /// -/// #[graphql_interface(for = HumanConnection)] +/// #[graphql_interface(for = HumanConnectionValue)] /// struct Connection { /// nodes: Vec, /// } From d5bbdd7d336082fe5afc95568e621dbb2d79659e Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Jun 2022 16:15:32 +0300 Subject: [PATCH 117/122] Corrections --- .github/dependabot.yml | 41 +- .github/workflows/book.yml | 57 - .github/workflows/ci.yml | 463 ++++++-- .gitignore | 10 +- rustfmt.toml => .rustfmt.toml | 0 Cargo.toml | 38 +- Makefile | 139 +++ Makefile.toml | 38 - RELEASING.md | 81 +- _build/azure-pipelines-template.yml | 71 -- _build/cargo-make.ps1 | 21 - _build/cargo-make.sh | 19 - _build/release.toml | 6 - _build/travis-juniper.enc | 1 - azure-pipelines.yml | 109 -- {juniper_benchmarks => benches}/Cargo.toml | 5 +- .../benches/benchmark.rs | 0 {juniper_benchmarks => benches}/src/lib.rs | 0 book/.gitignore | 2 + book/README.md | 67 ++ book/book.toml | 16 + {docs/book/content => book/src}/README.md | 0 {docs/book/content => book/src}/SUMMARY.md | 0 .../src}/advanced/dataloaders.md | 8 +- .../advanced/implicit_and_explicit_null.md | 0 .../content => book/src}/advanced/index.md | 0 .../src}/advanced/introspection.md | 0 .../src}/advanced/multiple_ops_per_request.md | 0 .../src}/advanced/non_struct_objects.md | 0 .../src}/advanced/objects_and_generics.md | 0 .../src}/advanced/subscriptions.md | 4 +- {docs/book/content => book/src}/quickstart.md | 2 +- .../src}/schema/schemas_and_mutations.md | 1 + .../content => book/src}/servers/hyper.md | 4 +- .../content => book/src}/servers/index.md | 0 .../book/content => book/src}/servers/iron.md | 4 +- .../content => book/src}/servers/official.md | 0 .../content => book/src}/servers/rocket.md | 4 +- .../src}/servers/third-party.md | 0 .../book/content => book/src}/servers/warp.md | 4 +- .../content => book/src}/styles/website.css | 0 .../book/content => book/src}/types/enums.md | 0 .../book/content => book/src}/types/index.md | 0 .../src}/types/input_objects.md | 0 .../content => book/src}/types/interfaces.md | 0 .../src}/types/objects/complex_fields.md | 0 .../src}/types/objects/defining_objects.md | 0 .../src}/types/objects/error_handling.md | 0 .../src}/types/objects/using_contexts.md | 0 .../content => book/src}/types/other-index.md | 0 .../content => book/src}/types/scalars.md | 0 .../book/content => book/src}/types/unions.md | 0 book/tests/Cargo.toml | 22 + book/tests/build.rs | 4 + {docs/book => book}/tests/src/lib.rs | 0 docs/book/.gitignore | 1 - docs/book/README.md | 48 - docs/book/book.toml | 11 - docs/book/ci-build.sh | 52 - docs/book/tests/.gitignore | 3 - docs/book/tests/Cargo.toml | 26 - docs/book/tests/build.rs | 4 - examples/actix_subscriptions/Cargo.toml | 18 +- examples/actix_subscriptions/Makefile.toml | 15 - examples/basic_subscriptions/.gitignore | 1 - examples/basic_subscriptions/Cargo.toml | 13 +- examples/basic_subscriptions/Makefile.toml | 15 - examples/warp_async/.gitignore | 1 - examples/warp_async/Cargo.toml | 15 +- examples/warp_async/Makefile.toml | 15 - examples/warp_subscriptions/.gitignore | 1 - examples/warp_subscriptions/Cargo.toml | 15 +- examples/warp_subscriptions/Makefile.toml | 15 - integration_tests/async_await/Cargo.toml | 11 - integration_tests/async_await/src/main.rs | 167 --- integration_tests/codegen_fail/Makefile.toml | 29 - ...tr_additional_non_nullable_argument.stderr | 7 - .../attr_field_non_output_return_type.stderr | 5 - .../attr_implementers_duplicate_pretty.stderr | 11 - .../struct/attr_missing_field.stderr | 7 - .../struct/attr_non_subtype_return.stderr | 7 - ...ve_additional_non_nullable_argument.stderr | 7 - ...derive_field_non_output_return_type.stderr | 5 - ...erive_implementers_duplicate_pretty.stderr | 11 - .../struct/derive_missing_field.stderr | 7 - .../struct/derive_non_subtype_return.stderr | 7 - .../additional_non_nullable_argument.stderr | 7 - .../trait/field_non_output_return_type.stderr | 5 - .../implementers_duplicate_pretty.stderr | 11 - .../fail/interface/trait/missing_field.stderr | 7 - .../trait/missing_field_argument.stderr | 7 - .../interface/trait/non_subtype_return.stderr | 7 - .../trait/wrong_argument_type.stderr | 7 - .../object/argument_non_input_type.stderr | 26 - .../attr_field_non_output_return_type.stderr | 5 - ...derive_field_non_output_return_type.stderr | 5 - .../field_non_output_return_type.stderr | 5 - .../fail/union/enum_non_object_variant.stderr | 7 - .../union/struct_non_object_variant.stderr | 7 - .../union/trait_fail_infer_context.stderr | 17 - .../union/trait_non_object_variant.stderr | 7 - integration_tests/juniper_tests/Makefile.toml | 12 - juniper/CHANGELOG.md | 1022 ++--------------- juniper/Cargo.toml | 45 +- juniper/LICENSE | 25 + juniper/Makefile.toml | 30 - juniper/README.md | 109 ++ juniper/release.toml | 127 +- juniper/src/integrations/bigdecimal.rs | 132 +++ juniper/src/integrations/bson.rs | 8 +- juniper/src/integrations/mod.rs | 4 + juniper/src/integrations/rust_decimal.rs | 123 ++ juniper/src/lib.rs | 112 +- juniper/src/macros/helper/mod.rs | 31 +- juniper/src/schema/meta.rs | 18 +- juniper/src/schema/model.rs | 21 +- juniper/src/schema/translate/mod.rs | 2 +- juniper/src/types/async_await.rs | 8 - juniper/src/types/base.rs | 8 - juniper/src/types/scalars.rs | 76 +- juniper_actix/.gitignore | 4 - juniper_actix/CHANGELOG.md | 38 +- juniper_actix/Cargo.toml | 33 +- juniper_actix/LICENSE | 2 +- juniper_actix/Makefile.toml | 16 - juniper_actix/README.md | 51 +- juniper_actix/release.toml | 11 + juniper_actix/src/lib.rs | 41 +- juniper_benchmarks/.gitignore | 3 - juniper_benchmarks/CHANGELOG.md | 0 juniper_benchmarks/Makefile.toml | 12 - juniper_codegen/CHANGELOG.md | 61 + juniper_codegen/Cargo.toml | 19 +- juniper_codegen/LICENSE | 25 + juniper_codegen/Makefile.toml | 11 - juniper_codegen/README.md | 24 + juniper_codegen/release.toml | 24 +- juniper_codegen/src/common/field/arg.rs | 16 +- juniper_codegen/src/common/parse/mod.rs | 8 +- juniper_codegen/src/derive_enum.rs | 153 --- juniper_codegen/src/graphql_enum/derive.rs | 140 +++ juniper_codegen/src/graphql_enum/mod.rs | 785 +++++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 16 +- .../src/graphql_interface/derive.rs | 1 + juniper_codegen/src/graphql_interface/mod.rs | 115 +- juniper_codegen/src/graphql_object/mod.rs | 56 +- juniper_codegen/src/graphql_scalar/mod.rs | 8 +- .../src/graphql_subscription/mod.rs | 4 +- juniper_codegen/src/graphql_union/mod.rs | 4 +- juniper_codegen/src/lib.rs | 172 ++- juniper_codegen/src/result.rs | 8 +- juniper_codegen/src/util/mod.rs | 226 ---- juniper_graphql_ws/CHANGELOG.md | 30 +- juniper_graphql_ws/Cargo.toml | 16 +- juniper_graphql_ws/LICENSE | 25 + juniper_graphql_ws/Makefile.toml | 33 - juniper_graphql_ws/README.md | 24 + juniper_graphql_ws/release.toml | 31 +- juniper_graphql_ws/src/lib.rs | 9 +- juniper_hyper/.gitignore | 2 - juniper_hyper/CHANGELOG.md | 72 +- juniper_hyper/Cargo.toml | 13 +- juniper_hyper/LICENSE | 2 +- juniper_hyper/README.md | 46 +- juniper_hyper/release.toml | 17 + juniper_hyper/src/lib.rs | 2 +- juniper_iron/.gitignore | 2 - juniper_iron/CHANGELOG.md | 70 +- juniper_iron/Cargo.toml | 19 +- juniper_iron/LICENSE | 4 +- juniper_iron/README.md | 46 +- juniper_iron/release.toml | 17 + juniper_iron/src/lib.rs | 108 +- juniper_rocket/.gitignore | 2 - juniper_rocket/CHANGELOG.md | 97 +- juniper_rocket/Cargo.toml | 13 +- juniper_rocket/LICENSE | 4 +- juniper_rocket/README.md | 46 +- juniper_rocket/examples/rocket_server.rs | 6 +- juniper_rocket/release.toml | 17 + juniper_rocket/src/lib.rs | 54 +- juniper_subscriptions/.gitignore | 4 - juniper_subscriptions/CHANGELOG.md | 27 +- juniper_subscriptions/Cargo.toml | 13 +- juniper_subscriptions/LICENSE | 2 +- juniper_subscriptions/Makefile.toml | 33 - juniper_subscriptions/README.md | 50 +- juniper_subscriptions/release.toml | 30 +- juniper_subscriptions/src/lib.rs | 11 +- juniper_warp/.gitignore | 4 - juniper_warp/CHANGELOG.md | 77 +- juniper_warp/Cargo.toml | 23 +- juniper_warp/LICENSE | 2 +- juniper_warp/Makefile.toml | 22 - juniper_warp/README.md | 46 +- juniper_warp/release.toml | 17 + juniper_warp/src/lib.rs | 41 +- release.toml | 4 + .../codegen_fail => tests/codegen}/Cargo.toml | 12 +- .../enum/derive_duplicated_variant_names.rs | 8 + .../derive_duplicated_variant_names.stderr | 7 + .../enum/derive_name_double_underscored.rs | 6 + .../derive_name_double_underscored.stderr | 7 + .../codegen}/fail/enum/derive_no_fields.rs | 0 .../fail/enum/derive_no_fields.stderr | 0 .../fail/enum/derive_no_variants.stderr | 7 + .../fail/enum/derive_variant_with_field.rs | 6 + .../enum/derive_variant_with_field.stderr | 7 + tests/codegen/fail/enum/derive_wrong_item.rs | 4 + .../fail/enum/derive_wrong_item.stderr | 5 + .../derive_incompatible_object.rs | 0 .../derive_incompatible_object.stderr | 31 + .../fail/input-object/derive_no_fields.rs | 0 .../fail/input-object/derive_no_fields.stderr | 0 .../fail/input-object/derive_no_underscore.rs | 0 .../input-object/derive_no_underscore.stderr | 0 .../fail/input-object/derive_unique_name.rs | 0 .../input-object/derive_unique_name.stderr | 0 .../fail/interface/attr_wrong_item.rs | 0 .../fail/interface/attr_wrong_item.stderr | 0 .../fail/interface/derive_wrong_item.rs | 0 .../fail/interface/derive_wrong_item.stderr | 0 .../attr_additional_non_nullable_argument.rs | 0 ...tr_additional_non_nullable_argument.stderr | 15 + .../fail/interface/struct/attr_cyclic_impl.rs | 0 .../interface/struct/attr_cyclic_impl.stderr | 0 .../struct/attr_field_double_underscored.rs | 0 .../attr_field_double_underscored.stderr | 0 .../attr_field_non_output_return_type.rs | 0 .../attr_field_non_output_return_type.stderr | 16 + .../interface/struct/attr_fields_duplicate.rs | 0 .../struct/attr_fields_duplicate.stderr | 0 .../attr_implementers_duplicate_pretty.rs | 0 .../attr_implementers_duplicate_pretty.stderr | 35 + .../attr_implementers_duplicate_ugly.rs | 0 .../attr_implementers_duplicate_ugly.stderr | 0 .../interface/struct/attr_missing_field.rs | 0 .../struct/attr_missing_field.stderr | 984 ++++++++++++++++ .../interface/struct/attr_missing_for_attr.rs | 0 .../struct/attr_missing_for_attr.stderr | 0 .../struct/attr_missing_impl_attr.rs | 0 .../struct/attr_missing_impl_attr.stderr | 0 .../struct/attr_missing_transitive_impl.rs | 0 .../attr_missing_transitive_impl.stderr | 0 .../struct/attr_name_double_underscored.rs | 0 .../attr_name_double_underscored.stderr | 0 .../fail/interface/struct/attr_no_fields.rs | 0 .../interface/struct/attr_no_fields.stderr | 0 .../struct/attr_non_subtype_return.rs | 0 .../struct/attr_non_subtype_return.stderr | 15 + .../interface/struct/attr_unnamed_field.rs | 0 .../struct/attr_unnamed_field.stderr | 0 ...derive_additional_non_nullable_argument.rs | 0 ...ve_additional_non_nullable_argument.stderr | 15 + .../interface/struct/derive_cyclic_impl.rs | 0 .../struct/derive_cyclic_impl.stderr | 0 .../struct/derive_field_double_underscored.rs | 0 .../derive_field_double_underscored.stderr | 0 .../derive_field_non_output_return_type.rs | 0 ...derive_field_non_output_return_type.stderr | 16 + .../struct/derive_fields_duplicate.rs | 0 .../struct/derive_fields_duplicate.stderr | 0 .../derive_implementers_duplicate_pretty.rs | 0 ...erive_implementers_duplicate_pretty.stderr | 35 + .../derive_implementers_duplicate_ugly.rs | 0 .../derive_implementers_duplicate_ugly.stderr | 0 .../interface/struct/derive_missing_field.rs | 0 .../struct/derive_missing_field.stderr | 984 ++++++++++++++++ .../struct/derive_missing_for_attr.rs | 0 .../struct/derive_missing_for_attr.stderr | 0 .../struct/derive_missing_impl_attr.rs | 0 .../struct/derive_missing_impl_attr.stderr | 0 .../struct/derive_missing_transitive_impl.rs | 0 .../derive_missing_transitive_impl.stderr | 0 .../struct/derive_name_double_underscored.rs | 0 .../derive_name_double_underscored.stderr | 0 .../fail/interface/struct/derive_no_fields.rs | 0 .../interface/struct/derive_no_fields.stderr | 0 .../struct/derive_non_subtype_return.rs | 0 .../struct/derive_non_subtype_return.stderr | 15 + .../interface/struct/derive_unnamed_field.rs | 0 .../struct/derive_unnamed_field.stderr | 0 .../trait/additional_non_nullable_argument.rs | 0 .../additional_non_nullable_argument.stderr | 15 + .../trait/argument_double_underscored.rs | 0 .../trait/argument_double_underscored.stderr | 0 .../trait/argument_non_input_type.rs | 0 .../trait/argument_non_input_type.stderr | 21 + .../trait/argument_wrong_default_array.rs | 0 .../trait/argument_wrong_default_array.stderr | 8 +- .../fail/interface/trait/cyclic_impl.rs | 0 .../fail/interface/trait/cyclic_impl.stderr | 0 .../trait/field_double_underscored.rs | 0 .../trait/field_double_underscored.stderr | 0 .../trait/field_non_output_return_type.rs | 0 .../trait/field_non_output_return_type.stderr | 16 + .../fail/interface/trait/fields_duplicate.rs | 0 .../interface/trait/fields_duplicate.stderr | 0 .../trait/implementers_duplicate_pretty.rs | 0 .../implementers_duplicate_pretty.stderr | 35 + .../trait/implementers_duplicate_ugly.rs | 0 .../trait/implementers_duplicate_ugly.stderr | 0 .../interface/trait/method_default_impl.rs | 0 .../trait/method_default_impl.stderr | 0 .../fail/interface/trait/missing_field.rs | 0 .../fail/interface/trait/missing_field.stderr | 984 ++++++++++++++++ .../interface/trait/missing_field_argument.rs | 0 .../trait/missing_field_argument.stderr | 15 + .../fail/interface/trait/missing_for_attr.rs | 0 .../interface/trait/missing_for_attr.stderr | 0 .../fail/interface/trait/missing_impl_attr.rs | 0 .../interface/trait/missing_impl_attr.stderr | 0 .../trait/missing_transitive_impl.rs | 0 .../trait/missing_transitive_impl.stderr | 0 .../trait/name_double_underscored.rs | 0 .../trait/name_double_underscored.stderr | 0 .../fail/interface/trait/no_fields.rs | 0 .../fail/interface/trait/no_fields.stderr | 0 .../interface/trait/non_subtype_return.rs | 0 .../interface/trait/non_subtype_return.stderr | 15 + .../interface/trait/wrong_argument_type.rs | 0 .../trait/wrong_argument_type.stderr | 15 + .../object/argument_double_underscored.rs | 0 .../object/argument_double_underscored.stderr | 0 .../fail/object/argument_non_input_type.rs | 0 .../object/argument_non_input_type.stderr | 57 + .../object/argument_wrong_default_array.rs | 0 .../argument_wrong_default_array.stderr | 8 +- .../object/attr_field_double_underscored.rs | 0 .../attr_field_double_underscored.stderr | 0 .../attr_field_non_output_return_type.rs | 0 .../attr_field_non_output_return_type.stderr | 16 + .../fail/object/attr_fields_duplicate.rs | 0 .../fail/object/attr_fields_duplicate.stderr | 0 .../object/attr_name_double_underscored.rs | 0 .../attr_name_double_underscored.stderr | 0 .../codegen}/fail/object/attr_no_fields.rs | 0 .../fail/object/attr_no_fields.stderr | 0 .../codegen}/fail/object/attr_wrong_item.rs | 0 .../fail/object/attr_wrong_item.stderr | 0 .../object/derive_field_double_underscored.rs | 0 .../derive_field_double_underscored.stderr | 0 .../derive_field_non_output_return_type.rs | 0 ...derive_field_non_output_return_type.stderr | 16 + .../fail/object/derive_fields_duplicate.rs | 0 .../object/derive_fields_duplicate.stderr | 0 .../object/derive_name_double_underscored.rs | 0 .../derive_name_double_underscored.stderr | 0 .../codegen}/fail/object/derive_no_fields.rs | 0 .../fail/object/derive_no_fields.stderr | 0 .../codegen}/fail/object/derive_wrong_item.rs | 0 .../fail/object/derive_wrong_item.stderr | 0 .../scalar/derive_input/attr_invalid_url.rs | 0 .../derive_input/attr_invalid_url.stderr | 0 .../derive_input/attr_transparent_and_with.rs | 0 .../attr_transparent_and_with.stderr | 0 .../attr_transparent_multiple_named_fields.rs | 0 ...r_transparent_multiple_named_fields.stderr | 0 ...ttr_transparent_multiple_unnamed_fields.rs | 0 ...transparent_multiple_unnamed_fields.stderr | 0 .../attr_transparent_unit_struct.rs | 0 .../attr_transparent_unit_struct.stderr | 0 .../scalar/derive_input/derive_invalid_url.rs | 0 .../derive_input/derive_invalid_url.stderr | 0 .../derive_transparent_and_with.rs | 0 .../derive_transparent_and_with.stderr | 0 ...erive_transparent_multiple_named_fields.rs | 0 ...e_transparent_multiple_named_fields.stderr | 0 ...ive_transparent_multiple_unnamed_fields.rs | 0 ...transparent_multiple_unnamed_fields.stderr | 0 .../derive_transparent_unit_struct.rs | 0 .../derive_transparent_unit_struct.stderr | 0 .../scalar/type_alias/attr_invalid_url.rs | 0 .../scalar/type_alias/attr_invalid_url.stderr | 0 .../type_alias/attr_with_not_all_resolvers.rs | 0 .../attr_with_not_all_resolvers.stderr | 0 .../type_alias/attr_without_resolvers.rs | 0 .../type_alias/attr_without_resolvers.stderr | 0 .../fail/scalar_value/missing_attributes.rs | 0 .../scalar_value/missing_attributes.stderr | 2 +- .../scalar_value/multiple_named_fields.rs | 0 .../scalar_value/multiple_named_fields.stderr | 0 .../scalar_value/multiple_unnamed_fields.rs | 0 .../multiple_unnamed_fields.stderr | 0 .../codegen}/fail/scalar_value/not_enum.rs | 0 .../fail/scalar_value/not_enum.stderr | 0 .../argument_double_underscored.rs | 0 .../argument_double_underscored.stderr | 0 .../subscription/argument_non_input_type.rs | 0 .../argument_non_input_type.stderr | 31 + .../argument_wrong_default_array.rs | 0 .../argument_wrong_default_array.stderr | 8 +- .../subscription/field_double_underscored.rs | 0 .../field_double_underscored.stderr | 0 .../field_non_output_return_type.rs | 0 .../field_non_output_return_type.stderr | 16 + .../fail/subscription/field_not_async.rs | 0 .../fail/subscription/field_not_async.stderr | 0 .../fail/subscription/fields_duplicate.rs | 0 .../fail/subscription/fields_duplicate.stderr | 0 .../subscription/name_double_underscored.rs | 0 .../name_double_underscored.stderr | 0 .../codegen}/fail/subscription/no_fields.rs | 0 .../fail/subscription/no_fields.stderr | 0 .../codegen}/fail/subscription/wrong_item.rs | 0 .../fail/subscription/wrong_item.stderr | 0 .../codegen}/fail/union/attr_wrong_item.rs | 0 .../fail/union/attr_wrong_item.stderr | 0 .../codegen}/fail/union/derive_wrong_item.rs | 0 .../fail/union/derive_wrong_item.stderr | 0 ...licts_with_variant_external_resolver_fn.rs | 0 ...s_with_variant_external_resolver_fn.stderr | 0 .../union/enum_name_double_underscored.rs | 0 .../union/enum_name_double_underscored.stderr | 0 .../codegen}/fail/union/enum_no_fields.rs | 0 .../codegen}/fail/union/enum_no_fields.stderr | 0 .../fail/union/enum_non_object_variant.rs | 0 .../fail/union/enum_non_object_variant.stderr | 17 + .../fail/union/enum_same_type_pretty.rs | 0 .../fail/union/enum_same_type_pretty.stderr | 0 .../fail/union/enum_same_type_ugly.rs | 0 .../fail/union/enum_same_type_ugly.stderr | 0 .../fail/union/enum_wrong_variant_field.rs | 0 .../union/enum_wrong_variant_field.stderr | 0 .../union/struct_name_double_underscored.rs | 0 .../struct_name_double_underscored.stderr | 0 .../codegen}/fail/union/struct_no_fields.rs | 0 .../fail/union/struct_no_fields.stderr | 0 .../fail/union/struct_non_object_variant.rs | 0 .../union/struct_non_object_variant.stderr | 17 + .../fail/union/struct_same_type_pretty.rs | 0 .../fail/union/struct_same_type_pretty.stderr | 0 .../fail/union/struct_same_type_ugly.rs | 0 .../fail/union/struct_same_type_ugly.stderr | 0 .../fail/union/trait_fail_infer_context.rs | 0 .../union/trait_fail_infer_context.stderr | 28 + ...hod_conflicts_with_external_resolver_fn.rs | 0 ...conflicts_with_external_resolver_fn.stderr | 0 .../union/trait_name_double_underscored.rs | 0 .../trait_name_double_underscored.stderr | 0 .../codegen}/fail/union/trait_no_fields.rs | 0 .../fail/union/trait_no_fields.stderr | 0 .../fail/union/trait_non_object_variant.rs | 0 .../union/trait_non_object_variant.stderr | 17 + .../fail/union/trait_same_type_pretty.rs | 0 .../fail/union/trait_same_type_pretty.stderr | 0 .../fail/union/trait_same_type_ugly.rs | 0 .../fail/union/trait_same_type_ugly.stderr | 0 .../fail/union/trait_with_attr_on_method.rs | 0 .../union/trait_with_attr_on_method.stderr | 0 .../union/trait_wrong_method_input_args.rs | 0 .../trait_wrong_method_input_args.stderr | 0 .../union/trait_wrong_method_return_type.rs | 0 .../trait_wrong_method_return_type.stderr | 0 .../codegen_fail => tests/codegen}/src/lib.rs | 0 .../integration}/Cargo.toml | 8 +- .../integration}/src/arc_fields.rs | 0 .../integration}/src/array.rs | 0 .../integration}/src/codegen/derive_enum.rs | 8 +- .../src/codegen/derive_input_object.rs | 8 +- .../codegen/derive_object_with_raw_idents.rs | 0 tests/integration/src/codegen/enum_derive.rs | 932 +++++++++++++++ .../src/codegen/interface_attr_struct.rs | 72 +- .../src/codegen/interface_attr_trait.rs | 177 ++- .../src/codegen/interface_derive.rs | 72 +- .../integration}/src/codegen/mod.rs | 2 +- .../integration}/src/codegen/object_attr.rs | 0 .../integration}/src/codegen/object_derive.rs | 0 .../src/codegen/scalar_attr_derive_input.rs | 0 .../src/codegen/scalar_attr_type_alias.rs | 0 .../integration}/src/codegen/scalar_derive.rs | 0 .../src/codegen/scalar_value_derive.rs | 0 .../src/codegen/subscription_attr.rs | 0 .../integration}/src/codegen/union_attr.rs | 0 .../integration}/src/codegen/union_derive.rs | 0 .../integration}/src/custom_scalar.rs | 2 +- .../integration}/src/explicit_null.rs | 0 .../src/infallible_as_field_error.rs | 0 tests/integration/src/inside_macro.rs | 39 + .../integration}/src/issue_371.rs | 0 .../integration}/src/issue_372.rs | 0 .../integration}/src/issue_398.rs | 0 .../integration}/src/issue_407.rs | 0 .../integration}/src/issue_500.rs | 0 .../integration}/src/issue_798.rs | 0 .../integration}/src/issue_914.rs | 0 .../integration}/src/issue_922.rs | 0 .../integration}/src/issue_925.rs | 0 .../integration}/src/issue_945.rs | 0 .../integration}/src/lib.rs | 4 + .../integration}/src/pre_parse.rs | 0 491 files changed, 8105 insertions(+), 3960 deletions(-) delete mode 100644 .github/workflows/book.yml rename rustfmt.toml => .rustfmt.toml (100%) create mode 100644 Makefile delete mode 100644 Makefile.toml delete mode 100644 _build/azure-pipelines-template.yml delete mode 100644 _build/cargo-make.ps1 delete mode 100755 _build/cargo-make.sh delete mode 100644 _build/release.toml delete mode 100644 _build/travis-juniper.enc delete mode 100644 azure-pipelines.yml rename {juniper_benchmarks => benches}/Cargo.toml (73%) rename {juniper_benchmarks => benches}/benches/benchmark.rs (100%) rename {juniper_benchmarks => benches}/src/lib.rs (100%) create mode 100644 book/.gitignore create mode 100644 book/README.md create mode 100644 book/book.toml rename {docs/book/content => book/src}/README.md (100%) rename {docs/book/content => book/src}/SUMMARY.md (100%) rename {docs/book/content => book/src}/advanced/dataloaders.md (98%) rename {docs/book/content => book/src}/advanced/implicit_and_explicit_null.md (100%) rename {docs/book/content => book/src}/advanced/index.md (100%) rename {docs/book/content => book/src}/advanced/introspection.md (100%) rename {docs/book/content => book/src}/advanced/multiple_ops_per_request.md (100%) rename {docs/book/content => book/src}/advanced/non_struct_objects.md (100%) rename {docs/book/content => book/src}/advanced/objects_and_generics.md (100%) rename {docs/book/content => book/src}/advanced/subscriptions.md (97%) rename {docs/book/content => book/src}/quickstart.md (99%) rename {docs/book/content => book/src}/schema/schemas_and_mutations.md (99%) rename {docs/book/content => book/src}/servers/hyper.md (95%) rename {docs/book/content => book/src}/servers/index.md (100%) rename {docs/book/content => book/src}/servers/iron.md (98%) rename {docs/book/content => book/src}/servers/official.md (100%) rename {docs/book/content => book/src}/servers/rocket.md (94%) rename {docs/book/content => book/src}/servers/third-party.md (100%) rename {docs/book/content => book/src}/servers/warp.md (95%) rename {docs/book/content => book/src}/styles/website.css (100%) rename {docs/book/content => book/src}/types/enums.md (100%) rename {docs/book/content => book/src}/types/index.md (100%) rename {docs/book/content => book/src}/types/input_objects.md (100%) rename {docs/book/content => book/src}/types/interfaces.md (100%) rename {docs/book/content => book/src}/types/objects/complex_fields.md (100%) rename {docs/book/content => book/src}/types/objects/defining_objects.md (100%) rename {docs/book/content => book/src}/types/objects/error_handling.md (100%) rename {docs/book/content => book/src}/types/objects/using_contexts.md (100%) rename {docs/book/content => book/src}/types/other-index.md (100%) rename {docs/book/content => book/src}/types/scalars.md (100%) rename {docs/book/content => book/src}/types/unions.md (100%) create mode 100644 book/tests/Cargo.toml create mode 100644 book/tests/build.rs rename {docs/book => book}/tests/src/lib.rs (100%) delete mode 100644 docs/book/.gitignore delete mode 100644 docs/book/README.md delete mode 100644 docs/book/book.toml delete mode 100755 docs/book/ci-build.sh delete mode 100644 docs/book/tests/.gitignore delete mode 100644 docs/book/tests/Cargo.toml delete mode 100644 docs/book/tests/build.rs delete mode 100644 examples/actix_subscriptions/Makefile.toml delete mode 100644 examples/basic_subscriptions/.gitignore delete mode 100644 examples/basic_subscriptions/Makefile.toml delete mode 100644 examples/warp_async/.gitignore delete mode 100644 examples/warp_async/Makefile.toml delete mode 100644 examples/warp_subscriptions/.gitignore delete mode 100644 examples/warp_subscriptions/Makefile.toml delete mode 100644 integration_tests/async_await/Cargo.toml delete mode 100644 integration_tests/async_await/src/main.rs delete mode 100644 integration_tests/codegen_fail/Makefile.toml delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr delete mode 100644 integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr delete mode 100644 integration_tests/codegen_fail/fail/union/trait_fail_infer_context.stderr delete mode 100644 integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr delete mode 100644 integration_tests/juniper_tests/Makefile.toml create mode 100644 juniper/LICENSE delete mode 100644 juniper/Makefile.toml create mode 100644 juniper/README.md create mode 100644 juniper/src/integrations/bigdecimal.rs create mode 100644 juniper/src/integrations/rust_decimal.rs delete mode 100644 juniper_actix/.gitignore delete mode 100644 juniper_actix/Makefile.toml create mode 100644 juniper_actix/release.toml delete mode 100644 juniper_benchmarks/.gitignore delete mode 100644 juniper_benchmarks/CHANGELOG.md delete mode 100644 juniper_benchmarks/Makefile.toml create mode 100644 juniper_codegen/CHANGELOG.md create mode 100644 juniper_codegen/LICENSE delete mode 100644 juniper_codegen/Makefile.toml create mode 100644 juniper_codegen/README.md delete mode 100644 juniper_codegen/src/derive_enum.rs create mode 100644 juniper_codegen/src/graphql_enum/derive.rs create mode 100644 juniper_codegen/src/graphql_enum/mod.rs create mode 100644 juniper_graphql_ws/LICENSE delete mode 100644 juniper_graphql_ws/Makefile.toml create mode 100644 juniper_graphql_ws/README.md delete mode 100644 juniper_hyper/.gitignore create mode 100644 juniper_hyper/release.toml delete mode 100644 juniper_iron/.gitignore create mode 100644 juniper_iron/release.toml delete mode 100644 juniper_rocket/.gitignore create mode 100644 juniper_rocket/release.toml delete mode 100644 juniper_subscriptions/.gitignore delete mode 100644 juniper_subscriptions/Makefile.toml delete mode 100644 juniper_warp/.gitignore delete mode 100644 juniper_warp/Makefile.toml create mode 100644 juniper_warp/release.toml create mode 100644 release.toml rename {integration_tests/codegen_fail => tests/codegen}/Cargo.toml (54%) create mode 100644 tests/codegen/fail/enum/derive_duplicated_variant_names.rs create mode 100644 tests/codegen/fail/enum/derive_duplicated_variant_names.stderr create mode 100644 tests/codegen/fail/enum/derive_name_double_underscored.rs create mode 100644 tests/codegen/fail/enum/derive_name_double_underscored.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/enum/derive_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/enum/derive_no_fields.stderr (100%) create mode 100644 tests/codegen/fail/enum/derive_no_variants.stderr create mode 100644 tests/codegen/fail/enum/derive_variant_with_field.rs create mode 100644 tests/codegen/fail/enum/derive_variant_with_field.stderr create mode 100644 tests/codegen/fail/enum/derive_wrong_item.rs create mode 100644 tests/codegen/fail/enum/derive_wrong_item.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_incompatible_object.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_incompatible_object.stderr (61%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_no_underscore.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_no_underscore.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_unique_name.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/input-object/derive_unique_name.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/attr_wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/attr_wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/derive_wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/derive_wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_additional_non_nullable_argument.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_cyclic_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_cyclic_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_field_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_field_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_field_non_output_return_type.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/attr_field_non_output_return_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_fields_duplicate.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_fields_duplicate.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_implementers_duplicate_pretty.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_implementers_duplicate_ugly.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_implementers_duplicate_ugly.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_field.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/attr_missing_field.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_for_attr.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_for_attr.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_impl_attr.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_impl_attr.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_transitive_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_missing_transitive_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_non_subtype_return.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_unnamed_field.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/attr_unnamed_field.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_additional_non_nullable_argument.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_cyclic_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_cyclic_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_field_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_field_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_field_non_output_return_type.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/derive_field_non_output_return_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_fields_duplicate.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_fields_duplicate.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_implementers_duplicate_pretty.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_implementers_duplicate_ugly.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_implementers_duplicate_ugly.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_field.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/derive_missing_field.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_for_attr.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_for_attr.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_impl_attr.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_impl_attr.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_transitive_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_missing_transitive_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_non_subtype_return.rs (100%) create mode 100644 tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_unnamed_field.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/struct/derive_unnamed_field.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/additional_non_nullable_argument.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/argument_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/argument_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/argument_non_input_type.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/argument_non_input_type.stderr (51%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/argument_wrong_default_array.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/argument_wrong_default_array.stderr (71%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/cyclic_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/cyclic_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/field_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/field_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/field_non_output_return_type.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/field_non_output_return_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/fields_duplicate.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/fields_duplicate.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/implementers_duplicate_pretty.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/implementers_duplicate_ugly.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/implementers_duplicate_ugly.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/method_default_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/method_default_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_field.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/missing_field.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_field_argument.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/missing_field_argument.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_for_attr.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_for_attr.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_impl_attr.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_impl_attr.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_transitive_impl.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/missing_transitive_impl.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/non_subtype_return.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/non_subtype_return.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/interface/trait/wrong_argument_type.rs (100%) create mode 100644 tests/codegen/fail/interface/trait/wrong_argument_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/object/argument_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/argument_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/argument_non_input_type.rs (100%) create mode 100644 tests/codegen/fail/object/argument_non_input_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/object/argument_wrong_default_array.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/argument_wrong_default_array.stderr (71%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_field_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_field_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_field_non_output_return_type.rs (100%) create mode 100644 tests/codegen/fail/object/attr_field_non_output_return_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_fields_duplicate.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_fields_duplicate.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/attr_wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_field_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_field_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_field_non_output_return_type.rs (100%) create mode 100644 tests/codegen/fail/object/derive_field_non_output_return_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_fields_duplicate.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_fields_duplicate.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/object/derive_wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_invalid_url.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_invalid_url.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_and_with.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_and_with.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_unit_struct.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/attr_transparent_unit_struct.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_invalid_url.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_invalid_url.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_and_with.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_and_with.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_unit_struct.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/derive_input/derive_transparent_unit_struct.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/type_alias/attr_invalid_url.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/type_alias/attr_invalid_url.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/type_alias/attr_with_not_all_resolvers.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/type_alias/attr_without_resolvers.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar/type_alias/attr_without_resolvers.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/missing_attributes.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/missing_attributes.stderr (71%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/multiple_named_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/multiple_named_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/multiple_unnamed_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/multiple_unnamed_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/not_enum.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/scalar_value/not_enum.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/argument_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/argument_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/argument_non_input_type.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/argument_non_input_type.stderr (50%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/argument_wrong_default_array.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/argument_wrong_default_array.stderr (71%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/field_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/field_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/field_non_output_return_type.rs (100%) create mode 100644 tests/codegen/fail/subscription/field_non_output_return_type.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/field_not_async.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/field_not_async.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/fields_duplicate.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/fields_duplicate.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/subscription/wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/attr_wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/attr_wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/derive_wrong_item.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/derive_wrong_item.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_non_object_variant.rs (100%) create mode 100644 tests/codegen/fail/union/enum_non_object_variant.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_same_type_pretty.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_same_type_pretty.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_same_type_ugly.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_same_type_ugly.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_wrong_variant_field.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/enum_wrong_variant_field.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_non_object_variant.rs (100%) create mode 100644 tests/codegen/fail/union/struct_non_object_variant.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_same_type_pretty.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_same_type_pretty.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_same_type_ugly.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/struct_same_type_ugly.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_fail_infer_context.rs (100%) create mode 100644 tests/codegen/fail/union/trait_fail_infer_context.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_method_conflicts_with_external_resolver_fn.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_name_double_underscored.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_name_double_underscored.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_no_fields.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_no_fields.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_non_object_variant.rs (100%) create mode 100644 tests/codegen/fail/union/trait_non_object_variant.stderr rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_same_type_pretty.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_same_type_pretty.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_same_type_ugly.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_same_type_ugly.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_with_attr_on_method.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_with_attr_on_method.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_wrong_method_input_args.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_wrong_method_input_args.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_wrong_method_return_type.rs (100%) rename {integration_tests/codegen_fail => tests/codegen}/fail/union/trait_wrong_method_return_type.stderr (100%) rename {integration_tests/codegen_fail => tests/codegen}/src/lib.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/Cargo.toml (74%) rename {integration_tests/juniper_tests => tests/integration}/src/arc_fields.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/array.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/derive_enum.rs (90%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/derive_input_object.rs (93%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/derive_object_with_raw_idents.rs (100%) create mode 100644 tests/integration/src/codegen/enum_derive.rs rename {integration_tests/juniper_tests => tests/integration}/src/codegen/interface_attr_struct.rs (97%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/interface_attr_trait.rs (94%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/interface_derive.rs (98%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/mod.rs (95%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/object_attr.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/object_derive.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/scalar_attr_derive_input.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/scalar_attr_type_alias.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/scalar_derive.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/scalar_value_derive.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/subscription_attr.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/union_attr.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/codegen/union_derive.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/custom_scalar.rs (98%) rename {integration_tests/juniper_tests => tests/integration}/src/explicit_null.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/infallible_as_field_error.rs (100%) create mode 100644 tests/integration/src/inside_macro.rs rename {integration_tests/juniper_tests => tests/integration}/src/issue_371.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_372.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_398.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_407.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_500.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_798.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_914.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_922.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_925.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/issue_945.rs (100%) rename {integration_tests/juniper_tests => tests/integration}/src/lib.rs (97%) rename {integration_tests/juniper_tests => tests/integration}/src/pre_parse.rs (100%) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f75ce68a8..1cf1a19bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,34 +1,11 @@ version: 2 updates: -- package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: warp - versions: - - 0.3.0 - - 0.3.1 - - dependency-name: tokio - versions: - - 1.1.0 - - 1.2.0 - - 1.3.0 - - 1.4.0 - - dependency-name: actix - versions: - - 0.11.0 - - dependency-name: actix-rt - versions: - - 2.0.0 - - 2.1.0 - - dependency-name: bytes - versions: - - 1.0.0 - - dependency-name: hyper - versions: - - 0.14.1 - - dependency-name: rand - versions: - - 0.8.0 + - package-ecosystem: cargo + directory: / + schedule: + interval: daily + + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index d7869b5b3..000000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Book - -on: - pull_request: - paths: - - 'docs/book/**' - push: - paths: - - 'docs/book/**' - -jobs: - tests: - name: Test code examples - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - - name: Test via skeptic - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path docs/book/tests/Cargo.toml - - deploy: - name: Deploy book on gh-pages - needs: [tests] - if: github.ref == 'refs/heads/master' - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install mdBook - uses: peaceiris/actions-mdbook@v1 - - - name: Render book - run: | - mdbook build -d gh-pages/master docs/book - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - keepFiles: true - publish_dir: docs/book/gh-pages diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index babf38677..d53190580 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,151 +1,390 @@ name: CI -on: [pull_request, push] +on: + push: + branches: ["master"] + tags: ["juniper*@*"] + pull_request: + branches: ["master"] -jobs: - ################################################### - # Formatting - ################################################### +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true - format: - name: Check formatting +env: + RUST_BACKTRACE: 1 - runs-on: ubuntu-latest +jobs: + ################ + # Pull Request # + ################ + + pr: + if: ${{ github.event_name == 'pull_request' + && !contains(github.event.head_commit.message, '[skip ci]') }} + needs: + - clippy + - example + - feature + - release-check + - rustfmt + - test + - wasm + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 + - run: true + + - - name: Install rust - uses: actions-rs/toolchain@v1 + + ########################## + # Linting and formatting # + ########################## + + clippy: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 with: - toolchain: stable - components: rustfmt profile: minimal - override: true + toolchain: stable + components: clippy + + - run: make cargo.lint - - name: Run rustfmt - uses: actions-rs/cargo@v1 + rustfmt: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper') + || !contains(github.event.head_commit.message, '[skip ci]') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 with: - command: fmt - args: -- --check + profile: minimal + toolchain: nightly + components: rustfmt + + - run: make cargo.fmt check=yes + - ################################################### - # Main Builds - ################################################### - build: - runs-on: ${{ matrix.os }} + ########### + # Testing # + ########### + + example: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper') + || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: fail-fast: false matrix: - rust: [stable, beta, nightly] - os: [ubuntu-latest, windows-latest, macOS-latest] - + example: + - actix_subscriptions + - basic_subscriptions + - warp_async + - warp_subscriptions + os: + - ubuntu + - macOS + - windows + toolchain: + - stable + - beta + - nightly + runs-on: ${{ matrix.os }}-latest steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install rust - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.rust }} profile: minimal + toolchain: ${{ matrix.toolchain }} override: true - - uses: davidB/rust-cargo-make@v1 + - run: cargo check -p example_${{ matrix.example }} + + feature: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper') + || !contains(github.event.head_commit.message, '[skip ci]') }} + strategy: + fail-fast: false + matrix: + include: + - { feature: , crate: juniper } + - { feature: bigdecimal, crate: juniper } + - { feature: bson, crate: juniper } + - { feature: chrono, crate: juniper } + - { feature: chrono-clock, crate: juniper } + - { feature: chrono-tz, crate: juniper } + - { feature: expose-test-schema, crate: juniper } + - { feature: graphql-parser, crate: juniper } + - { feature: rust_decimal, crate: juniper } + - { feature: schema-language, crate: juniper } + - { feature: serde_json, crate: juniper } + - { feature: time, crate: juniper } + - { feature: url, crate: juniper } + - { feature: uuid, crate: juniper } + - { feature: , crate: juniper_actix } + - { feature: subscriptions, crate: juniper_actix } + - { feature: , crate: juniper_warp } + - { feature: subscriptions, crate: juniper_warp } + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 with: - version: '0.20.0' + profile: minimal + toolchain: stable + override: true + + # TODO: Enable once MSRV is supported. + #- run: cargo +nightly update -Z minimal-versions - - name: Build and run tests + - run: cargo check -p ${{ matrix.crate }} --no-default-features + ${{ matrix.feature != '' + && format('--features {0}', matrix.feature) + || '' }} env: - CARGO_MAKE_RUN_CODECOV: true - run: | - cargo make workspace-ci-flow --no-workspace + RUSTFLAGS: -D warnings - ################################################### - # WASM Builds - ################################################### + package: + if: ${{ startsWith(github.ref, 'refs/tags/juniper') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable - wasm: - runs-on: ${{ matrix.os }} + - name: Parse crate name + id: crate + run: echo ::set-output + name=NAME::$(printf "$GITHUB_REF" | cut -d '/' -f3 + | cut -d '@' -f1) + - run: cargo package -p ${{ steps.crate.outputs.NAME }} + + test: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper') + || !contains(github.event.head_commit.message, '[skip ci]') }} strategy: + fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + crate: + - juniper_codegen + - juniper + - juniper_subscriptions + - juniper_graphql_ws + - juniper_integration_tests + - juniper_codegen_tests + - juniper_book_tests + - juniper_actix + - juniper_hyper + - juniper_iron + - juniper_rocket + - juniper_warp + os: + - ubuntu + - macOS + - windows + toolchain: + - stable + - beta + - nightly + exclude: + - crate: juniper_codegen_tests + toolchain: stable + - crate: juniper_codegen_tests + toolchain: beta + - crate: juniper_codegen_tests + os: macOS + - crate: juniper_codegen_tests + os: windows + - crate: juniper_book_tests + toolchain: beta + - crate: juniper_book_tests + toolchain: nightly + # TODO: LLVM ERROR: out of memory + - crate: juniper_integration_tests + os: windows + + runs-on: ${{ matrix.os }}-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + + - run: make test.cargo crate=${{ matrix.crate }} + wasm: + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper') + || !contains(github.event.head_commit.message, '[skip ci]') }} + strategy: + fail-fast: false + matrix: + crate: + - juniper_codegen + - juniper + toolchain: + - stable + - beta + - nightly + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + target: wasm32-unknown-unknown + override: true - - name: Install rust - uses: actions-rs/toolchain@v1 + - run: cargo check --target wasm32-unknown-unknown -p ${{ matrix.crate }} + + + + + ############# + # Releasing # + ############# + + release-check: + name: Check release automation + if: ${{ !startsWith(github.ref, 'refs/tags/juniper') + && (github.ref == 'refs/heads/master' + || !contains(github.event.head_commit.message, '[skip ci]')) }} + strategy: + fail-fast: false + matrix: + crate: + - juniper_codegen + - juniper + - juniper_subscriptions + - juniper_graphql_ws + - juniper_actix + - juniper_hyper + - juniper_iron + - juniper_rocket + - juniper_warp + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 with: + profile: minimal toolchain: stable - target: wasm32-unknown-unknown + + - run: cargo install cargo-release + + - run: make cargo.release crate=${{ matrix.crate }} ver=minor + exec=no install=no + + release-github: + name: Release on GitHub + needs: + - clippy + - example + - feature + - package + - rustfmt + - test + - wasm + if: ${{ startsWith(github.ref, 'refs/tags/juniper') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Parse crate name + id: crate + run: echo ::set-output + name=NAME::$(printf "$GITHUB_REF" | cut -d '/' -f3 + | cut -d '@' -f1) + - name: Parse release version + id: release + run: echo ::set-output + name=VERSION::$(printf "$GITHUB_REF" | cut -d '@' -f2) + + - name: Verify release version matches crate's Cargo manifest + run: >- + test "${{ steps.release.outputs.VERSION }}" \ + == "$(grep -m1 'version = "' ${{ steps.crate.outputs.NAME }}/Cargo.toml | cut -d '"' -f2)" + - name: Parse CHANGELOG link + id: changelog + run: echo ::set-output + name=LINK::https://github.com/${{ github.repository }}/blob/${{ steps.crate.outputs.NAME }}%40${{ steps.release.outputs.VERSION }}//${{ steps.crate.outputs.NAME }}/CHANGELOG.md#$(sed -n '/^## \[${{ steps.release.outputs.VERSION }}\]/{s/^## \[\(.*\)\][^0-9]*\([0-9].*\)/\1--\2/;s/[^0-9a-z-]*//g;p;}' ${{ steps.crate.outputs.NAME }}/CHANGELOG.md) + + - uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: ${{ steps.crate.outputs.NAME }} ${{ steps.release.outputs.VERSION }} + body: | + [API docs](https://docs.rs/${{ steps.crate.outputs.NAME }}/${{ steps.release.outputs.VERSION }}) + [Changelog](${{ steps.changelog.outputs.LINK }}) + prerelease: ${{ contains(steps.release.outputs.VERSION, '-') }} + + release-crate: + name: Release on crates.io + needs: ["release-github"] + if: ${{ startsWith(github.ref, 'refs/tags/juniper') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: profile: minimal - override: true + toolchain: stable + + - name: Parse crate name + id: crate + run: echo ::set-output + name=NAME::$(printf "$GITHUB_REF" | cut -d '/' -f3 + | cut -d '@' -f1) + + - name: Publish crate + run: cargo publish -p ${{ steps.crate.outputs.NAME }} + --token ${{ secrets.CRATESIO_TOKEN }} + + + + + ########## + # Deploy # + ########## + + deploy-book: + name: deploy Book + needs: ["test"] + if: ${{ github.ref == 'refs/heads/master' + || startsWith(github.ref, 'refs/tags/juniper@') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: peaceiris/actions-mdbook@v1 + + - run: make book.build out=gh-pages/master + if: ${{ github.ref == 'refs/heads/master' }} + + - run: make book.build out=gh-pages + if: ${{ startsWith(github.ref, 'refs/tags/juniper@') }} - - name: Check - uses: actions-rs/cargo@v1 + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 with: - command: check - args: --target wasm32-unknown-unknown --package juniper --package juniper_codegen - - ################################################### - # Releases - ################################################### - - #release: - # name: Check release automation - - # runs-on: ubuntu-latest - - # steps: - # - name: Checkout - # uses: actions/checkout@v2 - # with: - # fetch-depth: 20 - - # - name: Install rust - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # profile: minimal - # override: true - - # - uses: davidB/rust-cargo-make@v1 - # with: - # version: '0.20.0' - - # - name: Install cargo-release - # uses: actions-rs/cargo@v1 - # with: - # command: install - # args: --version=0.19.4 cargo-release - - # - name: Setup git - # run: | - # git config --global user.email "juniper@example.com" - # git config --global user.name "Release Test Bot" - - # - name: Dry run mode - # env: - # RELEASE_LEVEL: minor - # run: | - # cargo make release-dry-run - - # - name: Local test mode - # env: - # RELEASE_LEVEL: minor - # run: | - # cargo make release-local-test - - # - name: Echo local changes - # run: | - # git --no-pager log -p HEAD...HEAD~20 - - # - name: Run tests - # run: | - # cargo make workspace-ci-flow --no-workspace + github_token: ${{ secrets.GITHUB_TOKEN }} + keep_files: true + publish_dir: book/gh-pages diff --git a/.gitignore b/.gitignore index 64f40ab29..1e25d8ede 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ -target -Cargo.lock -.idea +/.idea/ +/.vscode/ +/*.iml +.DS_Store + +/Cargo.lock +/target/ diff --git a/rustfmt.toml b/.rustfmt.toml similarity index 100% rename from rustfmt.toml rename to .rustfmt.toml diff --git a/Cargo.toml b/Cargo.toml index a64f814f8..1a97d16ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,20 @@ [workspace] -# Order is important as this is the order the crates will be released. members = [ - "juniper_benchmarks", - "juniper_codegen", - "juniper", - "examples/basic_subscriptions", - "examples/warp_async", - "examples/warp_subscriptions", - "examples/actix_subscriptions", - "integration_tests/juniper_tests", - "integration_tests/async_await", - "integration_tests/codegen_fail", - "juniper_hyper", - "juniper_iron", - "juniper_rocket", - "juniper_subscriptions", - "juniper_graphql_ws", - "juniper_warp", - "juniper_actix", -] -exclude = [ - "docs/book/tests", + "benches", + "book/tests", + "examples/basic_subscriptions", + "examples/warp_async", + "examples/warp_subscriptions", + "examples/actix_subscriptions", + "juniper_codegen", + "juniper", + "juniper_hyper", + "juniper_iron", + "juniper_rocket", + "juniper_subscriptions", + "juniper_graphql_ws", + "juniper_warp", + "juniper_actix", + "tests/codegen", + "tests/integration", ] diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..e6a566bfa --- /dev/null +++ b/Makefile @@ -0,0 +1,139 @@ +############################### +# Common defaults/definitions # +############################### + +comma := , + +# Checks two given strings for equality. +eq = $(if $(or $(1),$(2)),$(and $(findstring $(1),$(2)),\ + $(findstring $(2),$(1))),1) + + + + +########### +# Aliases # +########### + +book: book.build + + +fmt: cargo.fmt + + +lint: cargo.lint + + +test: test.cargo + + +release: cargo.release + + + + +################## +# Cargo commands # +################## + +# Format Rust sources with rustfmt. +# +# Usage: +# make cargo.fmt [check=(no|yes)] + +cargo.fmt: + cargo +nightly fmt --all $(if $(call eq,$(check),yes),-- --check,) + + +# Lint Rust sources with Clippy. +# +# Usage: +# make cargo.lint + +cargo.lint: + cargo clippy --workspace --all-features -- -D warnings + + +# Release Rust crate. +# +# Read more about bump levels here: +# https://github.com/crate-ci/cargo-release/blob/master/docs/reference.md#bump-level +# +# Usage: +# make cargo.release crate= [ver=(release|)] +# ([exec=no]|exec=yes [push=(yes|no)]) +# [install=(yes|no)] + +cargo.release: +ifneq ($(install),no) + cargo install cargo-release +endif + cargo release -p $(crate) --all-features \ + $(if $(call eq,$(exec),yes),\ + --no-publish $(if $(call eq,$(push),no),--no-push,) --execute,\ + -v $(if $(call eq,$(CI),),,--no-publish)) \ + $(or $(ver),release) + + +cargo.test: test.cargo + + + + +#################### +# Testing commands # +#################### + +# Run Rust tests of Book. +# +# Usage: +# make test.book + +test.book: + @make test.cargo crate=juniper_book_tests + + +# Run Rust tests of project crates. +# +# Usage: +# make test.cargo [crate=] + +test.cargo: + cargo $(if $(call eq,$(crate),juniper_codegen_tests),+nightly,) test \ + $(if $(call eq,$(crate),),--workspace,-p $(crate)) --all-features + + + + +################# +# Book commands # +################# + +# Build Book. +# +# Usage: +# make book.build [out=] + +book.build: + mdbook build book/ $(if $(call eq,$(out),),,-d $(out)) + + +# Serve Book on some port. +# +# Usage: +# make book.serve [port=(3000|)] + +book.serve: + mdbook serve book/ -p=$(or $(port),3000) + + + + +################## +# .PHONY section # +################## + +.PHONY: book fmt lint release test \ + book.build book.serve \ + cargo.fmt cargo.lint cargo.release cargo.test \ + test.book test.cargo diff --git a/Makefile.toml b/Makefile.toml deleted file mode 100644 index a23a63ba7..000000000 --- a/Makefile.toml +++ /dev/null @@ -1,38 +0,0 @@ -# https://github.com/sagiegurari/cargo-make#automatically-extend-workspace-makefile -[env] -CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = "true" -CARGO_MAKE_CARGO_ALL_FEATURES = "" -CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = "integration_tests/*;examples/*;juniper_benchmarks;" - - -# Run `RELEASE_LEVEL=(patch|major|minor) cargo make release` to push a new release of every crate. -# -# Run `RELEASE_LEVEL=(patch|major|minor) CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS="crate1;crate2;" cargo make release` -# to push a new release of a subset of crates. -[tasks.release] -condition = { env_set = [ "RELEASE_LEVEL" ] } -command = "cargo-release" -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/../_build/release.toml", "--execute", "${RELEASE_LEVEL}"] - - -# Run `RELEASE_LEVEL=(patch|major|minor) cargo make release-dry-run` to do a dry run. -# -# Run `RELEASE_LEVEL=(patch|major|minor) CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS="crate1;crate2;" cargo make release-dry-run` -# to do a dry run of a subset of crates. -[tasks.release-dry-run] -condition = { env_set = [ "RELEASE_LEVEL" ] } -description = "Run `cargo-release --dry-run` for every crate" -command = "cargo-release" -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/../_build/release.toml", "--no-confirm", "--no-publish", "--no-push", "--no-tag", "${RELEASE_LEVEL}"] - - -# Run `RELEASE_LEVEL=(patch|major|minor) cargo make release-local-test` to actually make changes locally but -# not push them up to crates.io or Github. -# -# Run `RELEASE_LEVEL=(patch|major|minor) CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS="crate1;crate2;" cargo make release-local-test` to actually make changes locally but -# not push some crates up to crates.io or Github. -[tasks.release-local-test] -condition = { env_set = [ "RELEASE_LEVEL" ] } -description = "Run `cargo-release` for every crate, but only make changes locally" -command = "cargo-release" -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/../_build/release.toml", "--no-confirm", "${RELEASE_LEVEL}"] diff --git a/RELEASING.md b/RELEASING.md index 4fa10761d..1a869f6a2 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,74 +1,59 @@ -# How to release new crate versions +Releasing new crate versions +============================ -## Prerequisites - -It is generally best to start with a clean respository dedicated to a release so that no git weirdness happens: - -``` -git clone git@github.com:graphql-rust/juniper.git juniper_release; -cd juniper_release; -``` +Releasing of [workspace] crates of this project is performed by pushing the Git release tag (having `@` format), following by the [CI pipeline] creating a [GitHub release] and publishing the crate to [crates.io]. -We use the `nightly` toolchain when releasing. This is because some of our crates require nightly: +> __WARNING__: Only one [workspace] crate may be released at a time. So, if you need to release multiple [workspace] crates, do this sequentially. -`rustup default nightly` -We use [`cargo-make`](cargo-make) and [`cargo-release`](cargo-release) to automate crate releases. You will need to install them locally: -- `cargo install -f cargo-make` -- `cargo install -f cargo-release` -## Preparing for a release - -There are two general classes of releases: - -1. All public workspace crates should be released and all share the same release level ("patch", "minor", "major"). +## Prerequisites -2. A subset of workspace crates need to be released, or not all crate releases share the same release level. +We use [`cargo-release`] to automate crate releases. You will need to install it locally: +```bash +cargo install cargo-release +``` -**All release commands must be run from the root directory of the repository.** -## Determine new release level -For each crate, determine the desired release level (`patch`, `minor`, `major`). Set the `RELEASE_LEVEL` env variable to the desired release level. -## Determine which crates to release +## Preparing -If a subset of workspace crates need to be released, or not all crate releases share the same release level, set the `CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS` env -variable to choose specific workspace crates. The value is a list of semicolon-delineated crate names or a regular expressions. +To produce a new release a [workspace] crate, perform the following steps: -## Dry run +1. Check its `CHANGELOG.md` file to be complete and correctly formatted. The section for the new release __should start__ with `## master` header. Commit any changes you've made. -It is a good idea to do a dry run to sanity check what actions will be performed. +2. Determine a new release [bump level] (`patch`, `minor`, `major`, or default `release`). -- For case #1 above, run `cargo make release-dry-run`. -- For case #2 above, run `CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS="crate1;crate2" cargo make release-dry-run`. +3. Run the release process in dry-run mode and check the produced diffs to be made in the returned output. + ```bash + make release crate=juniper ver=minor + ``` - If the command finishes super quickly with no output you likely did not set `RELEASE_LEVEL`. +4. (Optional) Not everything may be captured in dry-run mode. It may be a good idea to run a local test, without pushing the created Git commit and tag. + ```bash + make release crate=juniper ver=minor exec=yes push=no + ``` -## Local test -Not everything is captured in the dry run. It is a good idea to run a local test. -In a local test, all the release actions are performed on your local checkout -but nothing is pushed to Github or crates.io. -- For case #1 above, run `cargo make release-local-test`. -- For case #2 above, run `CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS="crate1;crate2" cargo make release-local-test`. - If the command finishes super quickly with no output you likely did not set `RELEASE_LEVEL`. +## Executing -After, your local git repository should have the changes ready to push to Github. -Use `git rebase -i HEAD~10` and drop the new commits. +Once everything is prepared and checked, just execute the releasing process: +```bash +make release crate=juniper ver=minor exec=yes +``` -## Release +Once the [CI pipeline] for the pushed Git tag successfully finishes, the crate is fully released. -After testing locally and via a dry run, it is time to release. A release -consists of bumping versions, starting a new changelog section, pushing a tag to Github, and updating crates.io. This should all be handled by running the automated commands. -- For case #1 above, run `cargo make release`. -- For case #2 above, run `CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS="crate1;crate2" cargo make release`. - If the command finishes super quickly with no output you likely did not set `RELEASE_LEVEL`, -[cargo-make]: https://github.com/sagiegurari/cargo-make -[cargo-release]: https://github.com/sunng87/cargo-release +[`cargo-release`]: https://crates.io/crates/cargo-release +[CI pipeline]: /../../blob/master/.github/workflows/ci.yml +[crates.io]: https://crates.io +[GitHub release]: https://docs.github.com/repositories/releasing-projects-on-github/about-releases +[release level]: https://github.com/crate-ci/cargo-release/blob/master/docs/reference.md#bump-level +[workspace]: https://doc.rust-lang.org/cargo/reference/workspaces.html diff --git a/_build/azure-pipelines-template.yml b/_build/azure-pipelines-template.yml deleted file mode 100644 index 0352a9eb8..000000000 --- a/_build/azure-pipelines-template.yml +++ /dev/null @@ -1,71 +0,0 @@ -jobs: -- job: ${{ parameters.name }} - pool: - vmImage: ${{ parameters.vmImage }} - strategy: - matrix: - stable: - rustup_toolchain: stable - beta: - rustup_toolchain: beta - nightly: - rustup_toolchain: nightly - # TODO: re-enable once new versions are released. - # minimum_supported_version_plus_one: - # rustup_toolchain: 1.32.0 - #minimum_supported_version: - # rustup_toolchain: 1.33.0 - steps: - - ${{ if ne(parameters.name, 'Windows') }}: - # Linux and macOS. - - script: | - export CARGO_HOME="$HOME/.cargo" - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN - echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" - displayName: Install rust - - ${{ if eq(parameters.name, 'Windows') }}: - # Windows. - - script: | - set CARGO_HOME=%USERPROFILE%\.cargo - curl -sSf -o rustup-init.exe https://win.rustup.rs - rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" - displayName: Install rust (windows) - # All platforms. - - script: | - rustc -Vv - cargo -V - displayName: Query rust and cargo versions - - ${{ if eq(parameters.name, 'Linux') }}: - # Linux. - - script: _build/cargo-make.sh "0.20.0" "x86_64-unknown-linux-musl" - displayName: Install cargo-make binary - - ${{ if eq(parameters.name, 'macOS') }}: - # Mac. - - script: _build/cargo-make.sh "0.20.0" "x86_64-apple-darwin" - displayName: Install cargo-make binary - - ${{ if eq(parameters.name, 'Windows') }}: - # Windows. - - script: powershell -executionpolicy bypass -file _build/cargo-make.ps1 -version "0.20.0" -target "x86_64-pc-windows-msvc" - displayName: Install cargo-make binary - - ${{ if eq(parameters.name, 'Windows') }}: - # Windows. - - script: | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - cargo make workspace-ci-flow --no-workspace - env: { CARGO_MAKE_RUN_CODECOV: true } - displayName: Build and run tests - - ${{ if ne(parameters.name, 'Windows') }}: - # Not Windows. - - script: | - export PATH="$PATH:$HOME/.cargo/bin"; - cargo make workspace-ci-flow --no-workspace - env: { CARGO_MAKE_RUN_CODECOV: true } - displayName: Build and run tests - - - script: | - rustup target add wasm32-unknown-unknown - cargo check --target wasm32-unknown-unknown --package juniper --package juniper_codegen - displayName: Check WebAssembly target - condition: eq(variables['rustup_toolchain'], 'stable') diff --git a/_build/cargo-make.ps1 b/_build/cargo-make.ps1 deleted file mode 100644 index a1c7b5d63..000000000 --- a/_build/cargo-make.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -param ( - [Parameter(Mandatory=$true)][string]$version, - [Parameter(Mandatory=$true)][string]$target -) - -# Location to put cargo-make binary. -$cargobindir = "$env:userprofile\.cargo\bin" - -# Location to stage downloaded zip file. -$zipfile = "$env:temp\cargo-make.zip" - -$url = "https://github.com/sagiegurari/cargo-make/releases/download/${version}/cargo-make-v${version}-${target}.zip" - -# Download the zip file. -Invoke-WebRequest -Uri $url -OutFile $zipfile - -# Extract the binary to the correct location. -Expand-Archive -Path $zipfile -DestinationPath $cargobindir - -# Tell azure pipelines the PATH has changed for future steps. -Write-Host "##vso[task.setvariable variable=PATH;]%PATH%;$cargobindir" diff --git a/_build/cargo-make.sh b/_build/cargo-make.sh deleted file mode 100755 index a93d19dc0..000000000 --- a/_build/cargo-make.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -if [ -z ${1+x} ]; -then - echo "Missing version as first argument"; - exit 1 -fi - -if [ -z ${2+x} ]; -then - echo "Missing target as second argument"; - exit 1 -fi - -curl https://github.com/sagiegurari/cargo-make/releases/download/${1}/cargo-make-v${1}-${2}.zip -sSfL -o /tmp/cargo-make.zip; -unzip /tmp/cargo-make.zip; -mkdir -p "$HOME/.cargo/bin"; -mv cargo-make-*/* $HOME/.cargo/bin; -echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" diff --git a/_build/release.toml b/_build/release.toml deleted file mode 100644 index b0acba606..000000000 --- a/_build/release.toml +++ /dev/null @@ -1,6 +0,0 @@ -pre-release-commit-message = "Release {{crate_name}} {{version}}" -post-release-commit-message = "Bump {{crate_name}} version to {{next_version}}" -tag-message = "Release {{crate_name}} {{version}}" -pre-release-replacements = [ - {file="CHANGELOG.md", min=0, search="# master", replace="# master\n\n- Compatibility with the latest `juniper`.\n\n# [[{{version}}] {{date}}](https://github.com/graphql-rust/juniper/releases/tag/{{crate_name}}-{{version}})"}, -] diff --git a/_build/travis-juniper.enc b/_build/travis-juniper.enc deleted file mode 100644 index c0c769ae6..000000000 --- a/_build/travis-juniper.enc +++ /dev/null @@ -1 +0,0 @@ -´LÅØAtÙèù˜Û‡uÙË}k¦b÷Œ«iíãxN»ÔJÛb'OÅ’€6°¤ê·Äs–Þ`(¯vZeòaˆ°Î`ëPÝ‚W[DæW4¾®€Ïe¡·» «ï9¥¿4 ÀÖžgF÷‘ûa¸P‚ýŒ5ò õ/8FQܪ"Bß ~þBrº’¢Ž8@)†Ê0 yeÍĆ»·–²Š àCù·»º´F¹"m§±òö¡wûÑ?INŽTŠ(tõlß•SÀ%K_3ð¾2ò–§^Š^kMLMTJÛÕ½¾Z7cÁüzÖ&qÆsd²H‚¢^¨•‡ \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index d3dd337de..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,109 +0,0 @@ -jobs: - ################################################### - # Formatting - ################################################### - - - job: check_formatting - displayName: Check formatting - pool: - vmImage: 'ubuntu-latest' - steps: - - script: | - export CARGO_HOME="$HOME/.cargo" - curl https://sh.rustup.rs -sSf | sh -s -- -y - $HOME/.cargo/bin/rustup component add rustfmt - displayName: Install stable Rust - - script: | - $HOME/.cargo/bin/cargo fmt -- --check - displayName: Run rustfmt - - ################################################### - # Book - ################################################### - - - job: run_book_tests - displayName: Book code example tests - pool: - vmImage: 'ubuntu-latest' - steps: - - script: | - export CARGO_HOME="$HOME/.cargo" - curl https://sh.rustup.rs -sSf | sh -s -- -y - $HOME/.cargo/bin/rustup component add rustfmt - displayName: Install stable Rust - - script: | - cd docs/book/tests && $HOME/.cargo/bin/cargo test - displayName: Test book code examples via skeptic - - - job: build_book_master - displayName: Build rendered book on master branch and push to Github - pool: - vmImage: 'ubuntu-latest' - dependsOn: run_book_tests - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) - variables: - - group: github-keys - steps: - - task: InstallSSHKey@0 - inputs: - hostName: $(GHSshKnownHosts) - sshPublicKey: $(GHSshPub) - sshKeySecureFile: $(GHSshPriv) - - script: | - ./docs/book/ci-build.sh master - - ################################################### - # Main Builds - ################################################### - - - template: _build/azure-pipelines-template.yml - parameters: - name: Linux - vmImage: 'ubuntu-latest' - - - template: _build/azure-pipelines-template.yml - parameters: - name: macOS - vmImage: 'macOS-latest' - - - template: _build/azure-pipelines-template.yml - parameters: - name: Windows - vmImage: 'windows-latest' - - ################################################### - # Releases - ################################################### - - #- job: check_release_automation - # displayName: Check release automation - # pool: - # vmImage: 'ubuntu-latest' - # steps: - # - script: | - # export CARGO_HOME="$HOME/.cargo" - # curl https://sh.rustup.rs -sSf | sh -s -- -y - # echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" - # displayName: Install stable Rust - # - script: | - # _build/cargo-make.sh "0.20.0" "x86_64-unknown-linux-musl" - # displayName: Install cargo-make binary - # - script: | - # cargo install --debug --version=0.19.4 cargo-release - # displayName: Install cargo-release binary - # - script: | - # git config --local user.name "Release Test Bot" - # git config --local user.email "juniper@example.com" - # displayName: Set up git - # - script: | - # RELEASE_LEVEL="minor" cargo make release-dry-run - # displayName: Dry run mode - # - script: | - # RELEASE_LEVEL="minor" cargo make release-local-test - # displayName: Local test mode - # - script: | - # git --no-pager log -p HEAD...HEAD~20 - # # NOTE: this is rolled into one task due to onerous - # # "bash not found" error on Azure. - # cargo make workspace-ci-flow --no-workspace - # displayName: Echo local changes && make sure build and tests still work diff --git a/juniper_benchmarks/Cargo.toml b/benches/Cargo.toml similarity index 73% rename from juniper_benchmarks/Cargo.toml rename to benches/Cargo.toml index d2f499938..c5014f5af 100644 --- a/juniper_benchmarks/Cargo.toml +++ b/benches/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "juniper_benchmarks" -version = "0.1.0" +version = "0.0.0" edition = "2018" authors = ["Christoph Herzog "] +publish = false [dependencies] futures = "0.3" @@ -10,7 +11,7 @@ juniper = { path = "../juniper" } [dev-dependencies] criterion = "0.3" -tokio = { version = "1", features = ["rt-multi-thread"] } +tokio = { version = "1.0", features = ["rt-multi-thread"] } [[bench]] name = "benchmark" diff --git a/juniper_benchmarks/benches/benchmark.rs b/benches/benches/benchmark.rs similarity index 100% rename from juniper_benchmarks/benches/benchmark.rs rename to benches/benches/benchmark.rs diff --git a/juniper_benchmarks/src/lib.rs b/benches/src/lib.rs similarity index 100% rename from juniper_benchmarks/src/lib.rs rename to benches/src/lib.rs diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 000000000..130662c47 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1,2 @@ +/_rendered/ +/gh-pages/ diff --git a/book/README.md b/book/README.md new file mode 100644 index 000000000..f4b28d98c --- /dev/null +++ b/book/README.md @@ -0,0 +1,67 @@ +Juniper Book +============ + +Book containing the [`juniper`](https://docs.rs/juniper) user guide. + + + + +## Contributing + + +### Requirements + +The Book is built with [mdBook](https://github.com/rust-lang/mdBook). + +You may install it with: +```bash +cargo install mdbook +``` + + +### Local test server + +To launch a local test server that continually re-builds the Book and auto-reloads the page, run: +```bash +mdbook serve + +# or from project root dir: +make book.serve +``` + + +### Building + +You may build the Book to rendered HTML with this command: +```bash +mdbook build + +# or from project root dir: +make book +``` + +The output will be in the `_rendered/` directory. + + +### Testing + +To run the tests validating all code examples in the book, run: +```bash +cd tests/ +cargo test + +# or from project root dir: +cargo test -p juniper_book_tests + +# or via shortcut from project root dir: +make test.book +``` + + + + +## Test setup + +All Rust code examples in the book are compiled on the CI. + +This is done using the [skeptic](https://github.com/budziq/rust-skeptic) crate. diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 000000000..9fc671244 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,16 @@ +[book] +title = "Juniper Book (GraphQL server for Rust)" +description = "User guide for Juniper (GraphQL server library for Rust)." +language = "en" +multilingual = false +src = "src" + +[build] +build-dir = "_rendered" +create-missing = false + +[output.html] +git_repository_url = "https://github.com/graphql-rs/juniper" + +[rust] +edition = "2018" diff --git a/docs/book/content/README.md b/book/src/README.md similarity index 100% rename from docs/book/content/README.md rename to book/src/README.md diff --git a/docs/book/content/SUMMARY.md b/book/src/SUMMARY.md similarity index 100% rename from docs/book/content/SUMMARY.md rename to book/src/SUMMARY.md diff --git a/docs/book/content/advanced/dataloaders.md b/book/src/advanced/dataloaders.md similarity index 98% rename from docs/book/content/advanced/dataloaders.md rename to book/src/advanced/dataloaders.md index fe172d1db..560f60488 100644 --- a/docs/book/content/advanced/dataloaders.md +++ b/book/src/advanced/dataloaders.md @@ -50,12 +50,12 @@ DataLoader caching does not replace Redis, Memcache, or any other shared applica [dependencies] actix-identity = "0.4.0-beta.4" actix-rt = "1.0" -actix-web = {version = "2.0", features = []} -juniper = { git = "https://github.com/graphql-rust/juniper" } +actix-web = "2.0" +async-trait = "0.1.30" +dataloader = "0.12.0" futures = "0.3" +juniper = "0.16.0" postgres = "0.15.2" -dataloader = "0.12.0" -async-trait = "0.1.30" ``` ```rust, ignore diff --git a/docs/book/content/advanced/implicit_and_explicit_null.md b/book/src/advanced/implicit_and_explicit_null.md similarity index 100% rename from docs/book/content/advanced/implicit_and_explicit_null.md rename to book/src/advanced/implicit_and_explicit_null.md diff --git a/docs/book/content/advanced/index.md b/book/src/advanced/index.md similarity index 100% rename from docs/book/content/advanced/index.md rename to book/src/advanced/index.md diff --git a/docs/book/content/advanced/introspection.md b/book/src/advanced/introspection.md similarity index 100% rename from docs/book/content/advanced/introspection.md rename to book/src/advanced/introspection.md diff --git a/docs/book/content/advanced/multiple_ops_per_request.md b/book/src/advanced/multiple_ops_per_request.md similarity index 100% rename from docs/book/content/advanced/multiple_ops_per_request.md rename to book/src/advanced/multiple_ops_per_request.md diff --git a/docs/book/content/advanced/non_struct_objects.md b/book/src/advanced/non_struct_objects.md similarity index 100% rename from docs/book/content/advanced/non_struct_objects.md rename to book/src/advanced/non_struct_objects.md diff --git a/docs/book/content/advanced/objects_and_generics.md b/book/src/advanced/objects_and_generics.md similarity index 100% rename from docs/book/content/advanced/objects_and_generics.md rename to book/src/advanced/objects_and_generics.md diff --git a/docs/book/content/advanced/subscriptions.md b/book/src/advanced/subscriptions.md similarity index 97% rename from docs/book/content/advanced/subscriptions.md rename to book/src/advanced/subscriptions.md index 05e01d64b..90877c42c 100644 --- a/docs/book/content/advanced/subscriptions.md +++ b/book/src/advanced/subscriptions.md @@ -12,8 +12,8 @@ be returned to the end user. The [`juniper_subscriptions`][juniper_subscriptions provides a default connection implementation. Currently subscriptions are only supported on the `master` branch. Add the following to your `Cargo.toml`: ```toml [dependencies] -juniper = { git = "https://github.com/graphql-rust/juniper", branch = "master" } -juniper_subscriptions = { git = "https://github.com/graphql-rust/juniper", branch = "master" } +juniper = "0.16.0" +juniper_subscriptions = "0.17.0" ``` ### Schema Definition diff --git a/docs/book/content/quickstart.md b/book/src/quickstart.md similarity index 99% rename from docs/book/content/quickstart.md rename to book/src/quickstart.md index 1fbcd882a..b8ff72269 100644 --- a/docs/book/content/quickstart.md +++ b/book/src/quickstart.md @@ -8,7 +8,7 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch ```toml [dependencies] -juniper = "0.15" +juniper = "0.16.0" ``` ## Schema example diff --git a/docs/book/content/schema/schemas_and_mutations.md b/book/src/schema/schemas_and_mutations.md similarity index 99% rename from docs/book/content/schema/schemas_and_mutations.md rename to book/src/schema/schemas_and_mutations.md index c7ecc7d38..2502f83d2 100644 --- a/docs/book/content/schema/schemas_and_mutations.md +++ b/book/src/schema/schemas_and_mutations.md @@ -101,6 +101,7 @@ schema { query: Query } "; +# #[cfg(not(target_os = "windows"))] assert_eq!(result, expected); } ``` diff --git a/docs/book/content/servers/hyper.md b/book/src/servers/hyper.md similarity index 95% rename from docs/book/content/servers/hyper.md rename to book/src/servers/hyper.md index a9c0c7b81..f53f25c8d 100644 --- a/docs/book/content/servers/hyper.md +++ b/book/src/servers/hyper.md @@ -16,8 +16,8 @@ Juniper's Hyper integration is contained in the [`juniper_hyper`][juniper_hyper] ```toml [dependencies] -juniper = "0.15.7" -juniper_hyper = "0.8.0" +juniper = "0.16.0" +juniper_hyper = "0.9.0" ``` Included in the source is a [small example][example] which sets up a basic GraphQL and [GraphiQL] handler. diff --git a/docs/book/content/servers/index.md b/book/src/servers/index.md similarity index 100% rename from docs/book/content/servers/index.md rename to book/src/servers/index.md diff --git a/docs/book/content/servers/iron.md b/book/src/servers/iron.md similarity index 98% rename from docs/book/content/servers/iron.md rename to book/src/servers/iron.md index 7fd92e839..9c81fc4b5 100644 --- a/docs/book/content/servers/iron.md +++ b/book/src/servers/iron.md @@ -11,8 +11,8 @@ Juniper's Iron integration is contained in the `juniper_iron` crate: ```toml [dependencies] -juniper = "0.15.7" -juniper_iron = "0.7.4" +juniper = "0.16.0" +juniper_iron = "0.8.0" ``` Included in the source is a [small diff --git a/docs/book/content/servers/official.md b/book/src/servers/official.md similarity index 100% rename from docs/book/content/servers/official.md rename to book/src/servers/official.md diff --git a/docs/book/content/servers/rocket.md b/book/src/servers/rocket.md similarity index 94% rename from docs/book/content/servers/rocket.md rename to book/src/servers/rocket.md index 320e3d903..bf9768120 100644 --- a/docs/book/content/servers/rocket.md +++ b/book/src/servers/rocket.md @@ -10,8 +10,8 @@ Juniper's Rocket integration is contained in the [`juniper_rocket`][juniper_rock ```toml [dependencies] -juniper = "0.15.7" -juniper_rocket = "0.8.0" +juniper = "0.16.0" +juniper_rocket = "0.9.0" ``` Included in the source is a [small example][example] which sets up a basic GraphQL and [GraphiQL] handler. diff --git a/docs/book/content/servers/third-party.md b/book/src/servers/third-party.md similarity index 100% rename from docs/book/content/servers/third-party.md rename to book/src/servers/third-party.md diff --git a/docs/book/content/servers/warp.md b/book/src/servers/warp.md similarity index 95% rename from docs/book/content/servers/warp.md rename to book/src/servers/warp.md index ef5a30245..1f7338eb2 100644 --- a/docs/book/content/servers/warp.md +++ b/book/src/servers/warp.md @@ -10,8 +10,8 @@ Juniper's Warp integration is contained in the [`juniper_warp`][juniper_warp] cr ```toml [dependencies] -juniper = "0.15.7" -juniper_warp = "0.7.0" +juniper = "0.16.0" +juniper_warp = "0.8.0" ``` Included in the source is a [small example][example] which sets up a basic GraphQL and [GraphiQL] handler. diff --git a/docs/book/content/styles/website.css b/book/src/styles/website.css similarity index 100% rename from docs/book/content/styles/website.css rename to book/src/styles/website.css diff --git a/docs/book/content/types/enums.md b/book/src/types/enums.md similarity index 100% rename from docs/book/content/types/enums.md rename to book/src/types/enums.md diff --git a/docs/book/content/types/index.md b/book/src/types/index.md similarity index 100% rename from docs/book/content/types/index.md rename to book/src/types/index.md diff --git a/docs/book/content/types/input_objects.md b/book/src/types/input_objects.md similarity index 100% rename from docs/book/content/types/input_objects.md rename to book/src/types/input_objects.md diff --git a/docs/book/content/types/interfaces.md b/book/src/types/interfaces.md similarity index 100% rename from docs/book/content/types/interfaces.md rename to book/src/types/interfaces.md diff --git a/docs/book/content/types/objects/complex_fields.md b/book/src/types/objects/complex_fields.md similarity index 100% rename from docs/book/content/types/objects/complex_fields.md rename to book/src/types/objects/complex_fields.md diff --git a/docs/book/content/types/objects/defining_objects.md b/book/src/types/objects/defining_objects.md similarity index 100% rename from docs/book/content/types/objects/defining_objects.md rename to book/src/types/objects/defining_objects.md diff --git a/docs/book/content/types/objects/error_handling.md b/book/src/types/objects/error_handling.md similarity index 100% rename from docs/book/content/types/objects/error_handling.md rename to book/src/types/objects/error_handling.md diff --git a/docs/book/content/types/objects/using_contexts.md b/book/src/types/objects/using_contexts.md similarity index 100% rename from docs/book/content/types/objects/using_contexts.md rename to book/src/types/objects/using_contexts.md diff --git a/docs/book/content/types/other-index.md b/book/src/types/other-index.md similarity index 100% rename from docs/book/content/types/other-index.md rename to book/src/types/other-index.md diff --git a/docs/book/content/types/scalars.md b/book/src/types/scalars.md similarity index 100% rename from docs/book/content/types/scalars.md rename to book/src/types/scalars.md diff --git a/docs/book/content/types/unions.md b/book/src/types/unions.md similarity index 100% rename from docs/book/content/types/unions.md rename to book/src/types/unions.md diff --git a/book/tests/Cargo.toml b/book/tests/Cargo.toml new file mode 100644 index 000000000..f73d6fb9c --- /dev/null +++ b/book/tests/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "juniper_book_tests" +version = "0.0.0" +edition = "2018" +authors = ["Magnus Hallin "] +publish = false + +[dependencies] +derive_more = "0.99" +futures = "0.3" +iron = "0.6" +juniper = { path = "../../juniper" } +juniper_iron = { path = "../../juniper_iron" } +juniper_subscriptions = { path = "../../juniper_subscriptions" } +mount = "0.4" +serde_json = "1.0" +skeptic = "0.13" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "sync"] } +uuid = "1.0" + +[build-dependencies] +skeptic = "0.13" diff --git a/book/tests/build.rs b/book/tests/build.rs new file mode 100644 index 000000000..80e0cbe18 --- /dev/null +++ b/book/tests/build.rs @@ -0,0 +1,4 @@ +fn main() { + let files = skeptic::markdown_files_of_directory("../src/"); + skeptic::generate_doc_tests(&files); +} diff --git a/docs/book/tests/src/lib.rs b/book/tests/src/lib.rs similarity index 100% rename from docs/book/tests/src/lib.rs rename to book/tests/src/lib.rs diff --git a/docs/book/.gitignore b/docs/book/.gitignore deleted file mode 100644 index 04b969210..000000000 --- a/docs/book/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_rendered diff --git a/docs/book/README.md b/docs/book/README.md deleted file mode 100644 index 3c33ac516..000000000 --- a/docs/book/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Juniper Book - -Book containing the Juniper documentation. - -## Contributing - -### Requirements - -The book is built with [mdBook](https://github.com/rust-lang-nursery/mdBook). - -You can install it with: - -```bash -cargo install mdbook -``` - -### Starting a local test server - -To launch a local test server that continually re-builds the book and autoreloads the page, run: - -```bash -mdbook serve -``` - -### Building the book - -You can build the book to rendered HTML with this command: - -```bash -mdbook build -``` - -The output will be in the `./_rendered` directory. - -### Running the tests - -To run the tests validating all code examples in the book, run: - -```bash -cd ./tests -cargo test -``` - -## Test setup - -All Rust code examples in the book are compiled on the CI. - -This is done using the [skeptic](https://github.com/budziq/rust-skeptic) library. diff --git a/docs/book/book.toml b/docs/book/book.toml deleted file mode 100644 index 242dc1b19..000000000 --- a/docs/book/book.toml +++ /dev/null @@ -1,11 +0,0 @@ -[book] -title = "Juniper - GraphQL Server for Rust" -description = "Documentation for juniper, a GraphQL server library for Rust." -src = "content" - -[build] -build-dir = "_rendered" -create-missing = false - -[output.html] -git_repository_url = "https://github.com/graphql-rs/juniper" diff --git a/docs/book/ci-build.sh b/docs/book/ci-build.sh deleted file mode 100755 index 3b00bb4ec..000000000 --- a/docs/book/ci-build.sh +++ /dev/null @@ -1,52 +0,0 @@ -#! /usr/bin/env bash - -# Usage: ./ci-build.sh VERSION -# -# This script builds the book to HTML with mdbook -# commits and pushes the contents to the repo in the "gh-pages" branch. -# -# It is only inteded for use on the CI! - -# Enable strict error checking. -set -exo pipefail - -DIR=$(dirname $(readlink -f $0)) -MDBOOK="mdbook" - -cd $DIR - -# Verify version argument. - -if [[ -z "$1" ]]; then - echo "Missing required argument 'version': cargo make build-book VERSION" - exit -fi -VERSION="$1" - -# Download mdbook if not found. - -if [ $MDBOOK -h ]; then - echo "mdbook found..." -else - echo "mdbook not found. Downloading..." - curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v0.2.0/mdbook-v0.2.0-x86_64-unknown-linux-gnu.tar.gz | tar xzf - - mv ./mdbook /tmp/mdbook - MDBOOK="/tmp/mdbook" -fi - -$MDBOOK build -echo $VERSION > ./_rendered/VERSION -rm -rf /tmp/book-content -mv ./_rendered /tmp/book-content - -cd $DIR/../.. -git clean -fd -git checkout gh-pages -rm -rf $VERSION -mv /tmp/book-content ./$VERSION -git remote set-url --push origin git@github.com:graphql-rust/juniper.git -git config --local user.name "Juniper Bot" -git config --local user.email "juniper@example.com" -git add -A $VERSION -git diff-index --quiet HEAD || git commit -m "Updated book for $VERSION ***NO_CI***" -git push origin gh-pages diff --git a/docs/book/tests/.gitignore b/docs/book/tests/.gitignore deleted file mode 100644 index 6aa106405..000000000 --- a/docs/book/tests/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -**/*.rs.bk -Cargo.lock diff --git a/docs/book/tests/Cargo.toml b/docs/book/tests/Cargo.toml deleted file mode 100644 index c67ebe939..000000000 --- a/docs/book/tests/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "juniper_book_tests" -version = "0.1.0" -edition = "2018" -authors = ["Magnus Hallin "] -build = "build.rs" - -[dependencies] -juniper = { path = "../../../juniper" } -juniper_iron = { path = "../../../juniper_iron" } -juniper_subscriptions = { path = "../../../juniper_subscriptions" } - -derive_more = "0.99" -futures = "0.3" -iron = "0.5" -mount = "0.4" -skeptic = "0.13" -serde_json = "1.0" -tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } -uuid = "0.8" - -[build-dependencies] -skeptic = "0.13" - -[patch.crates-io] -juniper_codegen = { path = "../../../juniper_codegen" } diff --git a/docs/book/tests/build.rs b/docs/book/tests/build.rs deleted file mode 100644 index 425f644b2..000000000 --- a/docs/book/tests/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - let files = skeptic::markdown_files_of_directory("../content/"); - skeptic::generate_doc_tests(&files); -} diff --git a/examples/actix_subscriptions/Cargo.toml b/examples/actix_subscriptions/Cargo.toml index cec502e39..9bab7b3bc 100644 --- a/examples/actix_subscriptions/Cargo.toml +++ b/examples/actix_subscriptions/Cargo.toml @@ -1,20 +1,20 @@ [package] -name = "actix_subscriptions" -version = "0.1.0" +name = "example_actix_subscriptions" +version = "0.0.0" edition = "2018" authors = ["Mihai Dinculescu "] publish = false [dependencies] -actix-web = "4.0" actix-cors = "0.6" -futures = "0.3" -env_logger = "0.9" -serde = "1.0" -serde_json = "1.0" -rand = "0.8" -tokio = "1.0" +actix-web = "4.0" async-stream = "0.3" +env_logger = "0.9" +futures = "0.3" juniper = { path = "../../juniper", features = ["expose-test-schema"] } juniper_actix = { path = "../../juniper_actix", features = ["subscriptions"] } juniper_graphql_ws = { path = "../../juniper_graphql_ws" } +rand = "0.8" +serde = "1.0" +serde_json = "1.0" +tokio = "1.0" diff --git a/examples/actix_subscriptions/Makefile.toml b/examples/actix_subscriptions/Makefile.toml deleted file mode 100644 index dccba933b..000000000 --- a/examples/actix_subscriptions/Makefile.toml +++ /dev/null @@ -1,15 +0,0 @@ -[tasks.run] -disabled = true - -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true diff --git a/examples/basic_subscriptions/.gitignore b/examples/basic_subscriptions/.gitignore deleted file mode 100644 index eb5a316cb..000000000 --- a/examples/basic_subscriptions/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/basic_subscriptions/Cargo.toml b/examples/basic_subscriptions/Cargo.toml index 8367cd167..f03bdef4d 100644 --- a/examples/basic_subscriptions/Cargo.toml +++ b/examples/basic_subscriptions/Cargo.toml @@ -1,15 +1,14 @@ [package] -name = "basic_subscriptions" -version = "0.1.0" +name = "example_basic_subscriptions" +version = "0.0.0" edition = "2018" -publish = false authors = ["Jordao Rosario "] +publish = false [dependencies] futures = "0.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } - juniper = { path = "../../juniper" } juniper_subscriptions = { path = "../../juniper_subscriptions" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } diff --git a/examples/basic_subscriptions/Makefile.toml b/examples/basic_subscriptions/Makefile.toml deleted file mode 100644 index 13dc05ed3..000000000 --- a/examples/basic_subscriptions/Makefile.toml +++ /dev/null @@ -1,15 +0,0 @@ -[tasks.run] -disabled = true - -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true \ No newline at end of file diff --git a/examples/warp_async/.gitignore b/examples/warp_async/.gitignore deleted file mode 100644 index eb5a316cb..000000000 --- a/examples/warp_async/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/warp_async/Cargo.toml b/examples/warp_async/Cargo.toml index 8c6f70c55..94208317e 100644 --- a/examples/warp_async/Cargo.toml +++ b/examples/warp_async/Cargo.toml @@ -1,17 +1,16 @@ [package] -name = "warp_async" -version = "0.1.0" +name = "example_warp_async" +version = "0.0.0" edition = "2018" -publish = false authors = ["Christoph Herzog "] +publish = false [dependencies] +env_logger = "0.9" +futures = "0.3" juniper = { path = "../../juniper" } juniper_warp = { path = "../../juniper_warp" } - -env_logger = "0.9" -futures = "0.3.1" -log = "0.4.8" +log = "0.4" reqwest = { version = "0.11", features = ["rustls-tls"] } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } warp = "0.3" diff --git a/examples/warp_async/Makefile.toml b/examples/warp_async/Makefile.toml deleted file mode 100644 index 13dc05ed3..000000000 --- a/examples/warp_async/Makefile.toml +++ /dev/null @@ -1,15 +0,0 @@ -[tasks.run] -disabled = true - -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true \ No newline at end of file diff --git a/examples/warp_subscriptions/.gitignore b/examples/warp_subscriptions/.gitignore deleted file mode 100644 index eb5a316cb..000000000 --- a/examples/warp_subscriptions/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/warp_subscriptions/Cargo.toml b/examples/warp_subscriptions/Cargo.toml index 4c0b0025d..6e22c5a73 100644 --- a/examples/warp_subscriptions/Cargo.toml +++ b/examples/warp_subscriptions/Cargo.toml @@ -1,19 +1,18 @@ [package] -name = "warp_subscriptions" -version = "0.1.0" +name = "example_warp_subscriptions" +version = "0.0.0" edition = "2018" publish = false [dependencies] +async-stream = "0.3" env_logger = "0.9" futures = "0.3.1" +juniper = { path = "../../juniper" } +juniper_graphql_ws = { path = "../../juniper_graphql_ws" } +juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] } log = "0.4.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } warp = "0.3" -async-stream = "0.3" - -juniper = { path = "../../juniper" } -juniper_graphql_ws = { path = "../../juniper_graphql_ws" } -juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] } diff --git a/examples/warp_subscriptions/Makefile.toml b/examples/warp_subscriptions/Makefile.toml deleted file mode 100644 index 13dc05ed3..000000000 --- a/examples/warp_subscriptions/Makefile.toml +++ /dev/null @@ -1,15 +0,0 @@ -[tasks.run] -disabled = true - -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true \ No newline at end of file diff --git a/integration_tests/async_await/Cargo.toml b/integration_tests/async_await/Cargo.toml deleted file mode 100644 index b625a33f9..000000000 --- a/integration_tests/async_await/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "async_await" -version = "0.1.0" -edition = "2018" -authors = ["Christoph Herzog "] -publish = false - -[dependencies] -juniper = { path = "../../juniper" } -futures = "0.3.1" -tokio = { version = "1", features = ["rt", "time", "macros"] } \ No newline at end of file diff --git a/integration_tests/async_await/src/main.rs b/integration_tests/async_await/src/main.rs deleted file mode 100644 index 2b913a7e9..000000000 --- a/integration_tests/async_await/src/main.rs +++ /dev/null @@ -1,167 +0,0 @@ -use juniper::{graphql_object, GraphQLEnum}; - -#[derive(GraphQLEnum)] -enum UserKind { - Admin, - User, - Guest, -} - -struct User { - #[allow(dead_code)] - id: i32, - name: String, - kind: UserKind, -} - -#[graphql_object] -impl User { - async fn id(&self) -> i32 { - self.id - } - - async fn name(&self) -> &str { - &self.name - } - - async fn friends(&self) -> Vec { - (0..10) - .map(|index| User { - id: index, - name: format!("user{}", index), - kind: UserKind::User, - }) - .collect() - } - - async fn kind(&self) -> &UserKind { - &self.kind - } - - async fn delayed() -> bool { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - true - } -} - -struct Query; - -#[graphql_object] -impl Query { - fn field_sync(&self) -> &'static str { - "field_sync" - } - - async fn field_async_plain() -> String { - "field_async_plain".to_string() - } - - fn user(id: String) -> User { - User { - id: 1, - name: id, - kind: UserKind::User, - } - } - - async fn delayed() -> bool { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - true - } -} - -fn main() {} - -#[cfg(test)] -mod tests { - use juniper::{graphql_value, EmptyMutation, EmptySubscription, GraphQLError, RootNode, Value}; - - use super::Query; - - #[tokio::test] - async fn async_simple() { - let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new()); - let doc = r#"query { - fieldSync - fieldAsyncPlain - delayed - user(id: "user1") { - kind - name - delayed - } - }"#; - - let vars = Default::default(); - let (res, errs) = juniper::execute(doc, None, &schema, &vars, &()) - .await - .unwrap(); - - assert!(errs.is_empty()); - - let obj = res.into_object().unwrap(); - let value = Value::Object(obj); - - assert_eq!( - value, - graphql_value!({ - "delayed": true, - "fieldAsyncPlain": "field_async_plain", - "fieldSync": "field_sync", - "user": { - "delayed": true, - "kind": "USER", - "name": "user1", - }, - }), - ); - } - - #[tokio::test] - async fn async_field_validation_error() { - let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new()); - let doc = r#"query { - nonExistentField - fieldSync - fieldAsyncPlain - delayed - user(id: "user1") { - kind - name - delayed - } - }"#; - - let vars = Default::default(); - let result = juniper::execute(doc, None, &schema, &vars, &()).await; - assert!(result.is_err()); - - let error = result.err().unwrap(); - let is_validation_error = match error { - GraphQLError::ValidationError(_) => true, - _ => false, - }; - assert!(is_validation_error); - } - - // FIXME: test seems broken by design, re-enable later - // #[tokio::test] - // async fn resolve_into_stream_validation_error() { - // let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new()); - // let doc = r#" - // subscription { - // nonExistent - // } - // "#; - // let vars = Default::default(); - // let result = juniper::resolve_into_stream(doc, None, &schema, &vars, &()).await; - // assert!(result.is_err()); - - // let error = result.err().unwrap(); - // let is_validation_error = match error { - // GraphQLError::ValidationError(_) => true, - // _ => false, - // }; - // assert!(is_validation_error); - // } -} diff --git a/integration_tests/codegen_fail/Makefile.toml b/integration_tests/codegen_fail/Makefile.toml deleted file mode 100644 index 23316355b..000000000 --- a/integration_tests/codegen_fail/Makefile.toml +++ /dev/null @@ -1,29 +0,0 @@ -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true - -[tasks.test] -condition = { channels = ["nightly"] } -[tasks.test-custom] -condition = { channels = ["nightly"] } -[tasks.test-flow] -condition = { channels = ["nightly"] } -[tasks.test-multi-flow-phase] -condition = { channels = ["nightly"] } -[tasks.test-thread-safe] -condition = { channels = ["nightly"] } -[tasks.test-verbose] -condition = { channels = ["nightly"] } -[tasks.test-with-args] -condition = { channels = ["nightly"] } -[tasks.ci-coverage-flow] -condition = { channels = ["nightly"] } \ No newline at end of file diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr deleted file mode 100644 index 60a8f73cd..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 - | -16 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr deleted file mode 100644 index ecd251f1a..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/struct/attr_field_non_output_return_type.rs:10:9 - | -10 | id: ObjB, - | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr deleted file mode 100644 index c87ad2e06..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: duplicated attribute argument found - --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:9:34 - | -9 | #[graphql_interface(for = [ObjA, ObjA])] - | ^^^^ - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:4:18 - | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr deleted file mode 100644 index 47856d5d4..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/attr_missing_field.rs:11:5 - | -11 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr deleted file mode 100644 index 213ecb20d..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/attr_non_subtype_return.rs:11:5 - | -11 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/attr_non_subtype_return.rs:11:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr deleted file mode 100644 index cfe81a29b..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 - | -17 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr deleted file mode 100644 index fc418f480..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/struct/derive_field_non_output_return_type.rs:10:9 - | -10 | id: ObjB, - | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr deleted file mode 100644 index a4a803789..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: duplicated attribute argument found - --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:10:24 - | -10 | #[graphql(for = [ObjA, ObjA])] - | ^^^^ - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:4:18 - | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr deleted file mode 100644 index a8570a6cf..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/derive_missing_field.rs:12:5 - | -12 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr deleted file mode 100644 index 46c9de9de..000000000 --- a/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/struct/derive_non_subtype_return.rs:12:5 - | -12 | id: String, - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/derive_non_subtype_return.rs:12:5 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr deleted file mode 100644 index f513ef68e..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/additional_non_nullable_argument.rs:16:8 - | -16 | fn id(&self) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/trait/additional_non_nullable_argument.rs:16:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr deleted file mode 100644 index 2c6cf3a9f..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/trait/field_non_output_return_type.rs:10:21 - | -10 | fn id(&self) -> ObjB; - | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr deleted file mode 100644 index 5d5469027..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: duplicated attribute argument found - --> fail/interface/trait/implementers_duplicate_pretty.rs:9:34 - | -9 | #[graphql_interface(for = [ObjA, ObjA])] - | ^^^^ - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/trait/implementers_duplicate_pretty.rs:4:18 - | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr deleted file mode 100644 index d76c39ff6..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/missing_field.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/missing_field.rs:11:8 - | -11 | fn id(&self) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr deleted file mode 100644 index 60ead166d..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/missing_field_argument.rs:16:8 - | -16 | fn id(&self, is_present: bool) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/trait/missing_field_argument.rs:16:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr deleted file mode 100644 index ceefffb74..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/non_subtype_return.rs:11:8 - | -11 | fn id(&self) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/trait/non_subtype_return.rs:11:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr deleted file mode 100644 index 2921f2538..000000000 --- a/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0080]: evaluation of constant value failed - --> fail/interface/trait/wrong_argument_type.rs:16:8 - | -16 | fn id(&self, is_present: bool) -> &str; - | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 - | - = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr deleted file mode 100644 index d4a3b19a8..000000000 --- a/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr +++ /dev/null @@ -1,26 +0,0 @@ -error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/object/argument_non_input_type.rs:12:23 - | -12 | fn id(&self, obj: ObjA) -> &str { - | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` - -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/object/argument_non_input_type.rs:10:1 - | -10 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` - | -note: required by a bound in `Registry::<'r, S>::arg` - --> $WORKSPACE/juniper/src/executor/mod.rs - | - | T: GraphQLType + FromInputValue, - | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg` - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/object/argument_non_input_type.rs:10:1 - | -10 | #[graphql_object] - | ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr deleted file mode 100644 index 56c9587b8..000000000 --- a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/object/attr_field_non_output_return_type.rs:12:21 - | -12 | fn id(&self) -> ObjB { - | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr deleted file mode 100644 index 90a9be584..000000000 --- a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/object/derive_field_non_output_return_type.rs:10:9 - | -10 | id: ObjB, - | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr deleted file mode 100644 index ed30b222e..000000000 --- a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/subscription/field_non_output_return_type.rs:17:27 - | -17 | async fn id(&self) -> Stream<'static, ObjB> { - | ^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` diff --git a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr deleted file mode 100644 index f0f17fbca..000000000 --- a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied - --> fail/union/enum_non_object_variant.rs:9:10 - | -9 | #[derive(GraphQLUnion)] - | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` - | - = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr deleted file mode 100644 index 57ed7c4c0..000000000 --- a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied - --> fail/union/struct_non_object_variant.rs:9:10 - | -9 | #[derive(GraphQLUnion)] - | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` - | - = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/trait_fail_infer_context.stderr b/integration_tests/codegen_fail/fail/union/trait_fail_infer_context.stderr deleted file mode 100644 index 87258824d..000000000 --- a/integration_tests/codegen_fail/fail/union/trait_fail_infer_context.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0277]: the trait bound `CustomContext: FromContext` is not satisfied - --> fail/union/trait_fail_infer_context.rs:3:1 - | -3 | #[graphql_union] - | ^^^^^^^^^^^^^^^^ the trait `FromContext` is not implemented for `CustomContext` - | - = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0308]: mismatched types - --> fail/union/trait_fail_infer_context.rs:3:1 - | -3 | #[graphql_union] - | ^^^^^^^^^^^^^^^^ expected struct `CustomContext`, found struct `SubContext` - | - = note: expected reference `&CustomContext` - found reference `&SubContext` - = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr deleted file mode 100644 index 092bf452f..000000000 --- a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied - --> fail/union/trait_non_object_variant.rs:9:1 - | -9 | #[graphql_union] - | ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` - | - = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/juniper_tests/Makefile.toml b/integration_tests/juniper_tests/Makefile.toml deleted file mode 100644 index c1f8c3314..000000000 --- a/integration_tests/juniper_tests/Makefile.toml +++ /dev/null @@ -1,12 +0,0 @@ -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index d5e25043c..1bff5bcaf 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,907 +1,121 @@ -# master - -## Security - -- Fix panic on malformed queries with recursive fragments. *This is a potential denial-of-service attack vector.* Thanks to [@quapka](https://github.com/quapka) for the detailed vulnerability report and reproduction steps. - -## Breaking Changes - -- Replaced `Visitor` associated type with `DeserializeOwned` requirement in `ScalarValue` trait. ([#985](https://github.com/graphql-rust/juniper/pull/985)) -- Removed `Serialize` implementation from `#[derive(GraphQLScalarValue)]`macro, now should be provided explicitly. ([#985](https://github.com/graphql-rust/juniper/pull/985)) -- `#[graphql_object]` and `#[graphql_subscription]` macros expansion now preserves defined `impl` blocks "as is" and reuses defined methods in opaque way. ([#971](https://github.com/graphql-rust/juniper/pull/971)) -- `rename = ""` attribute's argument renamed to `rename_all = ""`. ([#971](https://github.com/graphql-rust/juniper/pull/971)) -- Upgrade `bson` feature to [2.0 version of its crate](https://github.com/mongodb/bson-rust/releases/tag/v2.0.0). ([#979](https://github.com/graphql-rust/juniper/pull/979)) -- Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987)) -- Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987)) -- Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) -- Redesign `#[graphql_interface]` macro: ([#1009](https://github.com/graphql-rust/juniper/pull/1009)) - - Remove support for `#[graphql_interface(dyn)]` (interface values as trait objects). - - Remove support for `downcast` (custom resolution into implementer types). - - Remove support for `async` trait methods (not required anymore). - - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). - - Forbid default impls on non-ignored trait methods. - - Support coercion of additional nullable arguments and return sub-typing on implementer. - - Support interfaces implementing other interfaces ([#1028](https://github.com/graphql-rust/juniper/pull/1028), [#1000](https://github.com/graphql-rust/juniper/issues/1000)) -- Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017)) - - Support generic scalars. - - Support structs with single named field. - - Support overriding resolvers. -- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014)) - - Mirror `#[derive(GraphQLScalar)]` macro. - - Support usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules). -- Rename `ScalarValue::as_boolean` to `ScalarValue::as_bool`. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) -- Change [`chrono` crate](https://docs.rs/chrono) GraphQL scalars according to the [graphql-scalars.dev](https://graphql-scalars.dev). ([#1010](https://github.com/graphql-rust/juniper/pull/1010)) -- Disable `chrono` feature by default. ([#1010](https://github.com/graphql-rust/juniper/pull/1010)) -- Remove `scalar-naivetime` feature. ([#1010](https://github.com/graphql-rust/juniper/pull/1010)) - -## Features - -- Support using Rust array as GraphQL list. ([#966](https://github.com/graphql-rust/juniper/pull/966), [#918](https://github.com/graphql-rust/juniper/issues/918)) -- Expose `GraphQLRequest` fields. ([#750](https://github.com/graphql-rust/juniper/issues/750)) -- `#[graphql_interface]` macro now supports `rename_all = ""` argument influencing its fields and their arguments. ([#971](https://github.com/graphql-rust/juniper/pull/971)) -- Use `null` in addition to `None` to create `Value::Null` in `graphql_value!` macro to mirror `serde_json::json!`. ([#996](https://github.com/graphql-rust/juniper/pull/996)) -- Add `From` impls to `InputValue` mirroring the ones for `Value` and provide better support for `Option` handling. ([#996](https://github.com/graphql-rust/juniper/pull/996)) -- Implement `graphql_input_value!` and `graphql_vars!` macros. ([#996](https://github.com/graphql-rust/juniper/pull/996)) -- Support [`time` crate](https://docs.rs/time) types as GraphQL scalars behind `time` feature. ([#1006](https://github.com/graphql-rust/juniper/pull/1006)) -- Add `specified_by_url` attribute argument to `#[derive(GraphQLScalarValue)]` and `#[graphql_scalar]` macros. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) -- Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) -- Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) -- Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005)) -- Implement `#[derive(ScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025)) -- Implement `#[derive(GraphQLInterface)]` macro to use structs as GraphQL interfaces. ([#1026](https://github.com/graphql-rust/juniper/pull/1026)) - -## Fixes - -- Allow spreading interface fragments on unions and other interfaces. ([#965](https://github.com/graphql-rust/juniper/pull/965), [#798](https://github.com/graphql-rust/juniper/issues/798)) -- Support expressions in `graphql_value!` macro. ([#996](https://github.com/graphql-rust/juniper/pull/996), [#503](https://github.com/graphql-rust/juniper/issues/503)) -- List coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004](https://github.com/graphql-rust/juniper/pull/1004)) - -# [[0.15.9] 2022-02-02](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.9) - -- Fix infinite recursion on malformed queries with nested recursive fragments. *This is a potential denial-of-service attack vector.* Thanks to [@quapka](https://github.com/quapka) for the detailed vulnerability report and reproduction steps. - -# [[0.15.8] 2022-01-26](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.8) - -- Fix panic on malformed queries with recursive fragments. *This is a potential denial-of-service attack vector.* Thanks to [@quapka](https://github.com/quapka) for the detailed vulnerability report and reproduction steps. - -# [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7) - -- Fix panic on spreading untyped union fragments ([#945](https://github.com/graphql-rust/juniper/issues/945)) - -# [[0.15.6] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.6) - -- Allow `RootNode::as_schema_language` and `RootNode::as_parser_document` for arbitrary type info ([#935](https://github.com/graphql-rust/juniper/pull/935)) - -# [[0.15.5] 2021-05-11](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.5) - -- Fix multiple fragments on sub types overriding each other ([#927](https://github.com/graphql-rust/juniper/pull/915)) -- Fix error extensions in subscriptions ([#927](https://github.com/graphql-rust/juniper/pull/927)) -- Fix fields on interfaces not being resolved when used with fragments ([#923](https://github.com/graphql-rust/juniper/pull/923)) - -# [[0.15.4] 2021-04-03](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.4) - -- Un-deprecate select_child, has_child, and child_names methods ([#900](https://github.com/graphql-rust/juniper/pull/#900)) - -# [[0.15.3] 2021-01-27](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.15.3) - -- Compatibility with the latest `syn` ([#861](https://github.com/graphql-rust/juniper/pull/861)) -- Fixed a regression in GraphQL Playground ([#856](https://github.com/graphql-rust/juniper/pull/856)) - -# [[0.15.2] 2021-01-15](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.15.2) - -- Update GraphQL Playground to v1.7.27. -- Add marker GraphQL trait implementations for Rust container types like `Box`([#847](https://github.com/graphql-rust/juniper/pull/847)) - -# [[0.15.1] 2020-12-12](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.15.1) - -- Support `Arc` in input and output objects. ([#822](https://github.com/graphql-rust/juniper/pull/822)) - -# [[0.15.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.15.0) - -## Features - -- Added async support. ([#2](https://github.com/graphql-rust/juniper/issues/2)) - - `execute()` is now async. Synchronous execution can still be used via `execute_sync()`. - - Field resolvers may optionally be declared as `async` and return a future. - -- Added *experimental* support for GraphQL subscriptions. ([#433](https://github.com/graphql-rust/juniper/pull/433)) - -- Added support for generating the [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language) representation of a schema using `RootNode::as_schema_language()`. ([#676](https://github.com/graphql-rust/juniper/pull/676)) - - This is controlled by the `schema-language` feature and is on by default. It may be turned off if you do not need this functionality to reduce dependencies and speed up compile times. - - Note that this is for generating the GraphQL Schema Language representation from the Rust schema. For the opposite--generating a Rust schema from a GraphQL Schema Language file--see the [`juniper_from_schema`](https://github.com/davidpdrsn/juniper-from-schema) project. - -- Most GraphQL spec violations are now caught at compile-time. ([#631](https://github.com/graphql-rust/juniper/pull/631)) - - The enhanced error messages now include the reason and a link to the spec. - For example, if you try to declare a GraphQL object with no fields: - ```rust - error: GraphQL object expects at least one field - --> $DIR/impl_no_fields.rs:4:1 - | - 4 | impl Object {} - | ^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Objects - ``` - -- [Raw identifiers](https://doc.rust-lang.org/edition-guide/rust-2018/module-system/raw-identifiers.html) are now supported in field and argument names. - -- Most error types now implement `std::error::Error`. ([#419](https://github.com/graphql-rust/juniper/pull/419)) - - `GraphQLError` - - `LexerError` - - `ParseError` - - `RuleError` - -- Support `chrono-tz::Tz` scalar behind a `chrono-tz` feature flag. ([#519](https://github.com/graphql-rust/juniper/pull/519)) - -- Added support for distinguishing between between implicit and explicit null. ([#795](https://github.com/graphql-rust/juniper/pull/795)) - -- Implement `IntoFieldError` for `std::convert::Infallible`. ([#796](https://github.com/graphql-rust/juniper/pull/796)) - -- Allow using `#[graphql(Scalar = DefaultScalarValue)]` for derive macro `GraphQLScalarValue` ([#807](https://github.com/graphql-rust/juniper/pull/807)) - -## Fixes - -- Massively improved the `#[graphql_union]` proc macro. ([#666](https://github.com/graphql-rust/juniper/pull/666)): - - Applicable to traits. - - Supports custom resolvers. - - Supports generics. - - Supports multiple `#[graphql_union]` attributes. - -- Massively improved the `#[derive(GraphQLUnion)]` macro. ([#666](https://github.com/graphql-rust/juniper/pull/666)): - - Applicable to enums and structs. - - Supports custom resolvers. - - Supports generics. - - Supports multiple `#[graphql]` attributes. - -- Massively improved the `#[graphql_interface]` macro. ([#682](https://github.com/graphql-rust/juniper/pull/682)): - - Applicable to traits and generates enum or trait object to represent a GraphQL interface (see the [example of migration from `graphql_interface!` macro](https://github.com/graphql-rust/juniper/commit/3472fe6d10d23472752b1a4cd26c6f3da767ae0e#diff-3506bce1e02051ceed41963a86ef59d660ee7d0cd26df1e9c87372918e3b01f0)). - - Supports passing context and executor to a field resolver. - - Supports custom downcast functions and methods. - - Supports generics. - - Supports multiple `#[graphql_interface]` attributes. - -- The `GraphQLEnum` derive now supports specifying a custom context. ([#621](https://github.com/graphql-rust/juniper/pull/621)) - - Example: - ```rust - #[derive(juniper::GraphQLEnum)] - #[graphql(context = CustomContext)] - enum TestEnum { - A, - } - ``` - -- Added support for renaming arguments within a GraphQL object. ([#631](https://github.com/graphql-rust/juniper/pull/631)) - - Example: - ```rust - #[graphql(arguments(argA(name = "test")))] - ``` - -- `SchemaType` is now public. - - This is helpful when using `context.getSchema()` inside of your field resolvers. - -- Improved lookahead visibility for aliased fields. ([#662](https://github.com/graphql-rust/juniper/pull/662)) - -- When enabled, the optional `bson` integration now requires `bson-1.0.0`. ([#678](https://github.com/graphql-rust/juniper/pull/678)) - -- Fixed panic on `executor.look_ahead()` for nested fragments ([#500](https://github.com/graphql-rust/juniper/issues/500)) - -## Breaking Changes - -- `GraphQLType` trait was split into 2 traits: ([#685](https://github.com/graphql-rust/juniper/pull/685)) - - An object-safe `GraphQLValue` trait containing resolving logic. - - A static `GraphQLType` trait containing GraphQL type information. - -- `juniper::graphiql` has moved to `juniper::http::graphiql`. - - `juniper::http::graphiql::graphiql_source()` now requires a second parameter for subscriptions. - -- Renamed the `object` proc macro to `graphql_object`. -- Removed the `graphql_object!` macro. Use the `#[graphql_object]` proc macro instead. -- Made `#[graphql_object]` macro to generate code generic over `ScalarValue` by default. ([#779](https://github.com/graphql-rust/juniper/pull/779)) - -- Renamed the `scalar` proc macro to `graphql_scalar`. -- Removed the `graphql_scalar!` macro. Use the `#[graphql_scalar]` proc macro instead. - -- Removed the deprecated `ScalarValue` custom derive. Use `GraphQLScalarValue` instead. - -- Removed the `graphql_interface!` macro. Use the `#[graphql_interface]` proc macro instead. - -- Removed the `graphql_union!` macro. Use the `#[graphql_union]` proc macro or custom resolvers for the `#[derive(GraphQLUnion)]` instead. - -- The `#[derive(GraphQLUnion)]` macro no longer generates `From` impls for enum variants. ([#666](https://github.com/graphql-rust/juniper/pull/666)) - - Consider using the [`derive_more`](https//docs.rs/derive_more) crate directly. - -- The `ScalarRefValue` trait has been removed as it was not required. - -- Prefixing variables or fields with an underscore now matches Rust's behavior. ([#684](https://github.com/graphql-rust/juniper/pull/684)) - -- The return type of `GraphQLType::resolve()` has been changed to `ExecutionResult`. - - This was done to unify the return type of all resolver methods. The previous `Value` return type was just an internal artifact of - error handling. - -- Subscription-related: - - Add subscription type to `RootNode`. - - Add subscription endpoint to `playground_source()`. - - Add subscription endpoint to `graphiql_source()`. - -- Specifying a scalar type via a string is no longer supported. ([#631](https://github.com/graphql-rust/juniper/pull/631)) - - For example, instead of `#[graphql(scalar = "DefaultScalarValue")]` use `#[graphql(scalar = DefaultScalarValue)]`. *Note the lack of quotes*. - -- Integration tests: - - Renamed `http::tests::HTTPIntegration` as `http::tests::HttpIntegration`. - - Added support for `application/graphql` POST request. - -- `RootNode::new()` now returns `RootNode` parametrized with `DefaultScalarValue`. For custom `ScalarValue` use `RootNode::new_with_scalar_value()` instead. ([#779](https://github.com/graphql-rust/juniper/pull/779)) - -- When using `LookAheadMethods` to access child selections, children are always found using their alias if it exists rather than their name. ([#662](https://github.com/graphql-rust/juniper/pull/662)) - - These methods are also deprecated in favor of the new `LookAheadMethods::children()` method. - -# [[0.14.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.2) - -- Fix incorrect validation with non-executed operations [#455](https://github.com/graphql-rust/juniper/issues/455) -- Correctly handle raw identifiers in field and argument names. - -# [[0.14.1] 2019-10-24](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.1) - -- Fix panic when an invalid scalar is used by a client [#434](https://github.com/graphql-rust/juniper/pull/434) -- `EmptyMutation` now implements `Send` [#443](https://github.com/graphql-rust/juniper/pull/443) - -# [[0.14.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.0) - -- Require `url` 2.x if `url` feature is enabled. -- Improve lookahead visitability. -- Add ability to parse 'subscription'. - -# [[0.13.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.13.1) - -- Fix a regression when using lookaheads with fragments containing nested types [#404](https://github.com/graphql-rust/juniper/pull/404) - -- Allow `mut` arguments for resolver functions in `#[object]` macros [#402](https://github.com/graphql-rust/juniper/pull/402) - -# [[0.13.0] 2019-07-19](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.13.0) - -### newtype ScalarValue derive - -See [#345](https://github.com/graphql-rust/juniper/pull/345). - -The newtype pattern can now be used with the `GraphQLScalarValue` custom derive -to easily implement custom scalar values that just wrap another scalar, -similar to serdes `#[serde(transparent)]` functionality. - -Example: - -```rust -#[derive(juniper::GraphQLScalarValue)] -struct UserId(i32); -``` - -### Other Changes - -- The `ID` scalar now implements Serde's `Serialize` and `Deserialize` -- Add support for `dyn` trait object syntax to procedural macros - -# [[0.12.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.12.0) - -### object macro - -The `graphql_object!` macro is deprecated and will be removed in the future. -It is replaced by the new [object](https://docs.rs/juniper/latest/juniper/macro.object.html) procedural macro. - -[#333](https://github.com/graphql-rust/juniper/pull/333) - -### 2018 Edition - -All crates were refactored to the Rust 2018 edition. - -This should not have any impact on your code, since juniper already was 2018 compatible. - -[#345](https://github.com/graphql-rust/juniper/pull/345) - -### Other changes - -- The minimum required Rust version is now `1.34.0`. -- The `GraphQLType` impl for () was removed to improve compile time safefty. [#355](https://github.com/graphql-rust/juniper/pull/355) -- The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. -- Added built-in support for the canonical schema introspection query via - `juniper::introspect()`. - [#307](https://github.com/graphql-rust/juniper/issues/307) -- Fix introspection query validity - The DirectiveLocation::InlineFragment had an invalid literal value, - which broke third party tools like apollo cli. -- Added GraphQL Playground integration. - The `DirectiveLocation::InlineFragment` had an invalid literal value, - which broke third party tools like apollo cli. -- The return type of `value::object::Object::iter/iter_mut` has changed to `impl Iter`. [#312](https://github.com/graphql-rust/juniper/pull/312) -- Add `GraphQLRequest::operation_name` [#353](https://github.com/graphql-rust/juniper/pull/353) - -# [0.11.1] 2018-12-19 - -## Changes - -- The minimum required Rust version is now `1.30`. -- All macros and the custom derives now support the macro system changes properly - and also support Rust 2018 edition crates. - - [#298](https://github.com/graphql-rust/juniper/pull/298) - -# [0.11.0] 2018-12-17 - -## Changes - -- The minimum required Rust version is now `1.30.0`. - - [#271](https://github.com/graphql-rust/juniper/pull/271) - -- Juniper is now generic about the exact representation of scalar values. This - allows downstream crates to add support for own scalar value representations. - - There are two use cases for this feature: - - - You want to support new scalar types not representable by the provided default - scalar value representation like for example `i64` - - You want to support a type from a third party crate that is not supported by juniper - - **Note:** This may need some changes in down stream code, especially if working with - generic code. To retain the current behaviour use `DefaultScalarValue` as scalar value type - - [#251](https://github.com/graphql-rust/juniper/pull/251) - -- The `GraphQLObject` and `GraphQLEnum` derives will mark graphql fields as - `@deprecated` when struct fields or enum variants are marked with the - builtin `#[deprecated]` attribute. - - The deprecation reason can be set using the `note = ...` meta item - (e.g. `#[deprecated(note = "Replaced by betterField")]`). - The `since` attribute is ignored. - - [#269](https://github.com/graphql-rust/juniper/pull/269) - -* There is an alternative syntax for setting a field's _description_ and - _deprecation reason_ in the `graphql_object!` and `graphql_interface!` macros. - - To **deprecate** a graphql field: - - ```rust - // Original syntax for setting deprecation reason - field deprecated "Reason" my_field() -> { ... } - - // New alternative syntax for deprecation reason. - #[deprecated(note = "Reason")] - field my_field() -> { ... } - - // You can now also deprecate without a reason. - #[deprecated] - field my_field() -> { ... } - ``` - - To set the **description** of a graphql field: - - ```rust - // Original syntax for field descriptions - field my_field() as "Description" -> { ... } - - // Original syntax for argument descriptions - field my_field( - floops: i32 as "The number of starfish to be returned. \ - Can't be more than 100.", - ) -> { - ... - } - - // New alternative syntax for field descriptions - /// Description - field my_field() -> { ... } - - // New alternative syntax for argument descriptions - field my_field( - /// The number of starfish to be returned. - /// Can't be more than 100. - arg: i32, - ) -> { - ... - } - - // You can also use raw strings and const &'static str. - // - // Multiple docstrings will be collapsed into a single - // description separated by newlines. - /// This is my field. - /// - /// Make sure not to filtz the bitlet. - /// Flitzing without a bitlet has undefined behaviour. - /// - #[doc = my_consts::ADDED_IN_VERSION_XYZ] - field my_field() -> { ... } - ``` - - [#269](https://github.com/graphql-rust/juniper/pull/269) - -# [0.10.0] 2018-09-13 - -## Changes - -- Changed serialization of `NaiveDate` when using the optional `chronos` support. - - **Note:** while this is not a Rust breaking change, if you relied on the serialization format (perhaps by storing serialized data in a database or making asumptions in your client code written in another language) it could be a breaking change for your application. - - [#151](https://github.com/graphql-rust/juniper/pull/151) - -- The `GraphQLObject`, `GraphQLInputObject`, and `GraphQLEnum` custom derives will reject - invalid [names](http://facebook.github.io/graphql/October2016/#Name) at compile time. - - [#170](https://github.com/graphql-rust/juniper/pull/170) - -- Large integers (> signed 32bit) are now deserialized as floats. Previously, - they produced an "integer out of range" error. For languages that do not - have a distinction between integer and floating point types (such as - javascript), this meant large whole floating point values could not be - decoded (because they were represented without a fractional value such as `.0`). - - [#179](https://github.com/graphql-rust/juniper/pull/179) - -- The `GraphQLObject`, `GraphQLInputObject`, and `GraphQLEnum` custom derives - now parse doc strings and use them as descriptions. This behavior can be - overridden by using an explicit GraphQL `description` annotation such as - `#[graphql(description = "my description")]`. [View documentation](https://graphql-rust.github.io/types/objects/defining_objects.html#defining-objects). - - [#194](https://github.com/graphql-rust/juniper/issues/194) - -- Introduced `IntoFieldError` trait to allow custom error handling - i.e. custom result type. The error type must implement this trait resolving - the errors into `FieldError`. [View documentation](https://graphql-rust.github.io/types/objects/error_handling.html#structured-errors). - - [#40](https://github.com/graphql-rust/juniper/issues/40) - -- `GraphQLType` and `ToInputValue` are now implemented for Arc - - [#212](https://github.com/graphql-rust/juniper/pull/212) - -- Error responses no longer have a _data_ field, instead, error details are stored in the _extensions_ field - - **Note:** while this is a breaking change, it is a necessary one to better align with the latest [GraphQL June 2018](https://facebook.github.io/graphql/June2018/#sec-Errors) specification, which defines the reserved _extensions_ field for error details. [View documentation](https://graphql-rust.github.io/types/objects/error_handling.html#structured-errors). - - [#219](https://github.com/graphql-rust/juniper/pull/219) - -* The `GraphQLObject` and `GraphQLInputObject` custom derives - now support lifetime annotations. - - [#225](https://github.com/graphql-rust/juniper/issues/225) - -* When using the `GraphQLObject` custom derive, fields can now be omitted by annotating the field with `#[graphql(skip)]`. [View documentation](https://graphql-rust.github.io/types/objects/defining_objects.html#skipping-fields). - - [#220](https://github.com/graphql-rust/juniper/issues/220) - -* Due to newer dependencies, the oldest Rust version supported is now 1.22.0 - - [#231](https://github.com/graphql-rust/juniper/pull/231) - -# [0.9.2] 2018-01-13 - -## Changes - -### `__typename` for unions - -The [`__typename`](http://graphql.org/learn/queries/#meta-fields) query meta-field now works on unions. - -[#112](https://github.com/graphql-rust/juniper/issues/112) - -### Debug impls. - -http::GraphQLRequest now implements `Debug`. - -# [0.9.0] 2017-12-03 - -## Changes - -This is the first release in a long time. -Quite a few changes have accumulated since `0.8.1`, including multiple breaking -changes. - -### Custom derive & macros - -Juniper has gained custom derive implementations for input objects, objects and -enums. - -- `#[derive(GraphQLInputObject)]` -- `#[derive(GraphQLEnum)]` -- `#[derive(GraphQLObject)]` - -The `graphql_enum!` and `graphql_input_object!` macros did not provide any more -benefits, so they have been removed! -All functionality is now covered by custom derive. -Check the [docs](https://graphql-rust.github.io) to find out more. - -### Web framework integrations - Iron & Rocket - -The iron and rocket integrations were removed from the main crate, and are now -available via the [juniper_iron](https://crates.io/crates/juniper_iron) and -[juniper_rocket](https://crates.io/crates/juniper_rocket) crates. - -### FieldError rewrite (custom data) - -The `FieldError` type now supports custom data with the `Value` type from -serde_json. Use this to populate the `data` field in returned errors. - -This also means that try! and `?` now work in resolvers, which is quite nice. - -Also, the `ResultExt` extension and the `jtry!` macro were removed, since they -are redundant now! - -### Dynamic Schemas - -Juniper has gained support for dynamic schemas, thanks to @srijs. - -That also means the type of `RootNode` has changed to include a lifetime. - -The repository was restructured to a multi crate workspace to enable several new -features like custom_derive and an extracted parser. - -#[#66](https://github.com/graphql-rust/juniper/pull/66) - -### Data Type Integrations - -Integrations with multiple popular crates was added to make working with them -easier. - -- uuid -- url -- chrono - -### Field Order - -To better comply with the specification, order of requested fields is -now preserved. - -[#82](https://github.com/graphql-rust/juniper/issues/82 - -### From/ToInputValue - -The `::from` and `::to` methods in `From/ToInputValue` were renamed to -`from/to_input_value()` to not conflict with other methods. - -[#90](https://github.com/graphql-rust/juniper/pull/90) - -### Other changes - -- Several small performance improvements -- Use [fnv](https://github.com/servo/rust-fnv) hash map for better performance - -## Contributors - -A big shoutout to the many contributors for this version, sorted alphabetically. - -- Cameron Eldridge -- Christian Legnitto -- Jacob Haslehurst -- Jane Keibler -- Magnus Hallin -- rushmorem -- Rushmore Mushambi -- Sagie Gur-Ari -- Sam Rijs -- Stanko Krtalić -- theduke -- thomas-jeepe - -# [0.8.1] – 2017-06-15 - -Tiny release to fix broken crate metadata on crates.io. - -# [0.8.0] – 2017-06-15 - -## Breaking changes - -- To better comply with the specification, and to avoid weird bugs with very - large positive or negative integers, support for `i64` has been completely - dropped and replaced with `i32`. `i64` is no longer a valid GraphQL type in - Juniper, and `InputValue`/`Value` can only represent 32 bit integers. - - If an incoming integer is out of range for a 32 bit signed integer type, an - error will be returned to the client. - ([#52](https://github.com/graphql-rust/juniper/issues/52), - [#49](https://github.com/graphql-rust/juniper/issues/49)) - -- Serde has been updated to 1.0. If your application depends on an older - version, you will need to first update your application before you can upgrade - to a more recent Juniper. ([#43](https://github.com/graphql-rust/juniper/pull/43)) - -- `rustc_serialize` support has been dropped since this library is now - deprecated. ([#51](https://github.com/graphql-rust/juniper/pull/51)) - -## New features - -- A new `rocket-handlers` feature now includes some tools to use the - [Rocket](https://rocket.rs) framework. [A simple - example](juniper_rocket/examples/rocket-server.rs) has been added to the examples folder. - -## Bugfixes - -- A panic in the parser has been replaced with a proper error - ([#44](https://github.com/graphql-rust/juniper/pull/44)) - -# [0.7.0] – 2017-02-26 - -### Breaking changes - -- The `iron-handlers` feature now depends on Iron 0.5 - ([#30](https://github.com/graphql-rust/juniper/pull/30)). Because of - this, support for Rust 1.12 has been dropped. It might still work if - you're not using the Iron integrations feature, however. - -### New features - -- Input objects defined by the `graphql_input_object!` can now be used - as default values to field arguments and other input object fields. - -# [0.6.3] – 2017-02-19 - -### New features - -- Add support for default values on input object fields - ([#28](https://github.com/graphql-rust/juniper/issues/28)) - -# [0.6.2] – 2017-02-05 - -### New features - -- The `null` literal is now supported in the GraphQL - language. ([#26](https://github.com/graphql-rust/juniper/pull/26)) -- Rustc-serialize is now optional, but enabled by default. If you - _only_ want Serde support, include Juniper without default features - and enable - Serde. ([#12](https://github.com/graphql-rust/juniper/pull/12)) -- The built-in `ID` type now has a public constructor and derives a - few traits (`Clone`, `Debug`, `Eq`, `PartialEq`, `From`, - `Deref`). ([#19](https://github.com/graphql-rust/juniper/pull/19)) -- Juniper is now built and tested against all Rust compilers since - version 1.12.1. - -### Minor breaking change - -- Serde has been updated to - 0.9. ([#25](https://github.com/graphql-rust/juniper/pull/25)) - -### Bugfixes - -- The built-in GraphiQL handler had a bug in variable serialization. - ([#16](https://github.com/graphql-rust/juniper/pull/16)) -- The example should now build and run without problems on - Windows. ([#15](https://github.com/graphql-rust/juniper/pull/15)) -- Object types now properly implement - `__typename`. ([#22](https://github.com/graphql-rust/juniper/pull/22)) -- String variables are now properly parsed into GraphQL - enums. ([#17](https://github.com/graphql-rust/juniper/pull/17)) - -# [0.6.1] – 2017-01-06 - -### New features - -- Optional Serde support - ([#8](https://github.com/graphql-rust/juniper/pull/8)) - -### Improvements - -- The `graphql_input_object!` macro can now be used to define input - objects as public Rust structs. -- GraphiQL in the Iron GraphiQL handler has been updated to 0.8.1 - (#[#11](https://github.com/graphql-rust/juniper/pull/11)) - -### Minor breaking changes - -Some undocumented but public APIs were changed. - -- `to_snake_case` correctly renamed to `to_camel_case` - ([#9](https://github.com/graphql-rust/juniper/pull/9)) -- JSON serialization of `GraphQLError` changed to be more consistent - with how other values were serialized - ([#10](https://github.com/graphql-rust/juniper/pull/10)). - -# [0.6.0] – 2017-01-02 - -TL;DR: Many big changes in how context types work and how they -interact with the executor. Not too much to worry about if you're only -using the macros and not deriving `GraphQLType` directly. - -### Breaking changes - -- The `executor` argument in all resolver methods is now - immutable. The executor instead uses interior mutability to store - errors in a thread-safe manner. - - This change could open up for asynchronous or multi-threaded - execution: you can today use something like rayon in your resolve - methods to let child nodes be concurrently resolved. - - **How to fix:** All field resolvers that looked like `field name(&mut executor` now should say `field name(&executor`. - -- The context type of `GraphQLType` is moved to an associated type; - meaning it's no longer generic. This only affects people who - implement the trait manually, _not_ macro users. - - This greatly simplifies a lot of code by ensuring that there only - can be one `GraphQLType` implementation for any given Rust - type. However, it has the downside that support for generic contexts - previously used in scalars, has been removed. Instead, use the new - context conversion features to accomplish the same task. - - **How to fix:** Instead of `impl GraphQLType for ...`, - you use `impl GraphQLType for ... { type Context = MyContext;`. - -- All context types must derive the `Context` marker trait. This is - part of an overarching change to allow different types to use - different contexts. - - **How to fix:** If you have written e.g. `graphql_object!(MyType: MyContext ...)` you will need to add `impl Context for MyContext {}`. Simple as that. - -- `Registry` and all meta type structs now takes one lifetime - parameter, which affects `GraphQLType`'s `meta` method. This only - affects people who implement the trait manually. - - **How to fix:** Change the type signature of `meta()` to read `fn meta<'r>(registry: &mut Registry<'r>) -> MetaType<'r>`. - -- The type builder methods on `Registry` no longer return functions - taking types or fields. Due to how the borrow checker works with - expressions, you will have to split up the instantiation into two - statements. This only affects people who implement the `GraphQLType` - trait manually. - - **How to fix:** Change the contents of your `meta()` methods to - something like this: - - ```rust - fn meta<'r>(registry: &mut Registry) -> MetaType<'r> { - let fields = &[ /* your fields ... */ ]; - - registry.build_object_type::(fields).into_meta() - } - ``` +`juniper` changelog +=================== + +All user visible changes to `juniper` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. + + + + +## master + +[Diff](/../../compare/juniper-v0.15.9...master) + +### BC Breaks + +- [October 2021] GraphQL spec: ([#1000]) + - Forbade [`__typename` field on `subscription` operations](https://spec.graphql.org/October2021#note-bc213). ([#1001]) + - Supported `isRepeatable` field on directives. ([#1003]) + - Supported `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003]) + - Supported directives on variables definitions. ([#1005]) +- Replaced `Visitor` associated type with `DeserializeOwned` requirement in `ScalarValue` trait. ([#985]) +- `#[graphql_object]` and `#[graphql_subscription]` expansions now preserve defined `impl` blocks "as is" and reuse defined methods in opaque way. ([#971]) +- Renamed `rename = ""` attribute argument to `rename_all = ""` (following `serde` style). ([#971]) +- Upgraded [`bson` crate] integration to [2.0 version](https://github.com/mongodb/bson-rust/releases/tag/v2.0.0). ([#979]) +- Upgraded [`uuid` crate] integration to [1.0 version](https://github.com/uuid-rs/uuid/releases/tag/1.0.0). ([#1057]) +- Made `FromInputValue` trait methods fallible to allow post-validation. ([#987]) +- Redesigned `#[graphql_interface]` macro: ([#1009]) + - Removed support for `dyn` attribute argument (interface values as trait objects). + - Removed support for `downcast` attribute argument (custom resolution into implementer types). + - Removed support for `async` trait methods (not required anymore). + - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). + - Forbade default implementations of non-ignored trait methods. + - Supported coercion of additional `null`able arguments and return sub-typing on implementer. + - Supported `rename_all = ""` attribute argument influencing all its fields and their arguments. ([#971]) +- Split `#[derive(GraphQLScalarValue)]` macro into: + - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) + - Supported generic `ScalarValue`. + - Supported structs with single named field. + - Supported overriding resolvers with external functions, methods or modules. + - Supported `specified_by_url` attribute argument. ([#1003], [#1000]) + - `#[derive(ScalarValue)]` for implementing `ScalarValue` trait: ([#1025]) + - Removed `Serialize` implementation (now should be provided explicitly). ([#985]) +- Redesigned `#[graphql_scalar]` macro: ([#1014]) + - Changed `from_input_value()` return type from `Option` to `Result`. ([#987]) + - Mirrored new `#[derive(GraphQLScalar)]` macro. + - Supported usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules]. +- Renamed `ScalarValue::as_boolean` method to `ScalarValue::as_bool`. ([#1025]) +- Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010]) + - Disabled `chrono` [Cargo feature] by default. + - Removed `scalar-naivetime` [Cargo feature]. ### Added -- Support for different contexts for different types. As GraphQL - schemas tend to get large, narrowing down the context type to - exactly what a given type needs is great for - encapsulation. Similarly, letting different subsystems use different - resources thorugh the context is also useful for the same reasons. - - Juniper supports two different methods of doing this, depending on - your needs: if you have two contexts where one can be converted into - the other _without any extra knowledge_, you can implement the new - `FromContext` trait. This is useful if you have multiple crates or - modules that all belong to the same GraphQL schema: - - ```rust - struct TopContext { - db: DatabaseConnection, - session: WebSession, - current_user: User, - } - - struct ModuleOneContext { - db: DatabaseConnection, // This module only requires a database connection - } - - impl Context for TopContext {} - impl Context for ModuleOneContext {} - - impl FromContext for ModuleOneContext { - fn from(ctx: &TopContext) -> ModuleOneContext { - ModuleOneContext { - db: ctx.db.clone() - } - } - } - - graphql_object!(Query: TopContext |&self| { - field item(&executor) -> Item { - executor.context().db.get_item() - } - }); - - // The `Item` type uses another context type - conversion is automatic - graphql_object!(Item: ModuleOneContext |&self| { - // ... - }); - ``` - - The other way is to manually perform the conversion in a field - resolver. This method is preferred when the child context needs - extra knowledge than what exists in the parent context: - - ```rust - // Each entity has its own context - struct TopContext { - entities: HashMap, - db: DatabaseConnection, - } - - struct EntityContext { - // fields - } - - impl Context for TopContext {} - impl Context for EntityContext {} - - graphql_object!(Query: TopContext |&self| { - // By returning a tuple (&Context, GraphQLType), you can tell the executor - // to switch out the context for the returned value. You can wrap the - // tuple in Option<>, FieldResult<>, FieldResult>, or just return - // the tuple without wrapping it. - field entity(&executor, key: i32) -> Option<(&EntityContext, Entity)> { - executor.context().entities.get(&key) - .map(|ctx| (ctx, executor.context().db.get_entity(key))) - } - }); - - graphql_object!(Entity: EntityContext |&self| { - // ... - }); - ``` - -### Improvements - -- Parser and query execution has now reduced the allocation overhead - by reusing as much as possible from the query source and meta type - information. - -# [0.5.3] – 2016-12-05 - -### Added - -- `jtry!`: Helper macro to produce `FieldResult`s from regular - `Result`s. Wherever you would be using `try!` in a regular function - or method, you can use `jtry!` in a field resolver: - - ```rust - graphql_object(MyType: Database |&self| { - field count(&executor) -> FieldResult { - let txn = jtry!(executor.context().transaction()); - - let count = jtry!(txn.execute("SELECT COUNT(*) FROM user")); - - Ok(count[0][0]) - } - }); - ``` - -### Changes - -- Relax context type trait requirements for the iron handler: your - contexts no longer have to be `Send + Sync`. - -- `RootNode` is now `Send` and `Sync` if both the mutation and query - types implement `Send` and `Sync`. - -### Bugfixes - -- `return` statements inside field resolvers no longer cause syntax - errors. - -## 0.5.2 – 2016-11-13 - -### Added - -- Support for marking fields and enum values deprecated. -- `input_object!` helper macro - -### Changes - -- The included example server now uses the simple Star Wars schema - used in query/introspection tests. - -### Bugfixes - -- The query validators - particularly ones concerned with validation - of input data and variables - have been improved significantly. A - large number of test cases have been added. - -- Macro syntax stability has also been improved. All syntactical edge - cases of the macros have gotten tests to verify their correctness. - -[0.8.1]: https://github.com/graphql-rust/juniper/compare/0.8.0...0.8.1 -[0.8.0]: https://github.com/graphql-rust/juniper/compare/0.7.0...0.8.0 -[0.7.0]: https://github.com/graphql-rust/juniper/compare/0.6.3...0.7.0 -[0.6.3]: https://github.com/graphql-rust/juniper/compare/0.6.2...0.6.3 -[0.6.2]: https://github.com/graphql-rust/juniper/compare/0.6.1...0.6.2 -[0.6.1]: https://github.com/graphql-rust/juniper/compare/0.6.0...0.6.1 -[0.6.0]: https://github.com/graphql-rust/juniper/compare/0.5.3...0.6.0 -[0.5.3]: https://github.com/graphql-rust/juniper/compare/0.5.2...0.5.3 +- Usage of Rust arrays as GraphQL lists. ([#966], [#918]) +- `From` implementations for `InputValue` mirroring the ones for `Value` and better support for `Option` handling. ([#996]) +- `null` in addition to `None` for creating `Value::Null` in `graphql_value!` macro (following `serde_json::json!` style). ([#996]) +- `graphql_input_value!` and `graphql_vars!` macros. ([#996]) +- [`time` crate] integration behind `time` [Cargo feature]. ([#1006]) +- `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026]) +- [`bigdecimal` crate] integration behind `bigdecimal` [Cargo feature]. ([#1060]) +- [`rust_decimal` crate] integration behind `rust_decimal` [Cargo feature]. ([#1060]) + +### Changed + +- Made `GraphQLRequest` fields public. ([#750]) +- Relaxed [object safety] requirement for `GraphQLValue` and `GraphQLValueAsync` traits. ([ba1ed85b]) + +## Fixed + +- Unsupported spreading GraphQL interface fragments on unions and other interfaces. ([#965], [#798]) +- Unsupported expressions in `graphql_value!` macro. ([#996], [#503]) +- Incorrect GraphQL list coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004]) +- All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) + +[#503]: /../../issues/503 +[#750]: /../../issues/750 +[#798]: /../../issues/798 +[#918]: /../../issues/918 +[#965]: /../../pull/965 +[#966]: /../../pull/966 +[#971]: /../../pull/971 +[#979]: /../../pull/979 +[#985]: /../../pull/985 +[#987]: /../../pull/987 +[#996]: /../../pull/996 +[#1000]: /../../issues/1000 +[#1001]: /../../pull/1001 +[#1003]: /../../pull/1003 +[#1004]: /../../pull/1004 +[#1005]: /../../pull/1005 +[#1006]: /../../pull/1006 +[#1009]: /../../pull/1009 +[#1010]: /../../pull/1010 +[#1014]: /../../pull/1014 +[#1017]: /../../pull/1017 +[#1025]: /../../pull/1025 +[#1026]: /../../pull/1026 +[#1051]: /../../issues/1051 +[#1054]: /../../pull/1054 +[#1057]: /../../pull/1057 +[#1060]: /../../pull/1060 +[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083 + + + + +## Previous releases + +See [old CHANGELOG](/../../blob/juniper-v0.15.9/juniper/CHANGELOG.md). + + + + +[`bson` crate]: https://docs.rs/bson +[`chrono` crate]: https://docs.rs/chrono +[`time` crate]: https://docs.rs/time +[Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html +[graphql-scalars.dev]: https://graphql-scalars.dev +[October 2021]: https://spec.graphql.org/October2021 +[object safety]: https://doc.rust-lang.org/reference/items/traits.html#object-safety +[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index db67b6f19..5c5fbc70c 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -1,22 +1,27 @@ [package] name = "juniper" version = "0.16.0-dev" +edition = "2018" +description = "GraphQL server library." +license = "BSD-2-Clause" authors = [ "Magnus Hallin ", "Christoph Herzog ", "Christian Legnitto ", + "Ilya Solovyiov ", + "Kai Ren ", ] -description = "GraphQL server library" -license = "BSD-2-Clause" documentation = "https://docs.rs/juniper" +homepage = "https://graphql-rust.github.io" repository = "https://github.com/graphql-rust/juniper" -readme = "../README.md" -keywords = ["graphql", "server", "web", "rocket"] -categories = ["web-programming"] -edition = "2018" +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["apollo", "graphql", "server", "web"] +exclude = ["/release.toml"] -[badges] -travis-ci = { repository = "graphql-rust/juniper" } +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [features] default = [ @@ -27,41 +32,41 @@ default = [ ] chrono-clock = ["chrono", "chrono/clock"] expose-test-schema = ["anyhow", "serde_json"] -graphql-parser-integration = ["graphql-parser"] -schema-language = ["graphql-parser-integration"] +schema-language = ["graphql-parser"] [dependencies] -juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" } - -anyhow = { version = "1.0.32", optional = true, default-features = false } +anyhow = { version = "1.0.32", default-features = false, optional = true } async-trait = "0.1.39" +bigdecimal = { version = "0.3", optional = true } bson = { version = "2.0", features = ["chrono-0_4"], optional = true } -chrono = { version = "0.4", default-features = false, optional = true } +chrono = { version = "0.4", features = ["alloc"], default-features = false, optional = true } chrono-tz = { version = "0.6", default-features = false, optional = true } fnv = "1.0.3" futures = { version = "0.3.1", features = ["alloc"], default-features = false } futures-enum = { version = "0.1.12", default-features = false } graphql-parser = { version = "0.4", optional = true } indexmap = { version = "1.0", features = ["serde-1"] } -serde = { version = "1.0.8", features = ["derive"], default-features = false } +juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" } +rust_decimal = { version = "1.0", default-features = false, optional = true } +serde = { version = "1.0.8", features = ["derive"] } serde_json = { version = "1.0.2", default-features = false, optional = true } smartstring = "1.0" static_assertions = "1.1" time = { version = "0.3", features = ["formatting", "macros", "parsing"], optional = true } url = { version = "2.0", optional = true } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -uuid = { version = "0.8", default-features = false, optional = true } +uuid = { version = "1.0", default-features = false, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } -uuid = { version = "0.8", default-features = false, features = ["wasm-bindgen"], optional = true } +# not used, to fix `bson` compilation only +uuid_08 = { version = "0.8", package = "uuid", default-features = false, features = ["wasm-bindgen"] } [dev-dependencies] bencher = "0.1.2" +chrono = { version = "0.4", features = ["alloc"], default-features = false } pretty_assertions = "1.0.0" serde_json = "1.0.2" -tokio = { version = "1", features = ["macros", "time", "rt-multi-thread"] } +tokio = { version = "1.0", features = ["macros", "time", "rt-multi-thread"] } [[bench]] name = "bench" diff --git a/juniper/LICENSE b/juniper/LICENSE new file mode 100644 index 000000000..f1c4508b1 --- /dev/null +++ b/juniper/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2016-2022, Magnus Hallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/juniper/Makefile.toml b/juniper/Makefile.toml deleted file mode 100644 index 6736f4771..000000000 --- a/juniper/Makefile.toml +++ /dev/null @@ -1,30 +0,0 @@ -# This is needed as the release config is at a different path than the top-level -# release config. -[env] -CARGO_MAKE_CARGO_ALL_FEATURES = "" - -[tasks.release] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--execute", "${RELEASE_LEVEL}"] - -[tasks.release-dry-run] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "--no-publish", "--no-push", "--no-tag", "${RELEASE_LEVEL}"] - -[tasks.release-local-test] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "${RELEASE_LEVEL}"] - -[tasks.test] -args = ["test", "--all-features"] -[tasks.test-custom] -args = ["test", "--all-features"] -[tasks.test-flow] -args = ["test", "--all-features"] -[tasks.test-multi-flow-phase] -args = ["test", "--all-features"] -[tasks.test-thread-safe] -args = ["test", "--all-features"] -[tasks.test-verbose] -args = ["test", "--all-features"] -[tasks.test-with-args] -args = ["test", "--all-features"] -[tasks.ci-coverage-flow] -args = ["test", "--all-features"] diff --git a/juniper/README.md b/juniper/README.md new file mode 100644 index 000000000..feb17329a --- /dev/null +++ b/juniper/README.md @@ -0,0 +1,109 @@ +Juniper (GraphQL server library for Rust) +========================================= + +[![Crates.io](https://img.shields.io/crates/v/juniper.svg?maxAge=2592000)](https://crates.io/crates/juniper) +[![Documentation](https://docs.rs/juniper/badge.svg)](https://docs.rs/juniper) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Juniper Book] ([current][Juniper Book] | [edge][Juniper Book edge]) +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper/CHANGELOG.md) + + +[GraphQL] is a data query language developed by [Facebook] and intended to serve mobile and web application frontends. + +*[Juniper]* makes it possible to write [GraphQL] servers in [Rust] that are type-safe and blazingly fast. We also try to make declaring and resolving [GraphQL] schemas as convenient as possible as [Rust] will allow. + +[Juniper] doesn't include a web server - instead it provides building blocks to make integration with existing servers straightforward, including embedded [GraphiQL] and/or [GraphQL Playground] for easy debugging. + + + + +## Getting Started + +The best place to get started is [Juniper Book], which contains guides with plenty of examples, covering all features of [Juniper]. + +To get started quickly and get a feel for Juniper, check out the ["Quickstart" section][1]. + +For specific information about macros, types and the [Juniper] API, the [API docs][Juniper] is the best place to look. + + + + +## Features + +[Juniper] supports the full [GraphQL] query language according to [October 2021 GraphQL specification](https://spec.graphql.org/October2021), including interfaces, unions, schema introspection, and validations. It does not, however, support the schema language. + +As an exception to other [GraphQL] libraries for other languages, [Juniper] builds non-`null` types by default. A field of type `Vec` will be converted into `[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be `Option>>`. + + + + +## Integrations + + +### Data types + +[Juniper] has automatic integration with some very common [Rust] crates to make building schemas a breeze. The types from these crates will be usable in your schemas automatically: +- [`bigdecimal`] (feature gated) +- [`bson`] +- [`chrono`] (feature gated) +- [`chrono-tz`] (feature gated) +- [`rust_decimal`] (feature gated) +- [`time`] (feature gated) +- [`url`] +- [`uuid`] + + +### Web servers + +- [`actix-web`] ([`juniper_actix`] crate) +- [`hyper`] ([`juniper_hyper`] crate) +- [`iron`] ([`juniper_iron`] crate) +- [`rocket`] ([`juniper_rocket`] crate) +- [`warp`] ([`juniper_warp`] crate) + + + + +## API Stability + +[Juniper] has not reached 1.0 yet, thus some API instability should be expected. + + + + +## License + +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper/LICENSE). + + + + +[`actix-web`]: https://docs.rs/actix-web +[`bigdecimal`]: https://docs.rs/bigdecimal +[`bson`]: https://docs.rs/bson +[`chrono`]: https://docs.rs/chrono +[`chrono-tz`]: https://docs.rs/chrono-tz +[`juniper_actix`]: https://docs.rs/juniper_actix +[`juniper_hyper`]: https://docs.rs/juniper_hyper +[`juniper_iron`]: https://docs.rs/juniper_iron +[`juniper_rocket`]: https://docs.rs/juniper_rocket +[`juniper_warp`]: https://docs.rs/juniper_warp +[`hyper`]: https://docs.rs/hyper +[`iron`]: https://docs.rs/iron +[`rocket`]: https://docs.rs/rocket +[`rust_decimal`]: https://docs.rs/rust_decimal +[`time`]: https://docs.rs/time +[`url`]: https://docs.rs/url +[`uuid`]: https://docs.rs/uuid +[`warp`]: https://docs.rs/warp +[Facebook]: https://facebook.com +[GraphiQL]: https://github.com/graphql/graphiql +[GraphQL]: http://graphql.org +[GraphQL Playground]: https://github.com/graphql/graphql-playground +[Juniper]: https://docs.rs/juniper +[Juniper Book]: https://graphql-rust.github.io +[Juniper Book edge]: https://graphql-rust.github.io/juniper/master +[Rust]: https://www.rust-lang.org + +[1]: https://graphql-rust.github.io/quickstart.html diff --git a/juniper/release.toml b/juniper/release.toml index d3dea3251..a8cc1d5fc 100644 --- a/juniper/release.toml +++ b/juniper/release.toml @@ -1,33 +1,94 @@ -pre-release-commit-message = "Release {{crate_name}} {{version}}" -post-release-commit-message = "Bump {{crate_name}} version to {{next_version}}" -tag-message = "Release {{crate_name}} {{version}}" -pre-release-replacements = [ - # Juniper's changelog - {file="CHANGELOG.md", min=0, search="# master", replace="# master\n\n- No changes yet\n\n# [[{{version}}] {{date}}](https://github.com/graphql-rust/juniper/releases/tag/{{crate_name}}-v{{version}})"}, - {file="src/lib.rs", min=0, search="docs.rs/juniper/[a-z0-9\\.-]+", replace="docs.rs/juniper/{{version}}"}, - # docs - {file="../docs/book/content/quickstart.md", min=0, search="juniper = \"[^\"]+\"", replace="juniper = \"{{version}}\""}, - # codegen - {file="../juniper_codegen/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - # Tests. - {file="../integration_tests/juniper_tests/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - # Hyper - {file="../juniper_hyper/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - {file="../juniper_hyper/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""}, - # Iron - {file="../juniper_iron/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - {file="../juniper_iron/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""}, - # Rocket - {file="../juniper_rocket/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - {file="../juniper_rocket/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""}, - # Warp - {file="../juniper_warp/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - {file="../juniper_warp/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""}, - # Subscriptions - {file="../juniper_subscriptions/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - # GraphQL-WS - {file="../juniper_graphql_ws/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - # Actix-Web - {file="../juniper_actix/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""}, - {file="../juniper_actix/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""}, -] +[[pre-release-replacements]] +file = "../book/src/advanced/dataloaders.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" +[[pre-release-replacements]] +file = "../book/src/advanced/subscriptions.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" +[[pre-release-replacements]] +file = "../book/src/quickstart.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" +[[pre-release-replacements]] +file = "../book/src/servers/hyper.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" +[[pre-release-replacements]] +file = "../book/src/servers/iron.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" +[[pre-release-replacements]] +file = "../book/src/servers/rocket.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" +[[pre-release-replacements]] +file = "../book/src/servers/warp.md" +exactly = 1 +search = "juniper = \"[^\"]+\"" +replace = "juniper = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_actix/Cargo.toml" +exactly = 2 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_graphql_ws/Cargo.toml" +exactly = 1 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_hyper/Cargo.toml" +exactly = 2 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_iron/Cargo.toml" +exactly = 2 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_rocket/Cargo.toml" +exactly = 2 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_subscriptions/Cargo.toml" +exactly = 1 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_warp/Cargo.toml" +exactly = 2 +search = "juniper = \\{ version = \"[^\"]+\"" +replace = "juniper = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "...master\\)" +replace = "...{{crate_name}}%40{{version}})" + +[[pre-release-replacements]] +file = "README.md" +exactly = 2 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper/src/integrations/bigdecimal.rs b/juniper/src/integrations/bigdecimal.rs new file mode 100644 index 000000000..a1635fcc8 --- /dev/null +++ b/juniper/src/integrations/bigdecimal.rs @@ -0,0 +1,132 @@ +//! GraphQL support for [`bigdecimal`] crate types. +//! +//! # Supported types +//! +//! | Rust type | GraphQL scalar | +//! |----------------|----------------| +//! | [`BigDecimal`] | `BigDecimal` | +//! +//! [`BigDecimal`]: bigdecimal::BigDecimal + +use std::str::FromStr as _; + +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; + +/// Big decimal type. +/// +/// Allows storing any real number to arbitrary precision; which avoids common +/// floating point errors (such as 0.1 + 0.2 ≠ 0.3) at the cost of complexity. +/// +/// Always serializes as `String`. But may be deserialized from `Int` and +/// `Float` values too. It's not recommended to deserialize from a `Float` +/// directly, as the floating point representation may be unexpected. +/// +/// See also [`bigdecimal`] crate for details. +/// +/// [`bigdecimal`]: https://docs.rs/bigdecimal +#[graphql_scalar( + with = bigdecimal_scalar, + parse_token(i32, f64, String), + specified_by_url = "https://docs.rs/bigdecimal", +)] +type BigDecimal = bigdecimal::BigDecimal; + +mod bigdecimal_scalar { + use std::convert::TryFrom as _; + + use super::*; + + pub(super) fn to_output(v: &BigDecimal) -> Value { + Value::scalar(v.to_string()) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + if let Some(i) = v.as_int_value() { + Ok(BigDecimal::from(i)) + } else if let Some(f) = v.as_float_value() { + BigDecimal::try_from(f) + .map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {}", e)) + } else { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + BigDecimal::from_str(s) + .map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {}", e)) + }) + } + } +} + +#[cfg(test)] +mod test { + use std::str::FromStr as _; + + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; + + use super::BigDecimal; + + #[test] + fn parses_correct_input() { + for (input, expected) in [ + (graphql_input_value!("4.20"), "4.20"), + (graphql_input_value!("0"), "0"), + ( + graphql_input_value!("999999999999.999999999"), + "999999999999.999999999", + ), + ( + graphql_input_value!("87553378877997984345"), + "87553378877997984345", + ), + (graphql_input_value!(123), "123"), + (graphql_input_value!(0), "0"), + (graphql_input_value!(43.44), "43.44"), + ] { + let input: InputValue = input; + let parsed = BigDecimal::from_input_value(&input); + let expected = BigDecimal::from_str(expected).unwrap(); + + assert!( + parsed.is_ok(), + "failed to parse `{:?}`: {:?}", + input, + parsed.unwrap_err(), + ); + assert_eq!(parsed.unwrap(), expected, "input: {:?}", input); + } + } + + #[test] + fn fails_on_invalid_input() { + for input in [ + graphql_input_value!(""), + graphql_input_value!("0,0"), + graphql_input_value!("12,"), + graphql_input_value!("1996-12-19T14:23:43"), + graphql_input_value!("i'm not even a number"), + graphql_input_value!(null), + graphql_input_value!(false), + ] { + let input: InputValue = input; + let parsed = BigDecimal::from_input_value(&input); + + assert!(parsed.is_err(), "allows input: {:?}", input); + } + } + + #[test] + fn formats_correctly() { + for raw in [ + "4.20", + "0", + "999999999999.999999999", + "87553378877997984345", + "123", + "43.44", + ] { + let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value(); + + assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw); + } + } +} diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 44e092056..2beedeb0c 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -28,7 +28,10 @@ mod utc_date_time { use super::*; pub(super) fn to_output(v: &UtcDateTime) -> Value { - Value::scalar((*v).to_rfc3339_string()) + Value::scalar( + (*v).try_to_rfc3339_string() + .expect("failed to format DateTime as RFC3339"), + ) } pub(super) fn from_input(v: &InputValue) -> Result { @@ -44,7 +47,6 @@ mod utc_date_time { #[cfg(test)] mod test { use bson::{oid::ObjectId, DateTime as UtcDateTime}; - use chrono::{DateTime, Utc}; use crate::{graphql_input_value, FromInputValue, InputValue}; @@ -61,6 +63,8 @@ mod test { #[test] fn utcdatetime_from_input() { + use chrono::{DateTime, Utc}; + let raw = "2020-03-23T17:38:32.446+00:00"; let input: InputValue = graphql_input_value!((raw)); diff --git a/juniper/src/integrations/mod.rs b/juniper/src/integrations/mod.rs index d966997f2..05f988cb4 100644 --- a/juniper/src/integrations/mod.rs +++ b/juniper/src/integrations/mod.rs @@ -1,11 +1,15 @@ //! Provides GraphQLType implementations for some external types +#[cfg(feature = "bigdecimal")] +pub mod bigdecimal; #[cfg(feature = "bson")] pub mod bson; #[cfg(feature = "chrono")] pub mod chrono; #[cfg(feature = "chrono-tz")] pub mod chrono_tz; +#[cfg(feature = "rust_decimal")] +pub mod rust_decimal; #[doc(hidden)] pub mod serde; #[cfg(feature = "time")] diff --git a/juniper/src/integrations/rust_decimal.rs b/juniper/src/integrations/rust_decimal.rs new file mode 100644 index 000000000..2e74db32d --- /dev/null +++ b/juniper/src/integrations/rust_decimal.rs @@ -0,0 +1,123 @@ +//! GraphQL support for [`rust_decimal`] crate types. +//! +//! # Supported types +//! +//! | Rust type | GraphQL scalar | +//! |-------------|----------------| +//! | [`Decimal`] | `Decimal` | +//! +//! [`Decimal`]: rust_decimal::Decimal + +use std::str::FromStr as _; + +use crate::{graphql_scalar, InputValue, ScalarValue, Value}; + +/// 128 bit representation of a fixed-precision decimal number. +/// +/// The finite set of values of `Decimal` scalar are of the form +/// m / 10e, where m is an integer such that +/// -296 < m < 296, and e is an integer between 0 and 28 +/// inclusive. +/// +/// Always serializes as `String`. But may be deserialized from `Int` and +/// `Float` values too. It's not recommended to deserialize from a `Float` +/// directly, as the floating point representation may be unexpected. +/// +/// See also [`rust_decimal`] crate for details. +/// +/// [`rust_decimal`]: https://docs.rs/rust_decimal +#[graphql_scalar( + with = rust_decimal_scalar, + parse_token(i32, f64, String), + specified_by_url = "https://docs.rs/rust_decimal", +)] +type Decimal = rust_decimal::Decimal; + +mod rust_decimal_scalar { + use std::convert::TryFrom as _; + + use super::*; + + pub(super) fn to_output(v: &Decimal) -> Value { + Value::scalar(v.to_string()) + } + + pub(super) fn from_input(v: &InputValue) -> Result { + if let Some(i) = v.as_int_value() { + Ok(Decimal::from(i)) + } else if let Some(f) = v.as_float_value() { + Decimal::try_from(f) + .map_err(|e| format!("Failed to parse `Decimal` from `Float`: {}", e)) + } else { + v.as_string_value() + .ok_or_else(|| format!("Expected `String`, found: {}", v)) + .and_then(|s| { + Decimal::from_str(s) + .map_err(|e| format!("Failed to parse `Decimal` from `String`: {}", e)) + }) + } + } +} + +#[cfg(test)] +mod test { + use std::str::FromStr as _; + + use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _}; + + use super::Decimal; + + #[test] + fn parses_correct_input() { + for (input, expected) in [ + (graphql_input_value!("4.20"), "4.20"), + (graphql_input_value!("0"), "0"), + (graphql_input_value!("999.999999999"), "999.999999999"), + (graphql_input_value!("875533788"), "875533788"), + (graphql_input_value!(123), "123"), + (graphql_input_value!(0), "0"), + (graphql_input_value!(43.44), "43.44"), + ] { + let input: InputValue = input; + let parsed = Decimal::from_input_value(&input); + let expected = Decimal::from_str(expected).unwrap(); + + assert!( + parsed.is_ok(), + "failed to parse `{:?}`: {:?}", + input, + parsed.unwrap_err(), + ); + assert_eq!(parsed.unwrap(), expected, "input: {:?}", input); + } + } + + #[test] + fn fails_on_invalid_input() { + for input in [ + graphql_input_value!(""), + graphql_input_value!("0,0"), + graphql_input_value!("12,"), + graphql_input_value!("1996-12-19T14:23:43"), + graphql_input_value!("99999999999999999999999999999999999999"), + graphql_input_value!("99999999999999999999999999999999999999.99"), + graphql_input_value!("i'm not even a number"), + graphql_input_value!(null), + graphql_input_value!(false), + ] { + let input: InputValue = input; + let parsed = Decimal::from_input_value(&input); + + assert!(parsed.is_err(), "allows input: {:?}", input); + } + } + + #[test] + fn formats_correctly() { + for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] { + let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value(); + + assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw); + } + } +} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 817206181..0373175e2 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -1,106 +1,11 @@ -/*! - -# GraphQL - -[GraphQL][graphql] is a data query language developed by Facebook intended to -serve mobile and web application frontends. - -*Juniper* makes it possible to write GraphQL servers in Rust that are -type-safe and blazingly fast. We also try to make declaring and resolving -GraphQL schemas as convenient as possible as Rust will allow. - -Juniper does not include a web server - instead it provides building blocks to -make integration with existing servers straightforward. It optionally provides a -pre-built integration for the [Iron][iron] and [Rocket] frameworks, including -embedded [Graphiql][graphiql] for easy debugging. - -* [Cargo crate](https://crates.io/crates/juniper) -* [API Reference][docsrs] -* [Book][book]: Guides and Examples - - -## Getting Started - -The best place to get started is the [Juniper Book][book], which contains -guides with plenty of examples, covering all features of Juniper. - -To get started quickly and get a feel for Juniper, check out the -[Quickstart][book_quickstart] section. - -For specific information about macros, types and the Juniper api, the -[API Reference][docsrs] is the best place to look. - -You can also check out [src/tests/schema.rs][test_schema_rs] to see a complex -schema including polymorphism with traits and interfaces. -For an example of web framework integration, -see the [rocket][rocket_examples] and [iron][iron_examples] examples folders. - - -## Features - -Juniper supports the full GraphQL query language according to the -[specification][graphql_spec], including interfaces, unions, schema -introspection, and validations. -It does not, however, support the schema language. - -As an exception to other GraphQL libraries for other languages, Juniper builds -non-null types by default. A field of type `Vec` will be converted into -`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be -`Option>>`. - -## Integrations - -### Data types - -Juniper has automatic integration with some very common Rust crates to make -building schemas a breeze. The types from these crates will be usable in -your Schemas automatically. - -* [uuid][uuid] -* [url][url] -* [chrono][chrono] -* [chrono-tz][chrono-tz] -* [time][time] -* [bson][bson] - -### Web Frameworks - -* [rocket][rocket] -* [iron][iron] - - -## API Stability - -Juniper has not reached 1.0 yet, thus some API instability should be expected. - -[graphql]: http://graphql.org -[graphiql]: https://github.com/graphql/graphiql -[iron]: https://github.com/iron/iron -[graphql_spec]: http://facebook.github.io/graphql -[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs -[tokio]: https://github.com/tokio-rs/tokio -[rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples -[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples -[Rocket]: https://rocket.rs -[book]: https://graphql-rust.github.io/ -[book_quickstart]: https://graphql-rust.github.io/quickstart.html -[docsrs]: https://docs.rs/juniper - -[uuid]: https://crates.io/crates/uuid -[url]: https://crates.io/crates/url -[chrono]: https://crates.io/crates/chrono -[chrono-tz]: https://crates.io/crates/chrono-tz -[time]: https://crates.io/crates/time -[bson]: https://crates.io/crates/bson - -*/ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] // Due to `schema_introspection` test. #![cfg_attr(test, recursion_limit = "256")] -#![doc(html_root_url = "https://docs.rs/juniper/0.15.9")] #![warn(missing_docs)] -// Required for using `juniper_codegen` macros inside this crate to resolve absolute `::juniper` -// path correctly, without errors. +// Required for using `juniper_codegen` macros inside this crate to resolve +// absolute `::juniper` path correctly, without errors. extern crate self as juniper; use std::fmt; @@ -166,18 +71,15 @@ pub use crate::{ LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables, }, introspection::IntrospectionFormat, - macros::helper::{ - subscription::{ExtractTypeFromStream, IntoFieldResult}, - AsDynGraphQLValue, - }, + macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult}, parser::{ParseError, ScalarToken, Spanning}, schema::{ meta, model::{RootNode, SchemaType}, }, types::{ - async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync}, - base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind}, + async_await::{GraphQLTypeAsync, GraphQLValueAsync}, + base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion}, nullable::Nullable, scalars::{EmptyMutation, EmptySubscription, ID}, diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 0f81018da..f6565e032 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -6,36 +6,7 @@ use std::fmt; use futures::future::{self, BoxFuture}; -use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, FieldError, 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); +use crate::FieldError; /// This trait is used by [`graphql_scalar!`] macro to retrieve [`Error`] type /// from a [`Result`]. diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 51692d5bb..1ac3f0e4e 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -50,10 +50,16 @@ pub struct ScalarMeta<'a, S> { pub description: Option, #[doc(hidden)] pub specified_by_url: Option>, - pub(crate) try_parse_fn: for<'b> fn(&'b InputValue) -> Result<(), FieldError>, - pub(crate) parse_fn: for<'b> fn(ScalarToken<'b>) -> Result>, + pub(crate) try_parse_fn: InputValueParseFn, + pub(crate) parse_fn: ScalarTokenParseFn, } +/// Shortcut for an [`InputValue`] parsing function. +pub type InputValueParseFn = for<'b> fn(&'b InputValue) -> Result<(), FieldError>; + +/// Shortcut for a [`ScalarToken`] parsing function. +pub type ScalarTokenParseFn = for<'b> fn(ScalarToken<'b>) -> Result>; + /// List type metadata #[derive(Debug)] pub struct ListMeta<'a> { @@ -92,7 +98,7 @@ pub struct EnumMeta<'a, S> { pub description: Option, #[doc(hidden)] pub values: Vec, - pub(crate) try_parse_fn: for<'b> fn(&'b InputValue) -> Result<(), FieldError>, + pub(crate) try_parse_fn: InputValueParseFn, } /// Interface type metadata @@ -127,7 +133,7 @@ pub struct InputObjectMeta<'a, S> { pub description: Option, #[doc(hidden)] pub input_fields: Vec>, - pub(crate) try_parse_fn: for<'b> fn(&'b InputValue) -> Result<(), FieldError>, + pub(crate) try_parse_fn: InputValueParseFn, } /// A placeholder for not-yet-registered types @@ -344,9 +350,7 @@ impl<'a, S> MetaType<'a, S> { /// `true` if it can be parsed as the provided type. /// /// Only scalars, enums, and input objects have parse functions. - pub fn input_value_parse_fn( - &self, - ) -> Option fn(&'b InputValue) -> Result<(), FieldError>> { + pub fn input_value_parse_fn(&self) -> Option> { match *self { MetaType::Scalar(ScalarMeta { ref try_parse_fn, .. diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 0576174e5..5544fbbdd 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, fmt}; use fnv::FnvHashMap; -#[cfg(feature = "graphql-parser-integration")] +#[cfg(feature = "graphql-parser")] use graphql_parser::schema::Document; use crate::{ @@ -13,7 +13,7 @@ use crate::{ GraphQLEnum, }; -#[cfg(feature = "graphql-parser-integration")] +#[cfg(feature = "graphql-parser")] use crate::schema::translate::{graphql_parser::GraphQLParserTranslator, SchemaTranslator}; /// Root query node of a schema @@ -171,7 +171,7 @@ where format!("{}", doc) } - #[cfg(feature = "graphql-parser-integration")] + #[cfg(feature = "graphql-parser")] /// The schema definition as a [`graphql_parser`](https://crates.io/crates/graphql-parser) /// [`Document`](https://docs.rs/graphql-parser/latest/graphql_parser/schema/struct.Document.html). pub fn as_parser_document(&'a self) -> Document<'a, &'a str> { @@ -193,22 +193,17 @@ impl<'a, S> SchemaType<'a, S> { SubscriptionT: GraphQLType, { let mut directives = FnvHashMap::default(); - let query_type_name: String; - let mutation_type_name: String; - let subscription_type_name: String; - let mut registry = Registry::new(FnvHashMap::default()); - query_type_name = registry + + let query_type_name = registry .get_type::(query_info) .innermost_name() .to_owned(); - - mutation_type_name = registry + let mutation_type_name = registry .get_type::(mutation_info) .innermost_name() .to_owned(); - - subscription_type_name = registry + let subscription_type_name = registry .get_type::(subscription_info) .innermost_name() .to_owned(); @@ -619,7 +614,7 @@ impl<'a, S> fmt::Display for TypeType<'a, S> { #[cfg(test)] mod test { - #[cfg(feature = "graphql-parser-integration")] + #[cfg(feature = "graphql-parser")] mod graphql_parser_integration { use crate::{graphql_object, EmptyMutation, EmptySubscription, RootNode}; diff --git a/juniper/src/schema/translate/mod.rs b/juniper/src/schema/translate/mod.rs index 2408af630..061218500 100644 --- a/juniper/src/schema/translate/mod.rs +++ b/juniper/src/schema/translate/mod.rs @@ -4,5 +4,5 @@ pub trait SchemaTranslator<'a, T> { fn translate_schema(s: &'a SchemaType) -> T; } -#[cfg(feature = "graphql-parser-integration")] +#[cfg(feature = "graphql-parser")] pub mod graphql_parser; diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index fdc4a037e..e32732757 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -111,14 +111,6 @@ 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; - /// 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 175be9293..d5c9e3941 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -294,14 +294,6 @@ 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; - /// 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/src/types/scalars.rs b/juniper/src/types/scalars.rs index 84a9947ee..66e26e74f 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -1,6 +1,5 @@ use std::{ - borrow::Cow, char, convert::From, fmt, marker::PhantomData, ops::Deref, rc::Rc, - thread::JoinHandle, u32, + char, convert::From, fmt, marker::PhantomData, ops::Deref, rc::Rc, thread::JoinHandle, u32, }; use serde::{Deserialize, Serialize}; @@ -270,79 +269,6 @@ where } } -impl<'s, S> reflect::WrappedType for Cow<'s, str> { - const VALUE: reflect::WrappedValue = 1; -} - -impl<'s, S> reflect::BaseType for Cow<'s, str> { - const NAME: reflect::Type = "String"; -} - -impl<'s, S> reflect::BaseSubTypes for Cow<'s, str> { - const NAMES: reflect::Types = &[>::NAME]; -} - -impl<'s, S> GraphQLType for Cow<'s, str> -where - S: ScalarValue, -{ - fn name(_: &()) -> 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() - } -} - -impl<'s, S> GraphQLValue for Cow<'s, str> -where - S: ScalarValue, -{ - type Context = (); - type TypeInfo = (); - - fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { - >::name(info) - } - - fn resolve( - &self, - _: &(), - _: Option<&[Selection]>, - _: &Executor, - ) -> ExecutionResult { - Ok(Value::scalar(self.to_string())) - } -} - -impl<'s, S> GraphQLValueAsync for Cow<'s, str> -where - S: ScalarValue + Send + Sync, -{ - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [Selection]>, - executor: &'a Executor, - ) -> crate::BoxFuture<'a, crate::ExecutionResult> { - use futures::future; - Box::pin(future::ready(self.resolve(info, selection_set, executor))) - } -} - -impl<'s, S> ToInputValue for Cow<'s, str> -where - S: ScalarValue, -{ - fn to_input_value(&self) -> InputValue { - InputValue::scalar(self.to_string()) - } -} - #[graphql_scalar(with = impl_boolean_scalar)] type Boolean = bool; diff --git a/juniper_actix/.gitignore b/juniper_actix/.gitignore deleted file mode 100644 index 0d722487a..000000000 --- a/juniper_actix/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/examples/**/target/**/* -**/*.rs.bk -Cargo.lock diff --git a/juniper_actix/CHANGELOG.md b/juniper_actix/CHANGELOG.md index ff493864a..d403d4da5 100644 --- a/juniper_actix/CHANGELOG.md +++ b/juniper_actix/CHANGELOG.md @@ -1,34 +1,32 @@ -# master +`juniper_actix` changelog +========================= -- Compatibility with the latest `juniper`. +All user visible changes to `juniper_actix` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.4.0] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.4.0) -- Require `actix-web` >= `4.0.0-beta8`. -- Compatibility with the latest `juniper`. -# [[0.2.5] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.2.5) -- Compatibility with the latest `juniper`. +## master -# [[0.2.4] 2021-04-03](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.2.4) +### BC Breaks -- Compatibility with the latest `juniper`. +- Switched to 4.0 version of [`actix-web` crate] and its ecosystem. ([#1034]) +- Switched to 0.16 version of [`juniper` crate]. +- Switched to 0.4 version of [`juniper_graphql_ws` crate]. -# [[0.2.3] 2021-01-27](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.2.3) +[#1034]: /../../pull/1034 -- Compatibility with the latest `juniper`. -- Fix Content-Type charset parsing ([#863](https://github.com/graphql-rust/juniper/pull/863)) -# [[0.2.2] 2021-01-15](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.2.2) -- Compatibility with the latest `juniper`. -# [[0.2.1] 2020-12-12](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.2.1) +## Previous releases -- Actix package updated to 3.3. +See [old CHANGELOG](/../../blob/juniper_actix-v0.4.0/juniper_actix/CHANGELOG.md). -# [[0.2.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_actix-0.2.0) -- Actix package updated to 3.0.0 -- Subscription support -- Initial Release + + + +[`actix-web` crate]: https://docs.rs/actix-web +[`juniper` crate]: https://docs.rs/juniper +[`juniper_graphql_ws` crate]: https://docs.rs/juniper_graphql_ws +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_actix/Cargo.toml b/juniper_actix/Cargo.toml index f805eb738..f46a74983 100644 --- a/juniper_actix/Cargo.toml +++ b/juniper_actix/Cargo.toml @@ -1,12 +1,21 @@ [package] name = "juniper_actix" -version = "0.5.0" +version = "0.5.0-dev" edition = "2018" -authors = ["Jordao Rosario "] -description = "Juniper GraphQL integration with Actix" +description = "`juniper` GraphQL integration with `actix-web`." license = "BSD-2-Clause" +authors = ["Jordao Rosario "] documentation = "https://docs.rs/juniper_actix" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_actix" repository = "https://github.com/graphql-rust/juniper" +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["actix-web", "apollo", "graphql", "juniper", "websocket"] +exclude = ["/examples/", "/release.toml"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [features] subscriptions = ["juniper_graphql_ws", "tokio"] @@ -14,30 +23,26 @@ subscriptions = ["juniper_graphql_ws", "tokio"] [dependencies] actix = "0.13" actix-http = "3.0" -http = "0.2.4" actix-web = "4.0" actix-web-actors = "4.1.0" - -juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } -juniper_graphql_ws = { version = "0.3.0", path = "../juniper_graphql_ws", optional = true } - anyhow = "1.0" futures = "0.3" +juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } +juniper_graphql_ws = { version = "0.4.0-dev", path = "../juniper_graphql_ws", optional = true } +http = "0.2.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" tokio = { version = "1.0", features = ["sync"], optional = true } [dev-dependencies] -actix-rt = "2" actix-cors = "0.6" actix-identity = "0.4" -tokio = "1.0" -async-stream = "0.3" +actix-rt = "2.0" actix-test = "=0.1.0-beta.13" - -juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } - +async-stream = "0.3" bytes = "1.0" env_logger = "0.9" +juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } log = "0.4" +tokio = "1.0" diff --git a/juniper_actix/LICENSE b/juniper_actix/LICENSE index 6771d89ff..3acbe949d 100644 --- a/juniper_actix/LICENSE +++ b/juniper_actix/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2018, Jordao Rosario +Copyright (c) 2018-2022, Jordao Rosario All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/juniper_actix/Makefile.toml b/juniper_actix/Makefile.toml deleted file mode 100644 index 66adc0a2e..000000000 --- a/juniper_actix/Makefile.toml +++ /dev/null @@ -1,16 +0,0 @@ -[tasks.test] -args = ["test", "--all-features"] -[tasks.test-custom] -args = ["test", "--all-features"] -[tasks.test-flow] -args = ["test", "--all-features"] -[tasks.test-multi-flow-phase] -args = ["test", "--all-features"] -[tasks.test-thread-safe] -args = ["test", "--all-features"] -[tasks.test-verbose] -args = ["test", "--all-features"] -[tasks.test-with-args] -args = ["test", "--all-features"] -[tasks.ci-coverage-flow] -args = ["test", "--all-features"] diff --git a/juniper_actix/README.md b/juniper_actix/README.md index 5f52cb989..833685d07 100644 --- a/juniper_actix/README.md +++ b/juniper_actix/README.md @@ -1,34 +1,49 @@ -# juniper_actix +`juniper_actix` crate +===================== + +[![Crates.io](https://img.shields.io/crates/v/juniper_actix.svg?maxAge=2592000)](https://crates.io/crates/juniper_actix) +[![Documentation](https://docs.rs/juniper_actix/badge.svg)](https://docs.rs/juniper_actix) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_actix/CHANGELOG.md) + +[`actix-web`] web server integration for [`juniper`] ([GraphQL] implementation for [Rust]). + +It's inspired and some parts are copied from [`juniper_warp`] crate. + + -This repository contains the [actix][actix] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust, its inspired and some parts are copied from [juniper_warp][juniper_warp]. ## Documentation -For documentation, including guides and examples, check out [Juniper][Juniper]. +For documentation, including guides and examples, check out [Juniper Book]. + +A basic usage example can also be found in the [API docs][`juniper_actix`]. + + -A basic usage example can also be found in the [API documentation][documentation]. ## Examples -Check [examples/actix_server][example] for example code of a working actix -server with GraphQL handlers. +Check [`examples/actix_server.rs`][1] for example code of a working [`actix-web`] server with [GraphQL] handlers. + -## Links -* [Juniper][Juniper] -* [API Reference][documentation] -* [actix][actix] ## License -This project is under the BSD-2 license. +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_actix/LICENSE). + -Check the LICENSE file for details. -[actix]: https://github.com/actix/actix-web -[Juniper]: https://github.com/graphql-rust/juniper + +[`actix-web`]: https://docs.rs/actix-web +[`juniper`]: https://docs.rs/juniper +[`juniper_actix`]: https://docs.rs/juniper_actix +[`juniper_warp`]: https://docs.rs/juniper_warp [GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_actix -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_actix/examples/actix_server.rs -[juniper_warp]:https://docs.rs/juniper_warp \ No newline at end of file +[Juniper Book]: https://graphql-rust.github.io +[Rust]: https://www.rust-lang.org + +[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_actix/examples/actix_server.rs + diff --git a/juniper_actix/release.toml b/juniper_actix/release.toml new file mode 100644 index 000000000..4806c63e0 --- /dev/null +++ b/juniper_actix/release.toml @@ -0,0 +1,11 @@ +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 3 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_actix/src/lib.rs b/juniper_actix/src/lib.rs index 935be89cf..c88c1b006 100644 --- a/juniper_actix/src/lib.rs +++ b/juniper_actix/src/lib.rs @@ -1,44 +1,7 @@ -/*! - -# juniper_actix - -This repository contains the [actix][actix] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust, its inspired and some parts are copied from [juniper_warp][juniper_warp] - -## Documentation - -For documentation, including guides and examples, check out [Juniper][Juniper]. - -A basic usage example can also be found in the [API documentation][documentation]. - -## Examples - -Check [examples/actix_server][example] for example code of a working actix -server with GraphQL handlers. - -## Links - -* [Juniper][Juniper] -* [API Reference][documentation] -* [actix][actix] - -## License - -This project is under the BSD-2 license. - -Check the LICENSE file for details. - -[actix]: https://github.com/actix/actix-web -[Juniper]: https://github.com/graphql-rust/juniper -[GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_actix -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_actix/examples/actix_server.rs -[juniper_warp]: https://github.com/graphql-rust/juniper/juniper_warp -*/ - +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] #![deny(missing_docs)] #![deny(warnings)] -#![doc(html_root_url = "https://docs.rs/juniper_actix/0.1.0")] use actix_web::{ error::JsonPayloadError, http::Method, web, Error, FromRequest, HttpMessage, HttpRequest, diff --git a/juniper_benchmarks/.gitignore b/juniper_benchmarks/.gitignore deleted file mode 100644 index 693699042..000000000 --- a/juniper_benchmarks/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock diff --git a/juniper_benchmarks/CHANGELOG.md b/juniper_benchmarks/CHANGELOG.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/juniper_benchmarks/Makefile.toml b/juniper_benchmarks/Makefile.toml deleted file mode 100644 index c1f8c3314..000000000 --- a/juniper_benchmarks/Makefile.toml +++ /dev/null @@ -1,12 +0,0 @@ -[tasks.release] -disabled = true -[tasks.release-some] -disabled = true -[tasks.release-local-test] -disabled = true -[tasks.release-some-local-test] -disabled = true -[tasks.release-dry-run] -disabled = true -[tasks.release-some-dry-run] -disabled = true diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md new file mode 100644 index 000000000..2ce86c70c --- /dev/null +++ b/juniper_codegen/CHANGELOG.md @@ -0,0 +1,61 @@ +`juniper_codegen` changelog +=========================== + +All user visible changes to `juniper_codegen` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. + + + + +## master + +### BC Breaks + +- `#[graphql_object]` and `#[graphql_subscription]` expansions now preserve defined `impl` blocks "as is" and reuse defined methods in opaque way. ([#971]) +- Renamed `rename = ""` attribute argument to `rename_all = ""` (following `serde` style). ([#971]) +- Redesigned `#[graphql_interface]` macro: ([#1009]) + - Removed support for `dyn` attribute argument (interface values as trait objects). + - Removed support for `downcast` attribute argument (custom resolution into implementer types). + - Removed support for `async` trait methods (not required anymore). + - Removed necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching their fields now). + - Forbade default implementations of non-ignored trait methods. + - Supported coercion of additional `null`able arguments and return sub-typing on implementer. + - Supported `rename_all = ""` attribute argument influencing all its fields and their arguments. ([#971]) +- Split `#[derive(GraphQLScalarValue)]` macro into: + - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) + - Supported generic `ScalarValue`. + - Supported structs with single named field. + - Supported overriding resolvers with external functions, methods or modules. + - Supported `specified_by_url` attribute argument. ([#1003], [#1000]) + - `#[derive(ScalarValue)]` for implementing `ScalarValue` trait: ([#1025]) + - Removed `Serialize` implementation (now should be provided explicitly). ([#985]) +- Redesigned `#[graphql_scalar]` macro: ([#1014]) + - Changed `from_input_value()` return type from `Option` to `Result`. ([#987]) + - Mirrored new `#[derive(GraphQLScalar)]` macro. + - Supported usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules]. + +### Added + +- `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026]) + +### Fixed + +- All procedural macros expansion inside `macro_rules!`. ([#1054], [#1051]) + +[#971]: /../../pull/971 +[#985]: /../../pull/985 +[#987]: /../../pull/987 +[#1000]: /../../issues/1000 +[#1003]: /../../pull/1003 +[#1009]: /../../pull/1009 +[#1014]: /../../pull/1014 +[#1017]: /../../pull/1017 +[#1025]: /../../pull/1025 +[#1026]: /../../pull/1026 +[#1051]: /../../issues/1051 +[#1054]: /../../pull/1054 + + + + +[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 07bd77879..79ba4d632 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -2,17 +2,20 @@ name = "juniper_codegen" version = "0.16.0-dev" edition = "2018" +description = "Code generation for `juniper` crate." +license = "BSD-2-Clause" authors = [ "Magnus Hallin ", "Christoph Herzog ", + "Ilya Solovyiov ", + "Kai Ren ", ] -description = "Internal custom derive trait for Juniper GraphQL" -license = "BSD-2-Clause" -documentation = "https://docs.rs/juniper" +documentation = "https://docs.rs/juniper-codegen" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_codegen" repository = "https://github.com/graphql-rust/juniper" - -[badges] -travis-ci = { repository = "graphql-rust/juniper" } +readme = "README.md" +keywords = ["codegen", "graphql", "juniper", "macros"] +exclude = ["/release.toml"] [lib] proc-macro = true @@ -21,11 +24,11 @@ proc-macro = true proc-macro-error = "1.0.2" proc-macro2 = "1.0.1" quote = "1.0.3" -syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit", "visit-mut"], default-features = false } +syn = { version = "1.0.90", features = ["extra-traits", "full", "parsing", "visit", "visit-mut"], default-features = false } url = "2.0" [dev-dependencies] derive_more = "0.99.7" futures = "0.3" -juniper = { version = "0.16.0-dev", path = "../juniper" } +juniper = { path = "../juniper" } serde = "1.0" diff --git a/juniper_codegen/LICENSE b/juniper_codegen/LICENSE new file mode 100644 index 000000000..f1c4508b1 --- /dev/null +++ b/juniper_codegen/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2016-2022, Magnus Hallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/juniper_codegen/Makefile.toml b/juniper_codegen/Makefile.toml deleted file mode 100644 index 58aa74fb2..000000000 --- a/juniper_codegen/Makefile.toml +++ /dev/null @@ -1,11 +0,0 @@ -# This is needed as the release config is at a different path than the top-level -# release config. - -[tasks.release] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--execute", "${RELEASE_LEVEL}"] - -[tasks.release-dry-run] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "--no-publish", "--no-push", "--no-tag", "${RELEASE_LEVEL}"] - -[tasks.release-local-test] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "${RELEASE_LEVEL}"] diff --git a/juniper_codegen/README.md b/juniper_codegen/README.md new file mode 100644 index 000000000..9ccfd60aa --- /dev/null +++ b/juniper_codegen/README.md @@ -0,0 +1,24 @@ +`juniper_codegen` crate +======================= + +[![Crates.io](https://img.shields.io/crates/v/juniper_codegen.svg?maxAge=2592000)](https://crates.io/crates/juniper_codegen) +[![Documentation](https://docs.rs/juniper_codegen/badge.svg)](https://docs.rs/juniper_codegen) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_codegen/CHANGELOG.md) + +Code generation for [`juniper`] crate. + +DO NOT use it directly, use [`juniper`] crate instead. + + + + +## License + +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_codegen/LICENSE). + + + + +[`juniper`]: https://docs.rs/juniper diff --git a/juniper_codegen/release.toml b/juniper_codegen/release.toml index 33a9caecd..b8548dbfd 100644 --- a/juniper_codegen/release.toml +++ b/juniper_codegen/release.toml @@ -1,7 +1,17 @@ -pre-release-commit-message = "Release {{crate_name}} {{version}}" -post-release-commit-message = "Bump {{crate_name}} version to {{next_version}}" -tag-message = "Release {{crate_name}} {{version}}" -pre-release-replacements = [ - {file="../juniper/Cargo.toml", min=0, search="juniper_codegen = \\{ version = \"[^\"]+\"", replace="juniper_codegen = { version = \"{{version}}\""}, - {file="src/lib.rs", min=0, search="docs.rs/juniper_codegen/[a-z0-9\\.-]+", replace="docs.rs/juniper_codegen/{{version}}"}, -] +[[pre-release-replacements]] +file = "../juniper/Cargo.toml" +exactly = 1 +search = "juniper_codegen = \\{ version = \"[^\"]+\"" +replace = "{{crate_name}} = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 2 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index fdd8f6480..9eeb72cf6 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -261,13 +261,13 @@ pub(crate) enum OnMethod { /// Regular [GraphQL field argument][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - Regular(OnField), + Regular(Box), /// [`Context`] passed into a [GraphQL field][2] resolving method. /// /// [`Context`]: juniper::Context /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - Context(syn::Type), + Context(Box), /// [`Executor`] passed into a [GraphQL field][2] resolving method. /// @@ -281,7 +281,7 @@ impl OnMethod { #[must_use] pub(crate) fn as_regular(&self) -> Option<&OnField> { if let Self::Regular(arg) = self { - Some(arg) + Some(&*arg) } else { None } @@ -292,7 +292,7 @@ impl OnMethod { #[must_use] pub(crate) fn context_ty(&self) -> Option<&syn::Type> { if let Self::Context(ty) = self { - Some(ty) + Some(&*ty) } else { None } @@ -411,7 +411,7 @@ impl OnMethod { .ok()?; if attr.context.is_some() { - return Some(Self::Context(argument.ty.unreferenced().clone())); + return Some(Self::Context(Box::new(argument.ty.unreferenced().clone()))); } if attr.executor.is_some() { return Some(Self::Executor); @@ -419,7 +419,7 @@ impl OnMethod { if let syn::Pat::Ident(name) = &*argument.pat { let arg = match name.ident.unraw().to_string().as_str() { "context" | "ctx" | "_context" | "_ctx" => { - Some(Self::Context(argument.ty.unreferenced().clone())) + Some(Self::Context(Box::new(argument.ty.unreferenced().clone()))) } "executor" | "_executor" => Some(Self::Executor), _ => None, @@ -459,11 +459,11 @@ impl OnMethod { return None; } - Some(Self::Regular(OnField { + Some(Self::Regular(Box::new(OnField { name, ty: argument.ty.as_ref().clone(), description: attr.description.as_ref().map(|d| d.as_ref().value()), default: attr.default.as_ref().map(|v| v.as_ref().clone()), - })) + }))) } } diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 0165f4c05..eaa946d99 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -127,6 +127,7 @@ impl TypeExt for syn::Type { fn unparenthesized(&self) -> &Self { match self { Self::Paren(ty) => ty.elem.unparenthesized(), + Self::Group(ty) => ty.elem.unparenthesized(), ty => ty, } } @@ -207,9 +208,10 @@ impl TypeExt for syn::Type { // These types unlikely will be used as GraphQL types. T::BareFn(_) | T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) => {} - // TODO: uncomment this, once lint is stabilized. + // Following the syn idiom for exhaustive matching on Type: + // https://github.com/dtolnay/syn/blob/1.0.90/src/ty.rs#L67-L87 + // TODO: #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))] // https://github.com/rust-lang/rust/issues/89554 - // #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))] _ => unimplemented!(), } } @@ -317,7 +319,7 @@ impl GenericsExt for syn::Generics { } /// Replaces [`Generics`] with default values: -/// - `'static` for [`Lifetime`]s +/// - `'static` for [`Lifetime`]s; /// - `::juniper::DefaultScalarValue` for [`Type`]s. /// /// [`Generics`]: syn::Generics diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs deleted file mode 100644 index 458bc43ea..000000000 --- a/juniper_codegen/src/derive_enum.rs +++ /dev/null @@ -1,153 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ext::IdentExt, spanned::Spanned, Data, Fields}; - -use crate::{ - result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer, RenameRule}, -}; - -pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result { - let ast_span = ast.span(); - - if !ast.generics.params.is_empty() { - return Err(error.custom_error(ast_span, "does not support generics or lifetimes")); - } - - let variants = match ast.data { - Data::Enum(enum_data) => enum_data.variants, - _ => return Err(error.custom_error(ast_span, "can only be applied to enums")), - }; - - // Parse attributes. - let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?; - let ident = &ast.ident; - let name = attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| ident.unraw().to_string()); - - let fields = variants - .into_iter() - .filter_map(|field| { - let span = field.span(); - let field_attrs = match util::FieldAttributes::from_attrs( - &field.attrs, - util::FieldAttributeParseMode::Object, - ) { - Ok(attrs) => attrs, - Err(err) => { - proc_macro_error::emit_error!(err); - return None; - } - }; - - let field_name = field.ident; - let name = field_attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| { - attrs - .rename - .unwrap_or(RenameRule::ScreamingSnakeCase) - .apply(&field_name.unraw().to_string()) - }); - - let resolver_code = quote!( #ident::#field_name ); - - let _type = match field.fields { - Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(), - _ => { - error.emit_custom( - field.fields.span(), - "all fields of the enum must be unnamed, e.g., None", - ); - return None; - } - }; - - if let Some(skip) = field_attrs.skip { - error.unsupported_attribute(skip.span(), UnsupportedAttribute::Skip); - return None; - } - - if name.starts_with("__") { - error.no_double_underscore(if let Some(name) = field_attrs.name { - name.span_ident() - } else { - field_name.span() - }); - } - - if let Some(default) = field_attrs.default { - error.unsupported_attribute_within( - default.span_ident(), - UnsupportedAttribute::Default, - ); - } - - Some(util::GraphQLTypeDefinitionField { - name, - _type, - args: Vec::new(), - description: field_attrs.description.map(SpanContainer::into_inner), - deprecation: field_attrs.deprecation.map(SpanContainer::into_inner), - resolver_code, - is_type_inferred: true, - is_async: false, - default: None, - span, - }) - }) - .collect::>(); - - proc_macro_error::abort_if_dirty(); - - if fields.is_empty() { - error.not_empty(ast_span); - } - if let Some(duplicates) = - crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) - { - error.duplicate(duplicates.iter()) - } - - if !attrs.interfaces.is_empty() { - attrs.interfaces.iter().for_each(|elm| { - error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface) - }); - } - - if let Some(scalar) = attrs.scalar { - error.unsupported_attribute(scalar.span_ident(), UnsupportedAttribute::Scalar); - } - - if !attrs.is_internal && name.starts_with("__") { - error.no_double_underscore(if let Some(name) = attrs.name { - name.span_ident() - } else { - ident.span() - }); - } - - proc_macro_error::abort_if_dirty(); - - let definition = util::GraphQLTypeDefiniton { - name, - _type: syn::parse_str(&ast.ident.to_string()).unwrap(), - context: attrs.context.map(SpanContainer::into_inner), - scalar: None, - description: attrs.description.map(SpanContainer::into_inner), - fields, - // NOTICE: only unit variants allow -> no generics possible - generics: syn::Generics::default(), - interfaces: vec![], - include_type_generics: true, - generic_scalar: true, - no_async: attrs.no_async.is_some(), - }; - - Ok(definition.into_enum_tokens()) -} diff --git a/juniper_codegen/src/graphql_enum/derive.rs b/juniper_codegen/src/graphql_enum/derive.rs new file mode 100644 index 000000000..b4197439a --- /dev/null +++ b/juniper_codegen/src/graphql_enum/derive.rs @@ -0,0 +1,140 @@ +//! Code generation for `#[derive(GraphQLEnum)]` macro. + +use proc_macro2::TokenStream; +use quote::ToTokens as _; +use std::collections::HashSet; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; + +use crate::{ + common::scalar, + result::GraphQLScope, + util::{span_container::SpanContainer, RenameRule}, +}; + +use super::{ContainerAttr, Definition, VariantAttr, VariantDefinition}; + +/// [`GraphQLScope`] of errors for `#[derive(GraphQLEnum)]` macro. +const ERR: GraphQLScope = GraphQLScope::EnumDerive; + +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input)?; + let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?; + + let data = if let syn::Data::Enum(data) = &ast.data { + data + } else { + return Err(ERR.custom_error(ast.span(), "can only be derived on enums")); + }; + + let mut has_ignored_variants = false; + let renaming = attr + .rename + .map(SpanContainer::into_inner) + .unwrap_or(RenameRule::ScreamingSnakeCase); + let variants = data + .variants + .iter() + .filter_map(|v| { + parse_variant(v, renaming).or_else(|| { + has_ignored_variants = true; + None + }) + }) + .collect::>(); + + proc_macro_error::abort_if_dirty(); + + if variants.is_empty() { + return Err(ERR.custom_error( + data.variants.span(), + "expected at least 1 non-ignored variant", + )); + } + + let unique_variants = variants.iter().map(|v| &v.name).collect::>(); + if unique_variants.len() != variants.len() { + return Err(ERR.custom_error( + data.variants.span(), + "expected all enum variants to have unique names", + )); + } + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| ast.ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| ast.ident.span()), + ); + } + + let context = attr + .context + .map_or_else(|| parse_quote! { () }, SpanContainer::into_inner); + + let description = attr.description.map(SpanContainer::into_inner); + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + proc_macro_error::abort_if_dirty(); + + let definition = Definition { + ident: ast.ident, + generics: ast.generics, + name, + description, + context, + scalar, + variants, + has_ignored_variants, + }; + + Ok(definition.into_token_stream()) +} + +/// Parses a [`VariantDefinition`] from the given struct field definition. +/// +/// Returns [`None`] if the parsing fails, or the enum variant is ignored. +fn parse_variant(v: &syn::Variant, renaming: RenameRule) -> Option { + let var_attr = VariantAttr::from_attrs("graphql", &v.attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if var_attr.ignore.is_some() { + return None; + } + + if !v.fields.is_empty() { + err_variant_with_fields(&v.fields)?; + } + + let name = var_attr.name.map_or_else( + || renaming.apply(&v.ident.unraw().to_string()), + SpanContainer::into_inner, + ); + + let description = var_attr.description.map(SpanContainer::into_inner); + + let deprecated = var_attr + .deprecated + .map(|desc| desc.into_inner().as_ref().map(syn::LitStr::value)); + + Some(VariantDefinition { + ident: v.ident.clone(), + name, + description, + deprecated, + }) +} + +/// Emits "no fields allowed for non-ignored variants" [`syn::Error`] pointing to +/// the given `span`. +pub fn err_variant_with_fields(span: &S) -> Option { + ERR.emit_custom(span.span(), "no fields allowed for non-ignored variants"); + None +} diff --git a/juniper_codegen/src/graphql_enum/mod.rs b/juniper_codegen/src/graphql_enum/mod.rs new file mode 100644 index 000000000..cc35b5de6 --- /dev/null +++ b/juniper_codegen/src/graphql_enum/mod.rs @@ -0,0 +1,785 @@ +//! Code generation for [GraphQL enums][1]. +//! +//! [1]: https://spec.graphql.org/October2021/#sec-Enums + +pub(crate) mod derive; + +use std::convert::TryInto as _; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, + token, +}; + +use crate::{ + common::{ + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + scalar, + }, + util::{ + filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, RenameRule, + }, +}; + +/// Available arguments behind `#[graphql]` attribute placed on an enum +/// definition, when generating code for [GraphQL enum][1] type. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Enums +#[derive(Debug, Default)] +struct ContainerAttr { + /// Explicitly specified name of [GraphQL enum][1] type. + /// + /// If [`None`], then Rust trait name is used by default. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + name: Option>, + + /// Explicitly specified [description][2] of [GraphQL enum][1] type. + /// + /// If [`None`], then Rust doc comment is used as [description][2], if any. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + /// [2]: https://spec.graphql.org/October2021/#sec-Descriptions + description: Option>, + + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL enum][1] type with. + /// + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + context: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to resolve this [GraphQL enum][1] type with. + /// + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type, which. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + scalar: Option>, + + /// Explicitly specified [`RenameRule`] for all variants of this + /// [GraphQL enum][1] type. + /// + /// If [`None`] then the default rule will be + /// [`RenameRule::ScreamingSnakeCase`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + rename: Option>, + + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. + is_internal: bool, +} + +impl Parse for ContainerAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ctx" | "context" | "Context" => { + input.parse::()?; + let ctx = input.parse::()?; + out.context + .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "rename" | "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + out.rename + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } + "internal" => { + out.is_internal = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl ContainerAttr { + /// Tries to merge two [`ContainerAttr`]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), + rename: try_merge_opt!(rename: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`ContainerAttr`] 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 attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); + } + + Ok(attr) + } +} + +/// Available arguments behind `#[graphql]` attribute when generating code for +/// [GraphQL enum][1]'s variant. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Enums +#[derive(Debug, Default)] +struct VariantAttr { + /// Explicitly specified name of [GraphQL enum][1] variant. + /// + /// If [`None`], then Rust trait name is used by default. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + name: Option>, + + /// Explicitly specified [description][2] of [GraphQL enum][1] variant. + /// + /// If [`None`], then Rust doc comment is used as [description][2], if any. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + /// [2]: https://spec.graphql.org/October2021/#sec-Descriptions + description: Option>, + + /// Explicitly specified [deprecation][2] of this [GraphQL enum][1]'s + /// variant. + /// + /// If [`None`], then Rust `#[deprecated]` attribute is used as the + /// [deprecation][2], if any. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + /// [2]: https://spec.graphql.org/October2021/#sec-Deprecation + deprecated: Option>>, + + /// Explicitly specified marker for the variant being ignored and not + /// included into [GraphQL enum][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + ignore: Option>, +} + +impl Parse for VariantAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "deprecated" => { + let mut reason = None; + if input.is_next::() { + input.parse::()?; + reason = Some(input.parse::()?); + } + out.deprecated + .replace(SpanContainer::new( + ident.span(), + reason.as_ref().map(|r| r.span()), + reason, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ignore" | "skip" => out + .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(out) + } +} + +impl VariantAttr { + /// Tries to merge two [`VariantAttr`]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 [`VariantAttr`] 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 attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); + } + + if attr.deprecated.is_none() { + attr.deprecated = get_deprecated(attrs).map(|sc| { + let span = sc.span_ident(); + sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span))) + }); + } + + Ok(attr) + } +} + +/// Representation of a [GraphQL enum][1]'s variant for code generation. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Enums +#[derive(Debug)] +pub(crate) struct VariantDefinition { + /// [`Ident`] of this [GraphQL enum][1]'s variant. + /// + /// [`Ident`]: syn::Ident + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + pub(crate) ident: syn::Ident, + + /// Name of this [GraphQL enum][1]'s variant in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + pub(crate) name: String, + + /// [Description][2] of this [GraphQL enum][1]'s variant to put into GraphQL + /// schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + pub(crate) description: Option, + + /// [Deprecation][2] of this [GraphQL enum][1]'s variant to put into GraphQL + /// schema. + /// + /// If inner [`Option`] is [`None`], then deprecation has no message + /// attached. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + /// [2]: https://spec.graphql.org/October2021/#sec-Deprecation + pub(crate) deprecated: Option>, +} + +/// Definition of [GraphQL enum][1] for code generation. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Enums +struct Definition { + /// [`Ident`] of this [GraphQL enum][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + ident: syn::Ident, + + /// [`syn::Generics`] of the enum describing the [GraphQL enum][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + generics: syn::Generics, + + /// Name of this [GraphQL enum][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + name: String, + + /// Description of this [GraphQL enum][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + description: Option, + + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL enum][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + context: syn::Type, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL enum][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + scalar: scalar::Type, + + /// Defined variants of this [GraphQL enum][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + variants: Vec, + + /// Indicates, whether this enum contains ignored variants. + has_ignored_variants: bool, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_input_and_output_type_tokens().to_tokens(into); + self.impl_graphql_type_tokens().to_tokens(into); + self.impl_graphql_value_tokens().to_tokens(into); + self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_from_input_value_tokens().to_tokens(into); + self.impl_to_input_value_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); + } +} + +impl Definition { + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL enum][1]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_input_and_output_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsInputType<#scalar> + for #ident#ty_generics + #where_clause {} + + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> + for #ident#ty_generics + #where_clause {} + } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL enum][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_graphql_type_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let name = &self.name; + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let variants_meta = self.variants.iter().map(|v| { + let name = &v.name; + let description = v.description.as_ref().map_or_else( + || quote! { None }, + |desc| quote! { Some(String::from(#desc)) }, + ); + let deprecation_status = match &v.deprecated { + None => quote! { ::juniper::meta::DeprecationStatus::Current }, + Some(None) => quote! { + ::juniper::meta::DeprecationStatus::Deprecated(None) + }, + Some(Some(reason)) => { + quote! { + ::juniper::meta::DeprecationStatus::Deprecated( + Some(String::from(#reason)) + ) + } + } + }; + + quote! { + ::juniper::meta::EnumValue { + name: String::from(#name), + description: #description, + deprecation_status: #deprecation_status, + } + } + }); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar> + for #ident#ty_generics + #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, + { + let variants = [#( #variants_meta ),*]; + + registry.build_enum_type::<#ident#ty_generics>(info, &variants) + #description + .into_meta() + } + } + } + } + + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL enum][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + let context = &self.context; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let variants = self.variants.iter().map(|v| { + let ident = &v.ident; + let name = &v.name; + + quote! { + Self::#ident => Ok(::juniper::Value::scalar(String::from(#name))), + } + }); + + let ignored = self.has_ignored_variants.then(|| { + quote! { + _ => Err(::juniper::FieldError::<#scalar>::from( + "Unable to resolve skipped enum variant", + )), + } + }); + + quote! { + impl#impl_generics ::juniper::GraphQLValue<#scalar> + for #ident#ty_generics + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + + fn resolve( + &self, + _: &(), + _: Option<&[::juniper::Selection<#scalar>]>, + _: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match self { + #( #variants )* + #ignored + } + } + } + } + } + + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL enum][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> + for #ident#ty_generics + #where_clause + { + fn resolve_async<'__a>( + &'__a self, + info: &'__a Self::TypeInfo, + selection_set: Option<&'__a [::juniper::Selection<#scalar>]>, + executor: &'__a ::juniper::Executor, + ) -> ::juniper::BoxFuture<'__a, ::juniper::ExecutionResult<#scalar>> { + let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); + Box::pin(::juniper::futures::future::ready(v)) + } + } + } + } + + /// Returns generated code implementing [`FromInputValue`] trait for this + /// [GraphQL enum][1]. + /// + /// [`FromInputValue`]: juniper::FromInputValue + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_from_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let variants = self.variants.iter().map(|v| { + let ident = &v.ident; + let name = &v.name; + + quote! { + Some(#name) => Ok(Self::#ident), + } + }); + + quote! { + impl#impl_generics ::juniper::FromInputValue<#scalar> + for #ident#ty_generics + #where_clause + { + type Error = ::std::string::String; + + fn from_input_value(v: &::juniper::InputValue<#scalar>) -> Result { + match v.as_enum_value().or_else(|| v.as_string_value()) { + #( #variants )* + _ => Err(::std::format!("Unknown enum value: {}", v)), + } + } + } + } + } + + /// Returns generated code implementing [`ToInputValue`] trait for this + /// [GraphQL enum][1]. + /// + /// [`ToInputValue`]: juniper::ToInputValue + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_to_input_value_tokens(&self) -> TokenStream { + let ident = &self.ident; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let variants = self.variants.iter().map(|v| { + let var_ident = &v.ident; + let name = &v.name; + + quote! { + #ident::#var_ident => ::juniper::InputValue::<#scalar>::scalar( + String::from(#name), + ), + } + }); + + let ignored = self.has_ignored_variants.then(|| { + quote! { + _ => panic!("Unable to resolve skipped enum variant"), + } + }); + + quote! { + impl#impl_generics ::juniper::ToInputValue<#scalar> + for #ident#ty_generics + #where_clause + { + fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { + match self { + #( #variants )* + #ignored + } + } + } + } + } + + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL enum][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`WrappedType`]: juniper::macros::reflect::WrappedType + /// [1]: https://spec.graphql.org/October2021/#sec-Enums + #[must_use] + fn impl_reflection_traits_tokens(&self) -> TokenStream { + let ident = &self.ident; + let name = &self.name; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + quote! { + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> + for #ident#ty_generics + #where_clause + { + const NAME: ::juniper::macros::reflect::Type = #name; + } + + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> + for #ident#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> + for #ident#ty_generics + #where_clause + { + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this enum. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ident = &self.ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ident#ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 5df2f494c..c9db92e25 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -131,6 +131,7 @@ fn expand_on_trait( .collect(), implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), suppress_dead_code: None, + src_intra_doc_link: format!("trait@{}", trait_ident), }; Ok(quote! { @@ -224,8 +225,8 @@ fn expand_on_derive_input( ) -> syn::Result { let attr = Attr::from_attrs("graphql_interface", &attrs)?; - let trait_ident = &ast.ident; - let trait_span = ast.span(); + let struct_ident = &ast.ident; + let struct_span = ast.span(); let data = match &mut ast.data { syn::Data::Struct(data) => data, @@ -242,13 +243,13 @@ fn expand_on_derive_input( .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| trait_ident.unraw().to_string()); + .unwrap_or_else(|| struct_ident.unraw().to_string()); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name .as_ref() .map(SpanContainer::span_ident) - .unwrap_or_else(|| trait_ident.span()), + .unwrap_or_else(|| struct_ident.span()), ); } @@ -271,10 +272,10 @@ fn expand_on_derive_input( proc_macro_error::abort_if_dirty(); if fields.is_empty() { - ERR.emit_custom(trait_span, "must have at least one field"); + ERR.emit_custom(struct_span, "must have at least one field"); } if !field::all_different(&fields) { - ERR.emit_custom(trait_span, "must have a different name for each field"); + ERR.emit_custom(struct_span, "must have a different name for each field"); } proc_macro_error::abort_if_dirty(); @@ -294,7 +295,7 @@ fn expand_on_derive_input( }) .unwrap_or_else(|| parse_quote! { () }); - let (enum_ident, enum_alias_ident) = enum_idents(trait_ident, attr.r#enum.as_deref()); + let (enum_ident, enum_alias_ident) = enum_idents(struct_ident, attr.r#enum.as_deref()); let generated_code = Definition { generics: ast.generics.clone(), vis: ast.vis.clone(), @@ -312,6 +313,7 @@ fn expand_on_derive_input( .collect(), implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), suppress_dead_code: None, + src_intra_doc_link: format!("struct@{}", struct_ident), }; Ok(quote! { diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 707ab49d3..1677c221c 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -104,6 +104,7 @@ pub fn expand(input: TokenStream) -> syn::Result { .collect(), implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())), + src_intra_doc_link: format!("struct@{}", struct_ident), } .into_token_stream()) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0d1bf13f9..8abbef8ca 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -31,12 +31,11 @@ use crate::{ util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; -/// Returns [`Ident`]s for generic enum deriving [`Clone`] and [`Copy`] on it -/// and enum alias which generic arguments are filled with +/// Returns [`syn::Ident`]s for a generic enum deriving [`Clone`] and [`Copy`] +/// on it and enum alias which generic arguments are filled with /// [GraphQL interface][1] implementers. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [`Ident`]: syn::Ident fn enum_idents( trait_ident: &syn::Ident, alias_ident: Option<&syn::Ident>, @@ -353,6 +352,12 @@ struct Definition { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces suppress_dead_code: Option<(syn::Ident, syn::Fields)>, + + /// Intra-doc link to the [`syn::Item`] defining this + /// [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + src_intra_doc_link: String, } impl ToTokens for Definition { @@ -384,7 +389,6 @@ impl Definition { let par = format_ident!("__I{}", id); parse_quote! { #par } }); - let variants_idents = self .implemented_for .iter() @@ -393,7 +397,6 @@ impl Definition { let interface_gens = &self.generics; let (interface_impl_gens, interface_ty_gens, interface_where_clause) = self.generics.split_for_impl(); - let (interface_gens_lifetimes, interface_gens_tys) = interface_gens .params .clone() @@ -407,13 +410,11 @@ impl Definition { enum_gens.params.extend(interface_gens_tys.clone()); enum_gens }; - let enum_alias_gens = { let mut enum_alias_gens = interface_gens.clone(); enum_alias_gens.move_bounds_to_where_clause(); enum_alias_gens }; - let enum_to_alias_gens = { interface_gens_lifetimes .into_iter() @@ -433,26 +434,42 @@ impl Definition { rest => quote! { #rest }, })) }; - - let phantom_variant = self.has_phantom_variant().then(|| { - let phantom_params = interface_gens.params.iter().filter_map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => return None, - }; - Some(quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - }) - }); - quote! { __Phantom(#(#phantom_params),*) } - }); + let enum_doc = format!( + "Enum building an opaque value represented by [`{}`]({}) \ + [GraphQL interface][0].\ + \n\n\ + [0]: https://spec.graphql.org/June2018/#sec-Interfaces", + self.name, self.src_intra_doc_link, + ); + let enum_alias_doc = format!( + "Opaque value represented by [`{}`]({}) [GraphQL interface][0].\ + \n\n\ + [0]: https://spec.graphql.org/June2018/#sec-Interfaces", + self.name, self.src_intra_doc_link, + ); + + let phantom_variant = self + .has_phantom_variant() + .then(|| { + let phantom_params = interface_gens.params.iter().filter_map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => return None, + }; + Some(quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + }) + }); + quote! { __Phantom(#(#phantom_params),*) } + }) + .into_iter(); let from_impls = self .implemented_for @@ -475,16 +492,18 @@ impl Definition { quote! { #[automatically_derived] #[derive(Clone, Copy, Debug)] + #[doc = #enum_doc] #vis enum #enum_ident#enum_gens { - #(#variants_idents(#variant_gens_pars),)* - #phantom_variant + #( #[doc(hidden)] #variants_idents(#variant_gens_pars), )* + #( #[doc(hidden)] #phantom_variant, )* } #[automatically_derived] + #[doc = #enum_alias_doc] #vis type #alias_ident#enum_alias_gens = - #enum_ident<#(#enum_to_alias_gens),*>; + #enum_ident<#( #enum_to_alias_gens ),*>; - #(#from_impls)* + #( #from_impls )* } } @@ -511,7 +530,7 @@ impl Definition { let none = Option::<#ident#const_gens>::None; match none { Some(unreachable) => { - #(let _ = unreachable.#fields;)* + #( let _ = unreachable.#fields; )* } None => {} } @@ -780,8 +799,8 @@ impl Definition { &self, info: &Self::TypeInfo, field: &str, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, + args: &::juniper::Arguments<'_, #scalar>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* @@ -801,8 +820,8 @@ impl Definition { &self, info: &Self::TypeInfo, type_name: &str, - _: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, + _: Option<&[::juniper::Selection<'_, #scalar>]>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { #downcast } @@ -851,8 +870,8 @@ impl Definition { &'b self, info: &'b Self::TypeInfo, field: &'b str, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, + args: &'b ::juniper::Arguments<'_, #scalar>, + executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match field { #( #fields_resolvers )* @@ -991,11 +1010,11 @@ impl Definition { ::juniper::macros::reflect::Name, ::juniper::macros::reflect::Type, ::juniper::macros::reflect::WrappedValue, - )] = &[#(( + )] = &[#( ( #args_names, <#args_tys as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, <#args_tys as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, - )),*]; + ) ),*]; } } }) @@ -1056,11 +1075,11 @@ impl Definition { fn call( &self, info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, + args: &::juniper::Arguments<'_, #scalar>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { match self { - #(#ty::#implemented_for_idents(v) => { + #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, #const_implemented_for, @@ -1072,7 +1091,7 @@ impl Definition { #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) - })* + } )* #unreachable_arm } } @@ -1136,11 +1155,11 @@ impl Definition { fn call<'b>( &'b self, info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, + args: &'b ::juniper::Arguments<'_, #scalar>, + executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match self { - #(#ty::#implemented_for_idents(v) => { + #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( #ty#const_ty_generics, #const_implemented_for, @@ -1152,7 +1171,7 @@ impl Definition { #scalar, { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) - })* + } )* #unreachable_arm } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 49459737b..0f3b05acb 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -500,7 +500,6 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_as_dyn_graphql_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); @@ -680,8 +679,8 @@ impl Definition { fn call( &self, info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, + args: &::juniper::Arguments<'_, #scalar>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { #resolve } @@ -741,8 +740,8 @@ impl Definition { fn call<'b>( &'b self, info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, + args: &'b ::juniper::Arguments<'_, #scalar>, + executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { let fut = #res; #resolving_code @@ -800,8 +799,8 @@ impl Definition { &self, info: &Self::TypeInfo, field: &str, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, + args: &::juniper::Arguments<'_, #scalar>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* @@ -857,8 +856,8 @@ impl Definition { &'b self, info: &'b Self::TypeInfo, field: &'b str, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, + args: &'b ::juniper::Arguments<'_, #scalar>, + executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { match field { #( #fields_resolvers )* @@ -868,43 +867,4 @@ impl Definition { } } } - - /// Returns generated code implementing [`AsDynGraphQLValue`] trait for this - /// [GraphQL object][1]. - /// - /// [`AsDynGraphQLValue`]: juniper::AsDynGraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - #[must_use] - fn impl_as_dyn_graphql_value_tokens(&self) -> Option { - if self.interfaces.is_empty() { - return None; - } - - let scalar = &self.scalar; - - let (impl_generics, where_clause) = self.impl_generics(true); - let ty = &self.ty; - - Some(quote! { - #[allow(non_snake_case)] - #[automatically_derived] - impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #where_clause - { - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - fn as_dyn_graphql_value( - &self, - ) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> { - self - } - - fn as_dyn_graphql_value_async( - &self, - ) -> &::juniper::DynGraphQLValueAsync<#scalar, Self::Context, Self::TypeInfo> { - self - } - } - }) - } } diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index a7de21bc9..556529805 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -432,8 +432,8 @@ impl Definition { fn resolve( &self, info: &(), - selection: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, + selection: Option<&[::juniper::Selection<'_, #scalar>]>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { #resolve } @@ -460,8 +460,8 @@ impl Definition { fn resolve_async<'b>( &'b self, info: &'b Self::TypeInfo, - selection_set: Option<&'b [::juniper::Selection<#scalar>]>, - executor: &'b ::juniper::Executor, + selection_set: Option<&'b [::juniper::Selection<'_, #scalar>]>, + executor: &'b ::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { use ::juniper::futures::future; let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); diff --git a/juniper_codegen/src/graphql_subscription/mod.rs b/juniper_codegen/src/graphql_subscription/mod.rs index ca64e0e0b..3c07c9ae9 100644 --- a/juniper_codegen/src/graphql_subscription/mod.rs +++ b/juniper_codegen/src/graphql_subscription/mod.rs @@ -57,8 +57,8 @@ impl Definition { &self, _: &Self::TypeInfo, _: &str, - _: &::juniper::Arguments<#scalar>, - _: &::juniper::Executor, + _: &::juniper::Arguments<'_, #scalar>, + _: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { Err(::juniper::FieldError::from( "Called `resolve_field` on subscription object", diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 4c3bafca5..21fb9cb45 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -554,8 +554,8 @@ impl Definition { &self, info: &Self::TypeInfo, type_name: &str, - _: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, + _: Option<&[::juniper::Selection<'_, #scalar>]>, + executor: &::juniper::Executor<'_, '_, Self::Context, #scalar>, ) -> ::juniper::ExecutionResult<#scalar> { let context = executor.context(); #( #variant_resolvers )* diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 3bffe9751..47b8e4013 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -1,10 +1,4 @@ -//! This crate supplies custom derive implementations for the -//! [juniper](https://github.com/graphql-rust/juniper) crate. -//! -//! You should not depend on juniper_codegen directly. -//! You only need the `juniper` crate. - -#![doc(html_root_url = "https://docs.rs/juniper_codegen/0.15.9")] +#![doc = include_str!("../README.md")] #![recursion_limit = "1024"] mod result; @@ -106,10 +100,10 @@ macro_rules! try_merge_hashset { }; } -mod derive_enum; mod derive_input_object; mod common; +mod graphql_enum; mod graphql_interface; mod graphql_object; mod graphql_scalar; @@ -121,17 +115,6 @@ use proc_macro::TokenStream; use proc_macro_error::{proc_macro_error, ResultExt as _}; use result::GraphQLScope; -#[proc_macro_error] -#[proc_macro_derive(GraphQLEnum, attributes(graphql))] -pub fn derive_enum(input: TokenStream) -> TokenStream { - let ast = syn::parse::(input).unwrap(); - let gen = derive_enum::impl_enum(ast, GraphQLScope::DeriveEnum); - match gen { - Ok(gen) => gen.into(), - Err(err) => proc_macro_error::abort!(err), - } -} - #[proc_macro_error] #[proc_macro_derive(GraphQLInputObject, attributes(graphql))] pub fn derive_input_object(input: TokenStream) -> TokenStream { @@ -143,6 +126,132 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { } } +/// `#[derive(GraphQLEnum)]` macro for deriving a [GraphQL enum][1] +/// implementation for enums. +/// +/// The `#[graphql]` helper attribute is used for configuring the derived +/// implementation. Specifying multiple `#[graphql]` attributes on the same +/// definition is totally okay. They all will be treated as a single attribute. +/// +/// ``` +/// use juniper::GraphQLEnum; +/// +/// #[derive(GraphQLEnum)] +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// } +/// ``` +/// +/// # Custom name, description and deprecation +/// +/// The name of [GraphQL enum][1] or its variants may be overridden with a +/// `name` attribute's argument. By default, a type name is used or +/// `SCREAMING_SNAKE_CASE` variant name. +/// +/// The description of [GraphQL enum][1] or its variants may be specified either +/// with a `description`/`desc` attribute's argument, or with a regular Rust doc +/// comment. +/// +/// A variant of [GraphQL enum][1] may be deprecated by specifying a +/// `deprecated` attribute's argument, or with regular Rust `#[deprecated]` +/// attribute. +/// +/// ``` +/// # use juniper::GraphQLEnum; +/// # +/// #[derive(GraphQLEnum)] +/// #[graphql( +/// // Rename the type for GraphQL by specifying the name here. +/// name = "AvailableEpisodes", +/// // You may also specify a description here. +/// // If present, doc comments will be ignored. +/// desc = "Possible episodes.", +/// )] +/// enum Episode { +/// /// Doc comment, also acting as description. +/// #[deprecated(note = "Don't use it")] +/// NewHope, +/// +/// #[graphql(name = "Jedi", desc = "Arguably the best one in the trilogy")] +/// #[graphql(deprecated = "Don't use it")] +/// Jedi, +/// +/// Empire, +/// } +/// ``` +/// +/// # Renaming policy +/// +/// By default, all [GraphQL enum][1] variants are renamed via +/// `SCREAMING_SNAKE_CASE` policy (so `NewHope` becomes `NEW_HOPE` variant in +/// GraphQL schema, and so on). This complies with default GraphQL naming +/// conventions [demonstrated in spec][1]. +/// +/// However, if you need for some reason apply another naming convention, it's +/// possible to do by using `rename_all` attribute's argument. At the moment it +/// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`, +/// `none` (disables any renaming). +/// +/// ``` +/// # use juniper::GraphQLEnum; +/// # +/// #[derive(GraphQLEnum)] +/// #[graphql(rename_all = "none")] // disables renaming +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// } +/// ``` +/// +/// # Ignoring struct fields +/// +/// To omit exposing a struct field in the GraphQL schema, use an `ignore` +/// attribute's argument directly on that field. Only ignored variants can +/// contain fields. +/// +/// ``` +/// # use juniper::GraphQLEnum; +/// # +/// #[derive(GraphQLEnum)] +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// #[graphql(ignore)] +/// Legends(T), +/// } +/// ``` +/// +/// # Custom `ScalarValue` +/// +/// By default, `#[derive(GraphQLEnum)]` macro generates code, which is generic +/// over a [`ScalarValue`] type. This can be changed with `scalar` attribute. +/// +/// ``` +/// # use juniper::{DefaultScalarValue, GraphQLEnum}; +/// # +/// #[derive(GraphQLEnum)] +/// #[graphql(scalar = DefaultScalarValue)] +/// enum Episode { +/// NewHope, +/// Empire, +/// Jedi, +/// } +/// ``` +/// +/// [`ScalarValue`]: juniper::ScalarValue +/// [1]: https://spec.graphql.org/October2021/#sec-Enums +#[proc_macro_error] +#[proc_macro_derive(GraphQLEnum, attributes(graphql))] +pub fn derive_enum(input: TokenStream) -> TokenStream { + graphql_enum::derive::expand(input.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLScalar)]` macro for deriving a [GraphQL scalar][0] /// implementation. /// @@ -680,7 +789,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// [GraphQL interface][1] can be represented with struct in case methods don't /// have any arguments: /// -/// ``` +/// ```rust /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this @@ -700,7 +809,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// /// Also [GraphQL interface][1] can be represented with trait: /// -/// ``` +/// ```rust /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this @@ -719,7 +828,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// ``` /// /// > __NOTE:__ Struct or trait representing interface acts only as a blueprint -/// > for names of methods, their arguments and return type. So isn't +/// > for names of methods, their arguments and return type, so isn't /// > actually used at a runtime. But no-one is stopping you from /// > implementing trait manually for your own usage. /// @@ -738,7 +847,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// The default value of a field argument may be specified with a `default` attribute argument (if /// no exact value is specified then [`Default::default`] is used). /// -/// ``` +/// ```rust /// # use juniper::graphql_interface; /// # /// #[graphql_interface(name = "Character", desc = "Possible episode characters.")] @@ -895,7 +1004,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// supports the following policies only: `SCREAMING_SNAKE_CASE`, `camelCase`, /// `none` (disables any renaming). /// -/// ``` +/// ```rust /// # use juniper::{graphql_interface, graphql_object}; /// # /// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming @@ -934,7 +1043,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// To omit some trait method to be assumed as a [GraphQL interface][1] field /// and ignore it, use an `ignore` attribute's argument directly on that method. /// -/// ``` +/// ```rust /// # use juniper::graphql_interface; /// # /// #[graphql_interface] @@ -958,7 +1067,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// or `ctx` then this argument is assumed as [`Context`] and will be omitted in GraphQL schema. /// Additionally, any argument may be marked as [`Context`] with a `context` attribute's argument. /// -/// ``` +/// ```rust /// # use std::collections::HashMap; /// # use juniper::{graphql_interface, graphql_object}; /// # @@ -1017,7 +1126,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// -/// ``` +/// ```rust /// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; /// # /// // NOTICE: Specifying `ScalarValue` as existing type parameter. @@ -1059,7 +1168,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// concrete [`ScalarValue`] type should be specified with a `scalar` /// attribute's argument. /// -/// ``` +/// ```rust /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// // NOTICE: Removing `Scalar` argument will fail compilation. @@ -1096,9 +1205,9 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// implementation for traits and its implementers. /// /// This macro is applicable only to structs and useful in case [interface][1] -/// methods doesn't have any arguments: +/// fields don't have any arguments: /// -/// ``` +/// ```rust /// use juniper::{GraphQLInterface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this @@ -1117,8 +1226,9 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// } /// ``` /// -/// For more info and possibilities see `#[`[`graphql_interface`]`]` macro. +/// For more info and possibilities see [`#[graphql_interface]`] macro. /// +/// [`#[graphql_interface]`]: crate::graphql_interface /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[proc_macro_error] #[proc_macro_derive(GraphQLInterface, attributes(graphql))] diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index 8b58339a5..5c979d690 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -9,6 +9,7 @@ use std::fmt; pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; pub enum GraphQLScope { + EnumDerive, InterfaceAttr, InterfaceDerive, ObjectAttr, @@ -19,19 +20,18 @@ pub enum GraphQLScope { UnionAttr, UnionDerive, DeriveInputObject, - DeriveEnum, } impl GraphQLScope { pub fn spec_section(&self) -> &str { match self { + Self::EnumDerive => "#sec-Enums", Self::InterfaceAttr | Self::InterfaceDerive => "#sec-Interfaces", Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars", Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars", Self::UnionAttr | Self::UnionDerive => "#sec-Unions", Self::DeriveInputObject => "#sec-Input-Objects", - Self::DeriveEnum => "#sec-Enums", } } } @@ -39,13 +39,13 @@ impl GraphQLScope { impl fmt::Display for GraphQLScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { + Self::EnumDerive => "enum", Self::InterfaceAttr | Self::InterfaceDerive => "interface", Self::ObjectAttr | Self::ObjectDerive => "object", Self::ScalarAttr | Self::ScalarDerive => "scalar", Self::ScalarValueDerive => "built-in scalars", Self::UnionAttr | Self::UnionDerive => "union", Self::DeriveInputObject => "input object", - Self::DeriveEnum => "enum", }; write!(f, "GraphQL {}", name) } @@ -56,9 +56,7 @@ impl fmt::Display for GraphQLScope { pub enum UnsupportedAttribute { Skip, Interface, - Scalar, Deprecation, - Default, } impl GraphQLScope { diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 510c3aeb2..bda4bc6bb 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -683,232 +683,6 @@ impl GraphQLTypeDefiniton { self.fields.iter().any(|field| field.is_async) } - pub fn into_enum_tokens(self) -> TokenStream { - let name = &self.name; - let ty = &self._type; - let context = self - .context - .as_ref() - .map(|ctx| quote!( #ctx )) - .unwrap_or_else(|| quote!(())); - - let scalar = self - .scalar - .as_ref() - .map(|s| quote!( #s )) - .unwrap_or_else(|| { - if self.generic_scalar { - // If generic_scalar is true, we always insert a generic scalar. - // See more comments below. - quote!(__S) - } else { - quote!(::juniper::DefaultScalarValue) - } - }); - - let description = self - .description - .as_ref() - .map(|description| quote!( .description(#description) )); - - let values = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - - let descr = variant - .description - .as_ref() - .map(|description| quote!(Some(#description.to_string()))) - .unwrap_or_else(|| quote!(None)); - - let depr = variant - .deprecation - .as_ref() - .map(|deprecation| match deprecation.reason.as_ref() { - Some(reason) => quote!( ::juniper::meta::DeprecationStatus::Deprecated(Some(#reason.to_string())) ), - None => quote!( ::juniper::meta::DeprecationStatus::Deprecated(None) ), - }) - .unwrap_or_else(|| quote!(::juniper::meta::DeprecationStatus::Current)); - - quote!( - ::juniper::meta::EnumValue { - name: #variant_name.to_string(), - description: #descr, - deprecation_status: #depr, - }, - ) - }); - - let resolves = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - let resolver_code = &variant.resolver_code; - - quote!( - &#resolver_code => ::juniper::Value::scalar(String::from(#variant_name)), - ) - }); - - let from_inputs = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - let resolver_code = &variant.resolver_code; - - quote!( - Some(#variant_name) => Ok(#resolver_code), - ) - }); - - let to_inputs = self.fields.iter().map(|variant| { - let variant_name = &variant.name; - let resolver_code = &variant.resolver_code; - - quote!( - &#resolver_code => - ::juniper::InputValue::scalar(#variant_name.to_string()), - ) - }); - - let mut generics = self.generics.clone(); - - 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 - .predicates - .push(parse_quote!(__S: ::juniper::ScalarValue)); - } - - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - let mut where_async = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); - where_async - .predicates - .push(parse_quote!( #scalar: Send + Sync )); - where_async.predicates.push(parse_quote!(Self: Sync)); - - let _async = quote!( - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty - #where_async - { - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [::juniper::Selection<#scalar>]>, - executor: &'a ::juniper::Executor, - ) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#scalar>> { - use ::juniper::futures::future; - let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor); - Box::pin(future::ready(v)) - } - } - ); - - let mut body = quote!( - impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty - #where_clause { } - - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty - #where_clause { } - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty - #where_clause - { - fn name(_: &()) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - _: &(), - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - registry.build_enum_type::<#ty>(&(), &[ - #( #values )* - ]) - #description - .into_meta() - } - } - - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty - #where_clause - { - type Context = #context; - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - fn resolve( - &self, - _: &(), - _: Option<&[::juniper::Selection<#scalar>]>, - _: &::juniper::Executor - ) -> ::juniper::ExecutionResult<#scalar> { - let v = match self { - #( #resolves )* - }; - Ok(v) - } - } - - impl#impl_generics ::juniper::FromInputValue<#scalar> for #ty - #where_clause - { - type Error = ::std::string::String; - - fn from_input_value( - v: &::juniper::InputValue<#scalar> - ) -> Result<#ty, Self::Error> { - match v.as_enum_value().or_else(|| v.as_string_value()) { - #( #from_inputs )* - _ => Err(format!("Unknown enum value: {}", v)), - } - } - } - - impl#impl_generics ::juniper::ToInputValue<#scalar> for #ty - #where_clause - { - fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - match self { - #( #to_inputs )* - } - } - } - - impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty - #where_clause - { - const NAME: ::juniper::macros::reflect::Type = #name; - } - - impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty - #where_clause - { - const NAMES: ::juniper::macros::reflect::Types = - &[>::NAME]; - } - - impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty - #where_clause - { - const VALUE: ::juniper::macros::reflect::WrappedValue = 1; - } - ); - - if !self.no_async { - body.extend(_async) - } - - body - } - pub fn into_input_object_tokens(self) -> TokenStream { let name = &self.name; let ty = &self._type; diff --git a/juniper_graphql_ws/CHANGELOG.md b/juniper_graphql_ws/CHANGELOG.md index 2d283d01b..a375a5bfb 100644 --- a/juniper_graphql_ws/CHANGELOG.md +++ b/juniper_graphql_ws/CHANGELOG.md @@ -1,8 +1,28 @@ -# master +`juniper_graphql_ws` changelog +============================== -- Compatibility with the latest `juniper`. +All user visible changes to `juniper_graphql_ws` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.2.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_graphql_ws-0.2.0) -- Fix null deserialization issue ([#735](https://github.com/graphql-rust/juniper/issues/735)) -- Initial Release + + +## master + +### BC Breaks + +- Switched to 0.16 version of [`juniper` crate]. +- Switched to 0.17 version of [`juniper_subscriptions` crate]. + + + + +## Previous releases + +See [old CHANGELOG](/../../blob/juniper_graphql_ws-v0.3.0/juniper_graphql_ws/CHANGELOG.md). + + + + +[`juniper` crate]: https://docs.rs/juniper +[`juniper_subscriptions` crate]: https://docs.rs/juniper_subscriptions +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_graphql_ws/Cargo.toml b/juniper_graphql_ws/Cargo.toml index f09062443..3b74f4bb6 100644 --- a/juniper_graphql_ws/Cargo.toml +++ b/juniper_graphql_ws/Cargo.toml @@ -1,19 +1,23 @@ [package] name = "juniper_graphql_ws" -version = "0.3.0" +version = "0.4.0-dev" edition = "2018" -authors = ["Christopher Brown "] +description = "GraphQL over WebSocket Protocol implementation for `juniper` crate." license = "BSD-2-Clause" -description = "GraphQL over WebSocket protocol implementation for Juniper" +authors = ["Christopher Brown "] documentation = "https://docs.rs/juniper_graphql_ws" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_graphql_ws" repository = "https://github.com/graphql-rust/juniper" -keywords = ["apollo", "graphql", "graphql-ws", "juniper"] +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["apollo", "graphql", "graphql-ws", "subscription", "websocket"] +exclude = ["/release.toml"] [dependencies] juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } -juniper_subscriptions = { version = "0.16.0", path = "../juniper_subscriptions" } +juniper_subscriptions = { version = "0.17.0-dev", path = "../juniper_subscriptions" } serde = { version = "1.0.8", features = ["derive"], default-features = false } -tokio = { version = "1", features = ["macros", "rt", "time"], default-features = false } +tokio = { version = "1.0", features = ["macros", "rt", "time"], default-features = false } [dev-dependencies] serde_json = "1.0" diff --git a/juniper_graphql_ws/LICENSE b/juniper_graphql_ws/LICENSE new file mode 100644 index 000000000..652dd95da --- /dev/null +++ b/juniper_graphql_ws/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2018-2022, Christopher Brown +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/juniper_graphql_ws/Makefile.toml b/juniper_graphql_ws/Makefile.toml deleted file mode 100644 index 8a804aba2..000000000 --- a/juniper_graphql_ws/Makefile.toml +++ /dev/null @@ -1,33 +0,0 @@ -# This is needed as the release config is at a different path than the top-level -# release config. - -[tasks.release] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--execute", "${RELEASE_LEVEL}"] - -[tasks.release-dry-run] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "--no-publish", "--no-push", "--no-tag", "${RELEASE_LEVEL}"] - -[tasks.release-local-test] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "${RELEASE_LEVEL}"] - - -[env] -CARGO_MAKE_CARGO_ALL_FEATURES = "" - -[tasks.build-verbose] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.build-verbose.windows] -condition = { rust_version = { min = "1.29.0" }, env = { "TARGET" = "x86_64-pc-windows-msvc" } } - -[tasks.test-verbose] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.test-verbose.windows] -condition = { rust_version = { min = "1.29.0" }, env = { "TARGET" = "x86_64-pc-windows-msvc" } } - -[tasks.ci-coverage-flow] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.ci-coverage-flow.windows] -disabled = true diff --git a/juniper_graphql_ws/README.md b/juniper_graphql_ws/README.md new file mode 100644 index 000000000..b3f18e1c5 --- /dev/null +++ b/juniper_graphql_ws/README.md @@ -0,0 +1,24 @@ +`juniper_graphql_ws` crate +========================== + +[![Crates.io](https://img.shields.io/crates/v/juniper_graphql_ws.svg?maxAge=2592000)](https://crates.io/crates/juniper_graphql_ws) +[![Documentation](https://docs.rs/juniper_graphql_ws/badge.svg)](https://docs.rs/juniper_graphql_ws) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_graphql_ws/CHANGELOG.md) + +This crate contains an implementation of the [GraphQL over WebSocket Protocol][1], as used by [Apollo]. + + + + +## License + +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_graphql_ws/LICENSE). + + + + +[Apollo]: https://www.apollographql.com + +[1]: https://github.com/apollographql/subscriptions-transport-ws/blob/0ce7a1e1eb687fe51214483e4735f50a2f2d5c79/PROTOCOL.md diff --git a/juniper_graphql_ws/release.toml b/juniper_graphql_ws/release.toml index a7356262a..8c424022e 100644 --- a/juniper_graphql_ws/release.toml +++ b/juniper_graphql_ws/release.toml @@ -1,8 +1,23 @@ -pre-release-commit-message = "Release {{crate_name}} {{version}}" -post-release-commit-message = "Bump {{crate_name}} version to {{next_version}}" -tag-message = "Release {{crate_name}} {{version}}" -pre-release-replacements = [ - {file="src/lib.rs", min=0, search="docs.rs/juniper_graphql_ws/[a-z0-9\\.-]+", replace="docs.rs/juniper_graphql_ws/{{version}}"}, -{file="../juniper_warp/Cargo.toml", min=0, search="juniper_graphql_ws = \\{ version = \"[^\"]+\"", replace="juniper_graphql_ws = { version = \"{{version}}\""}, -{file="../juniper_actix/Cargo.toml", min=0, search="juniper_graphql_ws = \\{ version = \"[^\"]+\"", replace="juniper_graphql_ws = { version = \"{{version}}\""}, -] +[[pre-release-replacements]] +file = "../juniper_actix/Cargo.toml" +exactly = 1 +search = "juniper_graphql_ws = \\{ version = \"[^\"]+\"" +replace = "juniper_graphql_ws = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_warp/Cargo.toml" +exactly = 1 +search = "juniper_graphql_ws = \\{ version = \"[^\"]+\"" +replace = "juniper_graphql_ws = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 2 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs index 51c2e914f..cfb8b9678 100644 --- a/juniper_graphql_ws/src/lib.rs +++ b/juniper_graphql_ws/src/lib.rs @@ -1,11 +1,4 @@ -/*! - -# juniper_graphql_ws - -This crate contains an implementation of the [graphql-ws protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/263844b5c1a850c1e29814564eb62cb587e5eaaf/PROTOCOL.md), as used by Apollo. - -*/ - +#![doc = include_str!("../README.md")] #![deny(missing_docs)] #![deny(warnings)] diff --git a/juniper_hyper/.gitignore b/juniper_hyper/.gitignore deleted file mode 100644 index a9d37c560..000000000 --- a/juniper_hyper/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock diff --git a/juniper_hyper/CHANGELOG.md b/juniper_hyper/CHANGELOG.md index 890990cd1..e0e722ea6 100644 --- a/juniper_hyper/CHANGELOG.md +++ b/juniper_hyper/CHANGELOG.md @@ -1,78 +1,26 @@ -# master +`juniper_hyper` changelog +========================= -- Compatibility with the latest `juniper`. +All user visible changes to `juniper_hyper` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.8.0] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.8.0) -- Compatibility with the latest `juniper`. -# [[0.7.3] 2022-02-02](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-v0.7.3) -- Compatibility with the latest `juniper`. +## master -# [[0.7.2] 2022-01-26](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-v0.7.2) +### BC Breaks -- Compatibility with the latest `juniper`. +- Switched to 0.16 version of [`juniper` crate]. -# [[0.7.1] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.7.1) -- Compatibility with the latest `juniper`. -# [[0.7.0] 2021-04-03](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.7.0) -- **[Breaking change]** Remove unneccesary `Result`s ([#889](https://github.com/graphql-rust/juniper/pull/889)) +## Previous releases -# [[0.6.3] 2021-01-27](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.6.3) +See [old CHANGELOG](/../../blob/juniper_hyper-v0.8.0/juniper_hyper/CHANGELOG.md). -- Compatibility with the latest `juniper`. -# [[0.6.2] 2021-01-15](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.6.2) -- Compatibility with the latest `juniper`. -# [[0.6.1] 2020-12-12](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.6.1) - -- Compatibility with the latest `juniper`. - -# [[0.6.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.6.0) - -- Compatibility with the latest `juniper`. - -## Breaking Changes - -- `juniper_hyper::graphiql` now requires a second parameter for subscriptions. -- `juniper_hyper::graphql` now executes the schema asynchronously. For blocking synchronous execution consider `juniper_hyper::graphql_sync` for use. -- `400 Bad Request` is now returned if POST HTTP request contains no or invalid `Content-Type` header. - -# [[0.5.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.5.2) - -- Compatibility with the latest `juniper`. - -# [[0.5.1] 2019-10-24](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.5.1) - -- Compatibility with the latest `juniper`. - -# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.5.0) - -- Compatibility with the latest `juniper`. - -# [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.4.1) - -- Compatibility with the latest `juniper`. - -# [[0.4.0] 2019-07-19](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.4.0) - -- Compatibility with the latest `juniper`. - -# [[0.3.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.3.0) - -- Compatibility with the latest `juniper`. - -# 0.2.0 [2018-12-18] - -## Breaking changes - -- The tokio threadpool managed by `hyper` is now used for - executing GraphQL operations as well. Previously a separate threadpool from `futures_cpupool` was required. - - [#256](https://github.com/graphql-rust/juniper/pull/256) +[`juniper` crate]: https://docs.rs/juniper +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml index 97ba5d0a3..72ab1f4fd 100644 --- a/juniper_hyper/Cargo.toml +++ b/juniper_hyper/Cargo.toml @@ -1,17 +1,22 @@ [package] name = "juniper_hyper" -version = "0.8.0" +version = "0.9.0-dev" edition = "2018" -authors = ["Damir Vandic "] -description = "Juniper GraphQL integration with Hyper" +description = "`juniper` GraphQL integration with `hyper`." license = "BSD-2-Clause" +authors = ["Damir Vandic "] documentation = "https://docs.rs/juniper_hyper" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_hyper" repository = "https://github.com/graphql-rust/juniper" +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["apollo", "graphql", "hyper", "juniper"] +exclude = ["/examples/", "/release.toml"] [dependencies] futures = "0.3.1" -juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } hyper = { version = "0.14", features = ["server", "runtime"] } +juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } serde_json = "1.0" tokio = "1.0" url = "2.0" diff --git a/juniper_hyper/LICENSE b/juniper_hyper/LICENSE index 3f82bd0b5..eeac3a203 100644 --- a/juniper_hyper/LICENSE +++ b/juniper_hyper/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2018, Damir Vandic +Copyright (c) 2018-2022, Damir Vandic All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/juniper_hyper/README.md b/juniper_hyper/README.md index f42ebc3c5..3d9d5d106 100644 --- a/juniper_hyper/README.md +++ b/juniper_hyper/README.md @@ -1,35 +1,45 @@ -# juniper_hyper +`juniper_hyper` crate +===================== + +[![Crates.io](https://img.shields.io/crates/v/juniper_hyper.svg?maxAge=2592000)](https://crates.io/crates/juniper_hyper) +[![Documentation](https://docs.rs/juniper_hyper/badge.svg)](https://docs.rs/juniper_hyper) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/CHANGELOG.md) + +[`hyper`] web server integration for [`juniper`] ([GraphQL] implementation for [Rust]). + + -This repository contains the [Hyper][Hyper] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. ## Documentation -For documentation, including guides and examples, check out [Juniper][Juniper]. +For documentation, including guides and examples, check out [Juniper Book]. + +A basic usage example can also be found in the [API docs][`juniper_hyper`]. + + -A basic usage example can also be found in the [API documentation][documentation]. ## Examples -Check [examples/hyper_server.rs][example] for example code of a working Hyper -server with GraphQL handlers. +Check [`examples/hyper_server.rs`][1] for example code of a working [`hyper`] server with [GraphQL] handlers. + -## Links -* [Juniper][Juniper] -* [API documentation][documentation] -* [Hyper][Hyper] ## License -This project is under the BSD-2 license. +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/LICENSE). + -Check the LICENSE file for details. -[Hyper]: https://hyper.rs -[Juniper]: https://github.com/graphql-rust/juniper -[GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_hyper -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/examples/hyper_server.rs +[`hyper`]: https://docs.rs/hyper +[`juniper`]: https://docs.rs/juniper +[`juniper_hyper`]: https://docs.rs/juniper_hyper +[GraphQL]: http://graphql.org +[Juniper Book]: https://graphql-rust.github.io +[Rust]: https://www.rust-lang.org +[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/examples/hyper_server.rs diff --git a/juniper_hyper/release.toml b/juniper_hyper/release.toml new file mode 100644 index 000000000..4818936db --- /dev/null +++ b/juniper_hyper/release.toml @@ -0,0 +1,17 @@ +[[pre-release-replacements]] +file = "../book/src/servers/hyper.md" +exactly = 1 +search = "juniper_hyper = \"[^\"]+\"" +replace = "juniper_hyper = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 3 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index d46df4527..ce9204ebc 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/juniper_hyper/0.2.0")] +#![doc = include_str!("../README.md")] use std::{error::Error, fmt, string::FromUtf8Error, sync::Arc}; diff --git a/juniper_iron/.gitignore b/juniper_iron/.gitignore deleted file mode 100644 index a9d37c560..000000000 --- a/juniper_iron/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock diff --git a/juniper_iron/CHANGELOG.md b/juniper_iron/CHANGELOG.md index 3938f03ef..467e15688 100644 --- a/juniper_iron/CHANGELOG.md +++ b/juniper_iron/CHANGELOG.md @@ -1,76 +1,26 @@ -# master +`juniper_iron` changelog +======================== -- Compatibility with the latest `juniper`. +All user visible changes to `juniper_iron` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.7.6] 2022-02-02](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-v0.7.6) -- Compatibility with the latest `juniper`. -# [[0.7.5] 2022-01-26](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-v0.7.5) -- Compatibility with the latest `juniper`. +## master -# [[0.7.4] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.7.4) +### BC Breaks -- Compatibility with the latest `juniper`. +- Switched to 0.16 version of [`juniper` crate]. -# [[0.7.3] 2021-04-03](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.7.3) -- Compatibility with the latest `juniper`. -# [[0.7.2] 2021-01-27](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.7.2) -- Compatibility with the latest `juniper`. +## Previous releases -# [[0.7.1] 2021-01-15](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.7.1) +See [old CHANGELOG](/../../blob/juniper_iron-v0.7.6/juniper_iron/CHANGELOG.md). -- Compatibility with the latest `juniper`. -# [[0.7.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.7.0) -- Compatibility with the latest `juniper`. -## Breaking Changes - -- `juniper_iron::GraphiQLHandler::new` now requires a second parameter for subscriptions. -- `400 Bad Request` is now returned if POST HTTP request contains no or invalid `Content-Type` header. - -# [[0.6.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.6.2) - -- Compatibility with the latest `juniper`. - -# [[0.6.1] 2019-10-24](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.6.1) - -- Compatibility with the latest `juniper`. - -# [[0.6.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.6.0) - -- Compatibility with the latest `juniper`. - -# [[0.5.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.5.1) - -- Compatibility with the latest `juniper`. - -# [[0.5.0] 2019-07-19](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.5.0) - -- Compatibility with the latest `juniper`. - -# [[0.4.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.4.0) - -- Compatibility with the latest `juniper`. - -# [0.3.0] 2018-12-17 - -- Compatibility with the latest `juniper`. - -## [0.2.0] - 2018-09-13 - -### Changed - -- (**breaking**) `context_factory` now returns `IronResult` instead of `Context`. - -## [0.1.2] - 2018-02-10 - -### Changed - -- Extend iron support to 0.6 +[`juniper` crate]: https://docs.rs/juniper +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml index 831d7599e..d4b4b7590 100644 --- a/juniper_iron/Cargo.toml +++ b/juniper_iron/Cargo.toml @@ -1,28 +1,33 @@ [package] name = "juniper_iron" -version = "0.7.6" +version = "0.8.0-dev" edition = "2018" +description = "`juniper` GraphQL integration with `iron`." +license = "BSD-2-Clause" authors = [ "Magnus Hallin ", "Christoph Herzog ", ] -description = "Iron integration for juniper" -license = "BSD-2-Clause" documentation = "https://docs.rs/juniper_iron" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_iron" repository = "https://github.com/graphql-rust/juniper" +readme = "README.md" +categories = ["web-programming", "web-programming::http-server"] +keywords = ["apollo", "graphql", "iron", "juniper"] +exclude = ["/examples/", "/release.toml"] [dependencies] futures = "0.3.1" -juniper = { version = "0.16.0-dev", path = "../juniper" } iron = ">= 0.5, < 0.7" +juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } serde_json = "1.0.2" urlencoded = ">= 0.5, < 0.7" [dev-dependencies] -juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } iron-test = "0.6" +juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } logger = "0.4" mount = "0.4" -percent-encoding = "2" +percent-encoding = "2.0" router = "0.6" -url = "2" +url = "2.0" diff --git a/juniper_iron/LICENSE b/juniper_iron/LICENSE index 0ccd1e17c..f1c4508b1 100644 --- a/juniper_iron/LICENSE +++ b/juniper_iron/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2016, Magnus Hallin +Copyright (c) 2016-2022, Magnus Hallin All rights reserved. Redistribution and use in source and binary forms, with or without @@ -22,4 +22,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/juniper_iron/README.md b/juniper_iron/README.md index 18b42a788..a1eaeae06 100644 --- a/juniper_iron/README.md +++ b/juniper_iron/README.md @@ -1,31 +1,45 @@ -# juniper_iron +`juniper_iron` crate +==================== + +[![Crates.io](https://img.shields.io/crates/v/juniper_iron.svg?maxAge=2592000)](https://crates.io/crates/juniper_iron) +[![Documentation](https://docs.rs/juniper_iron/badge.svg)](https://docs.rs/juniper_iron) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_iron/CHANGELOG.md) + +[`iron`] web framework integration for [`juniper`] ([GraphQL] implementation for [Rust]). + + + + +## Documentation + +For documentation, including guides and examples, check out [Juniper Book]. + +A basic usage example can also be found in the [API docs][`juniper_iron`]. -This repository contains the [Iron][Iron] web framework integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. -For documentation, including guides and examples, check out [Juniper][Juniper]. -A basic usage example can also be found in the [Api documentation][documentation]. ## Examples -Check [examples/iron_server.rs][example] for example code of a working Iron server with GraphQL handlers. +Check [`examples/iron_server.rs`][1] for example code of a working [`iron`] server with [GraphQL] handlers. + -## Links -* [Juniper][Juniper] -* [Api Reference][documetation] -* [Iron framework][Iron] ## License -This project is under the BSD-2 license. +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_iron/LICENSE). + + -Check the LICENSE file for details. -[Iron]: https://github.com/iron/iron -[Juniper]: https://github.com/graphql-rust/juniper +[`iron`]: https://docs.rs/iron +[`juniper`]: https://docs.rs/juniper +[`juniper_iron`]: https://docs.rs/juniper_iron [GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_iron -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_iron/examples/iron_server.rs +[Juniper Book]: https://graphql-rust.github.io +[Rust]: https://www.rust-lang.org +[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_iron/examples/iron_server.rs diff --git a/juniper_iron/release.toml b/juniper_iron/release.toml new file mode 100644 index 000000000..bbbf30d62 --- /dev/null +++ b/juniper_iron/release.toml @@ -0,0 +1,17 @@ +[[pre-release-replacements]] +file = "../book/src/servers/iron.md" +exactly = 1 +search = "juniper_iron = \"[^\"]+\"" +replace = "juniper_iron = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 3 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index cbef5ace0..6e104ea0d 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -1,110 +1,4 @@ -/*! - -# juniper_iron - -This repository contains the [Iron][Iron] web framework integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. - -For documentation, including guides and examples, check out [Juniper][Juniper]. - -A basic usage example can also be found in the [Api documentation][documentation]. - -## Links - -* [Juniper][Juniper] -* [Api Reference][documentation] -* [Iron framework][Iron] - -## Integrating with Iron - - -For example, continuing from the schema created above and using Iron to expose -the schema on an HTTP endpoint supporting both GET and POST requests: - -```rust,no_run -# use std::collections::HashMap; -# -use iron::prelude::*; -use juniper_iron::GraphQLHandler; -use juniper::{Context, EmptyMutation, EmptySubscription}; -# -# use juniper::FieldResult; -# -# struct User { id: String, name: String, friend_ids: Vec } -# struct QueryRoot; -# struct Database { users: HashMap } -# -# #[juniper::graphql_object(context = Database)] -# impl User { -# fn id(&self) -> FieldResult<&String> { -# Ok(&self.id) -# } -# -# fn name(&self) -> FieldResult<&String> { -# Ok(&self.name) -# } -# -# fn friends<'c>(&self, context: &'c Database) -> FieldResult> { -# Ok(self.friend_ids.iter() -# .filter_map(|id| context.users.get(id)) -# .collect()) -# } -# } -# -# #[juniper::graphql_object(context = Database, scalar = juniper::DefaultScalarValue)] -# impl QueryRoot { -# fn user(context: &Database, id: String) -> FieldResult> { -# Ok(context.users.get(&id)) -# } -# } - -// This function is executed for every request. Here, we would realistically -// provide a database connection or similar. For this example, we'll be -// creating the database from scratch. -fn context_factory(_: &mut Request) -> IronResult { - Ok(Database { - users: vec![ - ( "1000".to_owned(), User { - id: "1000".to_owned(), name: "Robin".to_owned(), - friend_ids: vec!["1001".to_owned()] } ), - ( "1001".to_owned(), User { - id: "1001".to_owned(), name: "Max".to_owned(), - friend_ids: vec!["1000".to_owned()] } ), - ].into_iter().collect() - }) -} - -impl Context for Database {} - -fn main() { - // GraphQLHandler takes a context factory function, the root object, - // and the mutation object. If we don't have any mutations to expose, we - // can use the empty tuple () to indicate absence. - let graphql_endpoint = GraphQLHandler::new( - context_factory, - QueryRoot, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); - - // Start serving the schema at the root on port 8080. - Iron::new(graphql_endpoint).http("localhost:8080").unwrap(); -} - -``` - -See the the [`GraphQLHandler`][3] documentation for more information on what request methods are -supported. - -[3]: ./struct.GraphQLHandler.html -[Iron]: https://github.com/iron/iron -[Juniper]: https://github.com/graphql-rust/juniper -[GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_iron - -*/ - -#![doc(html_root_url = "https://docs.rs/juniper_iron/0.3.0")] +#![doc = include_str!("../README.md")] use std::{error::Error, fmt, io::Read, ops::Deref as _}; diff --git a/juniper_rocket/.gitignore b/juniper_rocket/.gitignore deleted file mode 100644 index a9d37c560..000000000 --- a/juniper_rocket/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock diff --git a/juniper_rocket/CHANGELOG.md b/juniper_rocket/CHANGELOG.md index 9d047ea4d..236734563 100644 --- a/juniper_rocket/CHANGELOG.md +++ b/juniper_rocket/CHANGELOG.md @@ -1,101 +1,34 @@ -# master +`juniper_rocket` changelog +========================== -- Compatibility with the latest `juniper`. -- Provide `AsRef` and `AsMut` implementation for `GraphQLRequest` to its inner type ([#968](https://github.com/graphql-rust/juniper/pull/968), [#930](https://github.com/graphql-rust/juniper/issues/930)). +All user visible changes to `juniper_rocket` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.8.0] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.8.0) -- Require async rocket support (`rocket` >= 0.5-rc1). -- Compatibility with the latest `juniper`. -# [[0.7.1] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.7.1) -- Compatibility with the latest `juniper`. +## master -# [[0.7.0] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.7.0) +### BC Breaks -- Minimum `rocket` version is now `0.4.9`. Note this also bumps the [minimum required `rustc` version](https://github.com/SergioBenitez/Rocket/blob/v0.4/CHANGELOG.md#version-048-may-18-2021). -- Compatibility with the latest `juniper`. +- Switched to 0.16 version of [`juniper` crate]. -# [[0.6.3] 2021-04-03](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.6.3) +### Added -- Compatibility with the latest `juniper`. +- `AsRef` and `AsMut` implementation for `GraphQLRequest` to its inner type. ([#968], [#930]) -# [[0.6.2] 2021-01-27](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.6.2) +[#930]: /../../issues/930 +[#968]: /../../pull/968 -- Compatibility with the latest `juniper`. -# [[0.6.1] 2021-01-15](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.6.1) -- Compatibility with the latest `juniper`. -# [[0.6.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.6.0) +## Previous releases -- Compatibility with the latest `juniper`. -- Rocket integration does not require default features. -- Support `application/graphql` POST requests. +See [old CHANGELOG](/../../blob/juniper_rocket-v0.8.2/juniper_rocket/CHANGELOG.md). -## Breaking Changes -- `juniper_rocket::graphiql_source` now requires a second parameter for subscriptions -# [[0.5.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.2) -- Compatibility with the latest `juniper`. - -# [[0.5.1] 2019-10-24](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.1) - -- Compatibility with the latest `juniper`. - -# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.0) - -- Compatibility with the latest `juniper`. - -# [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.4.1) - -- Compatibility with the latest `juniper`. - -# [[0.4.0] 2019-07-19](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.4.0) - -- Compatibility with the latest `juniper`. - -# [[0.3.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.3.0) - -- Expose the operation names from `GraphQLRequest`. -- Compatibility with the latest `juniper`. - -# [0.2.0] 2018-12-17 - -### Rocket updated to v0.4 - -[Rocket](https://rocket.rs) integration now requires Rocket `0.4.0`. This is due -to changes with the way Rocket handles form parsing. Before this update, it was -impossible to leverage Rocket integration with Rocket beyond 0.3.x. - -Check out [Rocket's Changelog](https://github.com/SergioBenitez/Rocket/blob/v0.4/CHANGELOG.md) -for more details on the 0.4 release. - -# juniper_rocket [0.1.3] 2018-09-13 - -- Add `juniper-0.10.0` compatibility. - -# juniper_rocket [0.1.2] 2018-01-13 - -## Changes - -### Rocket updated to `0.3.6` - -[Rocket](https://rocket.rs) integration now requires Rocket `0.3.6` to -support building with recent Rust nightlies. - -Additional information and supported nightly versions can be found in [Rocket's changelog](https://github.com/SergioBenitez/Rocket/blob/master/CHANGELOG.md#version-036-jan-12-2018). - -[#125](https://github.com/graphql-rust/juniper/issues/125) - -### Decoding of query params - -When processing GET requests, query parameters were not properly url_decoded, - -This was fixed by [PR #122](https://github.com/graphql-rust/juniper/pull/128) by @LegNeato. - -This fixed the [issue #116](https://github.com/graphql-rust/juniper/issues/116). +[`juniper` crate]: https://docs.rs/juniper +[`rocket` crate]: https://docs.rs/rocket +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml index 08be804f6..e3ff1bec2 100644 --- a/juniper_rocket/Cargo.toml +++ b/juniper_rocket/Cargo.toml @@ -1,20 +1,25 @@ [package] name = "juniper_rocket" -version = "0.8.0" +version = "0.9.0-dev" edition = "2018" +description = "`juniper` GraphQL integration with `rocket`." +license = "BSD-2-Clause" authors = [ "Magnus Hallin ", "Christoph Herzog ", ] -description = "Juniper GraphQL integration with Rocket" -license = "BSD-2-Clause" documentation = "https://docs.rs/juniper_rocket" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_rocket" repository = "https://github.com/graphql-rust/juniper" +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["apollo", "graphql", "juniper", "rocket"] +exclude = ["/examples/", "/tests/", "/release.toml"] [dependencies] futures = "0.3.1" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } -rocket = { version = "0.5.0-rc.1", default-features = false } +rocket = { version = "=0.5.0-rc.2", default-features = false } serde_json = "1.0.2" [dev-dependencies] diff --git a/juniper_rocket/LICENSE b/juniper_rocket/LICENSE index 0ccd1e17c..f1c4508b1 100644 --- a/juniper_rocket/LICENSE +++ b/juniper_rocket/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2016, Magnus Hallin +Copyright (c) 2016-2022, Magnus Hallin All rights reserved. Redistribution and use in source and binary forms, with or without @@ -22,4 +22,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/juniper_rocket/README.md b/juniper_rocket/README.md index 9ff6f6e21..651e3a5df 100644 --- a/juniper_rocket/README.md +++ b/juniper_rocket/README.md @@ -1,35 +1,45 @@ -# juniper_rocket +`juniper_rocket` crate +====================== + +[![Crates.io](https://img.shields.io/crates/v/juniper_rocket.svg?maxAge=2592000)](https://crates.io/crates/juniper_rocket) +[![Documentation](https://docs.rs/juniper_rocket/badge.svg)](https://docs.rs/juniper_rocket) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/CHANGELOG.md) + +[`rocket`] web server integration for [`juniper`] ([GraphQL] implementation for [Rust]). + + -This repository contains the [Rocket][Rocket] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. ## Documentation -For documentation, including guides and examples, check out [Juniper][Juniper]. +For documentation, including guides and examples, check out [Juniper Book]. + +A basic usage example can also be found in the [API docs][`juniper_rocket`]. + + -A basic usage example can also be found in the [Api documentation][documentation]. ## Examples -Check [examples/rocket_server.rs][example] for example code of a working Rocket -server with GraphQL handlers. +Check [`examples/rocket_server.rs`][1] for example code of a working [`rocket`] server with [GraphQL] handlers. + -## Links -* [Juniper][Juniper] -* [Api Reference][documetation] -* [Rocket][Iron] ## License -This project is under the BSD-2 license. +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/LICENSE). + -Check the LICENSE file for details. -[Rocket]: https://rocket.rs -[Juniper]: https://github.com/graphql-rust/juniper -[GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_rocket -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs +[`juniper`]: https://docs.rs/juniper +[`juniper_rocket`]: https://docs.rs/juniper_rocket +[`rocket`]: https://docs.rs/rocket +[GraphQL]: http://graphql.org +[Juniper Book]: https://graphql-rust.github.io +[Rust]: https://www.rust-lang.org +[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs diff --git a/juniper_rocket/examples/rocket_server.rs b/juniper_rocket/examples/rocket_server.rs index 76a6b296a..16ae6d129 100644 --- a/juniper_rocket/examples/rocket_server.rs +++ b/juniper_rocket/examples/rocket_server.rs @@ -2,12 +2,12 @@ use juniper::{ tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; -use rocket::{response::content, Rocket, State}; +use rocket::{response::content, State}; type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[rocket::get("/")] -fn graphiql() -> content::Html { +fn graphiql() -> content::RawHtml { juniper_rocket::graphiql_source("/graphql", None) } @@ -31,7 +31,7 @@ fn post_graphql_handler( #[rocket::main] async fn main() { - Rocket::build() + let _ = rocket::build() .manage(Database::new()) .manage(Schema::new( Query, diff --git a/juniper_rocket/release.toml b/juniper_rocket/release.toml new file mode 100644 index 000000000..4b5480811 --- /dev/null +++ b/juniper_rocket/release.toml @@ -0,0 +1,17 @@ +[[pre-release-replacements]] +file = "../book/src/servers/rocket.md" +exactly = 1 +search = "juniper_rocket = \"[^\"]+\"" +replace = "juniper_rocket = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 3 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index 9d644c9f8..e6e60bee1 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -1,42 +1,4 @@ -/*! - -# juniper_rocket - -This repository contains the [Rocket][Rocket] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. - -## Documentation - -For documentation, including guides and examples, check out [Juniper][Juniper]. - -A basic usage example can also be found in the [Api documentation][documentation]. - -## Examples - -Check [examples/rocket_server.rs][example] for example code of a working Rocket -server with GraphQL handlers. - -## Links - -* [Juniper][Juniper] -* [Api Reference][documentation] -* [Rocket][Rocket] - -## License - -This project is under the BSD-2 license. - -Check the LICENSE file for details. - -[Rocket]: https://rocket.rs -[Juniper]: https://github.com/graphql-rust/juniper -[GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_rocket -[example]: https://github.com/graphql-rust/juniper_rocket/blob/master/examples/rocket_server.rs - -*/ - -#![doc(html_root_url = "https://docs.rs/juniper_rocket/0.7.1")] +#![doc = include_str!("../README.md")] use std::{borrow::Cow, io::Cursor}; @@ -84,8 +46,8 @@ pub struct GraphQLResponse(pub Status, pub String); pub fn graphiql_source( graphql_endpoint_url: &str, subscriptions_endpoint_url: Option<&str>, -) -> content::Html { - content::Html(juniper::http::graphiql::graphiql_source( +) -> content::RawHtml { + content::RawHtml(juniper::http::graphiql::graphiql_source( graphql_endpoint_url, subscriptions_endpoint_url, )) @@ -95,8 +57,8 @@ pub fn graphiql_source( pub fn playground_source( graphql_endpoint_url: &str, subscriptions_endpoint_url: Option<&str>, -) -> content::Html { - content::Html(juniper::http::playground::playground_source( +) -> content::RawHtml { + content::RawHtml(juniper::http::playground::playground_source( graphql_endpoint_url, subscriptions_endpoint_url, )) @@ -349,8 +311,12 @@ where }; Box::pin(async move { + let limit = req + .limits() + .get("graphql") + .unwrap_or_else(|| BODY_LIMIT.bytes()); + let mut reader = data.open(limit); let mut body = String::new(); - let mut reader = data.open(BODY_LIMIT.bytes()); if let Err(e) = reader.read_to_string(&mut body).await { return Failure((Status::InternalServerError, format!("{:?}", e))); } diff --git a/juniper_subscriptions/.gitignore b/juniper_subscriptions/.gitignore deleted file mode 100644 index 0d722487a..000000000 --- a/juniper_subscriptions/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/examples/**/target/**/* -**/*.rs.bk -Cargo.lock diff --git a/juniper_subscriptions/CHANGELOG.md b/juniper_subscriptions/CHANGELOG.md index e5789c84f..b6ba22050 100644 --- a/juniper_subscriptions/CHANGELOG.md +++ b/juniper_subscriptions/CHANGELOG.md @@ -1,7 +1,26 @@ -# master +`juniper_subscriptions` changelog +================================= -- Compatibility with the latest `juniper`. +All user visible changes to `juniper_subscriptions` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.15.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_subscriptions-0.15.0) -- Initial Release + + +## master + +### BC Breaks + +- Switched to 0.16 version of [`juniper` crate]. + + + + +## Previous releases + +See [old CHANGELOG](/../../blob/juniper_subscriptions-v0.16.0/juniper_subscriptions/CHANGELOG.md). + + + + +[`juniper` crate]: https://docs.rs/juniper +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_subscriptions/Cargo.toml b/juniper_subscriptions/Cargo.toml index d7cdf7b48..ddea6d984 100644 --- a/juniper_subscriptions/Cargo.toml +++ b/juniper_subscriptions/Cargo.toml @@ -1,12 +1,17 @@ [package] name = "juniper_subscriptions" -version = "0.16.0" +version = "0.17.0-dev" edition = "2018" -authors = ["nWacky "] -description = "Juniper SubscriptionCoordinator and SubscriptionConnection implementations" +description = "Juniper `SubscriptionCoordinator` and `SubscriptionConnection` implementations." license = "BSD-2-Clause" +authors = ["nWacky "] documentation = "https://docs.rs/juniper_subscriptions" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_subscriptions" repository = "https://github.com/graphql-rust/juniper" +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["graphql", "server", "subscription", "web", "websocket"] +exclude = ["/release.toml"] [dependencies] futures = "0.3.1" @@ -14,4 +19,4 @@ juniper = { version = "0.16.0-dev", path = "../juniper", default-features = fals [dev-dependencies] serde_json = "1.0" -tokio = { version = "1", features = ["macros", "rt"] } +tokio = { version = "1.0", features = ["macros", "rt"] } diff --git a/juniper_subscriptions/LICENSE b/juniper_subscriptions/LICENSE index 177ee843c..9374ec2c4 100644 --- a/juniper_subscriptions/LICENSE +++ b/juniper_subscriptions/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2018, Tom Houlé +Copyright (c) 2018-2022, Tom Houlé All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/juniper_subscriptions/Makefile.toml b/juniper_subscriptions/Makefile.toml deleted file mode 100644 index afbd4f968..000000000 --- a/juniper_subscriptions/Makefile.toml +++ /dev/null @@ -1,33 +0,0 @@ - -# This is needed as the release config is at a different path than the top-level -# release config. - -[tasks.release] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--execute", "${RELEASE_LEVEL}"] - -[tasks.release-dry-run] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "--no-publish", "--no-push", "--no-tag", "${RELEASE_LEVEL}"] - -[tasks.release-local-test] -args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/release.toml", "--no-confirm", "${RELEASE_LEVEL}"] - -[env] -CARGO_MAKE_CARGO_ALL_FEATURES = "" - -[tasks.build-verbose] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.build-verbose.windows] -condition = { rust_version = { min = "1.29.0" }, env = { "TARGET" = "x86_64-pc-windows-msvc" } } - -[tasks.test-verbose] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.test-verbose.windows] -condition = { rust_version = { min = "1.29.0" }, env = { "TARGET" = "x86_64-pc-windows-msvc" } } - -[tasks.ci-coverage-flow] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.ci-coverage-flow.windows] -disabled = true diff --git a/juniper_subscriptions/README.md b/juniper_subscriptions/README.md index 4c8e68595..e04493a08 100644 --- a/juniper_subscriptions/README.md +++ b/juniper_subscriptions/README.md @@ -1,37 +1,45 @@ -# juniper_subscriptions +`juniper_subscriptions` crate +============================= + +[![Crates.io](https://img.shields.io/crates/v/juniper_subscriptions.svg?maxAge=2592000)](https://crates.io/crates/juniper_subscriptions) +[![Documentation](https://docs.rs/juniper_subscriptions/badge.svg)](https://docs.rs/juniper_subscriptions) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_subscriptions/CHANGELOG.md) + +This repository contains `SubscriptionCoordinator` and `SubscriptionConnection` implementations for +[`juniper`], a [GraphQL] library for Rust. + +You need both this and [`juniper`] crate for usage. + + -This repository contains [SubscriptionCoordinator][SubscriptionCoordinator] and -[SubscriptionConnection][SubscriptionConnection] implementations for -[Juniper][Juniper], a [GraphQL][GraphQL] library for Rust. ## Documentation -For this crate's documentation, check out [API documentation][documentation]. +For this crate's documentation, check out [API docs](https://docs.rs/juniper_subscriptions). + +For `SubscriptionCoordinator` and `SubscriptionConnection` documentation, check out [`juniper` API docs][`juniper`]. + + -For `SubscriptionCoordinator` and `SubscriptionConnection` documentation, check -out [Juniper][Juniper]. ## Examples -Check [examples/warp_subscriptions][example] for example code of a working -[warp][warp] server with GraphQL subscription handlers. +Check [`examples/warp_subscriptions/`][1] for example code of a working [`warp`] server with [GraphQL] subscription handlers. + -## Links -* [Juniper][Juniper] -* [API Reference][documentation] -* [warp][warp] ## License -This project is under the BSD-2 license. +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_subscriptions/LICENSE). -Check the LICENSE file for details. -[warp]: https://github.com/seanmonstar/warp -[Juniper]: https://github.com/graphql-rust/juniper -[SubscriptionCoordinator]: https://docs.rs/juniper/latest/juniper/trait.SubscriptionCoordinator.html -[SubscriptionConnection]: https://docs.rs/juniper/latest/juniper/trait.SubscriptionConnection.html + + +[`juniper`]: https://docs.rs/juniper +[`warp`]: https://docs.rs/warp [GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_subscriptions -[example]: https://github.com/graphql-rust/juniper/blob/master/examples/warp_subscriptions/src/main.rs + +[1]: https://github.com/graphql-rust/juniper/blob/master/examples/warp_subscriptions/src/main.rs diff --git a/juniper_subscriptions/release.toml b/juniper_subscriptions/release.toml index 4108f6479..837f61aed 100644 --- a/juniper_subscriptions/release.toml +++ b/juniper_subscriptions/release.toml @@ -1,7 +1,23 @@ -pre-release-commit-message = "Release {{crate_name}} {{version}}" -post-release-commit-message = "Bump {{crate_name}} version to {{next_version}}" -tag-message = "Release {{crate_name}} {{version}}" -pre-release-replacements = [ - {file="../juniper_graphql_ws/Cargo.toml", min=0, search="juniper_subscriptions = \\{ version = \"[^\"]+\"", replace="juniper_subscriptions = { version = \"{{version}}\""}, - {file="src/lib.rs", min=0, search="docs.rs/juniper_subscriptions/[a-z0-9\\.-]+", replace="docs.rs/juniper_subscriptions/{{version}}"}, -] +[[pre-release-replacements]] +file = "../book/src/advanced/subscriptions.md" +exactly = 1 +search = "juniper_subscriptions = \"[^\"]+\"" +replace = "juniper_subscriptions = \"{{version}}\"" + +[[pre-release-replacements]] +file = "../juniper_graphql_ws/Cargo.toml" +exactly = 1 +search = "juniper_subscriptions = \\{ version = \"[^\"]+\"" +replace = "juniper_subscriptions = { version = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 3 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_subscriptions/src/lib.rs b/juniper_subscriptions/src/lib.rs index e09e11279..df6c5fe7e 100644 --- a/juniper_subscriptions/src/lib.rs +++ b/juniper_subscriptions/src/lib.rs @@ -1,15 +1,6 @@ -//! This crate supplies [`SubscriptionCoordinator`] and -//! [`SubscriptionConnection`] implementations for the -//! [juniper](https://github.com/graphql-rust/juniper) crate. -//! -//! You need both this and `juniper` crate. -//! -//! [`SubscriptionCoordinator`]: juniper::SubscriptionCoordinator -//! [`SubscriptionConnection`]: juniper::SubscriptionConnection - +#![doc = include_str!("../README.md")] #![deny(missing_docs)] #![deny(warnings)] -#![doc(html_root_url = "https://docs.rs/juniper_subscriptions/0.16.0")] use std::{ iter::FromIterator, diff --git a/juniper_warp/.gitignore b/juniper_warp/.gitignore deleted file mode 100644 index 0d722487a..000000000 --- a/juniper_warp/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/examples/**/target/**/* -**/*.rs.bk -Cargo.lock diff --git a/juniper_warp/CHANGELOG.md b/juniper_warp/CHANGELOG.md index 072d4c594..b33dfea77 100644 --- a/juniper_warp/CHANGELOG.md +++ b/juniper_warp/CHANGELOG.md @@ -1,83 +1,26 @@ -# master +`juniper_warp` changelog +======================== -- Compatibility with the latest `juniper`. +All user visible changes to `juniper_warp` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. -# [[0.7.0] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.7.0) -- Compatibility with the latest `juniper`. -# [[0.6.5] 2022-01-26](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-v0.6.5) -- Compatibility with the latest `juniper`. +## master -# [[0.6.4] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.6.4) +### BC Breaks -- Compatibility with the latest `juniper`. +- Switched to 0.16 version of [`juniper` crate]. -# [[0.6.3] 2021-04-03](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.6.3) -- Compatibility with the latest `juniper`. -# [[0.6.2] 2021-01-27](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.6.2) -- Compatibility with the latest `juniper`. +## Previous releases -# [[0.6.1] 2021-01-15](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.6.1) +See [old CHANGELOG](/../../blob/juniper_warp-v0.7.0/juniper_warp/CHANGELOG.md). -- Compatibility with the latest `juniper`. -# [[0.6.0] 2020-12-09](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.6.0) -- Compatibility with the latest `juniper`. -- Changed the implementation place of GraphQLBatchRequest and GraphQLBatchResponse in `juniper_warp` -to `juniper` to be reused in other http integrations, since this implementation was private. -## Breaking Changes - -- Update `playground_filter` to support subscription endpoint URLs. -- Update `warp` to 0.2. -- Rename synchronous `execute` to `execute_sync`, add asynchronous `execute` -- `juniper_warp::graphiql_filter` now requires a second parameter for subscriptions. -- `make_graphql_filter` and `make_graphql_filter_sync` now ignore POST HTTP requests with no or invalid `Content-Type` header. - -# [[0.5.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.5.2) - -- Compatibility with the latest `juniper`. - -# [[0.5.1] 2019-10-24](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.5.1) - -- Compatibility with the latest `juniper`. - -# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.5.0) - -- Compatibility with the latest `juniper`. - -# [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.4.1) - -- Compatibility with the latest `juniper`. - -# [[0.4.0] 2019-07-19](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.4.0) - -- Compatibility with the latest `juniper`. - -# [[0.3.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.3.0) - -- Compatibility with the latest `juniper`. - -# [0.2.0] 2018-12-17 - -- **[Breaking Change]** The minimum required `warp` version is now `0.1.8`. - - [#271](https://github.com/graphql-rust/juniper/pull/271) - -- **[Breaking Change]** The `graphql_handler` and `graphiql_handler` functions have been renamed to`graphql_filter` and `graphiql_filter` respectively. - - [#267](https://github.com/graphql-rust/juniper/pull/267) - -- **[Breaking Change]** A `CpuPool` from `futures_cpupool` is no longer used. Instead, `warp`'s underlying `tokio_threadpool` is leveraged. Because of this, `make_graphql_filter_with_thread_pool` is no longer necessary and has been removed. - - [#258](https://github.com/graphql-rust/juniper/pull/258) - -# juniper_warp [0.1] 2018-09-13 - -- Initial Release +[`juniper` crate]: https://docs.rs/juniper +[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_warp/Cargo.toml b/juniper_warp/Cargo.toml index 0950be371..b49e379dc 100644 --- a/juniper_warp/Cargo.toml +++ b/juniper_warp/Cargo.toml @@ -1,12 +1,21 @@ [package] name = "juniper_warp" -version = "0.7.0" +version = "0.8.0-dev" edition = "2018" -authors = ["Tom Houlé "] -description = "Juniper GraphQL integration with Warp" +description = "`juniper` GraphQL integration with `warp`." license = "BSD-2-Clause" +authors = ["Tom Houlé "] documentation = "https://docs.rs/juniper_warp" +homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_warp" repository = "https://github.com/graphql-rust/juniper" +readme = "README.md" +categories = ["asynchronous", "web-programming", "web-programming::http-server"] +keywords = ["apollo", "graphql", "juniper", "warp", "websocket"] +exclude = ["/examples/", "/release.toml"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [features] subscriptions = ["juniper_graphql_ws"] @@ -15,11 +24,11 @@ subscriptions = ["juniper_graphql_ws"] anyhow = "1.0" futures = "0.3.1" juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } -juniper_graphql_ws = { version = "0.3.0", path = "../juniper_graphql_ws", optional = true } +juniper_graphql_ws = { version = "0.4.0-dev", path = "../juniper_graphql_ws", optional = true } serde = { version = "1.0.75", features = ["derive"] } serde_json = "1.0.24" thiserror = "1.0" -tokio = { version = "1", features = ["rt-multi-thread"] } +tokio = { version = "1.0", features = ["rt-multi-thread"] } warp = "0.3" [dev-dependencies] @@ -27,5 +36,5 @@ env_logger = "0.9" juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } log = "0.4" percent-encoding = "2.1" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -url = "2" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +url = "2.0" diff --git a/juniper_warp/LICENSE b/juniper_warp/LICENSE index 177ee843c..9374ec2c4 100644 --- a/juniper_warp/LICENSE +++ b/juniper_warp/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2018, Tom Houlé +Copyright (c) 2018-2022, Tom Houlé All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/juniper_warp/Makefile.toml b/juniper_warp/Makefile.toml deleted file mode 100644 index e1437945a..000000000 --- a/juniper_warp/Makefile.toml +++ /dev/null @@ -1,22 +0,0 @@ -[env] -CARGO_MAKE_CARGO_ALL_FEATURES = "" - -[tasks.build-verbose] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.build-verbose.windows] -condition = { rust_version = { min = "1.29.0" }, env = { "TARGET" = "x86_64-pc-windows-msvc" } } - -[tasks.test-verbose] -condition = { rust_version = { min = "1.29.0" } } -args = ["test", "--all-features"] - -[tasks.test-verbose.windows] -condition = { rust_version = { min = "1.29.0" }, env = { "TARGET" = "x86_64-pc-windows-msvc" } } -args = ["test", "--all-features"] - -[tasks.ci-coverage-flow] -condition = { rust_version = { min = "1.29.0" } } - -[tasks.ci-coverage-flow.windows] -disabled = true diff --git a/juniper_warp/README.md b/juniper_warp/README.md index e283a6054..4148f4844 100644 --- a/juniper_warp/README.md +++ b/juniper_warp/README.md @@ -1,33 +1,45 @@ -# juniper_warp +`juniper_warp` crate +==================== + +[![Crates.io](https://img.shields.io/crates/v/juniper_warp.svg?maxAge=2592000)](https://crates.io/crates/juniper_warp) +[![Documentation](https://docs.rs/juniper_warp/badge.svg)](https://docs.rs/juniper_warp) +[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) + +- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_warp/CHANGELOG.md) + +[`warp`] web server integration for [`juniper`] ([GraphQL] implementation for [Rust]). + + -This repository contains the [warp][warp] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. ## Documentation -For documentation, including guides and examples, check out [Juniper][Juniper]. +For documentation, including guides and examples, check out [Juniper Book]. + +A basic usage example can also be found in the [API docs][`juniper_warp`]. + + -A basic usage example can also be found in the [API documentation][documentation]. ## Examples -Check [examples/warp_server][example] for example code of a working warp -server with GraphQL handlers. +Check [`examples/warp_server.rs`][1] for example code of a working [`warp`] server with [GraphQL] handlers. + -## Links -* [Juniper][Juniper] -* [API Reference][documentation] -* [warp][warp] ## License -This project is under the BSD-2 license. +This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_warp/LICENSE). -Check the LICENSE file for details. -[warp]: https://github.com/seanmonstar/warp -[Juniper]: https://github.com/graphql-rust/juniper + + +[`juniper`]: https://docs.rs/juniper +[`juniper_warp`]: https://docs.rs/juniper_warp +[`warp`]: https://docs.rs/warp [GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_warp -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_warp/examples/warp_server.rs +[Juniper Book]: https://graphql-rust.github.io +[Rust]: https://www.rust-lang.org + +[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_warp/examples/warp_server.rs diff --git a/juniper_warp/release.toml b/juniper_warp/release.toml new file mode 100644 index 000000000..283666df0 --- /dev/null +++ b/juniper_warp/release.toml @@ -0,0 +1,17 @@ +[[pre-release-replacements]] +file = "../book/src/servers/warp.md" +exactly = 1 +search = "juniper_warp = \"[^\"]+\"" +replace = "juniper_warp = \"{{version}}\"" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +exactly = 1 +search = "## master" +replace = "## [{{version}}] · {{date}}\n[{{version}}]: /../../tree/{{crate_name}}%40{{version}}/{{crate_name}}" + +[[pre-release-replacements]] +file = "README.md" +exactly = 3 +search = "graphql-rust/juniper/blob/[^/]+/" +replace = "graphql-rust/juniper/blob/{{crate_name}}%40{{version}}/" diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index af3e86f29..e7686b8dc 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -1,44 +1,7 @@ -/*! - -# juniper_warp - -This repository contains the [warp][warp] web server integration for -[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust. - -## Documentation - -For documentation, including guides and examples, check out [Juniper][Juniper]. - -A basic usage example can also be found in the [Api documentation][documentation]. - -## Examples - -Check [examples/warp_server.rs][example] for example code of a working warp -server with GraphQL handlers. - -## Links - -* [Juniper][Juniper] -* [Api Reference][documentation] -* [warp][warp] - -## License - -This project is under the BSD-2 license. - -Check the LICENSE file for details. - -[warp]: https://github.com/seanmonstar/warp -[Juniper]: https://github.com/graphql-rust/juniper -[GraphQL]: http://graphql.org -[documentation]: https://docs.rs/juniper_warp -[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_warp/examples/warp_server.rs - -*/ - +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] #![deny(missing_docs)] #![deny(warnings)] -#![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] use std::{collections::HashMap, str, sync::Arc}; diff --git a/release.toml b/release.toml new file mode 100644 index 000000000..09c0beb42 --- /dev/null +++ b/release.toml @@ -0,0 +1,4 @@ +allow-branch = ["*"] +pre-release-commit-message = "Prepare {{version}} release of `{{crate_name}}` crate" +tag-message = "{{version}} version of `{{crate_name}}` crate" +tag-name = "{{crate_name}}@{{version}}" diff --git a/integration_tests/codegen_fail/Cargo.toml b/tests/codegen/Cargo.toml similarity index 54% rename from integration_tests/codegen_fail/Cargo.toml rename to tests/codegen/Cargo.toml index f9f296c87..eb513bf32 100644 --- a/integration_tests/codegen_fail/Cargo.toml +++ b/tests/codegen/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "juniper_codegen_tests" -version = "0.1.0" -publish = false +version = "0.0.0" edition = "2018" +publish = false [dependencies] -juniper = { path = "../../juniper" } futures = "0.3.1" +juniper = { path = "../../juniper" } [dev-dependencies] -serde_json = { version = "1" } -tokio = { version = "1", features = ["rt", "time", "macros"] } -trybuild = "1.0.25" \ No newline at end of file +serde_json = { version = "1.0" } +tokio = { version = "1.0", features = ["rt", "time", "macros"] } +trybuild = "1.0.25" diff --git a/tests/codegen/fail/enum/derive_duplicated_variant_names.rs b/tests/codegen/fail/enum/derive_duplicated_variant_names.rs new file mode 100644 index 000000000..b68dca16e --- /dev/null +++ b/tests/codegen/fail/enum/derive_duplicated_variant_names.rs @@ -0,0 +1,8 @@ +#[derive(juniper::GraphQLEnum)] +enum Test { + Test, + #[graphql(name = "TEST")] + Test1, +} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_duplicated_variant_names.stderr b/tests/codegen/fail/enum/derive_duplicated_variant_names.stderr new file mode 100644 index 000000000..c323b4cc2 --- /dev/null +++ b/tests/codegen/fail/enum/derive_duplicated_variant_names.stderr @@ -0,0 +1,7 @@ +error: GraphQL enum expected all enum variants to have unique names + --> fail/enum/derive_duplicated_variant_names.rs:3:5 + | +3 | / Test, +4 | | #[graphql(name = "TEST")] +5 | | Test1, + | |__________^ diff --git a/tests/codegen/fail/enum/derive_name_double_underscored.rs b/tests/codegen/fail/enum/derive_name_double_underscored.rs new file mode 100644 index 000000000..3729d7f65 --- /dev/null +++ b/tests/codegen/fail/enum/derive_name_double_underscored.rs @@ -0,0 +1,6 @@ +#[derive(juniper::GraphQLEnum)] +enum __Test { + Test, +} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_name_double_underscored.stderr b/tests/codegen/fail/enum/derive_name_double_underscored.stderr new file mode 100644 index 000000000..fb1713abb --- /dev/null +++ b/tests/codegen/fail/enum/derive_name_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> fail/enum/derive_name_double_underscored.rs:2:6 + | +2 | enum __Test { + | ^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/enum/derive_no_fields.rs b/tests/codegen/fail/enum/derive_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/enum/derive_no_fields.rs rename to tests/codegen/fail/enum/derive_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/enum/derive_no_fields.stderr b/tests/codegen/fail/enum/derive_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/enum/derive_no_fields.stderr rename to tests/codegen/fail/enum/derive_no_fields.stderr diff --git a/tests/codegen/fail/enum/derive_no_variants.stderr b/tests/codegen/fail/enum/derive_no_variants.stderr new file mode 100644 index 000000000..165a52f65 --- /dev/null +++ b/tests/codegen/fail/enum/derive_no_variants.stderr @@ -0,0 +1,7 @@ +error: GraphQL enum expected at least 1 non-ignored variant + --> fail/enum/derive_no_variants.rs:1:10 + | +1 | #[derive(juniper::GraphQLEnum)] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the derive macro `juniper::GraphQLEnum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/enum/derive_variant_with_field.rs b/tests/codegen/fail/enum/derive_variant_with_field.rs new file mode 100644 index 000000000..39a737ef0 --- /dev/null +++ b/tests/codegen/fail/enum/derive_variant_with_field.rs @@ -0,0 +1,6 @@ +#[derive(juniper::GraphQLEnum)] +enum Test { + Variant(i32), +} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_variant_with_field.stderr b/tests/codegen/fail/enum/derive_variant_with_field.stderr new file mode 100644 index 000000000..fb10dd306 --- /dev/null +++ b/tests/codegen/fail/enum/derive_variant_with_field.stderr @@ -0,0 +1,7 @@ +error: GraphQL enum no fields allowed for non-ignored variants + --> fail/enum/derive_variant_with_field.rs:3:12 + | +3 | Variant(i32), + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Enums diff --git a/tests/codegen/fail/enum/derive_wrong_item.rs b/tests/codegen/fail/enum/derive_wrong_item.rs new file mode 100644 index 000000000..7b9335379 --- /dev/null +++ b/tests/codegen/fail/enum/derive_wrong_item.rs @@ -0,0 +1,4 @@ +#[derive(juniper::GraphQLEnum)] +struct Test {} + +fn main() {} diff --git a/tests/codegen/fail/enum/derive_wrong_item.stderr b/tests/codegen/fail/enum/derive_wrong_item.stderr new file mode 100644 index 000000000..72572c032 --- /dev/null +++ b/tests/codegen/fail/enum/derive_wrong_item.stderr @@ -0,0 +1,5 @@ +error: GraphQL enum can only be derived on enums + --> fail/enum/derive_wrong_item.rs:2:1 + | +2 | struct Test {} + | ^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.rs b/tests/codegen/fail/input-object/derive_incompatible_object.rs similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.rs rename to tests/codegen/fail/input-object/derive_incompatible_object.rs diff --git a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr b/tests/codegen/fail/input-object/derive_incompatible_object.stderr similarity index 61% rename from integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr rename to tests/codegen/fail/input-object/derive_incompatible_object.stderr index 53ac2dee6..e8e2a3ccf 100644 --- a/integration_tests/codegen_fail/fail/input-object/derive_incompatible_object.stderr +++ b/tests/codegen/fail/input-object/derive_incompatible_object.stderr @@ -3,6 +3,17 @@ error[E0277]: the trait bound `ObjectA: IsInputType<__S>` is not satisfied | 8 | field: ObjectA, | ^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjectA` + | + = help: the following other types implement trait `IsInputType`: + <&T as IsInputType> + as IsInputType> + as IsInputType> + > + > + > + as IsInputType> + <[T; N] as IsInputType> + and 13 others error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied --> fail/input-object/derive_incompatible_object.rs:6:10 @@ -10,6 +21,16 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied 6 | #[derive(juniper::GraphQLInputObject)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA` | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + and 10 others note: required by a bound in `Registry::<'r, S>::arg` --> $WORKSPACE/juniper/src/executor/mod.rs | @@ -23,6 +44,16 @@ error[E0277]: the trait bound `ObjectA: FromInputValue<__S>` is not satisfied 6 | #[derive(juniper::GraphQLInputObject)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjectA` | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + and 10 others = note: this error originates in the derive macro `juniper::GraphQLInputObject` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the current scope diff --git a/integration_tests/codegen_fail/fail/input-object/derive_no_fields.rs b/tests/codegen/fail/input-object/derive_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_no_fields.rs rename to tests/codegen/fail/input-object/derive_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/input-object/derive_no_fields.stderr b/tests/codegen/fail/input-object/derive_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_no_fields.stderr rename to tests/codegen/fail/input-object/derive_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/input-object/derive_no_underscore.rs b/tests/codegen/fail/input-object/derive_no_underscore.rs similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_no_underscore.rs rename to tests/codegen/fail/input-object/derive_no_underscore.rs diff --git a/integration_tests/codegen_fail/fail/input-object/derive_no_underscore.stderr b/tests/codegen/fail/input-object/derive_no_underscore.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_no_underscore.stderr rename to tests/codegen/fail/input-object/derive_no_underscore.stderr diff --git a/integration_tests/codegen_fail/fail/input-object/derive_unique_name.rs b/tests/codegen/fail/input-object/derive_unique_name.rs similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_unique_name.rs rename to tests/codegen/fail/input-object/derive_unique_name.rs diff --git a/integration_tests/codegen_fail/fail/input-object/derive_unique_name.stderr b/tests/codegen/fail/input-object/derive_unique_name.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/input-object/derive_unique_name.stderr rename to tests/codegen/fail/input-object/derive_unique_name.stderr diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item.rs b/tests/codegen/fail/interface/attr_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/attr_wrong_item.rs rename to tests/codegen/fail/interface/attr_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr b/tests/codegen/fail/interface/attr_wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/attr_wrong_item.stderr rename to tests/codegen/fail/interface/attr_wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs b/tests/codegen/fail/interface/derive_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/derive_wrong_item.rs rename to tests/codegen/fail/interface/derive_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr b/tests/codegen/fail/interface/derive_wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/derive_wrong_item.stderr rename to tests/codegen/fail/interface/derive_wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_additional_non_nullable_argument.rs rename to tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.rs diff --git a/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr new file mode 100644 index 000000000..a1b8ce2f8 --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_additional_non_nullable_argument.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | +16 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | +16 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/attr_additional_non_nullable_argument.rs:16:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.rs b/tests/codegen/fail/interface/struct/attr_cyclic_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.rs rename to tests/codegen/fail/interface/struct/attr_cyclic_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.stderr b/tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_cyclic_impl.stderr rename to tests/codegen/fail/interface/struct/attr_cyclic_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs b/tests/codegen/fail/interface/struct/attr_field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.rs rename to tests/codegen/fail/interface/struct/attr_field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr b/tests/codegen/fail/interface/struct/attr_field_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_field_double_underscored.stderr rename to tests/codegen/fail/interface/struct/attr_field_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs b/tests/codegen/fail/interface/struct/attr_field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_field_non_output_return_type.rs rename to tests/codegen/fail/interface/struct/attr_field_non_output_return_type.rs diff --git a/tests/codegen/fail/interface/struct/attr_field_non_output_return_type.stderr b/tests/codegen/fail/interface/struct/attr_field_non_output_return_type.stderr new file mode 100644 index 000000000..8ddf287d7 --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_field_non_output_return_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/struct/attr_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = help: the following other types implement trait `IsOutputType`: + <&T as IsOutputType> + as IsOutputType> + as IsOutputType> + > + > + > + as IsOutputType> + > + and 18 others diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs b/tests/codegen/fail/interface/struct/attr_fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.rs rename to tests/codegen/fail/interface/struct/attr_fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr b/tests/codegen/fail/interface/struct/attr_fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_fields_duplicate.stderr rename to tests/codegen/fail/interface/struct/attr_fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_pretty.rs rename to tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.rs diff --git a/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..a9f9fe376 --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_pretty.stderr @@ -0,0 +1,35 @@ +error: duplicated attribute argument found + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.rs rename to tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr b/tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_implementers_duplicate_ugly.stderr rename to tests/codegen/fail/interface/struct/attr_implementers_duplicate_ugly.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs b/tests/codegen/fail/interface/struct/attr_missing_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_field.rs rename to tests/codegen/fail/interface/struct/attr_missing_field.rs diff --git a/tests/codegen/fail/interface/struct/attr_missing_field.stderr b/tests/codegen/fail/interface/struct/attr_missing_field.stderr new file mode 100644 index 000000000..37afdb394 --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_missing_field.stderr @@ -0,0 +1,984 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/attr_missing_field.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_missing_field.rs:11:5 + | +11 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs b/tests/codegen/fail/interface/struct/attr_missing_for_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.rs rename to tests/codegen/fail/interface/struct/attr_missing_for_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr b/tests/codegen/fail/interface/struct/attr_missing_for_attr.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_for_attr.stderr rename to tests/codegen/fail/interface/struct/attr_missing_for_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs b/tests/codegen/fail/interface/struct/attr_missing_impl_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.rs rename to tests/codegen/fail/interface/struct/attr_missing_impl_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr b/tests/codegen/fail/interface/struct/attr_missing_impl_attr.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_impl_attr.stderr rename to tests/codegen/fail/interface/struct/attr_missing_impl_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.rs b/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.rs rename to tests/codegen/fail/interface/struct/attr_missing_transitive_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.stderr b/tests/codegen/fail/interface/struct/attr_missing_transitive_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_missing_transitive_impl.stderr rename to tests/codegen/fail/interface/struct/attr_missing_transitive_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs b/tests/codegen/fail/interface/struct/attr_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.rs rename to tests/codegen/fail/interface/struct/attr_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr b/tests/codegen/fail/interface/struct/attr_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_name_double_underscored.stderr rename to tests/codegen/fail/interface/struct/attr_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs b/tests/codegen/fail/interface/struct/attr_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.rs rename to tests/codegen/fail/interface/struct/attr_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr b/tests/codegen/fail/interface/struct/attr_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_no_fields.stderr rename to tests/codegen/fail/interface/struct/attr_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs b/tests/codegen/fail/interface/struct/attr_non_subtype_return.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_non_subtype_return.rs rename to tests/codegen/fail/interface/struct/attr_non_subtype_return.rs diff --git a/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr b/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr new file mode 100644 index 000000000..c5d9903cc --- /dev/null +++ b/tests/codegen/fail/interface/struct/attr_non_subtype_return.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_non_subtype_return.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/attr_non_subtype_return.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/attr_non_subtype_return.rs:11:5 + | +11 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/attr_non_subtype_return.rs:11:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs b/tests/codegen/fail/interface/struct/attr_unnamed_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.rs rename to tests/codegen/fail/interface/struct/attr_unnamed_field.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr b/tests/codegen/fail/interface/struct/attr_unnamed_field.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/attr_unnamed_field.stderr rename to tests/codegen/fail/interface/struct/attr_unnamed_field.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_additional_non_nullable_argument.rs rename to tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.rs diff --git a/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr new file mode 100644 index 000000000..aa1fd8eef --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_additional_non_nullable_argument.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 + | +17 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 + | +17 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/struct/derive_additional_non_nullable_argument.rs:17:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs b/tests/codegen/fail/interface/struct/derive_cyclic_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.rs rename to tests/codegen/fail/interface/struct/derive_cyclic_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr b/tests/codegen/fail/interface/struct/derive_cyclic_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_cyclic_impl.stderr rename to tests/codegen/fail/interface/struct/derive_cyclic_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs b/tests/codegen/fail/interface/struct/derive_field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.rs rename to tests/codegen/fail/interface/struct/derive_field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr b/tests/codegen/fail/interface/struct/derive_field_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_field_double_underscored.stderr rename to tests/codegen/fail/interface/struct/derive_field_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs b/tests/codegen/fail/interface/struct/derive_field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_field_non_output_return_type.rs rename to tests/codegen/fail/interface/struct/derive_field_non_output_return_type.rs diff --git a/tests/codegen/fail/interface/struct/derive_field_non_output_return_type.stderr b/tests/codegen/fail/interface/struct/derive_field_non_output_return_type.stderr new file mode 100644 index 000000000..bc9a79a75 --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_field_non_output_return_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/struct/derive_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = help: the following other types implement trait `IsOutputType`: + <&T as IsOutputType> + as IsOutputType> + as IsOutputType> + > + > + > + as IsOutputType> + > + and 18 others diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs b/tests/codegen/fail/interface/struct/derive_fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.rs rename to tests/codegen/fail/interface/struct/derive_fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr b/tests/codegen/fail/interface/struct/derive_fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_fields_duplicate.stderr rename to tests/codegen/fail/interface/struct/derive_fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_pretty.rs rename to tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.rs diff --git a/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..234878dec --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_pretty.stderr @@ -0,0 +1,35 @@ +error: duplicated attribute argument found + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:10:24 + | +10 | #[graphql(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.rs rename to tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr b/tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_implementers_duplicate_ugly.stderr rename to tests/codegen/fail/interface/struct/derive_implementers_duplicate_ugly.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs b/tests/codegen/fail/interface/struct/derive_missing_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_field.rs rename to tests/codegen/fail/interface/struct/derive_missing_field.rs diff --git a/tests/codegen/fail/interface/struct/derive_missing_field.stderr b/tests/codegen/fail/interface/struct/derive_missing_field.stderr new file mode 100644 index 000000000..a9a91ec79 --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_missing_field.stderr @@ -0,0 +1,984 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ + | | + | referenced constant has errors + | inside ` as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/struct/derive_missing_field.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_missing_field.rs:12:5 + | +12 | id: String, + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs b/tests/codegen/fail/interface/struct/derive_missing_for_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.rs rename to tests/codegen/fail/interface/struct/derive_missing_for_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr b/tests/codegen/fail/interface/struct/derive_missing_for_attr.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_for_attr.stderr rename to tests/codegen/fail/interface/struct/derive_missing_for_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs b/tests/codegen/fail/interface/struct/derive_missing_impl_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.rs rename to tests/codegen/fail/interface/struct/derive_missing_impl_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr b/tests/codegen/fail/interface/struct/derive_missing_impl_attr.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_impl_attr.stderr rename to tests/codegen/fail/interface/struct/derive_missing_impl_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.rs b/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.rs rename to tests/codegen/fail/interface/struct/derive_missing_transitive_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.stderr b/tests/codegen/fail/interface/struct/derive_missing_transitive_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_missing_transitive_impl.stderr rename to tests/codegen/fail/interface/struct/derive_missing_transitive_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs b/tests/codegen/fail/interface/struct/derive_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.rs rename to tests/codegen/fail/interface/struct/derive_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr b/tests/codegen/fail/interface/struct/derive_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_name_double_underscored.stderr rename to tests/codegen/fail/interface/struct/derive_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs b/tests/codegen/fail/interface/struct/derive_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.rs rename to tests/codegen/fail/interface/struct/derive_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr b/tests/codegen/fail/interface/struct/derive_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_no_fields.stderr rename to tests/codegen/fail/interface/struct/derive_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs b/tests/codegen/fail/interface/struct/derive_non_subtype_return.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_non_subtype_return.rs rename to tests/codegen/fail/interface/struct/derive_non_subtype_return.rs diff --git a/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr b/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr new file mode 100644 index 000000000..b62b6b0f9 --- /dev/null +++ b/tests/codegen/fail/interface/struct/derive_non_subtype_return.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_non_subtype_return.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/derive_non_subtype_return.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/struct/derive_non_subtype_return.rs:12:5 + | +12 | id: String, + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/struct/derive_non_subtype_return.rs:12:5 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs b/tests/codegen/fail/interface/struct/derive_unnamed_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.rs rename to tests/codegen/fail/interface/struct/derive_unnamed_field.rs diff --git a/integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr b/tests/codegen/fail/interface/struct/derive_unnamed_field.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/struct/derive_unnamed_field.stderr rename to tests/codegen/fail/interface/struct/derive_unnamed_field.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.rs b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/additional_non_nullable_argument.rs rename to tests/codegen/fail/interface/trait/additional_non_nullable_argument.rs diff --git a/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr new file mode 100644 index 000000000..4817ef41d --- /dev/null +++ b/tests/codegen/fail/interface/trait/additional_non_nullable_argument.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | +16 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | +16 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/trait/additional_non_nullable_argument.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.rs b/tests/codegen/fail/interface/trait/argument_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.rs rename to tests/codegen/fail/interface/trait/argument_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr b/tests/codegen/fail/interface/trait/argument_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/argument_double_underscored.stderr rename to tests/codegen/fail/interface/trait/argument_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.rs b/tests/codegen/fail/interface/trait/argument_non_input_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.rs rename to tests/codegen/fail/interface/trait/argument_non_input_type.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr b/tests/codegen/fail/interface/trait/argument_non_input_type.stderr similarity index 51% rename from integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr rename to tests/codegen/fail/interface/trait/argument_non_input_type.stderr index c532bab16..a085a84fe 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/argument_non_input_type.stderr +++ b/tests/codegen/fail/interface/trait/argument_non_input_type.stderr @@ -3,6 +3,17 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied | 10 | fn id(&self, obj: ObjA) -> &str; | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = help: the following other types implement trait `IsInputType`: + <&T as IsInputType> + as IsInputType> + as IsInputType> + > + > + as IsInputType> + <[T; N] as IsInputType> + <[T] as IsInputType> + and 12 others error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied --> fail/interface/trait/argument_non_input_type.rs:8:1 @@ -10,6 +21,16 @@ error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied 8 | #[graphql_interface] | ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + > + and 9 others note: required by a bound in `Registry::<'r, S>::arg` --> $WORKSPACE/juniper/src/executor/mod.rs | diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.rs b/tests/codegen/fail/interface/trait/argument_wrong_default_array.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.rs rename to tests/codegen/fail/interface/trait/argument_wrong_default_array.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr b/tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr rename to tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr index d609ce029..c8a00ea32 100644 --- a/integration_tests/codegen_fail/fail/interface/trait/argument_wrong_default_array.stderr +++ b/tests/codegen/fail/interface/trait/argument_wrong_default_array.stderr @@ -4,11 +4,15 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied 3 | #[graphql_interface] | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | - = help: the following implementations were found: + = help: the following other types implement trait `From`: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - and 11 others + <[T; LANES] as From>> + <[bool; LANES] as From>> + <[u128; 1] as From> + <[u128; 2] as From> + and 7 others = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.rs b/tests/codegen/fail/interface/trait/cyclic_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.rs rename to tests/codegen/fail/interface/trait/cyclic_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr b/tests/codegen/fail/interface/trait/cyclic_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/cyclic_impl.stderr rename to tests/codegen/fail/interface/trait/cyclic_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.rs b/tests/codegen/fail/interface/trait/field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.rs rename to tests/codegen/fail/interface/trait/field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr b/tests/codegen/fail/interface/trait/field_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/field_double_underscored.stderr rename to tests/codegen/fail/interface/trait/field_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.rs b/tests/codegen/fail/interface/trait/field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/field_non_output_return_type.rs rename to tests/codegen/fail/interface/trait/field_non_output_return_type.rs diff --git a/tests/codegen/fail/interface/trait/field_non_output_return_type.stderr b/tests/codegen/fail/interface/trait/field_non_output_return_type.stderr new file mode 100644 index 000000000..e1042ff9c --- /dev/null +++ b/tests/codegen/fail/interface/trait/field_non_output_return_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/interface/trait/field_non_output_return_type.rs:10:21 + | +10 | fn id(&self) -> ObjB; + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = help: the following other types implement trait `IsOutputType`: + <&T as IsOutputType> + as IsOutputType> + as IsOutputType> + > + > + > + as IsOutputType> + > + and 18 others diff --git a/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.rs b/tests/codegen/fail/interface/trait/fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.rs rename to tests/codegen/fail/interface/trait/fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr b/tests/codegen/fail/interface/trait/fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/fields_duplicate.stderr rename to tests/codegen/fail/interface/trait/fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.rs b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_pretty.rs rename to tests/codegen/fail/interface/trait/implementers_duplicate_pretty.rs diff --git a/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..13696d8da --- /dev/null +++ b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr @@ -0,0 +1,35 @@ +error: duplicated attribute argument found + --> fail/interface/trait/implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/trait/implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.rs b/tests/codegen/fail/interface/trait/implementers_duplicate_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.rs rename to tests/codegen/fail/interface/trait/implementers_duplicate_ugly.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr b/tests/codegen/fail/interface/trait/implementers_duplicate_ugly.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/implementers_duplicate_ugly.stderr rename to tests/codegen/fail/interface/trait/implementers_duplicate_ugly.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.rs b/tests/codegen/fail/interface/trait/method_default_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/method_default_impl.rs rename to tests/codegen/fail/interface/trait/method_default_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr b/tests/codegen/fail/interface/trait/method_default_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/method_default_impl.stderr rename to tests/codegen/fail/interface/trait/method_default_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field.rs b/tests/codegen/fail/interface/trait/missing_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_field.rs rename to tests/codegen/fail/interface/trait/missing_field.rs diff --git a/tests/codegen/fail/interface/trait/missing_field.stderr b/tests/codegen/fail/interface/trait/missing_field.stderr new file mode 100644 index 000000000..45913e165 --- /dev/null +++ b/tests/codegen/fail/interface/trait/missing_field.stderr @@ -0,0 +1,984 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ + | | + | referenced constant has errors + | inside ` as juniper::macros::reflect::Field<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as juniper::macros::reflect::Field<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: erroneous constant used + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ + | | + | referenced constant has errors + | inside ` as AsyncField<__S, id>>::call::_::check` at $WORKSPACE/juniper/src/macros/reflect.rs:751:36 + | inside ` as AsyncField<__S, id>>::call::_::RES` at $WORKSPACE/juniper/src/macros/reflect.rs:814:59 + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_field_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/trait/missing_field.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::assert_subtype` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::format_type` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.rs b/tests/codegen/fail/interface/trait/missing_field_argument.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_field_argument.rs rename to tests/codegen/fail/interface/trait/missing_field_argument.rs diff --git a/tests/codegen/fail/interface/trait/missing_field_argument.stderr b/tests/codegen/fail/interface/trait/missing_field_argument.stderr new file mode 100644 index 000000000..276222f71 --- /dev/null +++ b/tests/codegen/fail/interface/trait/missing_field_argument.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field_argument.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/trait/missing_field_argument.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/missing_field_argument.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/trait/missing_field_argument.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.rs b/tests/codegen/fail/interface/trait/missing_for_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.rs rename to tests/codegen/fail/interface/trait/missing_for_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr b/tests/codegen/fail/interface/trait/missing_for_attr.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_for_attr.stderr rename to tests/codegen/fail/interface/trait/missing_for_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.rs b/tests/codegen/fail/interface/trait/missing_impl_attr.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.rs rename to tests/codegen/fail/interface/trait/missing_impl_attr.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr b/tests/codegen/fail/interface/trait/missing_impl_attr.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_impl_attr.stderr rename to tests/codegen/fail/interface/trait/missing_impl_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.rs b/tests/codegen/fail/interface/trait/missing_transitive_impl.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.rs rename to tests/codegen/fail/interface/trait/missing_transitive_impl.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr b/tests/codegen/fail/interface/trait/missing_transitive_impl.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/missing_transitive_impl.stderr rename to tests/codegen/fail/interface/trait/missing_transitive_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.rs b/tests/codegen/fail/interface/trait/name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.rs rename to tests/codegen/fail/interface/trait/name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr b/tests/codegen/fail/interface/trait/name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/name_double_underscored.stderr rename to tests/codegen/fail/interface/trait/name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/no_fields.rs b/tests/codegen/fail/interface/trait/no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/no_fields.rs rename to tests/codegen/fail/interface/trait/no_fields.rs diff --git a/integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr b/tests/codegen/fail/interface/trait/no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/no_fields.stderr rename to tests/codegen/fail/interface/trait/no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.rs b/tests/codegen/fail/interface/trait/non_subtype_return.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/non_subtype_return.rs rename to tests/codegen/fail/interface/trait/non_subtype_return.rs diff --git a/tests/codegen/fail/interface/trait/non_subtype_return.stderr b/tests/codegen/fail/interface/trait/non_subtype_return.stderr new file mode 100644 index 000000000..92e123f23 --- /dev/null +++ b/tests/codegen/fail/interface/trait/non_subtype_return.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/non_subtype_return.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/trait/non_subtype_return.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/non_subtype_return.rs:11:8 + | +11 | fn id(&self) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/trait/non_subtype_return.rs:11:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.rs b/tests/codegen/fail/interface/trait/wrong_argument_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/trait/wrong_argument_type.rs rename to tests/codegen/fail/interface/trait/wrong_argument_type.rs diff --git a/tests/codegen/fail/interface/trait/wrong_argument_type.stderr b/tests/codegen/fail/interface/trait/wrong_argument_type.stderr new file mode 100644 index 000000000..3a47b725d --- /dev/null +++ b/tests/codegen/fail/interface/trait/wrong_argument_type.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/wrong_argument_type.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/wrong_argument_type.rs:16:8 + | +16 | fn id(&self, is_present: bool) -> &str; + | ^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/trait/wrong_argument_type.rs:16:8 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs b/tests/codegen/fail/object/argument_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/argument_double_underscored.rs rename to tests/codegen/fail/object/argument_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr b/tests/codegen/fail/object/argument_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr rename to tests/codegen/fail/object/argument_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs b/tests/codegen/fail/object/argument_non_input_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/argument_non_input_type.rs rename to tests/codegen/fail/object/argument_non_input_type.rs diff --git a/tests/codegen/fail/object/argument_non_input_type.stderr b/tests/codegen/fail/object/argument_non_input_type.stderr new file mode 100644 index 000000000..1683fef05 --- /dev/null +++ b/tests/codegen/fail/object/argument_non_input_type.stderr @@ -0,0 +1,57 @@ +error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied + --> fail/object/argument_non_input_type.rs:12:23 + | +12 | fn id(&self, obj: ObjA) -> &str { + | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = help: the following other types implement trait `IsInputType`: + <&T as IsInputType> + as IsInputType> + as IsInputType> + > + > + as IsInputType> + <[T; N] as IsInputType> + <[T] as IsInputType> + and 12 others + +error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied + --> fail/object/argument_non_input_type.rs:10:1 + | +10 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` + | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + > + and 9 others +note: required by a bound in `Registry::<'r, S>::arg` + --> $WORKSPACE/juniper/src/executor/mod.rs + | + | T: GraphQLType + FromInputValue, + | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg` + = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied + --> fail/object/argument_non_input_type.rs:10:1 + | +10 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` + | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + > + and 9 others + = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs b/tests/codegen/fail/object/argument_wrong_default_array.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs rename to tests/codegen/fail/object/argument_wrong_default_array.rs diff --git a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr b/tests/codegen/fail/object/argument_wrong_default_array.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr rename to tests/codegen/fail/object/argument_wrong_default_array.stderr index 1e65f81c4..76357529f 100644 --- a/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr +++ b/tests/codegen/fail/object/argument_wrong_default_array.stderr @@ -4,11 +4,15 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied 5 | #[graphql_object] | ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | - = help: the following implementations were found: + = help: the following other types implement trait `From`: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - and 11 others + <[T; LANES] as From>> + <[bool; LANES] as From>> + <[u128; 1] as From> + <[u128; 2] as From> + and 7 others = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs b/tests/codegen/fail/object/attr_field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs rename to tests/codegen/fail/object/attr_field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr b/tests/codegen/fail/object/attr_field_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr rename to tests/codegen/fail/object/attr_field_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs b/tests/codegen/fail/object/attr_field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs rename to tests/codegen/fail/object/attr_field_non_output_return_type.rs diff --git a/tests/codegen/fail/object/attr_field_non_output_return_type.stderr b/tests/codegen/fail/object/attr_field_non_output_return_type.stderr new file mode 100644 index 000000000..079f3566e --- /dev/null +++ b/tests/codegen/fail/object/attr_field_non_output_return_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/object/attr_field_non_output_return_type.rs:12:21 + | +12 | fn id(&self) -> ObjB { + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = help: the following other types implement trait `IsOutputType`: + <&T as IsOutputType> + as IsOutputType> + as IsOutputType> + > + > + > + as IsOutputType> + > + and 18 others diff --git a/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs b/tests/codegen/fail/object/attr_fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs rename to tests/codegen/fail/object/attr_fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr b/tests/codegen/fail/object/attr_fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr rename to tests/codegen/fail/object/attr_fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs b/tests/codegen/fail/object/attr_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs rename to tests/codegen/fail/object/attr_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr b/tests/codegen/fail/object/attr_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr rename to tests/codegen/fail/object/attr_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/object/attr_no_fields.rs b/tests/codegen/fail/object/attr_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_no_fields.rs rename to tests/codegen/fail/object/attr_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/object/attr_no_fields.stderr b/tests/codegen/fail/object/attr_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_no_fields.stderr rename to tests/codegen/fail/object/attr_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs b/tests/codegen/fail/object/attr_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_wrong_item.rs rename to tests/codegen/fail/object/attr_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr b/tests/codegen/fail/object/attr_wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr rename to tests/codegen/fail/object/attr_wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs b/tests/codegen/fail/object/derive_field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs rename to tests/codegen/fail/object/derive_field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr b/tests/codegen/fail/object/derive_field_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr rename to tests/codegen/fail/object/derive_field_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs b/tests/codegen/fail/object/derive_field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs rename to tests/codegen/fail/object/derive_field_non_output_return_type.rs diff --git a/tests/codegen/fail/object/derive_field_non_output_return_type.stderr b/tests/codegen/fail/object/derive_field_non_output_return_type.stderr new file mode 100644 index 000000000..2ce49070d --- /dev/null +++ b/tests/codegen/fail/object/derive_field_non_output_return_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/object/derive_field_non_output_return_type.rs:10:9 + | +10 | id: ObjB, + | ^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = help: the following other types implement trait `IsOutputType`: + <&T as IsOutputType> + as IsOutputType> + as IsOutputType> + > + > + > + as IsOutputType> + > + and 18 others diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs b/tests/codegen/fail/object/derive_fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs rename to tests/codegen/fail/object/derive_fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr b/tests/codegen/fail/object/derive_fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr rename to tests/codegen/fail/object/derive_fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs b/tests/codegen/fail/object/derive_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs rename to tests/codegen/fail/object/derive_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr b/tests/codegen/fail/object/derive_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr rename to tests/codegen/fail/object/derive_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.rs b/tests/codegen/fail/object/derive_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_no_fields.rs rename to tests/codegen/fail/object/derive_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr b/tests/codegen/fail/object/derive_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_no_fields.stderr rename to tests/codegen/fail/object/derive_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs b/tests/codegen/fail/object/derive_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_wrong_item.rs rename to tests/codegen/fail/object/derive_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr b/tests/codegen/fail/object/derive_wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr rename to tests/codegen/fail/object/derive_wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs b/tests/codegen/fail/scalar/derive_input/attr_invalid_url.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.rs rename to tests/codegen/fail/scalar/derive_input/attr_invalid_url.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr b/tests/codegen/fail/scalar/derive_input/attr_invalid_url.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_invalid_url.stderr rename to tests/codegen/fail/scalar/derive_input/attr_invalid_url.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.rs rename to tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_and_with.stderr rename to tests/codegen/fail/scalar/derive_input/attr_transparent_and_with.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs rename to tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr rename to tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_named_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs rename to tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr rename to tests/codegen/fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs b/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.rs rename to tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr b/tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/attr_transparent_unit_struct.stderr rename to tests/codegen/fail/scalar/derive_input/attr_transparent_unit_struct.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs b/tests/codegen/fail/scalar/derive_input/derive_invalid_url.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.rs rename to tests/codegen/fail/scalar/derive_input/derive_invalid_url.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr b/tests/codegen/fail/scalar/derive_input/derive_invalid_url.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_invalid_url.stderr rename to tests/codegen/fail/scalar/derive_input/derive_invalid_url.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs b/tests/codegen/fail/scalar/derive_input/derive_transparent_and_with.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.rs rename to tests/codegen/fail/scalar/derive_input/derive_transparent_and_with.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr b/tests/codegen/fail/scalar/derive_input/derive_transparent_and_with.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_and_with.stderr rename to tests/codegen/fail/scalar/derive_input/derive_transparent_and_with.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs b/tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs rename to tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr b/tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr rename to tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_named_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs b/tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs rename to tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr b/tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr rename to tests/codegen/fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs b/tests/codegen/fail/scalar/derive_input/derive_transparent_unit_struct.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.rs rename to tests/codegen/fail/scalar/derive_input/derive_transparent_unit_struct.rs diff --git a/integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr b/tests/codegen/fail/scalar/derive_input/derive_transparent_unit_struct.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/derive_input/derive_transparent_unit_struct.stderr rename to tests/codegen/fail/scalar/derive_input/derive_transparent_unit_struct.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.rs b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.rs rename to tests/codegen/fail/scalar/type_alias/attr_invalid_url.rs diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr b/tests/codegen/fail/scalar/type_alias/attr_invalid_url.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/attr_invalid_url.stderr rename to tests/codegen/fail/scalar/type_alias/attr_invalid_url.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.rs b/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.rs rename to tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.rs diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr b/tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr rename to tests/codegen/fail/scalar/type_alias/attr_with_not_all_resolvers.stderr diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.rs b/tests/codegen/fail/scalar/type_alias/attr_without_resolvers.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.rs rename to tests/codegen/fail/scalar/type_alias/attr_without_resolvers.rs diff --git a/integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr b/tests/codegen/fail/scalar/type_alias/attr_without_resolvers.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar/type_alias/attr_without_resolvers.stderr rename to tests/codegen/fail/scalar/type_alias/attr_without_resolvers.stderr diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.rs b/tests/codegen/fail/scalar_value/missing_attributes.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/missing_attributes.rs rename to tests/codegen/fail/scalar_value/missing_attributes.rs diff --git a/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr b/tests/codegen/fail/scalar_value/missing_attributes.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr rename to tests/codegen/fail/scalar_value/missing_attributes.stderr index 1e0ff3446..700a95fa2 100644 --- a/integration_tests/codegen_fail/fail/scalar_value/missing_attributes.stderr +++ b/tests/codegen/fail/scalar_value/missing_attributes.stderr @@ -1,4 +1,4 @@ -error: GraphQL built-in scalars missing `#[value(as_int, as_float)]` attributes. In case you are sure that it\'s ok, use `#[value(allow_missing_attributes)]` to suppress this error. +error: GraphQL built-in scalars missing `#[value(as_int, as_float)]` attributes. In case you are sure that it's ok, use `#[value(allow_missing_attributes)]` to suppress this error. --> fail/scalar_value/missing_attributes.rs:4:1 | 4 | / pub enum DefaultScalarValue { diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.rs b/tests/codegen/fail/scalar_value/multiple_named_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.rs rename to tests/codegen/fail/scalar_value/multiple_named_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.stderr b/tests/codegen/fail/scalar_value/multiple_named_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/multiple_named_fields.stderr rename to tests/codegen/fail/scalar_value/multiple_named_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.rs b/tests/codegen/fail/scalar_value/multiple_unnamed_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.rs rename to tests/codegen/fail/scalar_value/multiple_unnamed_fields.rs diff --git a/integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.stderr b/tests/codegen/fail/scalar_value/multiple_unnamed_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/multiple_unnamed_fields.stderr rename to tests/codegen/fail/scalar_value/multiple_unnamed_fields.stderr diff --git a/integration_tests/codegen_fail/fail/scalar_value/not_enum.rs b/tests/codegen/fail/scalar_value/not_enum.rs similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/not_enum.rs rename to tests/codegen/fail/scalar_value/not_enum.rs diff --git a/integration_tests/codegen_fail/fail/scalar_value/not_enum.stderr b/tests/codegen/fail/scalar_value/not_enum.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/scalar_value/not_enum.stderr rename to tests/codegen/fail/scalar_value/not_enum.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs b/tests/codegen/fail/subscription/argument_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs rename to tests/codegen/fail/subscription/argument_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr b/tests/codegen/fail/subscription/argument_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr rename to tests/codegen/fail/subscription/argument_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs b/tests/codegen/fail/subscription/argument_non_input_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs rename to tests/codegen/fail/subscription/argument_non_input_type.rs diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr b/tests/codegen/fail/subscription/argument_non_input_type.stderr similarity index 50% rename from integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr rename to tests/codegen/fail/subscription/argument_non_input_type.stderr index 43d9b36dd..f1f8538bf 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr +++ b/tests/codegen/fail/subscription/argument_non_input_type.stderr @@ -3,6 +3,17 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied | 17 | async fn id(&self, obj: ObjA) -> Stream<'static, &'static str> { | ^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = help: the following other types implement trait `IsInputType`: + <&T as IsInputType> + as IsInputType> + as IsInputType> + > + > + as IsInputType> + <[T; N] as IsInputType> + <[T] as IsInputType> + and 12 others error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied --> fail/subscription/argument_non_input_type.rs:15:1 @@ -10,6 +21,16 @@ error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied 15 | #[graphql_subscription] | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + > + and 9 others note: required by a bound in `Registry::<'r, S>::arg` --> $WORKSPACE/juniper/src/executor/mod.rs | @@ -23,4 +44,14 @@ error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied 15 | #[graphql_subscription] | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` | + = help: the following other types implement trait `FromInputValue`: + as FromInputValue> + as FromInputValue> + > + > + as FromInputValue> + <[T; N] as FromInputValue> + > + > + and 9 others = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs b/tests/codegen/fail/subscription/argument_wrong_default_array.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs rename to tests/codegen/fail/subscription/argument_wrong_default_array.rs diff --git a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr b/tests/codegen/fail/subscription/argument_wrong_default_array.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr rename to tests/codegen/fail/subscription/argument_wrong_default_array.stderr index eafad0546..ea5ccd3ca 100644 --- a/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr +++ b/tests/codegen/fail/subscription/argument_wrong_default_array.stderr @@ -4,11 +4,15 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied 10 | #[graphql_subscription] | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` | - = help: the following implementations were found: + = help: the following other types implement trait `From`: <&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>> <&'a [u32; 4] as From<&'a ppv_lite86::x86_64::vec128_storage>> <&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>> <&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>> - and 11 others + <[T; LANES] as From>> + <[bool; LANES] as From>> + <[u128; 1] as From> + <[u128; 2] as From> + and 7 others = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs b/tests/codegen/fail/subscription/field_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs rename to tests/codegen/fail/subscription/field_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr b/tests/codegen/fail/subscription/field_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr rename to tests/codegen/fail/subscription/field_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs b/tests/codegen/fail/subscription/field_non_output_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs rename to tests/codegen/fail/subscription/field_non_output_return_type.rs diff --git a/tests/codegen/fail/subscription/field_non_output_return_type.stderr b/tests/codegen/fail/subscription/field_non_output_return_type.stderr new file mode 100644 index 000000000..9b5be8a7d --- /dev/null +++ b/tests/codegen/fail/subscription/field_non_output_return_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> fail/subscription/field_non_output_return_type.rs:17:27 + | +17 | async fn id(&self) -> Stream<'static, ObjB> { + | ^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = help: the following other types implement trait `IsOutputType`: + <&T as IsOutputType> + as IsOutputType> + as IsOutputType> + > + > + > + as IsOutputType> + > + and 18 others diff --git a/integration_tests/codegen_fail/fail/subscription/field_not_async.rs b/tests/codegen/fail/subscription/field_not_async.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/field_not_async.rs rename to tests/codegen/fail/subscription/field_not_async.rs diff --git a/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr b/tests/codegen/fail/subscription/field_not_async.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/field_not_async.stderr rename to tests/codegen/fail/subscription/field_not_async.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs b/tests/codegen/fail/subscription/fields_duplicate.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs rename to tests/codegen/fail/subscription/fields_duplicate.rs diff --git a/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr b/tests/codegen/fail/subscription/fields_duplicate.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr rename to tests/codegen/fail/subscription/fields_duplicate.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs b/tests/codegen/fail/subscription/name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs rename to tests/codegen/fail/subscription/name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr b/tests/codegen/fail/subscription/name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr rename to tests/codegen/fail/subscription/name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/no_fields.rs b/tests/codegen/fail/subscription/no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/no_fields.rs rename to tests/codegen/fail/subscription/no_fields.rs diff --git a/integration_tests/codegen_fail/fail/subscription/no_fields.stderr b/tests/codegen/fail/subscription/no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/no_fields.stderr rename to tests/codegen/fail/subscription/no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/wrong_item.rs b/tests/codegen/fail/subscription/wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/wrong_item.rs rename to tests/codegen/fail/subscription/wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr b/tests/codegen/fail/subscription/wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/subscription/wrong_item.stderr rename to tests/codegen/fail/subscription/wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/union/attr_wrong_item.rs b/tests/codegen/fail/union/attr_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/attr_wrong_item.rs rename to tests/codegen/fail/union/attr_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/union/attr_wrong_item.stderr b/tests/codegen/fail/union/attr_wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/attr_wrong_item.stderr rename to tests/codegen/fail/union/attr_wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/union/derive_wrong_item.rs b/tests/codegen/fail/union/derive_wrong_item.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/derive_wrong_item.rs rename to tests/codegen/fail/union/derive_wrong_item.rs diff --git a/integration_tests/codegen_fail/fail/union/derive_wrong_item.stderr b/tests/codegen/fail/union/derive_wrong_item.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/derive_wrong_item.stderr rename to tests/codegen/fail/union/derive_wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs b/tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs rename to tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.rs diff --git a/integration_tests/codegen_fail/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr b/tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr rename to tests/codegen/fail/union/enum_external_resolver_fn_conflicts_with_variant_external_resolver_fn.stderr diff --git a/integration_tests/codegen_fail/fail/union/enum_name_double_underscored.rs b/tests/codegen/fail/union/enum_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_name_double_underscored.rs rename to tests/codegen/fail/union/enum_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/union/enum_name_double_underscored.stderr b/tests/codegen/fail/union/enum_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_name_double_underscored.stderr rename to tests/codegen/fail/union/enum_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/union/enum_no_fields.rs b/tests/codegen/fail/union/enum_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_no_fields.rs rename to tests/codegen/fail/union/enum_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/union/enum_no_fields.stderr b/tests/codegen/fail/union/enum_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_no_fields.stderr rename to tests/codegen/fail/union/enum_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.rs b/tests/codegen/fail/union/enum_non_object_variant.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_non_object_variant.rs rename to tests/codegen/fail/union/enum_non_object_variant.rs diff --git a/tests/codegen/fail/union/enum_non_object_variant.stderr b/tests/codegen/fail/union/enum_non_object_variant.stderr new file mode 100644 index 000000000..7b0472486 --- /dev/null +++ b/tests/codegen/fail/union/enum_non_object_variant.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied + --> fail/union/enum_non_object_variant.rs:9:10 + | +9 | #[derive(GraphQLUnion)] + | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` + | + = help: the following other types implement trait `GraphQLObject`: + <&T as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + > + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_pretty.rs b/tests/codegen/fail/union/enum_same_type_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_same_type_pretty.rs rename to tests/codegen/fail/union/enum_same_type_pretty.rs diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_pretty.stderr b/tests/codegen/fail/union/enum_same_type_pretty.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_same_type_pretty.stderr rename to tests/codegen/fail/union/enum_same_type_pretty.stderr diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.rs b/tests/codegen/fail/union/enum_same_type_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_same_type_ugly.rs rename to tests/codegen/fail/union/enum_same_type_ugly.rs diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr b/tests/codegen/fail/union/enum_same_type_ugly.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr rename to tests/codegen/fail/union/enum_same_type_ugly.stderr diff --git a/integration_tests/codegen_fail/fail/union/enum_wrong_variant_field.rs b/tests/codegen/fail/union/enum_wrong_variant_field.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_wrong_variant_field.rs rename to tests/codegen/fail/union/enum_wrong_variant_field.rs diff --git a/integration_tests/codegen_fail/fail/union/enum_wrong_variant_field.stderr b/tests/codegen/fail/union/enum_wrong_variant_field.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/enum_wrong_variant_field.stderr rename to tests/codegen/fail/union/enum_wrong_variant_field.stderr diff --git a/integration_tests/codegen_fail/fail/union/struct_name_double_underscored.rs b/tests/codegen/fail/union/struct_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_name_double_underscored.rs rename to tests/codegen/fail/union/struct_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/union/struct_name_double_underscored.stderr b/tests/codegen/fail/union/struct_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_name_double_underscored.stderr rename to tests/codegen/fail/union/struct_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/union/struct_no_fields.rs b/tests/codegen/fail/union/struct_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_no_fields.rs rename to tests/codegen/fail/union/struct_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/union/struct_no_fields.stderr b/tests/codegen/fail/union/struct_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_no_fields.stderr rename to tests/codegen/fail/union/struct_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.rs b/tests/codegen/fail/union/struct_non_object_variant.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_non_object_variant.rs rename to tests/codegen/fail/union/struct_non_object_variant.rs diff --git a/tests/codegen/fail/union/struct_non_object_variant.stderr b/tests/codegen/fail/union/struct_non_object_variant.stderr new file mode 100644 index 000000000..cd9090288 --- /dev/null +++ b/tests/codegen/fail/union/struct_non_object_variant.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied + --> fail/union/struct_non_object_variant.rs:9:10 + | +9 | #[derive(GraphQLUnion)] + | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` + | + = help: the following other types implement trait `GraphQLObject`: + <&T as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + > + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.rs b/tests/codegen/fail/union/struct_same_type_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_same_type_pretty.rs rename to tests/codegen/fail/union/struct_same_type_pretty.rs diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr b/tests/codegen/fail/union/struct_same_type_pretty.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_same_type_pretty.stderr rename to tests/codegen/fail/union/struct_same_type_pretty.stderr diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.rs b/tests/codegen/fail/union/struct_same_type_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_same_type_ugly.rs rename to tests/codegen/fail/union/struct_same_type_ugly.rs diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr b/tests/codegen/fail/union/struct_same_type_ugly.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr rename to tests/codegen/fail/union/struct_same_type_ugly.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_fail_infer_context.rs b/tests/codegen/fail/union/trait_fail_infer_context.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_fail_infer_context.rs rename to tests/codegen/fail/union/trait_fail_infer_context.rs diff --git a/tests/codegen/fail/union/trait_fail_infer_context.stderr b/tests/codegen/fail/union/trait_fail_infer_context.stderr new file mode 100644 index 000000000..ab1d30718 --- /dev/null +++ b/tests/codegen/fail/union/trait_fail_infer_context.stderr @@ -0,0 +1,28 @@ +error[E0277]: the trait bound `CustomContext: FromContext` is not satisfied + --> fail/union/trait_fail_infer_context.rs:3:1 + | +3 | #[graphql_union] + | ^^^^^^^^^^^^^^^^ the trait `FromContext` is not implemented for `CustomContext` + | + = help: the following other types implement trait `FromContext`: + <() as FromContext> + > + = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> fail/union/trait_fail_infer_context.rs:3:1 + | +3 | #[graphql_union] + | ^^^^^^^^^^^^^^^^ + | | + | expected struct `CustomContext`, found struct `SubContext` + | arguments to this function are incorrect + | + = note: expected reference `&CustomContext` + found reference `&SubContext` +note: associated function defined here + --> $WORKSPACE/juniper/src/executor/mod.rs + | + | fn into(self, ctx: &'a C) -> FieldResult, S>; + | ^^^^ + = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/trait_method_conflicts_with_external_resolver_fn.rs b/tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_method_conflicts_with_external_resolver_fn.rs rename to tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr b/tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr rename to tests/codegen/fail/union/trait_method_conflicts_with_external_resolver_fn.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_name_double_underscored.rs b/tests/codegen/fail/union/trait_name_double_underscored.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_name_double_underscored.rs rename to tests/codegen/fail/union/trait_name_double_underscored.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_name_double_underscored.stderr b/tests/codegen/fail/union/trait_name_double_underscored.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_name_double_underscored.stderr rename to tests/codegen/fail/union/trait_name_double_underscored.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_no_fields.rs b/tests/codegen/fail/union/trait_no_fields.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_no_fields.rs rename to tests/codegen/fail/union/trait_no_fields.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_no_fields.stderr b/tests/codegen/fail/union/trait_no_fields.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_no_fields.stderr rename to tests/codegen/fail/union/trait_no_fields.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.rs b/tests/codegen/fail/union/trait_non_object_variant.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_non_object_variant.rs rename to tests/codegen/fail/union/trait_non_object_variant.rs diff --git a/tests/codegen/fail/union/trait_non_object_variant.stderr b/tests/codegen/fail/union/trait_non_object_variant.stderr new file mode 100644 index 000000000..4783de9f2 --- /dev/null +++ b/tests/codegen/fail/union/trait_non_object_variant.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied + --> fail/union/trait_non_object_variant.rs:9:1 + | +9 | #[graphql_union] + | ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` + | + = help: the following other types implement trait `GraphQLObject`: + <&T as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + > + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + as GraphQLObject> + = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_pretty.rs b/tests/codegen/fail/union/trait_same_type_pretty.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_same_type_pretty.rs rename to tests/codegen/fail/union/trait_same_type_pretty.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_pretty.stderr b/tests/codegen/fail/union/trait_same_type_pretty.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_same_type_pretty.stderr rename to tests/codegen/fail/union/trait_same_type_pretty.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.rs b/tests/codegen/fail/union/trait_same_type_ugly.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_same_type_ugly.rs rename to tests/codegen/fail/union/trait_same_type_ugly.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr b/tests/codegen/fail/union/trait_same_type_ugly.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr rename to tests/codegen/fail/union/trait_same_type_ugly.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_with_attr_on_method.rs b/tests/codegen/fail/union/trait_with_attr_on_method.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_with_attr_on_method.rs rename to tests/codegen/fail/union/trait_with_attr_on_method.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_with_attr_on_method.stderr b/tests/codegen/fail/union/trait_with_attr_on_method.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_with_attr_on_method.stderr rename to tests/codegen/fail/union/trait_with_attr_on_method.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_wrong_method_input_args.rs b/tests/codegen/fail/union/trait_wrong_method_input_args.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_wrong_method_input_args.rs rename to tests/codegen/fail/union/trait_wrong_method_input_args.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_wrong_method_input_args.stderr b/tests/codegen/fail/union/trait_wrong_method_input_args.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_wrong_method_input_args.stderr rename to tests/codegen/fail/union/trait_wrong_method_input_args.stderr diff --git a/integration_tests/codegen_fail/fail/union/trait_wrong_method_return_type.rs b/tests/codegen/fail/union/trait_wrong_method_return_type.rs similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_wrong_method_return_type.rs rename to tests/codegen/fail/union/trait_wrong_method_return_type.rs diff --git a/integration_tests/codegen_fail/fail/union/trait_wrong_method_return_type.stderr b/tests/codegen/fail/union/trait_wrong_method_return_type.stderr similarity index 100% rename from integration_tests/codegen_fail/fail/union/trait_wrong_method_return_type.stderr rename to tests/codegen/fail/union/trait_wrong_method_return_type.stderr diff --git a/integration_tests/codegen_fail/src/lib.rs b/tests/codegen/src/lib.rs similarity index 100% rename from integration_tests/codegen_fail/src/lib.rs rename to tests/codegen/src/lib.rs diff --git a/integration_tests/juniper_tests/Cargo.toml b/tests/integration/Cargo.toml similarity index 74% rename from integration_tests/juniper_tests/Cargo.toml rename to tests/integration/Cargo.toml index d037598b5..2b2c3cc70 100644 --- a/integration_tests/juniper_tests/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "juniper_tests" -version = "0.1.0" +name = "juniper_integration_tests" +version = "0.0.0" edition = "2018" publish = false @@ -13,7 +13,7 @@ juniper_subscriptions = { path = "../../juniper_subscriptions" } [dev-dependencies] async-trait = "0.1.39" +fnv = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -fnv = "1.0" -tokio = { version = "1", features = ["rt", "macros", "time"] } +tokio = { version = "1.0", features = ["rt", "macros", "time"] } diff --git a/integration_tests/juniper_tests/src/arc_fields.rs b/tests/integration/src/arc_fields.rs similarity index 100% rename from integration_tests/juniper_tests/src/arc_fields.rs rename to tests/integration/src/arc_fields.rs diff --git a/integration_tests/juniper_tests/src/array.rs b/tests/integration/src/array.rs similarity index 100% rename from integration_tests/juniper_tests/src/array.rs rename to tests/integration/src/array.rs diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/tests/integration/src/codegen/derive_enum.rs similarity index 90% rename from integration_tests/juniper_tests/src/codegen/derive_enum.rs rename to tests/integration/src/codegen/derive_enum.rs index e3b5c7699..acaa49a8a 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/tests/integration/src/codegen/derive_enum.rs @@ -65,7 +65,7 @@ fn test_derived_enum() { ); // Ensure validity of meta info. - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = SomeEnum::meta(&(), &mut registry); assert_eq!(meta.name(), Some("Some")); @@ -100,21 +100,21 @@ fn test_derived_enum() { #[test] fn test_doc_comment() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = DocEnum::meta(&(), &mut registry); assert_eq!(meta.description(), Some("Enum doc.")); } #[test] fn test_multi_doc_comment() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = MultiDocEnum::meta(&(), &mut registry); assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4.")); } #[test] fn test_doc_comment_override() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = OverrideDocEnum::meta(&(), &mut registry); assert_eq!(meta.description(), Some("enum override")); } diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/tests/integration/src/codegen/derive_input_object.rs similarity index 93% rename from integration_tests/juniper_tests/src/codegen/derive_input_object.rs rename to tests/integration/src/codegen/derive_input_object.rs index f24f7bd59..f85b042ee 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/tests/integration/src/codegen/derive_input_object.rs @@ -114,7 +114,7 @@ fn test_derived_input_object() { ); // Validate meta info. - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = Input::meta(&(), &mut registry); assert_eq!(meta.name(), Some("MyInput")); assert_eq!(meta.description(), Some("input descr")); @@ -171,21 +171,21 @@ fn test_derived_input_object() { #[test] fn test_doc_comment() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = DocComment::meta(&(), &mut registry); assert_eq!(meta.description(), Some("Object comment.")); } #[test] fn test_multi_doc_comment() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = MultiDocComment::meta(&(), &mut registry); assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4.")); } #[test] fn test_doc_comment_override() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); + let mut registry: Registry<'_> = Registry::new(FnvHashMap::default()); let meta = OverrideDocComment::meta(&(), &mut registry); assert_eq!(meta.description(), Some("obj override")); } diff --git a/integration_tests/juniper_tests/src/codegen/derive_object_with_raw_idents.rs b/tests/integration/src/codegen/derive_object_with_raw_idents.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/derive_object_with_raw_idents.rs rename to tests/integration/src/codegen/derive_object_with_raw_idents.rs diff --git a/tests/integration/src/codegen/enum_derive.rs b/tests/integration/src/codegen/enum_derive.rs new file mode 100644 index 000000000..8c3731d6b --- /dev/null +++ b/tests/integration/src/codegen/enum_derive.rs @@ -0,0 +1,932 @@ +//! Tests for `#[derive(GraphQLEnum)]` macro. + +use juniper::{ + execute, graphql_object, graphql_value, graphql_vars, parser::SourcePosition, + DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, ScalarValue, +}; + +use crate::util::{schema, schema_with_scalar}; + +mod trivial { + use super::*; + + #[derive(GraphQLEnum)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_enum() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "ENUM"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "HUMAN", "description": null}, + {"name": "DROID", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod ignored_variant { + use super::*; + + #[derive(GraphQLEnum)] + enum Character { + Human, + #[allow(dead_code)] + #[graphql(ignore)] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + + fn droid() -> Character { + Character::Droid + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn err_on_droid() { + const DOC: &str = r#"{ + droid + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!(null), + vec![ExecutionError::new( + SourcePosition::new(14, 1, 12), + &["droid"], + FieldError::from("Unable to resolve skipped enum variant"), + )], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "HUMAN", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod ignored_generic_variant { + use super::*; + + #[derive(GraphQLEnum)] + enum Character { + Human, + Droid, + #[allow(dead_code)] + #[graphql(ignore)] + Ignored(T), + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character<()>) -> Character<()> { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "HUMAN", "description": null}, + {"name": "DROID", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + /// Character doc. + #[derive(GraphQLEnum)] + enum Character { + /// Human doc. + Human, + + /// Droid doc. + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_enum() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "ENUM"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "HUMAN", + "description": "Human doc.", + "isDeprecated": false, + "deprecationReason": null, + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": false, + "deprecationReason": null, + }, + ]}}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + #![allow(deprecated)] + + use super::*; + + /// Character doc. + #[derive(GraphQLEnum)] + enum Character { + /// Human doc. + #[deprecated] + Human, + + /// Droid doc. + #[deprecated(note = "Reason")] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"enumValues": []}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values_with_deprecated() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "HUMAN", + "description": "Human doc.", + "isDeprecated": true, + "deprecationReason": null, + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": true, + "deprecationReason": "Reason", + }, + ]}}), + vec![], + )), + ); + } +} + +mod deprecation_from_graphql_attr { + #![allow(deprecated)] + + use super::*; + + /// Character doc. + #[derive(GraphQLEnum)] + enum Character { + /// Human doc. + #[graphql(deprecated)] + Human, + + /// Droid doc. + #[graphql(deprecated = "Reason")] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn has_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"enumValues": []}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values_with_deprecated() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "HUMAN", + "description": "Human doc.", + "isDeprecated": true, + "deprecationReason": null, + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": true, + "deprecationReason": "Reason", + }, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + #![allow(deprecated)] + + use super::*; + + /// Doc comment. + #[derive(GraphQLEnum)] + #[graphql(name = "MyCharacter", desc = "Character doc.")] + enum Character { + /// Human doc. + #[graphql(name = "MY_HUMAN", desc = "My human doc.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + Human, + + /// Droid doc. + #[graphql(deprecated = "Reason")] + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "MyCharacter") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"description": "Character doc."}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "MyCharacter") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"enumValues": []}}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values_with_deprecated() { + const DOC: &str = r#"{ + __type(name: "MyCharacter") { + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + { + "name": "MY_HUMAN", + "description": "My human doc.", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, + { + "name": "DROID", + "description": "Droid doc.", + "isDeprecated": true, + "deprecationReason": "Reason", + }, + ]}}), + vec![], + )), + ); + } +} + +mod renamed_all_fields { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(rename_all = "none")] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: Human) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "Human"}), vec![])), + ); + } + + #[tokio::test] + async fn has_enum_values() { + const DOC: &str = r#"{ + __type(name: "Character") { + enumValues { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"enumValues": [ + {"name": "Human", "description": null}, + {"name": "Droid", "description": null}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = DefaultScalarValue)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = MyScalarValue)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod explicit_generic_scalar { + + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = S)] + enum Character { + Human, + Droid, + #[allow(dead_code)] + #[graphql(ignore)] + Scalar(S), + } + + struct QueryRoot; + + #[graphql_object(scalar = S: ScalarValue)] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLEnum)] + #[graphql(scalar = S: ScalarValue + Clone)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn pass_as_is(character: Character) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} + +mod explicit_custom_context { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + #[derive(GraphQLEnum)] + #[graphql(context = CustomContext)] + enum Character { + Human, + Droid, + } + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn pass_as_is(character: Character, _ctx: &CustomContext) -> Character { + character + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + passAsIs(character: HUMAN) + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext("ctx".into()); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, + Ok((graphql_value!({"passAsIs": "HUMAN"}), vec![])), + ); + } +} diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs b/tests/integration/src/codegen/interface_attr_struct.rs similarity index 97% rename from integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs rename to tests/integration/src/codegen/interface_attr_struct.rs index 190ae87e7..3fc521575 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_struct.rs +++ b/tests/integration/src/codegen/interface_attr_struct.rs @@ -574,7 +574,10 @@ mod trivial_async { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -809,7 +812,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -865,7 +871,10 @@ mod fallible_field { "kind": "INTERFACE", "fields": [{ "name": "id", - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + "type": { + "kind": "NON_NULL", + "ofType": {"name": "String"}, + }, }], }}), vec![], @@ -948,7 +957,10 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -970,7 +982,10 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1279,7 +1294,11 @@ mod explicit_name_description_and_deprecation { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + graphql_value!({"character": { + "myId": "human-32", + "a": "a", + "b": "b", + }}), vec![], )), ); @@ -3050,3 +3069,44 @@ mod branching_inheritance { ); } } + +mod preserves_visibility { + use super::*; + + #[allow(dead_code)] + type Foo = self::inner::CharacterValue; + + pub(crate) mod inner { + use super::*; + + #[graphql_interface(for = Human)] + pub(crate) struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + pub(crate) struct Human { + id: String, + home_planet: String, + } + } +} + +mod has_no_missing_docs { + #![deny(missing_docs)] + + use super::*; + + #[graphql_interface(for = Human)] + pub struct Character { + pub id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + pub struct Human { + id: String, + home_planet: String, + } +} diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs b/tests/integration/src/codegen/interface_attr_trait.rs similarity index 94% rename from integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs rename to tests/integration/src/codegen/interface_attr_trait.rs index f47fc4ccf..badeb7b94 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr_trait.rs +++ b/tests/integration/src/codegen/interface_attr_trait.rs @@ -551,7 +551,10 @@ mod trivial_async { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -573,7 +576,10 @@ mod trivial_async { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -783,7 +789,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -805,7 +814,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -861,7 +873,10 @@ mod fallible_field { "kind": "INTERFACE", "fields": [{ "name": "id", - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + "type": { + "kind": "NON_NULL", + "ofType": {"name": "String"}, + }, }], }}), vec![], @@ -963,7 +978,10 @@ mod generic { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1580,7 +1598,11 @@ mod explicit_name_description_and_deprecation { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"myId": "human-32", "a": "a", "b": "b"}}), + graphql_value!({"character": { + "myId": "human-32", + "a": "a", + "b": "b", + }}), vec![], )), ); @@ -1861,7 +1883,10 @@ mod explicit_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -1883,7 +1908,10 @@ mod explicit_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -1985,7 +2013,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2007,7 +2038,10 @@ mod custom_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2107,7 +2141,10 @@ mod explicit_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2129,7 +2166,10 @@ mod explicit_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2229,7 +2269,10 @@ mod bounded_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2251,7 +2294,10 @@ mod bounded_generic_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2384,7 +2430,10 @@ mod explicit_custom_context { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2406,7 +2455,10 @@ mod explicit_custom_context { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2537,7 +2589,10 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"humanId": "in-ctx", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "in-ctx", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2560,7 +2615,10 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"droidId": "in-droid", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "in-droid", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2694,7 +2752,10 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "humanId", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "humanId", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2716,7 +2777,10 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droidId", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droidId", + "primaryFunction": "run", + }}), vec![], )), ); @@ -2738,7 +2802,10 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"id": "id", "info": expected_info}}), + graphql_value!({"character": { + "id": "id", + "info": expected_info, + }}), vec![], )), ); @@ -2825,7 +2892,10 @@ mod ignored_method { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2940,7 +3010,10 @@ mod field_return_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2962,7 +3035,10 @@ mod field_return_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -3245,7 +3321,10 @@ mod nullable_argument_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -3267,7 +3346,10 @@ mod nullable_argument_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -3826,3 +3908,44 @@ mod branching_inheritance { ); } } + +mod preserves_visibility { + use super::*; + + #[allow(dead_code)] + type Foo = self::inner::CharacterValue; + + pub(crate) mod inner { + use super::*; + + #[graphql_interface(for = Human)] + pub(crate) trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + pub(crate) struct Human { + id: String, + home_planet: String, + } + } +} + +mod has_no_missing_docs { + #![deny(missing_docs)] + + use super::*; + + #[graphql_interface(for = Human)] + pub trait Character { + fn id(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + pub struct Human { + id: String, + home_planet: String, + } +} diff --git a/integration_tests/juniper_tests/src/codegen/interface_derive.rs b/tests/integration/src/codegen/interface_derive.rs similarity index 98% rename from integration_tests/juniper_tests/src/codegen/interface_derive.rs rename to tests/integration/src/codegen/interface_derive.rs index a7884ef60..6e68f4bd3 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_derive.rs +++ b/tests/integration/src/codegen/interface_derive.rs @@ -878,7 +878,10 @@ mod fallible_field { "kind": "INTERFACE", "fields": [{ "name": "id", - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + "type": { + "kind": "NON_NULL", + "ofType": {"name": "String"}, + }, }], }}), vec![], @@ -1571,7 +1574,10 @@ mod explicit_scalar { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -2806,22 +2812,9 @@ mod branching_inheritance { use super::*; #[derive(GraphQLInterface)] - #[graphql(for = [HumanValue, DroidValue, Luke, R2D2])] - struct Node { - id: ID, - } - - #[derive(GraphQLInterface)] - #[graphql(for = [HumanConnection, DroidConnection])] - struct Connection { - nodes: Vec, - } - - #[derive(GraphQLInterface)] - #[graphql(impl = NodeValue, for = Luke)] - struct Human { - id: ID, - home_planet: String, + #[graphql(for = Human)] + pub struct Character { + pub id: String, } #[derive(GraphQLObject)] @@ -3089,3 +3082,46 @@ mod branching_inheritance { ); } } + +mod preserves_visibility { + use super::*; + + #[allow(dead_code)] + type Foo = self::inner::CharacterValue; + + pub(crate) mod inner { + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + pub(crate) struct Character { + id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + pub(crate) struct Human { + id: String, + home_planet: String, + } + } +} + +mod has_no_missing_docs { + #![deny(missing_docs)] + + use super::*; + + #[derive(GraphQLInterface)] + #[graphql(for = Human)] + pub struct Character { + pub id: String, + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + pub struct Human { + id: String, + home_planet: String, + } +} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/tests/integration/src/codegen/mod.rs similarity index 95% rename from integration_tests/juniper_tests/src/codegen/mod.rs rename to tests/integration/src/codegen/mod.rs index 3a709c66a..830f6e1a3 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/tests/integration/src/codegen/mod.rs @@ -1,6 +1,6 @@ -mod derive_enum; mod derive_input_object; mod derive_object_with_raw_idents; +mod enum_derive; mod interface_attr_struct; mod interface_attr_trait; mod interface_derive; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/tests/integration/src/codegen/object_attr.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/object_attr.rs rename to tests/integration/src/codegen/object_attr.rs diff --git a/integration_tests/juniper_tests/src/codegen/object_derive.rs b/tests/integration/src/codegen/object_derive.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/object_derive.rs rename to tests/integration/src/codegen/object_derive.rs diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs b/tests/integration/src/codegen/scalar_attr_derive_input.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/scalar_attr_derive_input.rs rename to tests/integration/src/codegen/scalar_attr_derive_input.rs diff --git a/integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs b/tests/integration/src/codegen/scalar_attr_type_alias.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/scalar_attr_type_alias.rs rename to tests/integration/src/codegen/scalar_attr_type_alias.rs diff --git a/integration_tests/juniper_tests/src/codegen/scalar_derive.rs b/tests/integration/src/codegen/scalar_derive.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/scalar_derive.rs rename to tests/integration/src/codegen/scalar_derive.rs diff --git a/integration_tests/juniper_tests/src/codegen/scalar_value_derive.rs b/tests/integration/src/codegen/scalar_value_derive.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/scalar_value_derive.rs rename to tests/integration/src/codegen/scalar_value_derive.rs diff --git a/integration_tests/juniper_tests/src/codegen/subscription_attr.rs b/tests/integration/src/codegen/subscription_attr.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/subscription_attr.rs rename to tests/integration/src/codegen/subscription_attr.rs diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/tests/integration/src/codegen/union_attr.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/union_attr.rs rename to tests/integration/src/codegen/union_attr.rs diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/tests/integration/src/codegen/union_derive.rs similarity index 100% rename from integration_tests/juniper_tests/src/codegen/union_derive.rs rename to tests/integration/src/codegen/union_derive.rs diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/tests/integration/src/custom_scalar.rs similarity index 98% rename from integration_tests/juniper_tests/src/custom_scalar.rs rename to tests/integration/src/custom_scalar.rs index 68f4be4c1..7a8c6f94f 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/tests/integration/src/custom_scalar.rs @@ -31,7 +31,7 @@ impl<'de> Deserialize<'de> for MyScalarValue { impl<'de> de::Visitor<'de> for Visitor { type Value = MyScalarValue; - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("a valid input value") } diff --git a/integration_tests/juniper_tests/src/explicit_null.rs b/tests/integration/src/explicit_null.rs similarity index 100% rename from integration_tests/juniper_tests/src/explicit_null.rs rename to tests/integration/src/explicit_null.rs diff --git a/integration_tests/juniper_tests/src/infallible_as_field_error.rs b/tests/integration/src/infallible_as_field_error.rs similarity index 100% rename from integration_tests/juniper_tests/src/infallible_as_field_error.rs rename to tests/integration/src/infallible_as_field_error.rs diff --git a/tests/integration/src/inside_macro.rs b/tests/integration/src/inside_macro.rs new file mode 100644 index 000000000..f94b6f999 --- /dev/null +++ b/tests/integration/src/inside_macro.rs @@ -0,0 +1,39 @@ +//! Checks that `#[graphql_object]` macro correctly expands inside a declarative +//! macro definition. +//! See [#1051](https://github.com/graphql-rust/juniper/pull/1051) and +//! [#1054](https://github.com/graphql-rust/juniper/pull/1054) for details. + +use juniper::{ + graphql_object, graphql_value, graphql_vars, EmptyMutation, EmptySubscription, RootNode, +}; + +macro_rules! impl_id { + ($typename:ident) => { + #[graphql_object] + impl $typename { + fn id(&self) -> i32 { + 42 + } + } + }; +} + +struct Unit; +impl_id!(Unit); + +#[tokio::test] +async fn works() { + let query = r#" + query Unit { + id + } + "#; + + let schema = RootNode::new(Unit, EmptyMutation::new(), EmptySubscription::new()); + let (res, errs) = juniper::execute(query, None, &schema, &graphql_vars! {}, &()) + .await + .unwrap(); + + assert_eq!(errs.len(), 0); + assert_eq!(res, graphql_value!({"id": 42})); +} diff --git a/integration_tests/juniper_tests/src/issue_371.rs b/tests/integration/src/issue_371.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_371.rs rename to tests/integration/src/issue_371.rs diff --git a/integration_tests/juniper_tests/src/issue_372.rs b/tests/integration/src/issue_372.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_372.rs rename to tests/integration/src/issue_372.rs diff --git a/integration_tests/juniper_tests/src/issue_398.rs b/tests/integration/src/issue_398.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_398.rs rename to tests/integration/src/issue_398.rs diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/tests/integration/src/issue_407.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_407.rs rename to tests/integration/src/issue_407.rs diff --git a/integration_tests/juniper_tests/src/issue_500.rs b/tests/integration/src/issue_500.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_500.rs rename to tests/integration/src/issue_500.rs diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/tests/integration/src/issue_798.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_798.rs rename to tests/integration/src/issue_798.rs diff --git a/integration_tests/juniper_tests/src/issue_914.rs b/tests/integration/src/issue_914.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_914.rs rename to tests/integration/src/issue_914.rs diff --git a/integration_tests/juniper_tests/src/issue_922.rs b/tests/integration/src/issue_922.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_922.rs rename to tests/integration/src/issue_922.rs diff --git a/integration_tests/juniper_tests/src/issue_925.rs b/tests/integration/src/issue_925.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_925.rs rename to tests/integration/src/issue_925.rs diff --git a/integration_tests/juniper_tests/src/issue_945.rs b/tests/integration/src/issue_945.rs similarity index 100% rename from integration_tests/juniper_tests/src/issue_945.rs rename to tests/integration/src/issue_945.rs diff --git a/integration_tests/juniper_tests/src/lib.rs b/tests/integration/src/lib.rs similarity index 97% rename from integration_tests/juniper_tests/src/lib.rs rename to tests/integration/src/lib.rs index 2e59d8c30..98019c141 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(rust_2018_idioms)] + #[cfg(test)] mod arc_fields; #[cfg(test)] @@ -11,6 +13,8 @@ mod explicit_null; #[cfg(test)] mod infallible_as_field_error; #[cfg(test)] +mod inside_macro; +#[cfg(test)] mod issue_371; #[cfg(test)] mod issue_372; diff --git a/integration_tests/juniper_tests/src/pre_parse.rs b/tests/integration/src/pre_parse.rs similarity index 100% rename from integration_tests/juniper_tests/src/pre_parse.rs rename to tests/integration/src/pre_parse.rs From bc2cb2034f35a2d933e3661329d5dcc6e3c1349f Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Jun 2022 16:24:21 +0300 Subject: [PATCH 118/122] Corrections --- .../codegen/fail/enum/derive_no_fields.stderr | 10 +++--- .../implementers_duplicate_pretty.stderr | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr diff --git a/tests/codegen/fail/enum/derive_no_fields.stderr b/tests/codegen/fail/enum/derive_no_fields.stderr index 637156392..c61ee3390 100644 --- a/tests/codegen/fail/enum/derive_no_fields.stderr +++ b/tests/codegen/fail/enum/derive_no_fields.stderr @@ -1,7 +1,7 @@ -error: GraphQL enum expects at least one field - --> fail/enum/derive_no_fields.rs:2:1 +error: GraphQL enum expected at least 1 non-ignored variant + --> fail/enum/derive_no_fields.rs:1:10 | -2 | pub enum Test {} - | ^^^^^^^^^^^^^^^^ +1 | #[derive(juniper::GraphQLEnum)] + | ^^^^^^^^^^^^^^^^^^^^ | - = note: https://spec.graphql.org/June2018/#sec-Enums + = note: this error originates in the derive macro `juniper::GraphQLEnum` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr new file mode 100644 index 000000000..13696d8da --- /dev/null +++ b/tests/codegen/fail/interface/trait/implementers_duplicate_pretty.stderr @@ -0,0 +1,35 @@ +error: duplicated attribute argument found + --> fail/interface/trait/implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ + +error[E0412]: cannot find type `CharacterValue` in this scope + --> fail/interface/trait/implementers_duplicate_pretty.rs:4:18 + | +4 | #[graphql(impl = CharacterValue)] + | ^^^^^^^^^^^^^^ not found in this scope + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> fail/interface/trait/implementers_duplicate_pretty.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ referenced constant has errors + | + = note: this error originates in the macro `$crate::const_concat` (in Nightly builds, run with -Z macro-backtrace for more info) From 0240ab4b38fcaa58014a6bf8c32007925936064b Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Jun 2022 07:48:05 +0300 Subject: [PATCH 119/122] Fix integration tests --- .../src/codegen/interface_derive.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/integration/src/codegen/interface_derive.rs b/tests/integration/src/codegen/interface_derive.rs index 6e68f4bd3..17b49c33a 100644 --- a/tests/integration/src/codegen/interface_derive.rs +++ b/tests/integration/src/codegen/interface_derive.rs @@ -2812,9 +2812,22 @@ mod branching_inheritance { use super::*; #[derive(GraphQLInterface)] - #[graphql(for = Human)] - pub struct Character { - pub id: String, + #[graphql(for = [HumanValue, DroidValue, Luke, R2D2])] + struct Node { + id: ID, + } + + #[derive(GraphQLInterface)] + #[graphql(for = [HumanConnection, DroidConnection])] + struct Connection { + nodes: Vec, + } + + #[derive(GraphQLInterface)] + #[graphql(impl = NodeValue, for = Luke)] + struct Human { + id: ID, + home_planet: String, } #[derive(GraphQLObject)] From 2832e3578389073d3a09fa15124e76a788b76a18 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Jun 2022 08:48:59 +0300 Subject: [PATCH 120/122] Corrections --- book/src/types/interfaces.md | 2 -- juniper/CHANGELOG.md | 2 ++ juniper/src/macros/reflect.rs | 2 +- .../rules/possible_fragment_spreads.rs | 2 +- juniper_codegen/src/graphql_interface/attr.rs | 34 ++++++++++++------- .../src/graphql_interface/derive.rs | 17 ++++++---- juniper_codegen/src/graphql_interface/mod.rs | 12 +++---- juniper_codegen/src/lib.rs | 2 -- juniper_codegen/src/util/span_container.rs | 4 --- .../enum/derive_duplicated_variant_names.rs | 8 ----- .../derive_duplicated_variant_names.stderr | 7 ---- .../fail/enum/derive_no_variants.stderr | 7 ---- 12 files changed, 43 insertions(+), 56 deletions(-) delete mode 100644 tests/codegen/fail/enum/derive_duplicated_variant_names.rs delete mode 100644 tests/codegen/fail/enum/derive_duplicated_variant_names.stderr delete mode 100644 tests/codegen/fail/enum/derive_no_variants.stderr diff --git a/book/src/types/interfaces.md b/book/src/types/interfaces.md index 72d15e07c..a56efc957 100644 --- a/book/src/types/interfaces.md +++ b/book/src/types/interfaces.md @@ -146,11 +146,9 @@ Valid "subtypes": - interface implementer instead of an interface itself - `I implements T` in place of a T - `Vec` in place of a `Vec` - - ... - non-null value in place of a nullable: - `T` in place of a `Option` - `Vec` in place of a `Vec>` - - ... Those rules are recursively applied, so `Vec>` is a valid "subtype" of a `Option>>>>`. Also, GraphQL allows implementers to add nullable fields, which aren't present on an original interface. diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 1bff5bcaf..7a5aa27aa 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -31,6 +31,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Forbade default implementations of non-ignored trait methods. - Supported coercion of additional `null`able arguments and return sub-typing on implementer. - Supported `rename_all = ""` attribute argument influencing all its fields and their arguments. ([#971]) + - Supported interfaces implementing other interfaces. ([#1028]) - Split `#[derive(GraphQLScalarValue)]` macro into: - `#[derive(GraphQLScalar)]` for implementing GraphQL scalar: ([#1017]) - Supported generic `ScalarValue`. @@ -94,6 +95,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1017]: /../../pull/1017 [#1025]: /../../pull/1025 [#1026]: /../../pull/1026 +[#1028]: /../../pull/1028 [#1051]: /../../issues/1051 [#1054]: /../../pull/1054 [#1057]: /../../pull/1057 diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 724239866..2453b2b56 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -554,7 +554,7 @@ macro_rules! assert_interfaces_impls { /// Asserts that all `transitive` interfaces that are implemented by `interface` /// are also implemented by `implementor`. See [spec] for more info. /// -/// [spec]: https://spec.graphql.org/October2021/#sel-FAHbhBHCAACGB35P +/// [spec]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P #[macro_export] macro_rules! assert_transitive_implementations { ($scalar: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => { diff --git a/juniper/src/validation/rules/possible_fragment_spreads.rs b/juniper/src/validation/rules/possible_fragment_spreads.rs index cddf5e4e5..e78841d87 100644 --- a/juniper/src/validation/rules/possible_fragment_spreads.rs +++ b/juniper/src/validation/rules/possible_fragment_spreads.rs @@ -49,7 +49,7 @@ where // Even if there is no object type in the overlap of interfaces // implementers, it's ok to spread in case `frag_type` implements // `parent_type`. - // https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B + // https://spec.graphql.org/October2021#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { interface_names, .. }) = frag_type diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c9db92e25..e53c99dda 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -55,7 +55,8 @@ fn expand_on_trait( .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| trait_ident.unraw().to_string()); + .unwrap_or_else(|| trait_ident.unraw().to_string()) + .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -120,18 +121,22 @@ fn expand_on_trait( enum_ident, enum_alias_ident, name, - description: attr.description.as_deref().cloned(), + description: attr.description.map(|d| d.into_inner().into_boxed_str()), context, scalar, fields, implemented_for: attr .implemented_for - .iter() - .map(|c| c.inner().clone()) + .into_iter() + .map(SpanContainer::into_inner) + .collect(), + implements: attr + .implements + .into_iter() + .map(SpanContainer::into_inner) .collect(), - implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), suppress_dead_code: None, - src_intra_doc_link: format!("trait@{}", trait_ident), + src_intra_doc_link: format!("trait@{}", trait_ident).into_boxed_str(), }; Ok(quote! { @@ -243,7 +248,8 @@ fn expand_on_derive_input( .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| struct_ident.unraw().to_string()); + .unwrap_or_else(|| struct_ident.unraw().to_string()) + .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -302,18 +308,22 @@ fn expand_on_derive_input( enum_ident, enum_alias_ident, name, - description: attr.description.as_deref().cloned(), + description: attr.description.map(|d| d.into_inner().into_boxed_str()), context, scalar, fields, implemented_for: attr .implemented_for - .iter() - .map(|c| c.inner().clone()) + .into_iter() + .map(SpanContainer::into_inner) + .collect(), + implements: attr + .implements + .into_iter() + .map(SpanContainer::into_inner) .collect(), - implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), suppress_dead_code: None, - src_intra_doc_link: format!("struct@{}", struct_ident), + src_intra_doc_link: format!("struct@{}", struct_ident).into_boxed_str(), }; Ok(quote! { diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index 1677c221c..949a18427 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -33,7 +33,8 @@ pub fn expand(input: TokenStream) -> syn::Result { .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| struct_ident.unraw().to_string()); + .unwrap_or_else(|| struct_ident.unraw().to_string()) + .into_boxed_str(); if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -93,18 +94,22 @@ pub fn expand(input: TokenStream) -> syn::Result { enum_ident, enum_alias_ident, name, - description: attr.description.as_deref().cloned(), + description: attr.description.map(|d| d.into_inner().into_boxed_str()), context, scalar, fields, implemented_for: attr .implemented_for - .iter() - .map(|c| c.inner().clone()) + .into_iter() + .map(SpanContainer::into_inner) + .collect(), + implements: attr + .implements + .into_iter() + .map(SpanContainer::into_inner) .collect(), - implements: attr.implements.iter().map(|c| c.inner().clone()).collect(), suppress_dead_code: Some((ast.ident.clone(), data.fields.clone())), - src_intra_doc_link: format!("struct@{}", struct_ident), + src_intra_doc_link: format!("struct@{}", struct_ident).into_boxed_str(), } .into_token_stream()) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 8abbef8ca..16084e83d 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -306,12 +306,12 @@ struct Definition { /// Name of this [GraphQL interface][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - name: String, + name: Box, /// Description of this [GraphQL interface][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - description: Option, + description: Option>, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL interface][1]. @@ -357,7 +357,7 @@ struct Definition { /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - src_intra_doc_link: String, + src_intra_doc_link: Box, } impl ToTokens for Definition { @@ -643,7 +643,7 @@ impl Definition { #const_scalar, #ty#ty_const_generics, #const_impl_for, - #(#const_implements),* + #( #const_implements ),* ); } }); @@ -665,7 +665,7 @@ impl Definition { ::juniper::assert_implemented_for!( #const_scalar, #ty#ty_const_generics, - #(#const_implements),* + #( #const_implements ),* ); #( #transitive_checks )* } @@ -939,7 +939,7 @@ impl Definition { #where_clause { const NAMES: ::juniper::macros::reflect::Types = - &[#(<#implements as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*]; + &[#( <#implements as ::juniper::macros::reflect::BaseType<#scalar>>::NAME ),*]; } #[automatically_derived] diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index e1207f080..e0b095e70 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -927,11 +927,9 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// - interface implementer instead of an interface itself /// - `I implements T` in place of a T /// - `Vec` in place of a `Vec` -/// - ... /// - non-null value in place of a nullable: /// - `T` in place of a `Option` /// - `Vec` in place of a `Vec>` -/// - ... /// Those rules are recursively applied, so `Vec>` is a /// valid "subtype" of a `Option>>>>`. /// diff --git a/juniper_codegen/src/util/span_container.rs b/juniper_codegen/src/util/span_container.rs index 370f17a74..8b4c6b58f 100644 --- a/juniper_codegen/src/util/span_container.rs +++ b/juniper_codegen/src/util/span_container.rs @@ -45,10 +45,6 @@ impl SpanContainer { self.val } - pub fn inner(&self) -> &T { - &self.val - } - pub fn map U>(self, f: F) -> SpanContainer { SpanContainer { expr: self.expr, diff --git a/tests/codegen/fail/enum/derive_duplicated_variant_names.rs b/tests/codegen/fail/enum/derive_duplicated_variant_names.rs deleted file mode 100644 index b68dca16e..000000000 --- a/tests/codegen/fail/enum/derive_duplicated_variant_names.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[derive(juniper::GraphQLEnum)] -enum Test { - Test, - #[graphql(name = "TEST")] - Test1, -} - -fn main() {} diff --git a/tests/codegen/fail/enum/derive_duplicated_variant_names.stderr b/tests/codegen/fail/enum/derive_duplicated_variant_names.stderr deleted file mode 100644 index c323b4cc2..000000000 --- a/tests/codegen/fail/enum/derive_duplicated_variant_names.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: GraphQL enum expected all enum variants to have unique names - --> fail/enum/derive_duplicated_variant_names.rs:3:5 - | -3 | / Test, -4 | | #[graphql(name = "TEST")] -5 | | Test1, - | |__________^ diff --git a/tests/codegen/fail/enum/derive_no_variants.stderr b/tests/codegen/fail/enum/derive_no_variants.stderr deleted file mode 100644 index 165a52f65..000000000 --- a/tests/codegen/fail/enum/derive_no_variants.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error: GraphQL enum expected at least 1 non-ignored variant - --> fail/enum/derive_no_variants.rs:1:10 - | -1 | #[derive(juniper::GraphQLEnum)] - | ^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the derive macro `juniper::GraphQLEnum` (in Nightly builds, run with -Z macro-backtrace for more info) From f6bae6cbb7d1c05b76889f89c11e9f0d9d33479f Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Jun 2022 09:20:33 +0300 Subject: [PATCH 121/122] Fix codegen test --- .../codegen/fail/input-object/derive_incompatible_object.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/codegen/fail/input-object/derive_incompatible_object.stderr b/tests/codegen/fail/input-object/derive_incompatible_object.stderr index e8e2a3ccf..6767eead2 100644 --- a/tests/codegen/fail/input-object/derive_incompatible_object.stderr +++ b/tests/codegen/fail/input-object/derive_incompatible_object.stderr @@ -60,7 +60,7 @@ error[E0599]: no method named `to_input_value` found for struct `ObjectA` in the --> fail/input-object/derive_incompatible_object.rs:6:10 | 2 | struct ObjectA { - | -------------- method `to_input_value` not found for this + | ------- method `to_input_value` not found for this struct ... 6 | #[derive(juniper::GraphQLInputObject)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ObjectA` From 080f4f095fac3f0574228d513cf9eeeae0e2d898 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 27 Jun 2022 13:21:04 +0200 Subject: [PATCH 122/122] Corrections --- book/src/types/interfaces.md | 26 ++++++++------- juniper/src/macros/reflect.rs | 8 ++--- juniper/src/schema/meta.rs | 2 +- .../rules/possible_fragment_spreads.rs | 4 +-- juniper_codegen/src/graphql_interface/mod.rs | 27 +++++++-------- juniper_codegen/src/lib.rs | 33 ++++++++++--------- .../src/codegen/interface_attr_struct.rs | 14 ++++---- .../src/codegen/interface_attr_trait.rs | 4 +-- .../src/codegen/interface_derive.rs | 14 ++++---- 9 files changed, 69 insertions(+), 63 deletions(-) diff --git a/book/src/types/interfaces.md b/book/src/types/interfaces.md index a56efc957..949344f9f 100644 --- a/book/src/types/interfaces.md +++ b/book/src/types/interfaces.md @@ -76,6 +76,7 @@ struct Human { # fn main() {} ``` + ### Interfaces implementing other interfaces GraphQL allows implementing interfaces on other interfaces in addition to objects. @@ -138,20 +139,22 @@ struct Character { fn main() {} ``` -### GraphQL subtyping and additional nullable fields -GraphQL allows implementers (both objects and other interfaces) to return "subtypes" instead of an original value. Basically this allows you to impose additional bounds on implementation. +### GraphQL subtyping and additional `null`able fields + +GraphQL allows implementers (both objects and other interfaces) to return "subtypes" instead of an original value. Basically, this allows you to impose additional bounds on the implementation. -Valid "subtypes": -- interface implementer instead of an interface itself - - `I implements T` in place of a T - - `Vec` in place of a `Vec` +Valid "subtypes" are: +- interface implementer instead of an interface itself: + - `I implements T` in place of a `T`; + - `Vec` in place of a `Vec`. - non-null value in place of a nullable: - - `T` in place of a `Option` - - `Vec` in place of a `Vec>` -Those rules are recursively applied, so `Vec>` is a valid "subtype" of a `Option>>>>`. + - `T` in place of a `Option`; + - `Vec` in place of a `Vec>`. -Also, GraphQL allows implementers to add nullable fields, which aren't present on an original interface. +These rules are recursively applied, so `Vec>` is a valid "subtype" of a `Option>>>>`. + +Also, GraphQL allows implementers to add `null`able fields, which aren't present on an original interface. ```rust # extern crate juniper; @@ -194,7 +197,7 @@ impl Luke { fn home_planet(language: Option) -> &'static str { // ^^^^^^^^^^^^^^ - // Notice additional nullable field, which is missing on `Human`. + // Notice additional `null`able field, which is missing on `Human`. // Resolving `...on Human { homePlanet }` will provide `None` for this // argument. match language.as_deref() { @@ -257,6 +260,7 @@ struct Character { # fn main() {} ``` + ### Ignoring trait methods We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index 2453b2b56..a85296b1f 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -551,12 +551,12 @@ macro_rules! assert_interfaces_impls { }; } -/// Asserts that all `transitive` interfaces that are implemented by `interface` -/// are also implemented by `implementor`. See [spec] for more info. +/// Asserts that all [transitive interfaces][0] (the ones implemented by the +/// `$interface`) are also implemented by the `$implementor`. /// -/// [spec]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P +/// [0]: https://spec.graphql.org/October2021#sel-FAHbhBHCAACGB35P #[macro_export] -macro_rules! assert_transitive_implementations { +macro_rules! assert_transitive_impls { ($scalar: ty, $interface: ty, $implementor: ty $(, $transitive: ty)* $(,)?) => { const _: () = { $({ diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 1ac3f0e4e..eecef5905 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -602,7 +602,7 @@ impl<'a, S> InterfaceMeta<'a, S> { self } - /// Set the `interfaces` this [`InterfaceMeta`] interface implements. + /// Sets the `interfaces` this [`InterfaceMeta`] interface implements. /// /// Overwrites any previously set list of interfaces. #[must_use] diff --git a/juniper/src/validation/rules/possible_fragment_spreads.rs b/juniper/src/validation/rules/possible_fragment_spreads.rs index e78841d87..53ef05a34 100644 --- a/juniper/src/validation/rules/possible_fragment_spreads.rs +++ b/juniper/src/validation/rules/possible_fragment_spreads.rs @@ -47,7 +47,7 @@ where .and_then(|s| ctx.schema.concrete_type_by_name(s.item)), ) { // Even if there is no object type in the overlap of interfaces - // implementers, it's ok to spread in case `frag_type` implements + // implementers, it's OK to spread in case `frag_type` implements // `parent_type`. // https://spec.graphql.org/October2021#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { @@ -86,7 +86,7 @@ where self.fragment_types.get(spread.item.name.item), ) { // Even if there is no object type in the overlap of interfaces - // implementers, it's ok to spread in case `frag_type` implements + // implementers, it's OK to spread in case `frag_type` implements // `parent_type`. // https://spec.graphql.org/October2021/#sel-JALVFJNRDABABqDy5B if let MetaType::Interface(InterfaceMeta { diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 16084e83d..700fa3dc3 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -88,11 +88,11 @@ struct Attr { /// [2]: https://spec.graphql.org/June2018/#sec-Objects implemented_for: HashSet>, - /// Explicitly specified [GraphQL interfaces][2] this [interface][1] type - /// implements. + /// Explicitly specified [GraphQL interfaces, implemented][1] by this + /// [GraphQL interface][0]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b implements: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this @@ -303,14 +303,14 @@ struct Definition { /// [`implementers`]: Self::implementers enum_alias_ident: syn::Ident, - /// Name of this [GraphQL interface][1] in GraphQL schema. + /// Name of this [GraphQL interface][0] in GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces name: Box, - /// Description of this [GraphQL interface][1] to put into GraphQL schema. + /// Description of this [GraphQL interface][0] to put into GraphQL schema. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces description: Option>, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with @@ -340,9 +340,10 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces implemented_for: Vec, - /// Specified [GraphQL interfaces][1] this [interface][1] type implements. + /// [GraphQL interfaces implemented][1] by this [GraphQL interface][0]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sel-GAHbhBDABAB_E-0b implements: Vec, /// Unlike `#[graphql_interface]` maro, `#[derive(GraphQLInterface)]` can't @@ -354,9 +355,9 @@ struct Definition { suppress_dead_code: Option<(syn::Ident, syn::Fields)>, /// Intra-doc link to the [`syn::Item`] defining this - /// [GraphQL interface][1]. + /// [GraphQL interface][0]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces src_intra_doc_link: Box, } @@ -639,7 +640,7 @@ impl Definition { .collect::>(); let transitive_checks = const_impl_for.clone().map(|const_impl_for| { quote_spanned! { const_impl_for.span() => - ::juniper::assert_transitive_implementations!( + ::juniper::assert_transitive_impls!( #const_scalar, #ty#ty_const_generics, #const_impl_for, diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index e0b095e70..70c346eed 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -879,7 +879,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// objects. /// /// > __NOTE:__ Every interface has to specify all other interfaces/objects it -/// > implements or implemented for. Missing one of `for = ` or +/// > implements or is implemented for. Missing one of `for = ` or /// > `impl = ` attributes is an understandable compile-time error. /// /// ```rust @@ -917,23 +917,24 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// # GraphQL subtyping and additional nullable fields +/// # GraphQL subtyping and additional `null`able fields /// /// GraphQL allows implementers (both objects and other interfaces) to return -/// "subtypes" instead of an original value. Basically this allows you to impose -/// additional bounds on implementation. -/// -/// Valid "subtypes": -/// - interface implementer instead of an interface itself -/// - `I implements T` in place of a T -/// - `Vec` in place of a `Vec` -/// - non-null value in place of a nullable: -/// - `T` in place of a `Option` -/// - `Vec` in place of a `Vec>` -/// Those rules are recursively applied, so `Vec>` is a +/// "subtypes" instead of an original value. Basically, this allows you to +/// impose additional bounds on the implementation. +/// +/// Valid "subtypes" are: +/// - interface implementer instead of an interface itself: +/// - `I implements T` in place of a `T`; +/// - `Vec` in place of a `Vec`. +/// - non-`null` value in place of a `null`able: +/// - `T` in place of a `Option`; +/// - `Vec` in place of a `Vec>`. +/// +/// These rules are recursively applied, so `Vec>` is a /// valid "subtype" of a `Option>>>>`. /// -/// Also, GraphQL allows implementers to add nullable fields, which aren't +/// Also, GraphQL allows implementers to add `null`able fields, which aren't /// present on an original interface. /// /// ```rust @@ -974,10 +975,10 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// fn id(&self) -> &ID { /// &self.id /// } -/// +/// /// fn home_planet(language: Option) -> &'static str { /// // ^^^^^^^^^^^^^^ -/// // Notice additional nullable field, which is missing on `Human`. +/// // Notice additional `null`able field, which is missing on `Human`. /// // Resolving `...on Human { homePlanet }` will provide `None` for /// // this argument. /// match language.as_deref() { diff --git a/tests/integration/src/codegen/interface_attr_struct.rs b/tests/integration/src/codegen/interface_attr_struct.rs index 3fc521575..8d0e4f58e 100644 --- a/tests/integration/src/codegen/interface_attr_struct.rs +++ b/tests/integration/src/codegen/interface_attr_struct.rs @@ -2539,7 +2539,7 @@ mod nullable_argument_subtyping { } } -mod simple_inheritance { +mod simple_subtyping { use super::*; #[graphql_interface(for = [ResourceValue, Endpoint])] @@ -2786,7 +2786,7 @@ mod simple_inheritance { } } -mod branching_inheritance { +mod branching_subtyping { use super::*; #[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])] @@ -2928,7 +2928,7 @@ mod branching_inheritance { ... on Human { id homePlanet - } + } } } }"#; @@ -2958,7 +2958,7 @@ mod branching_inheritance { id homePlanet father - } + } } } }"#; @@ -2988,7 +2988,7 @@ mod branching_inheritance { nodes { id primaryFunction - } + } } } }"#; @@ -3017,7 +3017,7 @@ mod branching_inheritance { ... on Droid { id primaryFunction - } + } } } }"#; @@ -3047,7 +3047,7 @@ mod branching_inheritance { id primaryFunction charge - } + } } } }"#; diff --git a/tests/integration/src/codegen/interface_attr_trait.rs b/tests/integration/src/codegen/interface_attr_trait.rs index badeb7b94..150820fab 100644 --- a/tests/integration/src/codegen/interface_attr_trait.rs +++ b/tests/integration/src/codegen/interface_attr_trait.rs @@ -3378,7 +3378,7 @@ mod nullable_argument_subtyping { } } -mod simple_inheritance { +mod simple_subtyping { use super::*; #[graphql_interface(for = [ResourceValue, Endpoint])] @@ -3625,7 +3625,7 @@ mod simple_inheritance { } } -mod branching_inheritance { +mod branching_subtyping { use super::*; #[graphql_interface(for = [HumanValue, DroidValue, Luke, R2D2])] diff --git a/tests/integration/src/codegen/interface_derive.rs b/tests/integration/src/codegen/interface_derive.rs index 17b49c33a..bbc6a79ad 100644 --- a/tests/integration/src/codegen/interface_derive.rs +++ b/tests/integration/src/codegen/interface_derive.rs @@ -2559,7 +2559,7 @@ mod nullable_argument_subtyping { } } -mod simple_inheritance { +mod simple_subtyping { use super::*; #[derive(GraphQLInterface)] @@ -2808,7 +2808,7 @@ mod simple_inheritance { } } -mod branching_inheritance { +mod branching_subtyping { use super::*; #[derive(GraphQLInterface)] @@ -2954,7 +2954,7 @@ mod branching_inheritance { ... on Human { id homePlanet - } + } } } }"#; @@ -2984,7 +2984,7 @@ mod branching_inheritance { id homePlanet father - } + } } } }"#; @@ -3014,7 +3014,7 @@ mod branching_inheritance { nodes { id primaryFunction - } + } } } }"#; @@ -3043,7 +3043,7 @@ mod branching_inheritance { ... on Droid { id primaryFunction - } + } } } }"#; @@ -3073,7 +3073,7 @@ mod branching_inheritance { id primaryFunction charge - } + } } } }"#;