From 1790b144c75202b29f2ad70338a55f1827a34ab4 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 22 Jul 2021 13:01:14 +0300 Subject: [PATCH 01/39] Bootstrap --- juniper_codegen/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 38870ff28..f8a13113b 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -143,6 +143,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { } } +// TODO #[proc_macro_error] #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(input: TokenStream) -> TokenStream { @@ -495,7 +496,7 @@ pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream { /// struct UserID(String); /// /// #[juniper::graphql_scalar( -/// // You can rename the type for GraphQL by specifying the name here. +/// // 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. From f845c8eac5675595c9d6eb4b74bc07490fb324b9 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 22 Jul 2021 14:47:01 +0300 Subject: [PATCH 02/39] Impl barebone for objects --- juniper_codegen/src/graphql_object/attr.rs | 6 + juniper_codegen/src/graphql_object/derive.rs | 55 +++++ juniper_codegen/src/graphql_object/mod.rs | 247 +++++++++++++++++++ juniper_codegen/src/lib.rs | 7 +- juniper_codegen/src/result.rs | 8 +- juniper_codegen/src/util/mod.rs | 25 +- 6 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 juniper_codegen/src/graphql_object/attr.rs create mode 100644 juniper_codegen/src/graphql_object/derive.rs create mode 100644 juniper_codegen/src/graphql_object/mod.rs diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs new file mode 100644 index 000000000..b75a36a0b --- /dev/null +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -0,0 +1,6 @@ +//! Code generation for `#[graphql_object]` macro. + +use crate::result::GraphQLScope; + +/// [`GraphQLScope`] of errors for `#[graphql_object]` macro. +const ERR: GraphQLScope = GraphQLScope::ObjectAttr; diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs new file mode 100644 index 000000000..c228f4177 --- /dev/null +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -0,0 +1,55 @@ +//! Code generation for `#[derive(GraphQLObject)]` macro. + +use proc_macro2::TokenStream; +use proc_macro_error::ResultExt as _; +use quote::ToTokens; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; + +use crate::{result::GraphQLScope, util::span_container::SpanContainer}; + +use super::{ObjectDefinition, ObjectMeta}; + +/// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro. +const ERR: GraphQLScope = GraphQLScope::ObjectDerive; + +/// Expands `#[derive(GraphQLObject)]` macro into generated code. +pub fn expand(input: TokenStream) -> syn::Result { + let ast = syn::parse2::(input).unwrap_or_abort(); + + match &ast.data { + Data::Struct(_) => expand_struct(ast), + _ => Err(ERR.custom_error(ast.span(), "can only be derived forstructs")), + } + .map(ToTokens::into_token_stream) +} + +/// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a Rust struct. +fn expand_struct(ast: syn::DeriveInput) -> syn::Result { + let meta = ObjectMeta::from_attrs("graphql", &ast.attrs)?; + + let struct_span = ast.span(); + let struct_ident = ast.ident; + + let name = meta + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| struct_ident.unraw().to_string()); + if !meta.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + meta.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| struct_ident.span()), + ); + } + + Ok(ObjectDefinition { + name, + ty: parse_quote! { #struct_ident }, + generics: ast.generics, + description: meta.description.map(SpanContainer::into_inner), + context: meta.context.map(SpanContainer::into_inner), + scalar: meta.scalar.map(SpanContainer::into_inner), + }) +} diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs new file mode 100644 index 000000000..6c896b7ab --- /dev/null +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -0,0 +1,247 @@ +//! Code generation for [GraphQL object][1]. +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Objects + +pub mod attr; +pub mod derive; + +use std::{convert::TryInto as _, collections::HashSet}; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, + token, +}; + +use crate::{ + common::parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, +}; + +/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_object]`) +/// attribute when generating code for [GraphQL object][1] type. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Objects +#[derive(Debug, Default)] +struct ObjectMeta { + /// Explicitly specified name of [GraphQL object][1] type. + /// + /// If absent, then Rust type name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + name: Option>, + + /// Explicitly specified [description][2] of [GraphQL object][1] type. + /// + /// If absent, then Rust doc comment is used as [description][2], if any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + description: Option>, + + /// Explicitly specified type of `juniper::Context` to use for resolving + /// this [GraphQL object][1] type with. + /// + /// If absent, then unit type `()` is assumed as type of `juniper::Context`. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + context: Option>, + + /// Explicitly specified type of `juniper::ScalarValue` to use for resolving + /// this [GraphQL object][1] type with. + /// + /// If absent, then generated code will be generic over any + /// `juniper::ScalarValue` type, which, in turn, requires all [object][1] + /// fields to be generic over any `juniper::ScalarValue` type too. That's + /// why this type should be specified only if one of the variants implements + /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` + /// type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + scalar: Option>, + + /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] + /// type implements. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces + interfaces: HashSet>, + + /// Explicitly specified [`RenameRule`] for all fields of this + /// [GraphQL object][1] type. + /// + /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + rename_fields: Option>, + + /// Indicator whether the generated code is intended to be used only inside + /// the `juniper` library. + is_internal: bool, +} + +impl Parse for ObjectMeta { + fn parse(input: ParseStream) -> syn::Result { + let mut output = Self::default(); + + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + output + .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::()?; + output + .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::()?; + output + .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::()?; + output + .scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "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(); + output + .interfaces + .replace(SpanContainer::new(ident.span(), Some(iface_span), iface)) + .none_or_else(|_| err::dup_arg(iface_span))?; + } + } + "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + output + .rename_fields + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } + "internal" => { + output.is_internal = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + + Ok(output) + } +} + +impl ObjectMeta { + /// Tries to merge two [`ObjectMeta`]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), + interfaces: try_merge_hashset!(interfaces: self, another => span_joined), + rename_fields: try_merge_opt!(rename_fields: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`ObjectMeta`] from the given multiple `name`d + /// [`syn::Attribute`]s placed on a struct or impl block definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut meta = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if meta.description.is_none() { + meta.description = get_doc_comment(attrs); + } + + Ok(meta) + } +} + +struct ObjectDefinition { + /// Name of this [GraphQL object][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + pub name: String, + + /// Rust type that this [GraphQL object][1] is represented with. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + pub ty: syn::Type, + + /// Generics of the Rust type that this [GraphQL object][1] is implemented + /// for. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + pub generics: syn::Generics, + + /// Description of this [GraphQL object][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + pub description: Option, + + /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` + /// implementation with for this [GraphQL object][1]. + /// + /// If [`None`] then generated code will use unit type `()` as + /// `juniper::Context`. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + pub context: Option, + + /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` + /// implementation with for this [GraphQL object][1]. + /// + /// If [`None`] then generated code will be generic over any + /// `juniper::ScalarValue` type, which, in turn, requires all [object][1] + /// fields to be generic over any `juniper::ScalarValue` type too. That's + /// why this type should be specified only if one of the variants implements + /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` + /// type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + pub scalar: Option, +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index f8a13113b..28ee8e847 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -115,6 +115,7 @@ mod impl_scalar; mod common; mod graphql_interface; +mod graphql_object; mod graphql_union; use proc_macro::TokenStream; @@ -148,7 +149,7 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(input: TokenStream) -> TokenStream { let ast = syn::parse::(input).unwrap(); - let gen = derive_object::build_derive_object(ast, GraphQLScope::DeriveObject); + let gen = derive_object::build_derive_object(ast, GraphQLScope::ObjectDerive); match gen { Ok(gen) => gen.into(), Err(err) => proc_macro_error::abort!(err), @@ -471,7 +472,7 @@ pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from(impl_object::build_object( args, input, - GraphQLScope::ImplObject, + GraphQLScope::ObjectAttr, )) } @@ -545,7 +546,7 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea TokenStream::from(impl_object::build_subscription( args, input, - GraphQLScope::ImplObject, + GraphQLScope::ObjectAttr, )) } diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index 68c878ffa..4a921d394 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -11,22 +11,22 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/"; #[allow(unused_variables)] pub enum GraphQLScope { InterfaceAttr, + ObjectAttr, + ObjectDerive, UnionAttr, UnionDerive, - DeriveObject, DeriveInputObject, DeriveEnum, DeriveScalar, ImplScalar, - ImplObject, } impl GraphQLScope { pub fn spec_section(&self) -> &str { match self { Self::InterfaceAttr => "#sec-Interfaces", + Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects", Self::UnionAttr | Self::UnionDerive => "#sec-Unions", - Self::DeriveObject | Self::ImplObject => "#sec-Objects", Self::DeriveInputObject => "#sec-Input-Objects", Self::DeriveEnum => "#sec-Enums", Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars", @@ -38,8 +38,8 @@ impl fmt::Display for GraphQLScope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let name = match self { Self::InterfaceAttr => "interface", + Self::ObjectAttr | Self::ObjectDerive => "object", Self::UnionAttr | Self::UnionDerive => "union", - Self::DeriveObject | Self::ImplObject => "object", Self::DeriveInputObject => "input object", Self::DeriveEnum => "enum", Self::DeriveScalar | Self::ImplScalar => "scalar", diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 31c173ca3..3542cbabe 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -4,7 +4,7 @@ pub mod duplicate; pub mod parse_impl; pub mod span_container; -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, convert::TryFrom, str::FromStr}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; @@ -328,6 +328,20 @@ impl FromStr for RenameRule { } } +impl TryFrom<&syn::LitStr> for RenameRule { + type Error = syn::Error; + + fn try_from(lit: &syn::LitStr) -> syn::Result { + Self::from_str(&lit.value()).map_err(|_| syn::Error::new(lit.span(), "unknown rename rule")) + } +} + +impl Parse for RenameRule { + fn parse(input: ParseStream) -> syn::Result { + Self::try_from(&input.parse::()?) + } +} + #[derive(Default, Debug)] pub struct ObjectAttributes { pub name: Option>, @@ -400,13 +414,8 @@ impl Parse for ObjectAttributes { output.is_internal = true; } "rename" => { - input.parse::()?; - let val = input.parse::()?; - if let Ok(rename) = RenameRule::from_str(&val.value()) { - output.rename = Some(rename); - } else { - return Err(syn::Error::new(val.span(), "unknown rename rule")); - } + input.parse::()?; + output.rename = Some(input.parse::()?); } _ => { return Err(syn::Error::new(ident.span(), "unknown attribute")); From a7fa5d93c6d69efef905dff3fc97326d13bfb094 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 23 Jul 2021 17:28:53 +0300 Subject: [PATCH 03/39] Impl barebone for objects, vol.2 --- juniper_codegen/src/graphql_object/derive.rs | 6 +- juniper_codegen/src/graphql_object/mod.rs | 61 +++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index c228f4177..45e66ed98 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -7,7 +7,7 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; use crate::{result::GraphQLScope, util::span_container::SpanContainer}; -use super::{ObjectDefinition, ObjectMeta}; +use super::{Definition, ObjectMeta}; /// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro. const ERR: GraphQLScope = GraphQLScope::ObjectDerive; @@ -24,7 +24,7 @@ pub fn expand(input: TokenStream) -> syn::Result { } /// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a Rust struct. -fn expand_struct(ast: syn::DeriveInput) -> syn::Result { +fn expand_struct(ast: syn::DeriveInput) -> syn::Result { let meta = ObjectMeta::from_attrs("graphql", &ast.attrs)?; let struct_span = ast.span(); @@ -44,7 +44,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { ); } - Ok(ObjectDefinition { + Ok(Definition { name, ty: parse_quote! { #struct_ident }, generics: ast.generics, diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 6c896b7ab..a6b2a0168 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -201,7 +201,7 @@ impl ObjectMeta { } } -struct ObjectDefinition { +struct Definition { /// Name of this [GraphQL object][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects @@ -245,3 +245,62 @@ struct ObjectDefinition { /// [1]: https://spec.graphql.org/June2018/#sec-Objects pub scalar: Option, } + +impl Definition { + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL object][1]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let ty = self.ty.ty_tokens(); + + /* + let fields_marks = self.fields.iter().map(|field| { + let arguments_marks = field.arguments.iter().filter_map(|arg| { + let arg_ty = &arg.as_regular()?.ty; + Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) + }); + + let field_ty = &field.ty; + let resolved_ty = quote! { + <#field_ty as ::juniper::IntoResolvable< + '_, #scalar, _, >::Context, + >>::Type + }; + + quote! { + #( #arguments_marks )* + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + }); + + let interface_tys = self.implementers.iter().map(|iface| &iface.ty); + */ + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } + } + } + } +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + into.append_all(&[ + self.impl_output_type_tokens(), + ]); + } +} From 25cedde9bffcc5e236d31dfb8e4c99b376f17c70 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 26 Jul 2021 18:38:59 +0300 Subject: [PATCH 04/39] Impl barebone for objects, vol.3 --- juniper/src/lib.rs | 2 +- juniper/src/types/marker.rs | 52 +- juniper_codegen/src/common/field/arg.rs | 448 ++++++++++++ juniper_codegen/src/common/field/mod.rs | 377 ++++++++++ juniper_codegen/src/common/mod.rs | 1 + juniper_codegen/src/graphql_interface/attr.rs | 123 +--- juniper_codegen/src/graphql_interface/mod.rs | 656 +----------------- juniper_codegen/src/graphql_object/mod.rs | 173 +++-- juniper_codegen/src/graphql_union/mod.rs | 2 +- juniper_codegen/src/result.rs | 5 + 10 files changed, 1017 insertions(+), 822 deletions(-) create mode 100644 juniper_codegen/src/common/field/arg.rs create mode 100644 juniper_codegen/src/common/field/mod.rs diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index c4e9bd305..94fede25c 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -172,7 +172,7 @@ pub use crate::{ types::{ async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind}, - marker::{self, GraphQLInterface, GraphQLUnion}, + marker::{self, GraphQLInterface, GraphQLUnion, GraphQLObject}, nullable::Nullable, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 7c07f4aae..80c2966cb 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -9,26 +9,34 @@ use std::sync::Arc; use crate::{GraphQLType, ScalarValue}; -/// Maker object for GraphQL objects. +/// Maker trait for [GraphQL objects][1]. /// -/// This trait extends the GraphQLType and is only used to mark -/// object. During compile this addition information is required to -/// prevent unwanted structure compiling. If an object requires this -/// trait instead of the GraphQLType, then it explicitly requires an -/// GraphQL objects. Other types (scalars, enums, and input objects) -/// are not allowed. -pub trait GraphQLObjectType: GraphQLType { +/// This trait extends the [`GraphQLType`] and is only used to mark an [object][1]. During +/// compile this addition information is required to prevent unwanted structure compiling. If an +/// object requires this trait instead of the [`GraphQLType`], then it explicitly requires +/// [GraphQL objects][1]. Other types ([scalars][2], [enums][3], [interfaces][4], [input objects][5] +/// and [unions][6]) are not allowed. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Objects +/// [2]: https://spec.graphql.org/June2018/#sec-Scalars +/// [3]: https://spec.graphql.org/June2018/#sec-Enums +/// [4]: https://spec.graphql.org/June2018/#sec-Interfaces +/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects +/// [6]: https://spec.graphql.org/June2018/#sec-Unions +pub trait GraphQLObject: GraphQLType { /// An arbitrary function without meaning. /// - /// May contain compile timed check logic which ensures that types - /// are used correctly according to the GraphQL specification. + /// May contain compile timed check logic which ensures that types are used correctly according + /// to the [GraphQL specification][1]. + /// + /// [1]: https://spec.graphql.org/June2018/ fn mark() {} } -impl<'a, S, T> GraphQLObjectType for &T -where - T: GraphQLObjectType + ?Sized, - S: ScalarValue, +impl<'a, S, T> GraphQLObject for &T + where + T: GraphQLInterface + ?Sized, + S: ScalarValue, { #[inline] fn mark() { @@ -36,10 +44,10 @@ where } } -impl GraphQLObjectType for Box -where - T: GraphQLObjectType + ?Sized, - S: ScalarValue, +impl GraphQLObject for Box + where + T: GraphQLInterface + ?Sized, + S: ScalarValue, { #[inline] fn mark() { @@ -47,10 +55,10 @@ where } } -impl GraphQLObjectType for Arc -where - T: GraphQLObjectType + ?Sized, - S: ScalarValue, +impl GraphQLObject for Arc + where + T: GraphQLInterface + ?Sized, + S: ScalarValue, { #[inline] fn mark() { diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs new file mode 100644 index 000000000..258490540 --- /dev/null +++ b/juniper_codegen/src/common/field/arg.rs @@ -0,0 +1,448 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! of [GraphQL arguments][1]. + +use std::mem; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, + token, +}; + +use crate::{ + common::{ + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, TypeExt as _, + }, + ScalarValueType, + }, + result::GraphQLScope, + util::{filter_attrs, path_eq_single, span_container::SpanContainer, to_camel_case}, +}; + +/// Available metadata (arguments) behind `#[graphql]` attribute placed on a +/// method argument, when generating code for [GraphQL argument][1]. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments +#[derive(Debug, Default)] +pub(crate) struct Attr { + /// Explicitly specified name of a [GraphQL argument][1] represented by this + /// method argument. + /// + /// If [`None`], then `camelCased` Rust argument name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + pub(crate) name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL argument][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + pub(crate) description: Option>, + + /// Explicitly specified [default value][2] of this [GraphQL argument][1]. + /// + /// If the exact default expression is not specified, then the [`Default`] + /// value is used. + /// + /// If [`None`], then this [GraphQL argument][1] is considered as + /// [required][2]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments + pub(crate) default: Option>>, + + /// Explicitly specified marker indicating that this method argument doesn't + /// represent a [GraphQL argument][1], but is a [`Context`] being injected + /// into a [GraphQL field][2] resolving function. + /// + /// If absent, then the method argument still is considered as [`Context`] + /// if it's named `context` or `ctx`. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) context: Option>, + + /// Explicitly specified marker indicating that this method argument doesn't + /// represent a [GraphQL argument][1], but is an [`Executor`] being injected + /// into a [GraphQL field][2] resolving function. + /// + /// If absent, then the method argument still is considered as [`Executor`] + /// if it's named `executor`. + /// + /// [`Executor`]: juniper::Executor + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) executor: Option>, +} + +impl Parse for Attr { + fn parse(input: ParseStream) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse::()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "default" => { + let mut expr = None; + if input.is_next::() { + input.parse::()?; + expr = Some(input.parse::()?); + } else if input.is_next::() { + let inner; + let _ = syn::parenthesized!(inner in input); + expr = Some(inner.parse::()?); + } + out.default + .replace(SpanContainer::new( + ident.span(), + expr.as_ref().map(|e| e.span()), + expr, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ctx" | "context" | "Context" => { + let span = ident.span(); + out.context + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))? + } + "exec" | "executor" => { + let span = ident.span(); + out.executor + .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 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), + default: try_merge_opt!(default: self, another), + context: try_merge_opt!(context: self, another), + executor: try_merge_opt!(executor: self, another), + }) + } + + /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a function argument. + pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if let Some(context) = &attr.context { + if attr.name.is_some() + || attr.description.is_some() + || attr.default.is_some() + || attr.executor.is_some() + { + return Err(syn::Error::new( + context.span(), + "`context` attribute argument is not composable with any other arguments", + )); + } + } + + if let Some(executor) = &attr.executor { + if attr.name.is_some() + || attr.description.is_some() + || attr.default.is_some() + || attr.context.is_some() + { + return Err(syn::Error::new( + executor.span(), + "`executor` attribute argument is not composable with any other arguments", + )); + } + } + + Ok(attr) + } + + /// Checks whether this [`Attr`] doesn't contain arguments related to an + /// [`OnField`] argument. + #[must_use] + fn ensure_no_regular_arguments(&self) -> syn::Result<()> { + if let Some(span) = &self.name { + return Err(Self::err_disallowed(&span, "name")); + } + if let Some(span) = &self.description { + return Err(Self::err_disallowed(&span, "description")); + } + if let Some(span) = &self.default { + return Err(Self::err_disallowed(&span, "default")); + } + Ok(()) + } + + /// Emits "argument is not allowed" [`syn::Error`] for the given `arg` + /// pointing to the given `span`. + #[must_use] + fn err_disallowed(span: &S, arg: &str) -> syn::Error { + syn::Error::new( + span.span(), + format!( + "attribute argument `#[graphql({} = ...)]` is not allowed here", + arg, + ), + ) + } +} + +/// Representation of a [GraphQL field argument][1] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments +#[derive(Debug)] +pub(crate) struct OnField { + /// Rust type that this [GraphQL field argument][1] is represented by. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + pub(crate) ty: syn::Type, + + /// Name of this [GraphQL field argument][2] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + pub(crate) name: String, + + /// [Description][2] of this [GraphQL field argument][1] to put into GraphQL + /// schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + pub(crate) description: Option, + + /// Default value of this [GraphQL field argument][1] in GraphQL schema. + /// + /// If outer [`Option`] is [`None`], then this [argument][1] is a + /// [required][2] one. + /// + /// If inner [`Option`] is [`None`], then the [`Default`] value is used. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments + pub(crate) default: Option>, +} + +/// Possible kinds of Rust method arguments for code generation. +#[derive(Debug)] +pub(crate) enum OnMethod { + /// Regular [GraphQL field argument][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments + Regular(OnField), + + /// [`Context`] passed into a [GraphQL field][2] resolving method. + /// + /// [`Context`]: juniper::Context + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + Context(syn::Type), + + /// [`Executor`] passed into a [GraphQL field][2] resolving method. + /// + /// [`Executor`]: juniper::Executor + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + Executor, +} + +impl OnMethod { + /// Returns this argument as the one [`OnField`], if it represents the one. + #[must_use] + pub(crate) fn as_regular(&self) -> Option<&OnField> { + if let Self::Regular(arg) = self { + Some(arg) + } else { + None + } + } + /// Returns this [`OnField`] argument's name, if it represents the one. + #[must_use] + pub(crate) fn name(&self) -> Option<&str> { + self.as_regular().map(|a| a.name.as_str()) + } + + /// Returns [`syn::Type`] of this [`OnMethod::Context`], if it represents + /// the one. + #[must_use] + pub(crate) fn context_ty(&self) -> Option<&syn::Type> { + if let Self::Context(ty) = self { + Some(ty) + } else { + None + } + } + + /// Returns generated code for the [`marker::IsOutputType::mark`] method, + /// which performs static checks for this argument, if it represents an + /// [`OnField`] one. + /// + /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark + #[must_use] + pub(crate) fn method_mark_tokens(&self, scalar: &ScalarValueType) -> Option { + let ty = &self.as_regular()?.ty; + Some(quote! { + <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); + }) + } + + /// Returns generated code for the [`GraphQLType::meta`] method, which + /// registers this argument in [`Registry`], if it represents an [`OnField`] + /// argument. + /// + /// [`GraphQLType::meta`]: juniper::GraphQLType::meta + /// [`Registry`]: juniper::Registry + #[must_use] + pub(crate) fn method_meta_tokens(&self) -> Option { + let arg = self.as_regular()?; + + let (name, ty) = (&arg.name, &arg.ty); + + let description = arg + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let method = if let Some(val) = &arg.default { + let val = val + .as_ref() + .map(|v| quote! { (#v).into() }) + .unwrap_or_else(|| quote! { <#ty as Default>::default() }); + quote! { .arg_with_default::<#ty>(#name, &#val, info) } + } else { + quote! { .arg::<#ty>(#name, info) } + }; + + Some(quote! { .argument(registry#method#description) }) + } + + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, + /// which provides the value of this [`OnMethod`] argument to be passed into + /// a trait method call. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + #[must_use] + pub(crate) fn method_resolve_field_tokens(&self) -> TokenStream { + match self { + Self::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + let err_text = format!( + "Internal error: missing argument `{}` - validation must have failed", + &name, + ); + quote! { + args.get::<#ty>(#name).expect(#err_text) + } + } + + Self::Context(_) => quote! { + ::juniper::FromContext::from(executor.context()) + }, + + Self::Executor => quote! { &executor }, + } + } + + /// Parses an [`OnMethod`] argument from the given Rust method argument + /// definition. + /// + /// Returns [`None`] if parsing fails and emits parsing errors into the + /// given `scope`. + pub(crate) fn parse(argument: &mut syn::PatType, scope: &GraphQLScope) -> Option { + let orig_attrs = argument.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + argument.attrs = mem::take(&mut argument.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); + + let attr = Attr::from_attrs("graphql", &orig_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.context.is_some() { + return Some(Self::Context(argument.ty.unreferenced().clone())); + } + if attr.executor.is_some() { + return Some(Self::Executor); + } + if let syn::Pat::Ident(name) = &*argument.pat { + let arg = match name.ident.unraw().to_string().as_str() { + "context" | "ctx" => Some(Self::Context(argument.ty.unreferenced().clone())), + "executor" => Some(Self::Executor), + _ => None, + }; + if arg.is_some() { + attr.ensure_no_regular_arguments() + .map_err(|e| scope.error(e).emit()) + .ok()?; + return arg; + } + } + + let name = if let Some(name) = attr.name.as_ref() { + name.as_ref().value() + } else if let syn::Pat::Ident(name) = &*argument.pat { + to_camel_case(&name.ident.unraw().to_string()) + } else { + scope + .custom( + argument.pat.span(), + "method argument should be declared as a single identifier", + ) + .note(String::from( + "use `#[graphql(name = ...)]` attribute to specify custom argument's \ + name without requiring it being a single identifier", + )) + .emit(); + return None; + }; + if name.starts_with("__") { + scope.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| argument.pat.span()), + ); + return None; + } + + Some(Self(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/field/mod.rs b/juniper_codegen/src/common/field/mod.rs new file mode 100644 index 000000000..5dd1bee4a --- /dev/null +++ b/juniper_codegen/src/common/field/mod.rs @@ -0,0 +1,377 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! of [GraphQL fields][1]. + +pub(crate) mod arg; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, + token, +}; + +use crate::{ + common::{ + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + ScalarValueType, + }, + util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, +}; + +pub(crate) use self::arg::{OnField as Argument, OnMethod as MethodArgument}; + +/// Available metadata (arguments) behind `#[graphql]` attribute placed on a +/// [GraphQL field][1] definition. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields +#[derive(Debug, Default)] +pub(crate) struct Attr { + /// Explicitly specified name of this [GraphQL field][1]. + /// + /// If [`None`], then `camelCased` Rust method name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) name: Option>, + + /// Explicitly specified [description][2] of this [GraphQL field][1]. + /// + /// If [`None`], then Rust doc comment is used as the [description][2], if + /// any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + pub(crate) description: Option>, + + /// Explicitly specified [deprecation][2] of this [GraphQL field][1]. + /// + /// If [`None`], then Rust `#[deprecated]` attribute is used as the + /// [deprecation][2], if any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation + pub(crate) deprecated: Option>>, + + /// Explicitly specified marker indicating that this method (or struct + /// field) should be omitted by code generation and not considered as the + /// [GraphQL field][1] definition. + /// + /// [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 { + fn parse(input: ParseStream) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse::()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "deprecated" => { + let mut reason = None; + if input.is_next::() { + input.parse::()?; + reason = Some(input.parse::()?); + } + 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))?, + "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)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl Attr { + /// Tries to merge two [`Attrs`]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), + downcast: try_merge_opt!(downcast: self, another), + }) + } + + /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a [GraphQL field][1] definition. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + 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 let Some(ignore) = &attr.ignore { + if attr.name.is_some() + || attr.description.is_some() + || attr.deprecated.is_some() + || attr.downcast.is_some() + { + return Err(syn::Error::new( + ignore.span(), + "`ignore` attribute argument is not composable with any other arguments", + )); + } + } + + 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(); + sc.map(|desc| syn::LitStr::new(&desc, span)) + }); + } + + 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 field][1] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields +#[derive(Debug)] +pub(crate) struct Definition { + /// Rust type that this [GraphQL field][1] is represented by (method return + /// type or struct field type). + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) ty: syn::Type, + + /// Name of this [GraphQL field][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) name: String, + + /// [Description][2] of this [GraphQL field][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + pub(crate) description: Option, + + /// [Deprecation][2] of this [GraphQL field][1] to put into GraphQL schema. + /// + /// If inner [`Option`] is [`None`], then deprecation has no message + /// attached. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation + pub(crate) deprecated: Option>, + + /// Ident of the Rust method (or struct field) representing this + /// [GraphQL field][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) ident: syn::Ident, + + /// Rust [`MethodArgument`]s required to call the method representing this + /// [GraphQL field][1]. + /// + /// If [`None`] then this [GraphQL field][1] is represented by a struct + /// field. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) arguments: Option>, + + /// Indicator whether this [GraphQL field][1] should be resolved + /// asynchronously. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) is_async: bool, +} + +impl Definition { + /// Indicates whether this [GraphQL field][1] is represented by a method, + /// not a struct field. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + #[must_use] + pub(crate) fn is_method(&self) -> bool { + self.arguments.is_none() + } + + /// Returns generated code for the [`marker::IsOutputType::mark`] method, + /// which performs static checks for this [GraphQL field][1]. + /// + /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + #[must_use] + pub(crate) fn method_mark_tokens(&self, scalar: &ScalarValueType) -> TokenStream { + let args = self.arguments.unwrap_or_default(); + let args_marks = args.iter().filter_map(|a| a.method_mark_tokens(scalar)); + + let ty = &self.ty; + let resolved_ty = quote! { + <#ty as ::juniper::IntoResolvable< + '_, #scalar, _, >::Context, + >>::Type + }; + + quote! { + #( #args_marks )* + <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + } + } + + /// Returns generated code for the [`GraphQLType::meta`] method, which + /// registers this [GraphQL field][1] in [`Registry`]. + /// + /// [`GraphQLType::meta`]: juniper::GraphQLType::meta + /// [`Registry`]: juniper::Registry + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + #[must_use] + pub(crate) fn method_meta_tokens(&self) -> TokenStream { + let (name, ty) = (&self.name, &self.ty); + + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let deprecated = self.deprecated.as_ref().map(|reason| { + let reason = reason + .as_ref() + .map(|rsn| quote! { Some(#rsn) }) + .unwrap_or_else(|| quote! { None }); + quote! { .deprecated(#reason) } + }); + + let args = self + .arguments + .iter() + .filter_map(MethodArgument::method_meta_tokens); + + quote! { + registry.field_convert::<#ty, _, Self::Context>(#name, info) + #( #args )* + #description + #deprecated + } + } + + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this + /// [`Field`] synchronously. + /// + /// Returns [`None`] if this [`Field::is_async`]. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + #[must_use] + pub(crate) fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option { + if self.is_async { + return None; + } + + let (name, ty, method) = (&self.name, &self.ty, &self.method); + + let arguments = self + .arguments + .iter() + .map(field::MethodArgument::method_resolve_field_tokens); + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #name => { + let res: #ty = ::#method(self #( , #arguments )*); + #resolving_code + } + }) + } + + /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which + /// resolves this [`Field`] asynchronously. + /// + /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async + #[must_use] + pub(crate) fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream { + let (name, ty, method) = (&self.name, &self.ty, &self.method); + + let arguments = self + .arguments + .iter() + .map(field::MethodArgument::method_resolve_field_tokens); + + let mut fut = quote! { ::#method(self #( , #arguments )*) }; + if !self.is_async { + fut = quote! { ::juniper::futures::future::ready(#fut) }; + } + + let resolving_code = gen::async_resolving_code(Some(ty)); + + quote! { + #name => { + let fut = #fut; + #resolving_code + } + } + } +} diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 88498c1e8..12f9e64a8 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -1,5 +1,6 @@ //! Common functions, definitions and extensions for code generation, used by this crate. +pub(crate) mod field; pub(crate) mod gen; pub(crate) mod parse; diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 44187befa..e9b9a3663 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -8,6 +8,7 @@ use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ common::{ + field, parse::{self, TypeExt as _}, ScalarValueType, }, @@ -16,8 +17,8 @@ use crate::{ }; use super::{ - inject_async_trait, ArgumentMeta, Definition, EnumType, Field, FieldArgument, ImplMeta, - Implementer, ImplementerDowncast, MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type, + inject_async_trait, Definition, EnumType, Field, FieldArgument, ImplMeta, Implementer, + ImplementerDowncast, TraitMeta, TraitObjectType, Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -156,7 +157,7 @@ pub fn expand_on_trait( fields.iter().find_map(|f| { f.arguments .iter() - .find_map(MethodArgument::context_ty) + .find_map(field::MethodArgument::context_ty) .cloned() }) }) @@ -372,7 +373,7 @@ impl TraitMethod { .filter(|attr| !path_eq_single(&attr.path, "graphql")) .collect(); - let meta = MethodMeta::from_attrs("graphql", &method_attrs) + let meta = field::Attr::from_attrs("graphql", &method_attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; @@ -435,17 +436,17 @@ impl TraitMethod { /// /// Returns [`None`] if parsing fails. #[must_use] - fn parse_field(method: &mut syn::TraitItemMethod, meta: MethodMeta) -> Option { + fn parse_field(method: &mut syn::TraitItemMethod, attr: field::Attr) -> Option { let method_ident = &method.sig.ident; - let name = meta + let name = attr .name .as_ref() .map(|m| m.as_ref().value()) .unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( - meta.name + attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| method_ident.span()), @@ -476,7 +477,7 @@ impl TraitMethod { args_iter .filter_map(|arg| match arg { syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => Self::parse_field_argument(arg), + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, &ERR), }) .collect() }; @@ -487,8 +488,8 @@ impl TraitMethod { }; ty.lifetimes_anonymized(); - let description = meta.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = meta + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr .deprecated .as_ref() .map(|d| d.as_ref().as_ref().map(syn::LitStr::value)); @@ -503,108 +504,6 @@ impl TraitMethod { is_async: method.sig.asyncness.is_some(), }) } - - /// Parses [`MethodArgument`] from the given trait method argument definition. - /// - /// Returns [`None`] if parsing fails. - #[must_use] - fn parse_field_argument(argument: &mut syn::PatType) -> Option { - let argument_attrs = argument.attrs.clone(); - - // Remove repeated attributes from the method, to omit incorrect expansion. - argument.attrs = mem::take(&mut argument.attrs) - .into_iter() - .filter(|attr| !path_eq_single(&attr.path, "graphql")) - .collect(); - - let meta = ArgumentMeta::from_attrs("graphql", &argument_attrs) - .map_err(|e| proc_macro_error::emit_error!(e)) - .ok()?; - - if meta.context.is_some() { - return Some(MethodArgument::Context(argument.ty.unreferenced().clone())); - } - if meta.executor.is_some() { - return Some(MethodArgument::Executor); - } - if let syn::Pat::Ident(name) = &*argument.pat { - let arg = match name.ident.unraw().to_string().as_str() { - "context" | "ctx" => { - Some(MethodArgument::Context(argument.ty.unreferenced().clone())) - } - "executor" => Some(MethodArgument::Executor), - _ => None, - }; - if arg.is_some() { - ensure_no_regular_field_argument_meta(&meta)?; - return arg; - } - } - - let name = if let Some(name) = meta.name.as_ref() { - name.as_ref().value() - } else if let syn::Pat::Ident(name) = &*argument.pat { - to_camel_case(&name.ident.unraw().to_string()) - } else { - ERR.custom( - argument.pat.span(), - "trait method argument should be declared as a single identifier", - ) - .note(String::from( - "use `#[graphql(name = ...)]` attribute to specify custom argument's name without \ - requiring it being a single identifier", - )) - .emit(); - return None; - }; - if name.starts_with("__") { - ERR.no_double_underscore( - meta.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| argument.pat.span()), - ); - return None; - } - - Some(MethodArgument::Regular(FieldArgument { - name, - ty: argument.ty.as_ref().clone(), - description: meta.description.as_ref().map(|d| d.as_ref().value()), - default: meta.default.as_ref().map(|v| v.as_ref().clone()), - })) - } -} - -/// Checks whether the given [`ArgumentMeta`] doesn't contain arguments related to -/// [`FieldArgument`]. -#[must_use] -fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> { - if let Some(span) = &meta.name { - return err_disallowed_attr(&span, "name"); - } - if let Some(span) = &meta.description { - return err_disallowed_attr(&span, "description"); - } - if let Some(span) = &meta.default { - return err_disallowed_attr(&span, "default"); - } - Some(()) -} - -/// Emits "argument is not allowed" [`syn::Error`] for the given `arg` pointing to the given `span`. -#[must_use] -fn err_disallowed_attr(span: &S, arg: &str) -> Option { - ERR.custom( - span.span(), - format!( - "attribute argument `#[graphql({} = ...)]` is not allowed here", - arg, - ), - ) - .emit(); - - None } /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`. diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 78dfa81af..0b4035788 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -17,7 +17,7 @@ use syn::{ use crate::{ common::{ - gen, + field, gen, parse::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, @@ -367,344 +367,6 @@ impl ImplMeta { } } -/// Available metadata (arguments) behind `#[graphql]` attribute placed on a trait method -/// definition, when generating code for [GraphQL interface][1] type. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug, Default)] -struct MethodMeta { - /// Explicitly specified name of a [GraphQL field][1] represented by this trait method. - /// - /// If absent, then `camelCased` Rust method name is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - name: Option>, - - /// Explicitly specified [description][2] of this [GraphQL field][1]. - /// - /// If absent, then Rust doc comment is used as the [description][2], if any. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - description: Option>, - - /// Explicitly specified [deprecation][2] of this [GraphQL field][1]. - /// - /// If absent, then Rust `#[deprecated]` attribute is used as the [deprecation][2], if any. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Deprecation - deprecated: Option>>, - - /// Explicitly specified marker indicating that this trait method should be omitted by code - /// generation and not considered in the [GraphQL interface][1] type definition. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ignore: Option>, - - /// Explicitly specified marker indicating that this trait method doesn't represent a - /// [GraphQL field][1], but is a downcasting function into the [GraphQL object][2] implementer - /// type returned by this trait method. - /// - /// Once this marker is specified, the [GraphQL object][2] implementer type cannot be downcast - /// via another trait method or [`TraitMeta::external_downcasts`] function. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - downcast: Option>, -} - -impl Parse for MethodMeta { - fn parse(input: ParseStream) -> syn::Result { - let mut output = Self::default(); - - while !input.is_empty() { - let ident = input.parse::()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let name = input.parse::()?; - output - .name - .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "desc" | "description" => { - input.parse::()?; - let desc = input.parse::()?; - output - .description - .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "deprecated" => { - let mut reason = None; - if input.is_next::() { - input.parse::()?; - reason = Some(input.parse::()?); - } - output - .deprecated - .replace(SpanContainer::new( - ident.span(), - reason.as_ref().map(|r| r.span()), - reason, - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "ignore" | "skip" => output - .ignore - .replace(SpanContainer::new(ident.span(), None, ident.clone())) - .none_or_else(|_| err::dup_arg(&ident))?, - "downcast" => output - .downcast - .replace(SpanContainer::new(ident.span(), None, ident.clone())) - .none_or_else(|_| err::dup_arg(&ident))?, - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - - Ok(output) - } -} - -impl MethodMeta { - /// Tries to merge two [`MethodMeta`]s into a single one, reporting about duplicates, if any. - fn try_merge(self, mut another: Self) -> syn::Result { - Ok(Self { - name: try_merge_opt!(name: self, another), - 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), - }) - } - - /// Parses [`MethodMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a - /// method definition. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let mut meta = filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - - if let Some(ignore) = &meta.ignore { - if meta.name.is_some() - || meta.description.is_some() - || meta.deprecated.is_some() - || meta.downcast.is_some() - { - return Err(syn::Error::new( - ignore.span(), - "`ignore` attribute argument is not composable with any other arguments", - )); - } - } - - if let Some(downcast) = &meta.downcast { - if meta.name.is_some() - || meta.description.is_some() - || meta.deprecated.is_some() - || meta.ignore.is_some() - { - return Err(syn::Error::new( - downcast.span(), - "`downcast` attribute argument is not composable with any other arguments", - )); - } - } - - if meta.description.is_none() { - meta.description = get_doc_comment(attrs).map(|sc| { - let span = sc.span_ident(); - sc.map(|desc| syn::LitStr::new(&desc, span)) - }); - } - - if meta.deprecated.is_none() { - meta.deprecated = get_deprecated(attrs).map(|sc| { - let span = sc.span_ident(); - sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span))) - }); - } - - Ok(meta) - } -} - -/// Available metadata (arguments) behind `#[graphql]` attribute placed on a trait method argument, -/// when generating code for [GraphQL interface][1] type. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug, Default)] -struct ArgumentMeta { - /// Explicitly specified name of a [GraphQL argument][1] represented by this method argument. - /// - /// If absent, then `camelCased` Rust argument name is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - name: Option>, - - /// Explicitly specified [description][2] of this [GraphQL argument][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - description: Option>, - - /// Explicitly specified [default value][2] of this [GraphQL argument][1]. - /// - /// If the exact default expression is not specified, then the [`Default::default`] value is - /// used. - /// - /// If absent, then this [GraphQL argument][1] is considered as [required][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Required-Arguments - default: Option>>, - - /// Explicitly specified marker indicating that this method argument doesn't represent a - /// [GraphQL argument][1], but is a [`Context`] being injected into a [GraphQL field][2] - /// resolving function. - /// - /// If absent, then the method argument still is considered as [`Context`] if it's named - /// `context` or `ctx`. - /// - /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - context: Option>, - - /// Explicitly specified marker indicating that this method argument doesn't represent a - /// [GraphQL argument][1], but is a [`Executor`] being injected into a [GraphQL field][2] - /// resolving function. - /// - /// If absent, then the method argument still is considered as [`Context`] if it's named - /// `executor`. - /// - /// [`Executor`]: juniper::Executor - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - executor: Option>, -} - -impl Parse for ArgumentMeta { - fn parse(input: ParseStream) -> syn::Result { - let mut output = Self::default(); - - while !input.is_empty() { - let ident = input.parse::()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let name = input.parse::()?; - output - .name - .replace(SpanContainer::new(ident.span(), Some(name.span()), name)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "desc" | "description" => { - input.parse::()?; - let desc = input.parse::()?; - output - .description - .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "default" => { - let mut expr = None; - if input.is_next::() { - input.parse::()?; - expr = Some(input.parse::()?); - } else if input.is_next::() { - let inner; - let _ = syn::parenthesized!(inner in input); - expr = Some(inner.parse::()?); - } - output - .default - .replace(SpanContainer::new( - ident.span(), - expr.as_ref().map(|e| e.span()), - expr, - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "ctx" | "context" | "Context" => { - let span = ident.span(); - output - .context - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))? - } - "exec" | "executor" => { - let span = ident.span(); - output - .executor - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))? - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - - Ok(output) - } -} - -impl ArgumentMeta { - /// Tries to merge two [`ArgumentMeta`]s into a single one, reporting about duplicates, if any. - fn try_merge(self, mut another: Self) -> syn::Result { - Ok(Self { - name: try_merge_opt!(name: self, another), - description: try_merge_opt!(description: self, another), - default: try_merge_opt!(default: self, another), - context: try_merge_opt!(context: self, another), - executor: try_merge_opt!(executor: self, another), - }) - } - - /// Parses [`ArgumentMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a - /// function argument. - fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let meta = filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - - if let Some(context) = &meta.context { - if meta.name.is_some() - || meta.description.is_some() - || meta.default.is_some() - || meta.executor.is_some() - { - return Err(syn::Error::new( - context.span(), - "`context` attribute argument is not composable with any other arguments", - )); - } - } - - if let Some(executor) = &meta.executor { - if meta.name.is_some() - || meta.description.is_some() - || meta.default.is_some() - || meta.context.is_some() - { - return Err(syn::Error::new( - executor.span(), - "`executor` attribute argument is not composable with any other arguments", - )); - } - } - - Ok(meta) - } -} - /// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -742,10 +404,11 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces scalar: ScalarValueType, - /// Defined [`Field`]s of this [GraphQL interface][1]. + /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fields: Vec, + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + fields: Vec, /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// @@ -781,7 +444,6 @@ impl Definition { let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let ty = self.ty.ty_tokens(); let name = &self.name; @@ -797,7 +459,7 @@ impl Definition { a.cmp(&b) }); - let fields_meta = self.fields.iter().map(Field::method_meta_tokens); + let fields_meta = self.fields.iter().map(field::Definition::method_meta_tokens); quote! { #[automatically_derived] @@ -1010,16 +672,14 @@ impl Definition { let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let ty = self.ty.ty_tokens(); let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); - - let all_implers_unique = if impler_tys.len() > 1 { - Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) - } else { - None - }; + let all_implers_unique = (impler_tys.len() > 1).then(|| { + quote! { + Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) + } + }); quote! { #[automatically_derived] @@ -1027,8 +687,7 @@ impl Definition { { fn mark() { #all_implers_unique - - #( <#impler_tys as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } } @@ -1045,27 +704,9 @@ impl Definition { let generics = self.ty.impl_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let ty = self.ty.ty_tokens(); - let fields_marks = self.fields.iter().map(|field| { - let arguments_marks = field.arguments.iter().filter_map(|arg| { - let arg_ty = &arg.as_regular()?.ty; - Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) - }); - - let field_ty = &field.ty; - let resolved_ty = quote! { - <#field_ty as ::juniper::IntoResolvable< - '_, #scalar, _, >::Context, - >>::Type - }; - - quote! { - #( #arguments_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - } - }); + let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); let impler_tys = self.implementers.iter().map(|impler| &impler.ty); @@ -1095,279 +736,6 @@ impl ToTokens for Definition { } } -/// Representation of [GraphQL interface][1] field [argument][2] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments -#[derive(Debug)] -struct FieldArgument { - /// Rust type that this [GraphQL field argument][2] is represented by. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - ty: syn::Type, - - /// Name of this [GraphQL field argument][2] in GraphQL schema. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - name: String, - - /// [Description][1] of this [GraphQL field argument][2] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - description: Option, - - /// Default value of this [GraphQL field argument][2] in GraphQL schema. - /// - /// If outer [`Option`] is [`None`], then this [argument][2] is a [required][3] one. - /// - /// If inner [`Option`] is [`None`], then the [`Default::default`] value is used. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Arguments - /// [3]: https://spec.graphql.org/June2018/#sec-Required-Arguments - default: Option>, -} - -/// Possible kinds of Rust trait method arguments for code generation. -#[derive(Debug)] -enum MethodArgument { - /// Regular [GraphQL field argument][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments - Regular(FieldArgument), - - /// [`Context`] passed into a [GraphQL field][2] resolving method. - /// - /// [`Context`]: juniper::Context - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - Context(syn::Type), - - /// [`Executor`] passed into a [GraphQL field][2] resolving method. - /// - /// [`Executor`]: juniper::Executor - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - Executor, -} - -impl MethodArgument { - /// Returns this [`MethodArgument`] as a [`FieldArgument`], if it represents one. - #[must_use] - fn as_regular(&self) -> Option<&FieldArgument> { - if let Self::Regular(arg) = self { - Some(arg) - } else { - None - } - } - - /// Returns [`syn::Type`] of this [`MethodArgument::Context`], if it represents one. - #[must_use] - fn context_ty(&self) -> Option<&syn::Type> { - if let Self::Context(ty) = self { - Some(ty) - } else { - None - } - } - - /// Returns generated code for the [`GraphQLType::meta`] method, which registers this - /// [`MethodArgument`] in [`Registry`], if it represents a [`FieldArgument`]. - /// - /// [`GraphQLType::meta`]: juniper::GraphQLType::meta - /// [`Registry`]: juniper::Registry - #[must_use] - fn method_meta_tokens(&self) -> Option { - let arg = self.as_regular()?; - - let (name, ty) = (&arg.name, &arg.ty); - - let description = arg - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - let method = if let Some(val) = &arg.default { - let val = val - .as_ref() - .map(|v| quote! { (#v).into() }) - .unwrap_or_else(|| quote! { <#ty as Default>::default() }); - quote! { .arg_with_default::<#ty>(#name, &#val, info) } - } else { - quote! { .arg::<#ty>(#name, info) } - }; - - Some(quote! { .argument(registry#method#description) }) - } - - /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which provides the - /// value of this [`MethodArgument`] to be passed into a trait method call. - /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - #[must_use] - fn method_resolve_field_tokens(&self) -> TokenStream { - match self { - Self::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - let err_text = format!( - "Internal error: missing argument `{}` - validation must have failed", - &name, - ); - - quote! { - args.get::<#ty>(#name).expect(#err_text) - } - } - - Self::Context(_) => quote! { - ::juniper::FromContext::from(executor.context()) - }, - - Self::Executor => quote! { &executor }, - } - } -} - -/// Representation of [GraphQL interface][1] [field][2] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields -#[derive(Debug)] -struct Field { - /// Rust type that this [GraphQL field][2] is represented by (method return type). - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - ty: syn::Type, - - /// Name of this [GraphQL field][2] in GraphQL schema. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - name: String, - - /// [Description][1] of this [GraphQL field][2] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Descriptions - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - description: Option, - - /// [Deprecation][1] of this [GraphQL field][2] to put into GraphQL schema. - /// - /// If inner [`Option`] is [`None`], then deprecation has no message attached. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Deprecation - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - deprecated: Option>, - - /// Name of Rust trait method representing this [GraphQL field][2]. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - method: syn::Ident, - - /// Rust trait [`MethodArgument`]s required to call the trait method representing this - /// [GraphQL field][2]. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - arguments: Vec, - - /// Indicator whether this [GraphQL field][2] should be resolved asynchronously. - /// - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - is_async: bool, -} - -impl Field { - /// Returns generated code for the [`GraphQLType::meta`] method, which registers this - /// [`Field`] in [`Registry`]. - /// - /// [`GraphQLType::meta`]: juniper::GraphQLType::meta - /// [`Registry`]: juniper::Registry - #[must_use] - fn method_meta_tokens(&self) -> TokenStream { - let (name, ty) = (&self.name, &self.ty); - - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - let deprecated = self.deprecated.as_ref().map(|reason| { - let reason = reason - .as_ref() - .map(|rsn| quote! { Some(#rsn) }) - .unwrap_or_else(|| quote! { None }); - quote! { .deprecated(#reason) } - }); - - let arguments = self - .arguments - .iter() - .filter_map(MethodArgument::method_meta_tokens); - - quote! { - registry.field_convert::<#ty, _, Self::Context>(#name, info) - #( #arguments )* - #description - #deprecated - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this - /// [`Field`] synchronously. - /// - /// Returns [`None`] if this [`Field::is_async`]. - /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - #[must_use] - fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option { - if self.is_async { - return None; - } - - let (name, ty, method) = (&self.name, &self.ty, &self.method); - - let arguments = self - .arguments - .iter() - .map(MethodArgument::method_resolve_field_tokens); - - let resolving_code = gen::sync_resolving_code(); - - Some(quote! { - #name => { - let res: #ty = ::#method(self #( , #arguments )*); - #resolving_code - } - }) - } - - /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which - /// resolves this [`Field`] asynchronously. - /// - /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async - #[must_use] - fn method_resolve_field_async_tokens(&self, trait_ty: &syn::Type) -> TokenStream { - let (name, ty, method) = (&self.name, &self.ty, &self.method); - - let arguments = self - .arguments - .iter() - .map(MethodArgument::method_resolve_field_tokens); - - let mut fut = quote! { ::#method(self #( , #arguments )*) }; - if !self.is_async { - fut = quote! { ::juniper::futures::future::ready(#fut) }; - } - - let resolving_code = gen::async_resolving_code(Some(ty)); - - quote! { - #name => { - let fut = #fut; - #resolving_code - } - } - } -} - /// Representation of custom downcast into an [`Implementer`] from a [GraphQL interface][1] type for /// code generation. /// diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index a6b2a0168..4f76f2e78 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -5,7 +5,7 @@ pub mod attr; pub mod derive; -use std::{convert::TryInto as _, collections::HashSet}; +use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; @@ -17,11 +17,17 @@ use syn::{ }; use crate::{ - common::parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, + common::{ + field, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + ScalarValueType, + }, + util::{ + filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, RenameRule, }, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; /// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_object]`) @@ -201,27 +207,28 @@ impl ObjectMeta { } } +#[derive(Debug)] struct Definition { /// Name of this [GraphQL object][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub name: String, + name: String, /// Rust type that this [GraphQL object][1] is represented with. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub ty: syn::Type, + ty: syn::Type, /// Generics of the Rust type that this [GraphQL object][1] is implemented /// for. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub generics: syn::Generics, + generics: syn::Generics, /// Description of this [GraphQL object][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub description: Option, + description: Option, /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` /// implementation with for this [GraphQL object][1]. @@ -230,63 +237,143 @@ struct Definition { /// `juniper::Context`. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub context: Option, + context: Option, - /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL object][1]. /// - /// If [`None`] then generated code will be generic over any - /// `juniper::ScalarValue` type, which, in turn, requires all [object][1] - /// fields to be generic over any `juniper::ScalarValue` type too. That's - /// why this type should be specified only if one of the variants implements - /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` - /// type. + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + scalar: ScalarValueType, + + /// Defined [GraphQL fields][2] of this [GraphQL object][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + fields: Vec, + + /// [GraphQL interfaces][2] implemented by this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub scalar: Option, + /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces + interfaces: Vec, } impl Definition { - /// Returns generated code implementing [`marker::IsOutputType`] trait for - /// this [GraphQL object][1]. + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL object][1]. /// - /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - fn impl_output_type_tokens(&self) -> TokenStream { + fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let ty = &self.ty; + + let name = &self.name; + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let fields_meta = self + .fields + .iter() + .map(field::Definition::method_meta_tokens); + + // Sorting is required to preserve/guarantee the order of interfaces registered in schema. + let mut interface_tys: Vec<_> = self.interfaces.iter().map(|iface| &iface.ty).collect(); + interface_tys.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); + a.cmp(&b) + }); + let interfaces = (!interface_tys.is_empty()).then(|| { + quote! { + .interfaces(&[ + #( registry.get_type::<#interface_tys>(info), )* + ]) + } + }); - let ty = self.ty.ty_tokens(); + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty#ty_generics #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } - /* - let fields_marks = self.fields.iter().map(|field| { - let arguments_marks = field.arguments.iter().filter_map(|arg| { - let arg_ty = &arg.as_regular()?.ty; - Some(quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }) - }); + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar> + ) -> ::juniper::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + let fields = [ + #( #fields_meta, )* + ]; + registry.build_object_type::<#ty>(info, &fields) + #description + #interfaces + .into_meta() + } + } + } + } - let field_ty = &field.ty; - let resolved_ty = quote! { - <#field_ty as ::juniper::IntoResolvable< - '_, #scalar, _, >::Context, - >>::Type - }; + /// Returns generated code implementing [`GraphQLObject`] trait for this + /// [GraphQL object][1]. + /// + /// [`GraphQLObject`]: juniper::GraphQLObject + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_graphql_object_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_generics, where_clause) = self.generics(); + let ty = &self.ty; + let interface_tys: Vec<_> = self.interfaces.iter().map(|iface| &iface.ty).collect(); + let all_interfaces_unique = (interface_tys.len() > 1).then(|| { quote! { - #( #arguments_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); + ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); } }); - let interface_tys = self.implementers.iter().map(|iface| &iface.ty); - */ + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause + { + fn mark() { + #all_interfaces_unique + #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + } + } + } + } + + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL object][1]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_generics, where_clause) = self.generics(); + let ty = &self.ty; + + let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); + + let interface_tys = self.interfaces.iter().map(|iface| &iface.ty); 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 )* @@ -300,7 +387,9 @@ impl Definition { impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { into.append_all(&[ + self.impl_graphql_object_tokens(), self.impl_output_type_tokens(), + self.impl_graphql_type_tokens(), ]); } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index d34541708..3be2a7c96 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -563,7 +563,7 @@ impl ToTokens for UnionDefinition { fn mark() { #all_variants_unique - #( <#var_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* + #( <#var_types as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } }; diff --git a/juniper_codegen/src/result.rs b/juniper_codegen/src/result.rs index 4a921d394..f15e180ef 100644 --- a/juniper_codegen/src/result.rs +++ b/juniper_codegen/src/result.rs @@ -69,6 +69,11 @@ impl GraphQLScope { .note(self.spec_link()) } + pub fn error(&self, err: syn::Error) -> Diagnostic { + Diagnostic::spanned(err.span(), Level::Error, format!("{} {}", self, err)) + .note(self.spec_link()) + } + pub fn emit_custom>(&self, span: Span, msg: S) { self.custom(span, msg).emit() } From 0f23ea5b3b066f8513c8dc913d9f1dbc007861e9 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 27 Jul 2021 11:45:53 +0300 Subject: [PATCH 05/39] Impl barebone for objects, vol.4 --- juniper/src/lib.rs | 2 +- juniper/src/types/marker.rs | 18 +- juniper_codegen/src/common/field/mod.rs | 124 ++++++++-- juniper_codegen/src/graphql_interface/mod.rs | 178 ++++++-------- juniper_codegen/src/graphql_object/mod.rs | 232 ++++++++++++++++--- 5 files changed, 386 insertions(+), 168 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 94fede25c..8c361bfef 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -172,7 +172,7 @@ pub use crate::{ types::{ async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind}, - marker::{self, GraphQLInterface, GraphQLUnion, GraphQLObject}, + marker::{self, GraphQLInterface, GraphQLObject, GraphQLUnion}, nullable::Nullable, scalars::{EmptyMutation, EmptySubscription, ID}, subscriptions::{ diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 80c2966cb..25bee26c1 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -34,9 +34,9 @@ pub trait GraphQLObject: GraphQLType { } impl<'a, S, T> GraphQLObject for &T - where - T: GraphQLInterface + ?Sized, - S: ScalarValue, +where + T: GraphQLInterface + ?Sized, + S: ScalarValue, { #[inline] fn mark() { @@ -45,9 +45,9 @@ impl<'a, S, T> GraphQLObject for &T } impl GraphQLObject for Box - where - T: GraphQLInterface + ?Sized, - S: ScalarValue, +where + T: GraphQLInterface + ?Sized, + S: ScalarValue, { #[inline] fn mark() { @@ -56,9 +56,9 @@ impl GraphQLObject for Box } impl GraphQLObject for Arc - where - T: GraphQLInterface + ?Sized, - S: ScalarValue, +where + T: GraphQLInterface + ?Sized, + S: ScalarValue, { #[inline] fn mark() { diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 5dd1bee4a..9131cfeb6 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -14,6 +14,7 @@ use syn::{ use crate::{ common::{ + gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -242,6 +243,14 @@ pub(crate) struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) arguments: Option>, + /// [`syn::Receiver`] of the Rust method representing this + /// [GraphQL field][1]. + /// + /// If [`None`] then this method has no receiver. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) receiver: Option, + /// Indicator whether this [GraphQL field][1] should be resolved /// asynchronously. /// @@ -259,6 +268,44 @@ impl Definition { self.arguments.is_none() } + /// Returns generated code that panics about unknown [GraphQL field][1] + /// tried to be resolved in the [`GraphQLValue::resolve_field`] method. + /// + /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + #[must_use] + pub(crate) fn method_resolve_field_panic_no_field_tokens( + scalar: &ScalarValueType, + ) -> TokenStream { + quote! { + panic!( + "Field `{}` not found on type `{}`", + field, + >::name(info).unwrap(), + ) + } + } + + /// Returns generated code that panics 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_panic_async_field_tokens( + field_names: &[&str], + scalar: &ScalarValueType, + ) -> TokenStream { + quote! { + #( #field_names )|* => panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + field, + >::name(info).unwrap(), + ), + } + } + /// Returns generated code for the [`marker::IsOutputType::mark`] method, /// which performs static checks for this [GraphQL field][1]. /// @@ -318,49 +365,88 @@ impl Definition { } } - /// Returns generated code for the [`GraphQLValue::resolve_field`] method, which resolves this - /// [`Field`] synchronously. + /// Returns generated code for the [`GraphQLValue::resolve_field`] method, + /// which resolves this [GraphQL field][1] synchronously. /// - /// Returns [`None`] if this [`Field::is_async`]. + /// Returns [`None`] if this [`Definition::is_async`]. /// /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] - pub(crate) fn method_resolve_field_tokens(&self, trait_ty: &syn::Type) -> Option { + pub(crate) fn method_resolve_field_tokens( + &self, + trait_ty: Option<&syn::Type>, + ) -> Option { if self.is_async { return None; } - let (name, ty, method) = (&self.name, &self.ty, &self.method); + let (name, ty, ident) = (&self.name, &self.ty, &self.ident); - let arguments = self - .arguments - .iter() - .map(field::MethodArgument::method_resolve_field_tokens); + let res = if self.is_method() { + let args = self + .arguments + .as_ref() + .unwrap() + .iter() + .map(MethodArgument::method_resolve_field_tokens); + + let rcv = self.receiver.is_some().then(|| { + quote! { self, } + }); + + if trait_ty.is_some() { + quote! { ::#ident(#rcv #( #args ),*) } + } else { + quote! { Self::#ident(#rcv #( #args ),*) } + } + } else { + quote! { self.#ident } + }; let resolving_code = gen::sync_resolving_code(); Some(quote! { #name => { - let res: #ty = ::#method(self #( , #arguments )*); + let res: #ty = #res; #resolving_code } }) } - /// Returns generated code for the [`GraphQLValueAsync::resolve_field_async`] method, which - /// resolves this [`Field`] asynchronously. + /// Returns generated code for the + /// [`GraphQLValueAsync::resolve_field_async`] method, which resolves this + /// [GraphQL field][1] asynchronously. /// /// [`GraphQLValueAsync::resolve_field_async`]: 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, trait_ty: &syn::Type) -> TokenStream { - let (name, ty, method) = (&self.name, &self.ty, &self.method); + pub(crate) fn method_resolve_field_async_tokens( + &self, + trait_ty: Option<&syn::Type>, + ) -> TokenStream { + let (name, ty, ident) = (&self.name, &self.ty, &self.ident); + + let mut fut = if self.is_method() { + let args = self + .arguments + .as_ref() + .unwrap() + .iter() + .map(MethodArgument::method_resolve_field_tokens); - let arguments = self - .arguments - .iter() - .map(field::MethodArgument::method_resolve_field_tokens); + let rcv = self.receiver.is_some().then(|| { + quote! { self, } + }); - let mut fut = quote! { ::#method(self #( , #arguments )*) }; + if trait_ty.is_some() { + quote! { ::#ident(#rcv #( #args ),*) } + } else { + quote! { Self::#ident(#rcv #( #args ),*) } + } + } else { + quote! { self.#ident } + }; if !self.is_async { fut = quote! { ::juniper::futures::future::ready(#fut) }; } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0b4035788..71a1fc791 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -416,21 +416,76 @@ struct Definition { implementers: Vec, } +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.ty.to_token_stream().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); + } +} + impl Definition { - /// Returns generated code that panics about unknown field tried to be resolved on this + /// 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 panic_no_field_tokens(&self) -> TokenStream { + fn impl_graphql_interface_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let generics = self.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let ty = self.ty.ty_tokens(); + + let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let all_implers_unique = (impler_tys.len() > 1).then(|| { + quote! { + Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) + } + }); + quote! { - panic!( - "Field `{}` not found on type `{}`", - field, - >::name(info).unwrap(), - ) + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #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.ty.impl_generics(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let ty = self.ty.ty_tokens(); + + let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); + + let impler_tys = self.implementers.iter().map(|impler| &impler.ty); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } + } } } @@ -459,7 +514,10 @@ impl Definition { a.cmp(&b) }); - let fields_meta = self.fields.iter().map(field::Definition::method_meta_tokens); + let fields_meta = self + .fields + .iter() + .map(field::Definition::method_meta_tokens); quote! { #[automatically_derived] @@ -507,32 +565,18 @@ impl Definition { let fields_resolvers = self .fields .iter() - .filter_map(|f| f.method_resolve_field_tokens(&trait_ty)); + .filter_map(|f| f.method_resolve_field_tokens(Some(&trait_ty))); let async_fields_panic = { let names = self .fields .iter() - .filter_map(|field| { - if field.is_async { - Some(&field.name) - } else { - None - } - }) + .filter_map(|f| f.is_async.then(|| f.name.as_str())) .collect::>(); - if names.is_empty() { - None - } else { - Some(quote! { - #( #names )|* => panic!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - field, - >::name(info).unwrap(), - ), - }) - } + (!names.is_empty()).then(|| { + field::Definition::method_resolve_field_panic_async_field_tokens(&names, scalar) + }) }; - let no_field_panic = self.panic_no_field_tokens(); + let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); let custom_downcast_checks = self .implementers @@ -621,8 +665,8 @@ impl Definition { let fields_resolvers = self .fields .iter() - .map(|f| f.method_resolve_field_async_tokens(&trait_ty)); - let no_field_panic = self.panic_no_field_tokens(); + .map(|f| f.method_resolve_field_async_tokens(Some(&trait_ty))); + let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); let custom_downcasts = self .implementers @@ -660,80 +704,6 @@ impl Definition { } } } - - /// 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 generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let ty = self.ty.ty_tokens(); - - let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); - let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { - Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) - } - }); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #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.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let ty = self.ty.ty_tokens(); - - let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); - - let impler_tys = self.implementers.iter().map(|impler| &impler.ty); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause - { - fn mark() { - #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* - } - } - } - } -} - -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - into.append_all(&[ - self.ty.to_token_stream(), - self.impl_graphql_interface_tokens(), - self.impl_output_type_tokens(), - self.impl_graphql_type_tokens(), - self.impl_graphql_value_tokens(), - self.impl_graphql_value_async_tokens(), - ]); - } } /// Representation of custom downcast into an [`Implementer`] from a [GraphQL interface][1] type for diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 4f76f2e78..90d702606 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -260,7 +260,77 @@ struct Definition { interfaces: Vec, } +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_graphql_object_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_as_dyn_graphql_value_tokens().to_tokens(into()); + } +} + impl Definition { + /// Returns generated code implementing [`GraphQLObject`] trait for this + /// [GraphQL object][1]. + /// + /// [`GraphQLObject`]: juniper::GraphQLObject + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_graphql_object_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_generics, where_clause) = self.generics(); + let ty = &self.ty; + + let interface_tys: Vec<_> = self.interfaces.iter().map(|iface| &iface.ty).collect(); + let all_interfaces_unique = (interface_tys.len() > 1).then(|| { + quote! { + ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); + } + }); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause + { + fn mark() { + #all_interfaces_unique + #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + } + } + } + } + + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL object][1]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_generics, where_clause) = self.generics(); + let ty = &self.ty; + + let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); + + let interface_tys = self.interfaces.iter().map(|iface| &iface.ty); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty#ty_generics #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -324,72 +394,164 @@ impl Definition { } } - /// Returns generated code implementing [`GraphQLObject`] trait for this + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL object][1]. /// - /// [`GraphQLObject`]: juniper::GraphQLObject + /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - fn impl_graphql_object_tokens(&self) -> TokenStream { + fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); - let (impl_generics, ty_generics, where_clause) = self.generics(); + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let ty = &self.ty; - let interface_tys: Vec<_> = self.interfaces.iter().map(|iface| &iface.ty).collect(); - let all_interfaces_unique = (interface_tys.len() > 1).then(|| { - quote! { - ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); - } - }); + let name = &self.name; + + let fields_resolvers = self + .fields + .iter() + .filter_map(|f| f.method_resolve_field_tokens(None)); + let async_fields_panic = { + 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_panic_async_field_tokens(&names, scalar) + }) + }; + let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics #where_clause { - fn mark() { - #all_interfaces_unique - #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + 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_panic + _ => #no_field_panic, + } + } + + fn concrete_type_name( + &self, + _: &Self::Context, + _: &Self::TypeInfo, + ) -> String { + #name.to_string() } } } } - /// Returns generated code implementing [`marker::IsOutputType`] trait for - /// this [GraphQL object][1]. + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL object][1]. /// - /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - fn impl_output_type_tokens(&self) -> TokenStream { + fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.generics(); - let ty = &self.ty; - - let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let mut where_clause = where_clause + .cloned() + .unwrap_or_else(|| parse_quote! { where }); + where_clause.predicates.push(parse_quote! { Self: Sync }); + if self.scalar.is_generic() { + where_clause + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + let ty = &self.ty.ty_tokens(); - let interface_tys = self.interfaces.iter().map(|iface| &iface.ty); + let fields_resolvers = self + .fields + .iter() + .map(|f| f.method_resolve_field_async_tokens(None)); + let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics #where_clause { - fn mark() { - #( #fields_marks )* - #( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match field { + #( #fields_resolvers )* + _ => #no_field_panic, + } } } } } -} -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - into.append_all(&[ - self.impl_graphql_object_tokens(), - self.impl_output_type_tokens(), - self.impl_graphql_type_tokens(), - ]); + /// 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, ty_generics, where_clause) = self.generics.split_for_impl(); + let mut where_clause = where_clause + .cloned() + .unwrap_or_else(|| parse_quote! { where }); + where_clause.predicates.push(parse_quote! { Self: Sync }); + if self.scalar.is_generic() { + where_clause + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + let ty = &self.ty.ty_tokens(); + + Some(quote! { + #[automatically_derived] + impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty#ty_generics #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 + } + } + }) } } From 8c6c5565e3afa12b4ede83a8e12b923f42699a64 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 27 Jul 2021 15:22:47 +0300 Subject: [PATCH 06/39] Impl barebone for objects, vol.5 --- juniper_codegen/src/common/field/mod.rs | 22 ++- juniper_codegen/src/common/mod.rs | 33 ++++- juniper_codegen/src/graphql_interface/attr.rs | 76 ++++------- juniper_codegen/src/graphql_interface/mod.rs | 42 ++++-- juniper_codegen/src/graphql_object/derive.rs | 128 ++++++++++++++++-- juniper_codegen/src/graphql_object/mod.rs | 41 +++--- juniper_codegen/src/util/mod.rs | 2 +- 7 files changed, 244 insertions(+), 100 deletions(-) diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 9131cfeb6..28ed02899 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -243,13 +243,11 @@ pub(crate) struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) arguments: Option>, - /// [`syn::Receiver`] of the Rust method representing this - /// [GraphQL field][1]. - /// - /// If [`None`] then this method has no receiver. + /// Indicator whether the Rust method representing this [GraphQL field][1] + /// has a [`syn::Receiver`]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - pub(crate) receiver: Option, + pub(crate) has_receiver: bool, /// Indicator whether this [GraphQL field][1] should be resolved /// asynchronously. @@ -391,7 +389,7 @@ impl Definition { .iter() .map(MethodArgument::method_resolve_field_tokens); - let rcv = self.receiver.is_some().then(|| { + let rcv = self.has_receiver.then(|| { quote! { self, } }); @@ -435,7 +433,7 @@ impl Definition { .iter() .map(MethodArgument::method_resolve_field_tokens); - let rcv = self.receiver.is_some().then(|| { + let rcv = self.has_receiver.then(|| { quote! { self, } }); @@ -461,3 +459,13 @@ impl Definition { } } } + +/// Checks whether all [GraphQL fields][1] fields have different names. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields +#[must_use] +pub(crate) fn all_different(fields: &[Definition]) -> bool { + let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect(); + names.dedup(); + names.len() == fields.len() +} diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 12f9e64a8..9485ea5f0 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -31,6 +31,12 @@ pub(crate) enum ScalarValueType { ImplicitGeneric, } +impl ToTokens for ScalarValueType { + fn to_tokens(&self, into: &mut TokenStream) { + self.ty().to_tokens(into) + } +} + impl ScalarValueType { /// Indicates whether this [`ScalarValueType`] is generic. #[must_use] @@ -81,10 +87,29 @@ impl ScalarValueType { } } } -} -impl ToTokens for ScalarValueType { - fn to_tokens(&self, into: &mut TokenStream) { - self.ty().to_tokens(into) + /// Parses [`ScalarValueType`] from the given `explicit` type definition (if + /// any), checking whether it contains in the giving `generics`. + #[must_use] + pub(crate) fn parse(explicit: Option<&syn::Type>, generics: &syn::Generics) -> Self { + if let Some(scalar_ty) = explicit { + generics + .params + .iter() + .find_map(|p| { + if let syn::GenericParam::Type(tp) = p { + let ident = &tp.ident; + let ty: syn::Type = parse_quote! { #ident }; + if &ty == scalar_ty { + return Some(&tp.ident); + } + } + None + }) + .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) + .unwrap_or_else(|| ScalarValueType::Concrete(scalar_ty.clone())) + } else { + ScalarValueType::ImplicitGeneric + } } } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index e9b9a3663..6e51aac53 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -13,7 +13,7 @@ use crate::{ ScalarValueType, }, result::GraphQLScope, - util::{path_eq_single, span_container::SpanContainer, to_camel_case}, + util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; use super::{ @@ -69,27 +69,7 @@ pub fn expand_on_trait( ); } - let scalar = meta - .scalar - .as_ref() - .map(|sc| { - ast.generics - .params - .iter() - .find_map(|p| { - if let syn::GenericParam::Type(tp) = p { - let ident = &tp.ident; - let ty: syn::Type = parse_quote! { #ident }; - if &ty == sc.as_ref() { - return Some(&tp.ident); - } - } - None - }) - .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) - .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) - }) - .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); + let scalar = ScalarValueType::parse(meta.scalar.as_deref(), &ast.generics); let mut implementers: Vec<_> = meta .implementers @@ -114,10 +94,16 @@ pub fn expand_on_trait( 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) { + 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) { @@ -142,8 +128,7 @@ pub fn expand_on_trait( if fields.is_empty() { ERR.emit_custom(trait_span, "must have at least one field"); } - - if !all_fields_different(&fields) { + if !field::all_different(&fields) { ERR.emit_custom(trait_span, "must have a different name for each field"); } @@ -155,10 +140,11 @@ pub fn expand_on_trait( .map(|c| c.as_ref().clone()) .or_else(|| { fields.iter().find_map(|f| { - f.arguments - .iter() - .find_map(field::MethodArgument::context_ty) - .cloned() + f.arguments.and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) }) }) .or_else(|| { @@ -349,7 +335,7 @@ enum TraitMethod { /// Method represents a [`Field`] of [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Field(Field), + Field(field::Definition), /// Method represents a custom downcasting function into the [`Implementer`] of /// [GraphQL interface][1]. @@ -364,7 +350,7 @@ impl TraitMethod { /// Returns [`None`] if the trait method marked with `#[graphql(ignore)]` attribute, /// or parsing fails. #[must_use] - fn parse(method: &mut syn::TraitItemMethod) -> Option { + 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. @@ -385,7 +371,7 @@ impl TraitMethod { return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?))); } - Some(Self::Field(Self::parse_field(method, meta)?)) + Some(Self::Field(Self::parse_field(method, meta, renaming)?)) } /// Parses [`TraitMethod::Downcast`] from the given trait method definition. @@ -436,14 +422,18 @@ impl TraitMethod { /// /// Returns [`None`] if parsing fails. #[must_use] - fn parse_field(method: &mut syn::TraitItemMethod, attr: field::Attr) -> Option { + 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(|| to_camel_case(&method_ident.unraw().to_string())); + .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); if name.starts_with("__") { ERR.no_double_underscore( attr.name @@ -492,15 +482,16 @@ impl TraitMethod { let deprecated = attr .deprecated .as_ref() - .map(|d| d.as_ref().as_ref().map(syn::LitStr::value)); + .map(|d| d.as_deref().map(syn::LitStr::value)); - Some(Field { + Some(field::Definition { name, ty, description, deprecated, - method: method_ident.clone(), - arguments, + ident: method_ident.clone(), + arguments: Some(arguments), + has_receiver: method.sig.receiver().is_some(), is_async: method.sig.asyncness.is_some(), }) } @@ -567,12 +558,3 @@ fn err_duplicate_downcast( )) .emit() } - -/// Checks whether all [GraphQL interface][1] fields have different names. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -fn all_fields_different(fields: &[Field]) -> bool { - let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect(); - names.dedup(); - names.len() == fields.len() -} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 71a1fc791..d6960c3a8 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -4,7 +4,10 @@ pub mod attr; -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + convert::TryInto as _, +}; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; @@ -24,7 +27,9 @@ use crate::{ }, ScalarValueType, }, - util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, + util::{ + filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, RenameRule, + }, }; /// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait @@ -116,6 +121,14 @@ struct TraitMeta { /// [2]: https://spec.graphql.org/June2018/#sec-Objects external_downcasts: HashMap>, + /// 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 + rename_fields: Option>, + /// Indicator whether the generated code is intended to be used only inside the [`juniper`] /// library. is_internal: bool, @@ -214,6 +227,18 @@ impl Parse for TraitMeta { .insert(ty, dwncst_spanned) .none_or_else(|_| err::dup_arg(dwncst_span))? } + "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + output + .rename_fields + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } "internal" => { output.is_internal = true; } @@ -243,6 +268,7 @@ impl TraitMeta { 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, }) } @@ -250,12 +276,12 @@ impl TraitMeta { /// Parses [`TraitMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait /// definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let mut meta = filter_attrs(name, attrs) + let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - if let Some(as_dyn) = &meta.r#dyn { - if meta.r#enum.is_some() { + 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", @@ -263,11 +289,11 @@ impl TraitMeta { } } - if meta.description.is_none() { - meta.description = get_doc_comment(attrs); + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); } - Ok(meta) + Ok(attr) } } diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index 45e66ed98..242fa4b6b 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -1,13 +1,23 @@ //! Code generation for `#[derive(GraphQLObject)]` macro. +use std::mem; + use proc_macro2::TokenStream; use proc_macro_error::ResultExt as _; use quote::ToTokens; -use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; -use crate::{result::GraphQLScope, util::span_container::SpanContainer}; +use crate::{ + common::{ + field, + parse::{self, TypeExt as _}, + ScalarValueType, + }, + result::GraphQLScope, + util::{span_container::SpanContainer, RenameRule}, +}; -use super::{Definition, ObjectMeta}; +use super::{Attr, Definition}; /// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro. const ERR: GraphQLScope = GraphQLScope::ObjectDerive; @@ -17,39 +27,131 @@ pub fn expand(input: TokenStream) -> syn::Result { let ast = syn::parse2::(input).unwrap_or_abort(); match &ast.data { - Data::Struct(_) => expand_struct(ast), - _ => Err(ERR.custom_error(ast.span(), "can only be derived forstructs")), + syn::Data::Struct(_) => expand_struct(ast), + _ => Err(ERR.custom_error(ast.span(), "can only be derived for structs")), } .map(ToTokens::into_token_stream) } -/// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a Rust struct. +/// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a +/// Rust struct. fn expand_struct(ast: syn::DeriveInput) -> syn::Result { - let meta = ObjectMeta::from_attrs("graphql", &ast.attrs)?; + let attr = Attr::from_attrs("graphql", &ast.attrs)?; let struct_span = ast.span(); let struct_ident = ast.ident; - let name = meta + let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| struct_ident.unraw().to_string()); - if !meta.is_internal && name.starts_with("__") { + if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( - meta.name + attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| struct_ident.span()), ); } + let scalar = ScalarValueType::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![]; + if let syn::Data::Struct(data) = &ast.data { + if let syn::Fields::Named(fs) = &data.fields { + fields = fs + .iter() + .filter_map(|f| parse_field(f, &renaming)) + .collect(); + } else { + ERR.custom(struct_span, "only named fields are allowed") + .emit(); + } + } + + 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(); + Ok(Definition { name, ty: parse_quote! { #struct_ident }, generics: ast.generics, - description: meta.description.map(SpanContainer::into_inner), - context: meta.context.map(SpanContainer::into_inner), - scalar: meta.scalar.map(SpanContainer::into_inner), + description: attr.description.map(SpanContainer::into_inner), + context: attr.context.map(SpanContainer::into_inner), + scalar, + fields, + interfaces: attr + .interfaces + .iter() + .map(|ty| ty.as_deref().clone()) + .collect(), + }) +} + +/// Parses a [`field::Definition`] from the given Rust struct [`syn::Field`]. +/// +/// Returns [`None`] if parsing fails, or the struct field is ignored. +#[must_use] +fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option { + 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 field_ident = field.ident.as_ref().unwrap(); + + 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.unparenthesized().clone(); + ty.lifetimes_anonymized(); + + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr + .deprecated + .as_ref() + .map(|d| d.as_deref().map(syn::LitStr::value)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: field_ident.clone(), + arguments: None, + has_receiver: false, + is_async: false, }) } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 90d702606..338986aa7 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -30,22 +30,22 @@ use crate::{ }, }; -/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_object]`) -/// attribute when generating code for [GraphQL object][1] type. +/// Available arguments behind `#[graphql]` (or `#[graphql_object]`) attribute +/// when generating code for [GraphQL object][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[derive(Debug, Default)] -struct ObjectMeta { - /// Explicitly specified name of [GraphQL object][1] type. +struct Attr { + /// Explicitly specified name of this [GraphQL object][1] type. /// - /// If absent, then Rust type name is used by default. + /// If [`None`], then Rust type name is used by default. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects name: Option>, - /// Explicitly specified [description][2] of [GraphQL object][1] type. + /// Explicitly specified [description][2] of this [GraphQL object][1] type. /// - /// If absent, then Rust doc comment is used as [description][2], if any. + /// If [`None`], then Rust doc comment is used as [description][2], if any. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions @@ -54,7 +54,8 @@ struct ObjectMeta { /// Explicitly specified type of `juniper::Context` to use for resolving /// this [GraphQL object][1] type with. /// - /// If absent, then unit type `()` is assumed as type of `juniper::Context`. + /// If [`None`], then unit type `()` is assumed as a type of + /// `juniper::Context`. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects context: Option>, @@ -62,7 +63,7 @@ struct ObjectMeta { /// Explicitly specified type of `juniper::ScalarValue` to use for resolving /// this [GraphQL object][1] type with. /// - /// If absent, then generated code will be generic over any + /// If [`None`], then generated code will be generic over any /// `juniper::ScalarValue` type, which, in turn, requires all [object][1] /// fields to be generic over any `juniper::ScalarValue` type too. That's /// why this type should be specified only if one of the variants implements @@ -88,11 +89,11 @@ struct ObjectMeta { rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside - /// the `juniper` library. + /// the [`juniper`] library. is_internal: bool, } -impl Parse for ObjectMeta { +impl Parse for Attr { fn parse(input: ParseStream) -> syn::Result { let mut output = Self::default(); @@ -177,8 +178,8 @@ impl Parse for ObjectMeta { } } -impl ObjectMeta { - /// Tries to merge two [`ObjectMeta`]s into a single one, reporting about +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 { @@ -192,18 +193,18 @@ impl ObjectMeta { }) } - /// Parses [`ObjectMeta`] from the given multiple `name`d - /// [`syn::Attribute`]s placed on a struct or impl block definition. + /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a struct or impl block definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let mut meta = filter_attrs(name, attrs) + let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - if meta.description.is_none() { - meta.description = get_doc_comment(attrs); + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); } - Ok(meta) + Ok(attr) } } @@ -257,7 +258,7 @@ struct Definition { /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces - interfaces: Vec, + interfaces: HashSet, } impl ToTokens for Definition { diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 1fb1db9bd..eb279b13b 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -295,7 +295,7 @@ pub fn is_valid_name(field_name: &str) -> bool { } /// The different possible ways to change case of fields in a struct, or variants in an enum. -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum RenameRule { /// Don't apply a default rename rule. None, From 2fdaeddd28b3180ee31944e7cefdc48dddbfffc0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 27 Jul 2021 18:25:37 +0300 Subject: [PATCH 07/39] Impl #[derive(GraphQLObject)], break #[graphql_interface] --- .../src/codegen/derive_object.rs | 2 +- .../juniper_tests/src/codegen/mod.rs | 2 +- integration_tests/juniper_tests/src/lib.rs | 5 +- juniper/src/types/marker.rs | 6 +- juniper_codegen/src/common/field/arg.rs | 3 +- juniper_codegen/src/common/field/mod.rs | 24 +-- juniper_codegen/src/derive_object.rs | 137 ------------------ juniper_codegen/src/graphql_interface/attr.rs | 12 +- juniper_codegen/src/graphql_interface/mod.rs | 4 +- juniper_codegen/src/graphql_object/derive.rs | 15 +- juniper_codegen/src/graphql_object/mod.rs | 97 ++++++++----- juniper_codegen/src/lib.rs | 10 +- juniper_codegen/src/util/mod.rs | 8 +- 13 files changed, 106 insertions(+), 219 deletions(-) delete mode 100644 juniper_codegen/src/derive_object.rs diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index 2a5336aba..9f3a65017 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -61,7 +61,7 @@ struct SkippedFieldObj { } #[derive(GraphQLObject, Debug, PartialEq)] -#[graphql(rename = "none")] +#[graphql(rename_all = "none")] struct NoRenameObj { one_field: bool, another_field: i32, diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 0b45de8d3..5af0c834f 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -5,7 +5,7 @@ mod derive_object_with_raw_idents; mod derive_scalar; mod impl_object; mod impl_scalar; -mod interface_attr; +//mod interface_attr; mod scalar_value_transparent; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index 5fe40091d..e2a86e056 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -1,11 +1,13 @@ -#[cfg(test)] +/*#[cfg(test)] mod arc_fields; #[cfg(test)] mod array; + */ #[cfg(test)] mod codegen; #[cfg(test)] mod custom_scalar; +/* #[cfg(test)] mod explicit_null; #[cfg(test)] @@ -32,3 +34,4 @@ mod issue_925; mod issue_945; #[cfg(test)] mod pre_parse; +*/ diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 25bee26c1..b04a41906 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -35,7 +35,7 @@ pub trait GraphQLObject: GraphQLType { impl<'a, S, T> GraphQLObject for &T where - T: GraphQLInterface + ?Sized, + T: GraphQLObject + ?Sized, S: ScalarValue, { #[inline] @@ -46,7 +46,7 @@ where impl GraphQLObject for Box where - T: GraphQLInterface + ?Sized, + T: GraphQLObject + ?Sized, S: ScalarValue, { #[inline] @@ -57,7 +57,7 @@ where impl GraphQLObject for Arc where - T: GraphQLInterface + ?Sized, + T: GraphQLObject + ?Sized, S: ScalarValue, { #[inline] diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 258490540..9d4cc6840 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -8,7 +8,6 @@ use quote::quote; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, - parse_quote, spanned::Spanned, token, }; @@ -438,7 +437,7 @@ impl OnMethod { return None; } - Some(Self(OnField { + Some(Self::Regular(OnField { name, ty: argument.ty.as_ref().clone(), description: attr.description.as_ref().map(|d| d.as_ref().value()), diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 28ed02899..3d41eb3cd 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -24,7 +24,7 @@ use crate::{ util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, }; -pub(crate) use self::arg::{OnField as Argument, OnMethod as MethodArgument}; +pub(crate) use self::arg::OnMethod as MethodArgument; /// Available metadata (arguments) behind `#[graphql]` attribute placed on a /// [GraphQL field][1] definition. @@ -263,7 +263,7 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] pub(crate) fn is_method(&self) -> bool { - self.arguments.is_none() + self.arguments.is_some() } /// Returns generated code that panics about unknown [GraphQL field][1] @@ -311,8 +311,10 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] pub(crate) fn method_mark_tokens(&self, scalar: &ScalarValueType) -> TokenStream { - let args = self.arguments.unwrap_or_default(); - let args_marks = args.iter().filter_map(|a| a.method_mark_tokens(scalar)); + let args_marks = self + .arguments + .iter() + .flat_map(|args| args.iter().filter_map(|a| a.method_mark_tokens(scalar))); let ty = &self.ty; let resolved_ty = quote! { @@ -353,7 +355,7 @@ impl Definition { let args = self .arguments .iter() - .filter_map(MethodArgument::method_meta_tokens); + .flat_map(|args| args.iter().filter_map(MethodArgument::method_meta_tokens)); quote! { registry.field_convert::<#ty, _, Self::Context>(#name, info) @@ -379,7 +381,7 @@ impl Definition { return None; } - let (name, ty, ident) = (&self.name, &self.ty, &self.ident); + let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); let res = if self.is_method() { let args = self @@ -399,7 +401,8 @@ impl Definition { quote! { Self::#ident(#rcv #( #args ),*) } } } else { - quote! { self.#ident } + ty = parse_quote! { _ }; + quote! { &self.#ident } }; let resolving_code = gen::sync_resolving_code(); @@ -423,7 +426,7 @@ impl Definition { &self, trait_ty: Option<&syn::Type>, ) -> TokenStream { - let (name, ty, ident) = (&self.name, &self.ty, &self.ident); + let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); let mut fut = if self.is_method() { let args = self @@ -443,13 +446,14 @@ impl Definition { quote! { Self::#ident(#rcv #( #args ),*) } } } else { - quote! { self.#ident } + 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)); + let resolving_code = gen::async_resolving_code(Some(&ty)); quote! { #name => { diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs deleted file mode 100644 index 6d0af8fa4..000000000 --- a/juniper_codegen/src/derive_object.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::{ - result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer, RenameRule}, -}; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{self, ext::IdentExt, spanned::Spanned, Data, Fields}; - -pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result { - let ast_span = ast.span(); - let struct_fields = match ast.data { - Data::Struct(data) => match data.fields { - Fields::Named(fields) => fields.named, - _ => return Err(error.custom_error(ast_span, "only named fields are allowed")), - }, - _ => return Err(error.custom_error(ast_span, "can only be applied to structs")), - }; - - // 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 = struct_fields - .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(e) => { - proc_macro_error::emit_error!(e); - return None; - } - }; - - if field_attrs.skip.is_some() { - return None; - } - - let field_name = &field.ident.unwrap(); - let name = field_attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| { - attrs - .rename - .unwrap_or(RenameRule::CamelCase) - .apply(&field_name.unraw().to_string()) - }); - - 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, - ); - } - - let resolver_code = quote!( - &self . #field_name - ); - - Some(util::GraphQLTypeDefinitionField { - name, - _type: field.ty, - args: Vec::new(), - description: field_attrs.description.map(SpanContainer::into_inner), - deprecation: field_attrs.deprecation.map(SpanContainer::into_inner), - resolver_code, - default: None, - is_type_inferred: true, - is_async: false, - span, - }) - }) - .collect::>(); - - // Early abort after checking all fields - proc_macro_error::abort_if_dirty(); - - if let Some(duplicates) = - crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str()) - { - error.duplicate(duplicates.iter()); - } - - if !attrs.is_internal && name.starts_with("__") { - error.no_double_underscore(if let Some(name) = attrs.name { - name.span_ident() - } else { - ident.span() - }); - } - - if fields.is_empty() { - error.not_empty(ast_span); - } - - // Early abort after GraphQL properties - 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: attrs.scalar.map(SpanContainer::into_inner), - description: attrs.description.map(SpanContainer::into_inner), - fields, - generics: ast.generics, - interfaces: attrs - .interfaces - .into_iter() - .map(SpanContainer::into_inner) - .collect(), - include_type_generics: true, - generic_scalar: true, - no_async: attrs.no_async.is_some(), - }; - - Ok(definition.into_tokens()) -} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 6e51aac53..88e1222c1 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -17,8 +17,8 @@ use crate::{ }; use super::{ - inject_async_trait, Definition, EnumType, Field, FieldArgument, ImplMeta, Implementer, - ImplementerDowncast, TraitMeta, TraitObjectType, Type, + inject_async_trait, Definition, EnumType, ImplMeta, Implementer, ImplementerDowncast, + TraitMeta, TraitObjectType, Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -94,7 +94,7 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); - let renaming = attr + let renaming = meta .rename_fields .as_deref() .copied() @@ -140,7 +140,7 @@ pub fn expand_on_trait( .map(|c| c.as_ref().clone()) .or_else(|| { fields.iter().find_map(|f| { - f.arguments.and_then(|f| { + f.arguments.as_ref().and_then(|f| { f.iter() .find_map(field::MethodArgument::context_ty) .cloned() @@ -481,8 +481,8 @@ impl TraitMethod { let description = attr.description.as_ref().map(|d| d.as_ref().value()); let deprecated = attr .deprecated - .as_ref() - .map(|d| d.as_deref().map(syn::LitStr::value)); + .as_deref() + .map(|d| d.as_ref().map(syn::LitStr::value)); Some(field::Definition { name, diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index d6960c3a8..62244f6f0 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -27,9 +27,7 @@ use crate::{ }, ScalarValueType, }, - util::{ - filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, RenameRule, - }, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; /// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index 242fa4b6b..b8326701a 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -1,18 +1,12 @@ //! Code generation for `#[derive(GraphQLObject)]` macro. -use std::mem; - use proc_macro2::TokenStream; use proc_macro_error::ResultExt as _; use quote::ToTokens; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ - common::{ - field, - parse::{self, TypeExt as _}, - ScalarValueType, - }, + common::{field, parse::TypeExt as _, ScalarValueType}, result::GraphQLScope, util::{span_container::SpanContainer, RenameRule}, }; @@ -69,6 +63,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { if let syn::Data::Struct(data) = &ast.data { if let syn::Fields::Named(fs) = &data.fields { fields = fs + .named .iter() .filter_map(|f| parse_field(f, &renaming)) .collect(); @@ -100,7 +95,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { interfaces: attr .interfaces .iter() - .map(|ty| ty.as_deref().clone()) + .map(|ty| ty.as_ref().clone()) .collect(), }) } @@ -141,8 +136,8 @@ fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option (TokenStream, TokenStream, Option) { + let (_, ty_generics, _) = self.generics.split_for_impl(); + + let mut generics = self.generics.clone(); + + let scalar = &self.scalar; + if self.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 for_async { + generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Sync }); + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + ( + quote! { #impl_generics }, + quote! { #ty_generics }, + where_clause.cloned(), + ) + } + /// Returns generated code implementing [`GraphQLObject`] trait for this /// [GraphQL object][1]. /// @@ -282,10 +329,10 @@ impl Definition { fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.generics(); + let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let interface_tys: Vec<_> = self.interfaces.iter().map(|iface| &iface.ty).collect(); + let interface_tys: Vec<_> = self.interfaces.iter().collect(); let all_interfaces_unique = (interface_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); @@ -313,12 +360,12 @@ impl Definition { fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.generics(); + let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); - let interface_tys = self.interfaces.iter().map(|iface| &iface.ty); + let interface_tys = self.interfaces.iter(); quote! { #[automatically_derived] @@ -341,7 +388,7 @@ impl Definition { fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; @@ -356,7 +403,7 @@ impl Definition { .map(field::Definition::method_meta_tokens); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. - let mut interface_tys: Vec<_> = self.interfaces.iter().map(|iface| &iface.ty).collect(); + let mut interface_tys: Vec<_> = self.interfaces.iter().collect(); interface_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) @@ -405,7 +452,7 @@ impl Definition { let scalar = &self.scalar; let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; @@ -471,17 +518,8 @@ impl Definition { fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let mut where_clause = where_clause - .cloned() - .unwrap_or_else(|| parse_quote! { where }); - where_clause.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_generic() { - where_clause - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - let ty = &self.ty.ty_tokens(); + let (impl_generics, ty_generics, where_clause) = self.impl_generics(true); + let ty = &self.ty; let fields_resolvers = self .fields @@ -522,17 +560,8 @@ impl Definition { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let mut where_clause = where_clause - .cloned() - .unwrap_or_else(|| parse_quote! { where }); - where_clause.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_generic() { - where_clause - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - let ty = &self.ty.ty_tokens(); + let (impl_generics, ty_generics, where_clause) = self.impl_generics(true); + let ty = &self.ty; Some(quote! { #[automatically_derived] diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 28ee8e847..c3d314f70 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -108,7 +108,6 @@ macro_rules! try_merge_hashset { mod derive_enum; mod derive_input_object; -mod derive_object; mod derive_scalar_value; mod impl_object; mod impl_scalar; @@ -148,12 +147,9 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { #[proc_macro_error] #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(input: TokenStream) -> TokenStream { - let ast = syn::parse::(input).unwrap(); - let gen = derive_object::build_derive_object(ast, GraphQLScope::ObjectDerive); - match gen { - Ok(gen) => gen.into(), - Err(err) => proc_macro_error::abort!(err), - } + self::graphql_object::derive::expand(input.into()) + .unwrap_or_abort() + .into() } /// This custom derive macro implements the #[derive(GraphQLScalarValue)] diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index eb279b13b..eed57f0be 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -328,17 +328,17 @@ impl FromStr for RenameRule { } } -impl TryFrom<&syn::LitStr> for RenameRule { +impl TryFrom for RenameRule { type Error = syn::Error; - fn try_from(lit: &syn::LitStr) -> syn::Result { + fn try_from(lit: syn::LitStr) -> syn::Result { Self::from_str(&lit.value()).map_err(|_| syn::Error::new(lit.span(), "unknown rename rule")) } } impl Parse for RenameRule { fn parse(input: ParseStream) -> syn::Result { - Self::try_from(&input.parse::()?) + Self::try_from(input.parse::()?) } } @@ -1059,7 +1059,7 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::marker::GraphQLObjectType<#scalar> for #ty #type_generics_tokens #where_clause + impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #type_generics_tokens #where_clause { } impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens From 962d8e6ad8aeded69cfcc75ddf9e42deaca39bab Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 28 Jul 2021 11:46:42 +0300 Subject: [PATCH 08/39] Fix #[derive(GraphQLObject)] and #[graphql_interface] --- integration_tests/juniper_tests/src/codegen/mod.rs | 2 +- integration_tests/juniper_tests/src/lib.rs | 5 +---- juniper_codegen/src/graphql_interface/mod.rs | 4 +--- juniper_codegen/src/graphql_object/mod.rs | 14 +++++++------- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 5af0c834f..0b45de8d3 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -5,7 +5,7 @@ mod derive_object_with_raw_idents; mod derive_scalar; mod impl_object; mod impl_scalar; -//mod interface_attr; +mod interface_attr; mod scalar_value_transparent; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/lib.rs b/integration_tests/juniper_tests/src/lib.rs index e2a86e056..5fe40091d 100644 --- a/integration_tests/juniper_tests/src/lib.rs +++ b/integration_tests/juniper_tests/src/lib.rs @@ -1,13 +1,11 @@ -/*#[cfg(test)] +#[cfg(test)] mod arc_fields; #[cfg(test)] mod array; - */ #[cfg(test)] mod codegen; #[cfg(test)] mod custom_scalar; -/* #[cfg(test)] mod explicit_null; #[cfg(test)] @@ -34,4 +32,3 @@ mod issue_925; mod issue_945; #[cfg(test)] mod pre_parse; -*/ diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 62244f6f0..a7c4e9b62 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -467,9 +467,7 @@ impl Definition { let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { - Some(quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); }) - } + quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); quote! { diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index b9f7110ec..2d941c1ee 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -332,19 +332,19 @@ impl Definition { let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let interface_tys: Vec<_> = self.interfaces.iter().collect(); - let all_interfaces_unique = (interface_tys.len() > 1).then(|| { - quote! { - ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); - } - }); + let interface_tys = self.interfaces.iter(); + // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, + // but considering generics. + //let interface_tys: Vec<_> = self.interfaces.iter().collect(); + //let all_interfaces_unique = (interface_tys.len() > 1).then(|| { + // quote! { ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); } + //}); quote! { #[automatically_derived] impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause { fn mark() { - #all_interfaces_unique #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* } } From d0a8c84682dd996fd266056f70e2b47fda8d24f9 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 28 Jul 2021 14:41:13 +0300 Subject: [PATCH 09/39] Impl #[graphql_object], vol.1 --- juniper/src/executor_tests/directives.rs | 4 +- juniper/src/executor_tests/executor.rs | 364 ++--- .../src/executor_tests/introspection/mod.rs | 10 +- juniper/src/executor_tests/variables.rs | 11 +- juniper/src/macros/tests/args.rs | 1183 ++++------------- juniper/src/macros/tests/impl_object.rs | 25 +- juniper/src/macros/tests/object.rs | 20 +- juniper/src/macros/tests/union.rs | 2 +- juniper/src/parser/tests/value.rs | 7 +- juniper/src/schema/model.rs | 8 +- juniper/src/schema/schema.rs | 55 +- juniper/src/tests/fixtures/starwars/schema.rs | 23 +- juniper_codegen/src/common/field/arg.rs | 21 +- juniper_codegen/src/common/parse/mod.rs | 32 +- juniper_codegen/src/graphql_interface/attr.rs | 26 +- juniper_codegen/src/graphql_object/attr.rs | 227 +++- juniper_codegen/src/graphql_object/derive.rs | 3 +- juniper_codegen/src/impl_object.rs | 9 - juniper_codegen/src/lib.rs | 35 +- juniper_codegen/src/util/mod.rs | 389 ------ 20 files changed, 791 insertions(+), 1663 deletions(-) diff --git a/juniper/src/executor_tests/directives.rs b/juniper/src/executor_tests/directives.rs index 791fe2c68..50cc5a0ea 100644 --- a/juniper/src/executor_tests/directives.rs +++ b/juniper/src/executor_tests/directives.rs @@ -9,11 +9,11 @@ struct TestType; #[crate::graphql_object] impl TestType { - fn a() -> &str { + fn a() -> &'static str { "a" } - fn b() -> &str { + fn b() -> &'static str { "b" } } diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index fb45ba175..d8aa33b95 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -1,9 +1,9 @@ mod field_execution { use crate::{ ast::InputValue, + graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::Value, }; struct DataType; @@ -11,22 +11,22 @@ mod field_execution { #[crate::graphql_object] impl DataType { - fn a() -> &str { + fn a() -> &'static str { "Apple" } - fn b() -> &str { + fn b() -> &'static str { "Banana" } - fn c() -> &str { + fn c() -> &'static str { "Cookie" } - fn d() -> &str { + fn d() -> &'static str { "Donut" } - fn e() -> &str { + fn e() -> &'static str { "Egg" } - fn f() -> &str { + fn f() -> &'static str { "Fish" } @@ -41,13 +41,13 @@ mod field_execution { #[crate::graphql_object] impl DeepDataType { - fn a() -> &str { + fn a() -> &'static str { "Already Been Done" } - fn b() -> &str { + fn b() -> &'static str { "Boring" } - fn c() -> Vec> { + fn c() -> Vec> { vec![Some("Contrived"), None, Some("Confusing")] } @@ -64,30 +64,31 @@ mod field_execution { EmptySubscription::<()>::new(), ); let doc = r" - query Example($size: Int) { - a, - b, - x: c - ...c - f - ...on DataType { - pic(size: $size) - } - deep { - a - b - c - deeper { - a - b - } + query Example($size: Int) { + a, + b, + x: c + ...c + f + ...on DataType { + pic(size: $size) + } + deep { + a + b + c + deeper { + a + b + } + } } - } - fragment c on DataType { - d - e - }"; + fragment c on DataType { + d + e + } + "; let vars = vec![("size".to_owned(), InputValue::scalar(100))] .into_iter() @@ -103,60 +104,31 @@ mod field_execution { assert_eq!( result, - Value::object( - vec![ - ("a", Value::scalar("Apple")), - ("b", Value::scalar("Banana")), - ("x", Value::scalar("Cookie")), - ("d", Value::scalar("Donut")), - ("e", Value::scalar("Egg")), - ("f", Value::scalar("Fish")), - ("pic", Value::scalar("Pic of size: 100")), - ( - "deep", - Value::object( - vec![ - ("a", Value::scalar("Already Been Done")), - ("b", Value::scalar("Boring")), - ( - "c", - Value::list(vec![ - Value::scalar("Contrived"), - Value::null(), - Value::scalar("Confusing"), - ]), - ), - ( - "deeper", - Value::list(vec![ - Value::object( - vec![ - ("a", Value::scalar("Apple")), - ("b", Value::scalar("Banana")), - ] - .into_iter() - .collect(), - ), - Value::null(), - Value::object( - vec![ - ("a", Value::scalar("Apple")), - ("b", Value::scalar("Banana")), - ] - .into_iter() - .collect(), - ), - ]), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect() - ) + graphql_value!({ + "a": "Apple", + "b": "Banana", + "x": "Cookie", + "d": "Donut", + "e": "Egg", + "f": "Fish", + "pic": "ic of size: 100", + "deep": { + "a": "Already Been Done", + "b": "Boring", + "c": ["Contrived", None, "Confusing"], + "deeper": [ + { + "a": "Apple", + "b": "Banana", + }, + None, + { + "a": "Apple", + "b": "Banana", + }, + ], + }, + }), ); } } @@ -165,20 +137,19 @@ mod merge_parallel_fragments { use crate::{ schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::Value, }; struct Type; #[crate::graphql_object] impl Type { - fn a() -> &str { + fn a() -> &'static str { "Apple" } - fn b() -> &str { + fn b() -> &'static str { "Banana" } - fn c() -> &str { + fn c() -> &'static str { "Cherry" } fn deep() -> Type { @@ -194,15 +165,16 @@ mod merge_parallel_fragments { EmptySubscription::<()>::new(), ); let doc = r" - { a, ...FragOne, ...FragTwo } - fragment FragOne on Type { - b - deep { b, deeper: deep { b } } - } - fragment FragTwo on Type { - c - deep { c, deeper: deep { c } } - }"; + { a, ...FragOne, ...FragTwo } + fragment FragOne on Type { + b + deep { b, deeper: deep { b } } + } + fragment FragTwo on Type { + c + deep { c, deeper: deep { c } } + } + "; let vars = vec![].into_iter().collect(); @@ -216,37 +188,18 @@ mod merge_parallel_fragments { assert_eq!( result, - Value::object( - vec![ - ("a", Value::scalar("Apple")), - ("b", Value::scalar("Banana")), - ( - "deep", - Value::object( - vec![ - ("b", Value::scalar("Banana")), - ( - "deeper", - Value::object( - vec![ - ("b", Value::scalar("Banana")), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect(), - ), - ), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect(), - ), - ), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect() - ) + graphql_value!({ + "a": "Apple", + "b": "Banana", + "deep": { + "b": "Banana", + "deeper": { + "b": "Banana", + "c": "Cherry", + }, + "c": "Cherry", + }, + }), ); } } @@ -255,7 +208,6 @@ mod merge_parallel_inline_fragments { use crate::{ schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::Value, }; struct Type; @@ -263,13 +215,13 @@ mod merge_parallel_inline_fragments { #[crate::graphql_object] impl Type { - fn a() -> &str { + fn a() -> &'static str { "Apple" } - fn b() -> &str { + fn b() -> &'static str { "Banana" } - fn c() -> &str { + fn c() -> &'static str { "Cherry" } fn deep() -> Type { @@ -282,13 +234,13 @@ mod merge_parallel_inline_fragments { #[crate::graphql_object] impl Other { - fn a() -> &str { + fn a() -> &'static str { "Apple" } - fn b() -> &str { + fn b() -> &'static str { "Banana" } - fn c() -> &str { + fn c() -> &'static str { "Cherry" } fn deep() -> Type { @@ -307,29 +259,29 @@ mod merge_parallel_inline_fragments { EmptySubscription::<()>::new(), ); let doc = r" - { a, ...FragOne } - fragment FragOne on Type { - b - deep: deep { + { a, ...FragOne } + fragment FragOne on Type { b - deeper: other { - deepest: deep { - b - } - } - - ... on Type { - c + deep: deep { + b deeper: other { deepest: deep { - c + b + } + } + + ... on Type { + c + deeper: other { + deepest: deep { + c + } } } } + c } - - c - }"; + "; let vars = vec![].into_iter().collect(); @@ -343,61 +295,25 @@ mod merge_parallel_inline_fragments { assert_eq!( result, - Value::object( - vec![ - ("a", Value::scalar("Apple")), - ("b", Value::scalar("Banana")), - ( - "deep", - Value::object( - vec![ - ("b", Value::scalar("Banana")), - ( - "deeper", - Value::list(vec![ - Value::object( - vec![( - "deepest", - Value::object( - vec![ - ("b", Value::scalar("Banana")), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - Value::object( - vec![( - "deepest", - Value::object( - vec![ - ("b", Value::scalar("Banana")), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ]), - ), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect(), - ), - ), - ("c", Value::scalar("Cherry")), - ] - .into_iter() - .collect() - ) + graphql_value!({ + "a": "Apple", + "b": "Banana", + "deep": { + "b": "Banana", + "deeper": [{ + "deepest": { + "b": "Banana", + "c": "Cherry", + }, + "deepest": { + "b": "Banana", + "c": "Cherry", + }, + }], + "c": "Cherry", + }, + "c": "Cherry", + }), ); } } @@ -405,9 +321,9 @@ mod merge_parallel_inline_fragments { mod threads_context_correctly { use crate::{ executor::Context, + graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::Value, }; struct Schema; @@ -454,14 +370,7 @@ mod threads_context_correctly { println!("Result: {:#?}", result); - assert_eq!( - result, - Value::object( - vec![("a", Value::scalar("Context value"))] - .into_iter() - .collect() - ) - ); + assert_eq!(result, graphql_value!({"a": "Context value"})); } } @@ -475,6 +384,7 @@ mod dynamic_context_switching { schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::Value, + Executor, ScalarValue, }; struct Schema; @@ -494,7 +404,11 @@ mod dynamic_context_switching { #[graphql_object(context = OuterContext)] impl Schema { - fn item_opt(_context: &OuterContext, key: i32) -> Option<(&InnerContext, ItemRef)> { + fn item_opt<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, OuterContext, S>, + _context: &OuterContext, + key: i32, + ) -> Option<(&'e InnerContext, ItemRef)> { executor.context().items.get(&key).map(|c| (c, ItemRef)) } @@ -653,11 +567,9 @@ mod dynamic_context_switching { EmptyMutation::::new(), EmptySubscription::::new(), ); - let doc = r" - { + let doc = r"{ missing: itemRes(key: 2) { value } - } - "; + }"; let vars = vec![].into_iter().collect(); @@ -880,13 +792,13 @@ mod propagates_errors_to_nullable_fields { fn non_nullable_field() -> Inner { Inner } - fn nullable_error_field() -> FieldResult> { + fn nullable_error_field() -> FieldResult> { Err("Error for nullableErrorField")? } - fn non_nullable_error_field() -> FieldResult<&str> { + fn non_nullable_error_field() -> FieldResult<&'static str> { Err("Error for nonNullableErrorField")? } - fn custom_error_field() -> Result<&str, CustomError> { + fn custom_error_field() -> Result<&'static str, CustomError> { Err(CustomError::NotFound) } } @@ -1170,7 +1082,7 @@ mod named_operations { #[crate::graphql_object] impl Schema { - fn a(p: Option) -> &str { + fn a(p: Option) -> &'static str { let _ = p; "b" } diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 74403ff85..8bf6a14e6 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -56,13 +56,11 @@ impl Root { Sample::One } - #[graphql(arguments( - first(description = "The first number",), - second(description = "The second number", default = 123), - ))] - /// A sample scalar field on the object - fn sample_scalar(first: i32, second: i32) -> Scalar { + fn sample_scalar( + #[graphql(description = "The first number")] first: i32, + #[graphql(description = "The second number", default = 123)] second: i32, + ) -> Scalar { Scalar(first + second) } } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 76f73e014..3b810aa36 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -77,14 +77,9 @@ impl TestType { format!("{:?}", input) } - #[graphql( - arguments( - input( - default = "Hello World".to_string(), - ) - ) - )] - fn field_with_default_argument_value(input: String) -> String { + fn field_with_default_argument_value( + #[graphql(default = "Hello World".to_string())] input: String, + ) -> String { format!("{:?}", input) } diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index 9f5d038d8..e274f121d 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -1,43 +1,28 @@ -#![allow(unused)] - use crate::{ executor::Variables, + graphql_object, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Value}, - GraphQLInputObject, + DefaultScalarValue, Executor, GraphQLInputObject, ScalarValue, Value, }; -struct Root; - -/* - -Syntax to validate: - -* No args at all -* Executor arg vs. no executor arg -* Single arg vs. multi arg -* Trailing comma vs. no trailing comma -* Default value vs. no default value -* Complex default value -* Description vs. no description - -*/ - #[derive(GraphQLInputObject, Debug)] struct Point { x: i32, } -#[crate::graphql_object] +struct Root; + +#[graphql_object] impl Root { fn simple() -> i32 { 0 } - fn exec_arg(_executor: &Executor) -> i32 { + + fn exec_arg(_executor: &Executor<'_, '_, (), S>) -> i32 { 0 } - fn exec_arg_and_more(_executor: &Executor, arg: i32) -> i32 { + fn exec_arg_and_more(_executor: &Executor<'_, '_, (), S>, arg: i32) -> i32 { arg } @@ -53,122 +38,79 @@ impl Root { arg1 + arg2 } - #[graphql(arguments(arg(description = "The arg")))] - fn single_arg_descr(arg: i32) -> i32 { - arg - } - - #[graphql(arguments(r#arg(description = "The arg")))] - fn single_arg_descr_raw_idents(arg: i32) -> i32 { + fn single_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { arg } - #[graphql(arguments( - arg1(description = "The first arg",), - arg2(description = "The second arg") - ))] - fn multi_args_descr(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 + fn single_arg_descr_raw_idents(#[graphql(description = "The arg")] r#arg: i32) -> i32 { + r#arg } - #[graphql(arguments( - r#arg1(description = "The first arg",), - r#arg2(description = "The second arg") - ))] - fn multi_args_descr_raw_idents(arg1: i32, arg2: i32) -> i32 { + fn multi_args_descr( + #[graphql(description = "The first arg")] arg1: i32, + #[graphql(description = "The second arg")] arg2: i32, + ) -> i32 { arg1 + arg2 } - #[graphql(arguments( - arg1(description = "The first arg",), - arg2(description = "The second arg",) - ))] - fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 + fn multi_args_descr_raw_idents( + #[graphql(description = "The first arg")] r#arg1: i32, + #[graphql(description = "The second arg")] r#arg2: i32, + ) -> i32 { + r#arg1 + r#arg2 } fn attr_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { 0 } - fn attr_arg_descr_collapse( - #[graphql(description = "The first arg")] - #[graphql(description = "and more details")] - arg: i32, - ) -> i32 { - 0 - } - - #[graphql(arguments(arg(default = 123,),))] - fn arg_with_default(arg: i32) -> i32 { + fn arg_with_default(#[graphql(default = 123)] arg: i32) -> i32 { arg } - #[graphql(arguments(arg1(default = 123,), arg2(default = 456,)))] - fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 - } - - #[graphql(arguments(arg1(default = 123,), arg2(default = 456,),))] - fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default( + #[graphql(default = 123)] arg1: i32, + #[graphql(default = 456)] arg2: i32, + ) -> i32 { arg1 + arg2 } - #[graphql(arguments(arg(default = 123, description = "The arg")))] - fn arg_with_default_descr(arg: i32) -> i32 { - arg - } - - #[graphql(arguments(r#arg(default = 123, description = "The arg")))] - fn arg_with_default_descr_raw_ident(arg: i32) -> i32 { + fn arg_with_default_descr(#[graphql(default = 123, description = "The arg")] arg: i32) -> i32 { arg } - #[graphql(arguments( - arg1(default = 123, description = "The first arg"), - arg2(default = 456, description = "The second arg") - ))] - fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 - } - - #[graphql(arguments( - r#arg1(default = 123, description = "The first arg"), - r#arg2(default = 456, description = "The second arg") - ))] - fn multi_args_with_default_descr_raw_ident(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 + fn arg_with_default_descr_raw_ident( + #[graphql(default = 123, description = "The arg")] r#arg: i32, + ) -> i32 { + r#arg } - #[graphql(arguments( - arg1(default = 123, description = "The first arg",), - arg2(default = 456, description = "The second arg",) - ))] - fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_descr( + #[graphql(default = 123, description = "The first arg")] arg1: i32, + #[graphql(default = 456, description = "The second arg")] arg2: i32, + ) -> i32 { arg1 + arg2 } - #[graphql(arguments( - r#arg1(default = 123, description = "The first arg",), - r#arg2(default = 456, description = "The second arg",) - ))] - fn multi_args_with_default_trailing_comma_descr_raw_ident(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 + fn multi_args_with_default_descr_raw_ident( + #[graphql(default = 123, description = "The first arg")] r#arg1: i32, + #[graphql(default = 456, description = "The second arg")] r#arg2: i32, + ) -> i32 { + r#arg1 + r#arg2 } - #[graphql( - arguments( - arg1( - default = "test".to_string(), - description = "A string default argument", - ), - arg2( - default = Point{ x: 1 }, - description = "An input object default argument", - ) - ), - )] - fn args_with_complex_default(arg1: String, arg2: Point) -> i32 { + fn args_with_complex_default( + #[graphql( + default = "test".to_string(), + description = "A string default argument", + )] + arg1: String, + #[graphql( + default = Point { x: 1 }, + description = "An input object default argument", + )] + arg2: Point, + ) -> i32 { let _ = arg1; let _ = arg2; 0 @@ -179,8 +121,7 @@ async fn run_args_info_query(field_name: &str, f: F) where F: Fn(&Vec>) -> (), { - let doc = r#" - { + let doc = r#"{ __type(name: "Root") { fields { name @@ -197,8 +138,7 @@ where } } } - } - "#; + }"#; let schema = RootNode::new( Root {}, EmptyMutation::<()>::new(), @@ -261,7 +201,7 @@ async fn introspect_field_simple() { run_args_info_query("simple", |args| { assert_eq!(args.len(), 0); }) - .await; + .await } #[tokio::test] @@ -269,896 +209,349 @@ async fn introspect_field_exec_arg() { run_args_info_query("execArg", |args| { assert_eq!(args.len(), 0); }) - .await; + .await } #[tokio::test] async fn introspect_field_exec_arg_and_more() { run_args_info_query("execArgAndMore", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::null()), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": None, + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_single_arg() { run_args_info_query("singleArg", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::null()), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": None, + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args() { run_args_info_query("multiArgs", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::null()), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::null()), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": None, + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": None, + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args_trailing_comma() { run_args_info_query("multiArgsTrailingComma", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::null()), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::null()), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": None, + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": None, + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_single_arg_descr() { run_args_info_query("singleArgDescr", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": "The arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_single_arg_descr_raw_idents() { run_args_info_query("singleArgDescrRawIdents", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": "The arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args_descr() { run_args_info_query("multiArgsDescr", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": "The first arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": "The second arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args_descr_raw_idents() { run_args_info_query("multiArgsDescrRawIdents", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_field_multi_args_descr_trailing_comma() { - run_args_info_query("multiArgsDescrTrailingComma", |args| { - assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": "The first arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": "The second arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_attr_arg_descr() { run_args_info_query("attrArgDescr", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }); -} - -#[tokio::test] -async fn introspect_field_attr_arg_descr_collapse() { - run_args_info_query("attrArgDescrCollapse", |args| { - assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg\nand more details")), - ("defaultValue", Value::null()), - ( - "type", - Value::object( - vec![ - ("name", Value::null()), - ( - "ofType", - Value::object( - vec![("name", Value::scalar("Int"))].into_iter().collect(), - ), - ), - ] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": "The arg", + "defaultValue": None, + "type": { + "name": None, + "ofType": { "name": "Int" }, + }, + }))); + }) + .await } #[tokio::test] async fn introspect_field_arg_with_default() { run_args_info_query("argWithDefault", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::null()), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": None, + "defaultValue": "123", + "type": { + "name": "Int", + "ofType": None, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args_with_default() { run_args_info_query("multiArgsWithDefault", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::null()), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::null()), - ("defaultValue", Value::scalar("456")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_field_multi_args_with_default_trailing_comma() { - run_args_info_query("multiArgsWithDefaultTrailingComma", |args| { - assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::null()), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::null()), - ("defaultValue", Value::scalar("456")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": None, + "defaultValue": "123", + "type": { + "name": "Int", + "ofType": None, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": None, + "defaultValue": "456", + "type": { + "name": "Int", + "ofType": None, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_arg_with_default_descr() { run_args_info_query("argWithDefaultDescr", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg")), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": "The arg", + "defaultValue": "123", + "type": { + "name": "Int", + "ofType": None, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_arg_with_default_descr_raw_ident() { run_args_info_query("argWithDefaultDescrRawIdent", |args| { assert_eq!(args.len(), 1); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg")), - ("description", Value::scalar("The arg")), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg", + "description": "The arg", + "defaultValue": "123", + "type": { + "name": "Int", + "ofType": None, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args_with_default_descr() { run_args_info_query("multiArgsWithDefaultDescr", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::scalar("456")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": "The first arg", + "defaultValue": "123", + "type": { + "name": "Int", + "ofType": None, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": "The second arg", + "defaultValue": "456", + "type": { + "name": "Int", + "ofType": None, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_multi_args_with_default_descr_raw_ident() { run_args_info_query("multiArgsWithDefaultDescrRawIdent", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::scalar("456")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_field_multi_args_with_default_trailing_comma_descr() { - run_args_info_query("multiArgsWithDefaultTrailingCommaDescr", |args| { - assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::scalar("456")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_field_multi_args_with_default_trailing_comma_descr_raw_ident() { - run_args_info_query("multiArgsWithDefaultTrailingCommaDescrRawIdent", |args| { - assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("The first arg")), - ("defaultValue", Value::scalar("123")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ("description", Value::scalar("The second arg")), - ("defaultValue", Value::scalar("456")), - ( - "type", - Value::object( - vec![("name", Value::scalar("Int")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": "The first arg", + "defaultValue": "123", + "type": { + "name": "Int", + "ofType": None, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": "The second arg", + "defaultValue": "456", + "type": { + "name": "Int", + "ofType": None, + }, + }))); }) - .await; + .await } #[tokio::test] async fn introspect_field_args_with_complex_default() { run_args_info_query("argsWithComplexDefault", |args| { assert_eq!(args.len(), 2); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg1")), - ("description", Value::scalar("A string default argument")), - ("defaultValue", Value::scalar(r#""test""#)), - ( - "type", - Value::object( - vec![("name", Value::scalar("String")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); - - assert!(args.contains(&Value::object( - vec![ - ("name", Value::scalar("arg2")), - ( - "description", - Value::scalar("An input object default argument"), - ), - ("defaultValue", Value::scalar(r#"{x: 1}"#)), - ( - "type", - Value::object( - vec![("name", Value::scalar("Point")), ("ofType", Value::null())] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ))); + assert!(args.contains(&graphql_value!({ + "name": "arg1", + "description": "A string default argument", + "defaultValue": r#""test""#, + "type": { + "name": "String", + "ofType": None, + }, + }))); + assert!(args.contains(&graphql_value!({ + "name": "arg2", + "description": "An input object default argument", + "defaultValue": r#"{x: 1}"#, + "type": { + "name": "Point", + "ofType": None, + }, + }))); }) - .await; + .await } diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index d82607da7..336371e7f 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -1,5 +1,8 @@ +use crate::{ + graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, RootNode, +}; + use super::util; -use crate::{graphql_value, EmptyMutation, EmptySubscription, RootNode}; #[derive(Default)] struct Context { @@ -34,14 +37,12 @@ struct Query { } #[crate::graphql_object( - scalar = crate::DefaultScalarValue, - name = "Query", + name = "Query", + scalar = DefaultScalarValue, context = Context, - // FIXME: make async work - noasync )] /// Query Description. -impl<'a> Query { +impl Query { #[graphql(description = "With Self Description")] fn with_self(&self) -> bool { self.b @@ -51,11 +52,11 @@ impl<'a> Query { 100 } - fn with_executor(_exec: &Executor) -> bool { + fn with_executor(_executor: &Executor<'_, '_, Context>) -> bool { true } - fn with_executor_and_self(&self, _exec: &Executor) -> bool { + fn with_executor_and_self(&self, _executor: &Executor<'_, '_, Context>) -> bool { true } @@ -86,13 +87,11 @@ impl<'a> Query { arg1 } - #[graphql(arguments(default_arg(default = true)))] - fn default_argument(default_arg: bool) -> bool { + fn default_argument(#[graphql(default = true)] default_arg: bool) -> bool { default_arg } - #[graphql(arguments(arg(description = "my argument description")))] - fn arg_with_description(arg: bool) -> bool { + fn arg_with_description(#[graphql(description = "my argument description")] arg: bool) -> bool { arg } @@ -100,7 +99,7 @@ impl<'a> Query { WithContext } - fn with_lifetime_child(&self) -> WithLifetime<'a> { + fn with_lifetime_child(&self) -> WithLifetime<'static> { WithLifetime { value: "blub" } } diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs index 8a10ec644..8a5137276 100644 --- a/juniper/src/macros/tests/object.rs +++ b/juniper/src/macros/tests/object.rs @@ -8,8 +8,6 @@ Syntax to validate: * Nullable/fallible context switching */ -#![allow(dead_code)] - use std::marker::PhantomData; use crate::{ @@ -19,7 +17,7 @@ use crate::{ schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, Object, Value}, - GraphQLObject, + Executor, GraphQLObject, ScalarValue, }; struct CustomName; @@ -82,19 +80,27 @@ struct CtxSwitcher; #[graphql_object(context = InnerContext)] impl CtxSwitcher { - fn ctx_switch_always() -> (&InnerContext, InnerType) { + fn ctx_switch_always<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, InnerContext, S>, + ) -> (&'e InnerContext, InnerType) { (executor.context(), InnerType { a: 0 }) } - fn ctx_switch_opt() -> Option<(&InnerContext, InnerType)> { + fn ctx_switch_opt<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, InnerContext, S>, + ) -> Option<(&'e InnerContext, InnerType)> { Some((executor.context(), InnerType { a: 0 })) } - fn ctx_switch_res() -> FieldResult<(&InnerContext, InnerType)> { + fn ctx_switch_res<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, InnerContext, S>, + ) -> FieldResult<(&'e InnerContext, InnerType)> { Ok((executor.context(), InnerType { a: 0 })) } - fn ctx_switch_res_opt() -> FieldResult> { + fn ctx_switch_res_opt<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, InnerContext, S>, + ) -> FieldResult> { Ok(Some((executor.context(), InnerType { a: 0 }))) } } diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs index f36010b65..f796ecd2c 100644 --- a/juniper/src/macros/tests/union.rs +++ b/juniper/src/macros/tests/union.rs @@ -83,7 +83,7 @@ impl Root { fn custom_name() -> CustomName { CustomName::Concrete(Concrete) } - fn with_lifetime() -> WithLifetime<'_> { + fn with_lifetime(&self) -> WithLifetime<'_> { WithLifetime::Int(PhantomData) } fn with_generics() -> WithGenerics { diff --git a/juniper/src/parser/tests/value.rs b/juniper/src/parser/tests/value.rs index fe425cd1e..c8b1ac9b9 100644 --- a/juniper/src/parser/tests/value.rs +++ b/juniper/src/parser/tests/value.rs @@ -30,11 +30,8 @@ struct Foo { struct Query; -#[crate::graphql_object(Scalar = S)] -impl<'a, S> Query -where - S: crate::ScalarValue + 'a, -{ +#[crate::graphql_object] +impl Query { fn int_field() -> i32 { 42 } diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index b0516fe04..953904cb0 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -640,12 +640,8 @@ mod test { fn whatever() -> String { "foo".to_string() } - fn arr(stuff: Vec) -> Option<&str> { - if stuff.is_empty() { - None - } else { - Some("stuff") - } + fn arr(stuff: Vec) -> Option<&'static str> { + (!stuff.is_empty()).then(|| "stuff") } fn fruit() -> Fruit { Fruit::Apple diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index a877afe78..ae30223b9 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -131,11 +131,9 @@ where #[crate::graphql_object( name = "__Schema" - Context = SchemaType<'a, S>, - Scalar = S, + context = SchemaType<'a, S>, + scalar = S, internal, - // FIXME: make this redundant. - noasync, )] impl<'a, S> SchemaType<'a, S> where @@ -174,11 +172,9 @@ where #[crate::graphql_object( name = "__Type" - Context = SchemaType<'a, S>, - Scalar = S, + context = SchemaType<'a, S>, + scalar = S, internal, - // FIXME: make this redundant. - noasync, )] impl<'a, S> TypeType<'a, S> where @@ -206,8 +202,10 @@ where } } - #[graphql(arguments(include_deprecated(default = false)))] - fn fields(&self, include_deprecated: bool) -> Option>> { + fn fields( + &self, + #[graphql(default = false)] include_deprecated: bool, + ) -> Option>> { match *self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( @@ -292,8 +290,10 @@ where } } - #[graphql(arguments(include_deprecated(default = false)))] - fn enum_values(&self, include_deprecated: bool) -> Option> { + fn enum_values( + &self, + #[graphql(default = false)] include_deprecated: bool, + ) -> Option> { match *self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values @@ -308,11 +308,9 @@ where #[crate::graphql_object( name = "__Field", - Context = SchemaType<'a, S>, - Scalar = S, + context = SchemaType<'a, S>, + scalar = S, internal, - // FIXME: make this redundant. - noasync, )] impl<'a, S> Field<'a, S> where @@ -348,11 +346,9 @@ where #[crate::graphql_object( name = "__InputValue", - Context = SchemaType<'a, S>, - Scalar = S, + context = SchemaType<'a, S>, + scalar = S, internal, - // FIXME: make this redundant. - noasync, )] impl<'a, S> Argument<'a, S> where @@ -376,17 +372,8 @@ where } } -#[crate::graphql_object( - name = "__EnumValue", - Scalar = S, - internal, - // FIXME: make this redundant. - noasync, -)] -impl<'a, S> EnumValue -where - S: crate::ScalarValue + 'a, -{ +#[crate::graphql_object(name = "__EnumValue", internal)] +impl EnumValue { fn name(&self) -> &String { &self.name } @@ -406,11 +393,9 @@ where #[crate::graphql_object( name = "__Directive", - Context = SchemaType<'a, S>, - Scalar = S, + context = SchemaType<'a, S>, + scalar = S, internal, - // FIXME: make this redundant. - noasync, )] impl<'a, S> DirectiveType<'a, S> where diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 4b84568cb..28f7bd979 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -9,21 +9,26 @@ pub struct Query; #[graphql_object(context = Database)] /// The root query object of the schema impl Query { - #[graphql(arguments(id(description = "id of the human")))] - fn human(database: &Database, id: String) -> Option<&Human> { + fn human( + database: &Database, + #[graphql(description = "id of the human")] id: String, + ) -> Option<&Human> { database.get_human(&id) } - #[graphql(arguments(id(description = "id of the droid")))] - fn droid(database: &Database, id: String) -> Option<&Droid> { + fn droid( + database: &Database, + #[graphql(description = "id of the droid")] id: String, + ) -> Option<&Droid> { database.get_droid(&id) } - #[graphql(arguments(episode( - description = "If omitted, returns the hero of the whole saga. \ - If provided, returns the hero of that particular episode" - )))] - fn hero(database: &Database, episode: Option) -> Option { + fn hero( + database: &Database, + #[graphql(description = "If omitted, returns the hero of the whole saga. \ + If provided, returns the hero of that particular episode")] + episode: Option, + ) -> Option { Some(database.get_hero(episode)) } } diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 9d4cc6840..8acce515a 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -21,7 +21,7 @@ use crate::{ ScalarValueType, }, result::GraphQLScope, - util::{filter_attrs, path_eq_single, span_container::SpanContainer, to_camel_case}, + util::{filter_attrs, path_eq_single, span_container::SpanContainer, RenameRule}, }; /// Available metadata (arguments) behind `#[graphql]` attribute placed on a @@ -285,11 +285,6 @@ impl OnMethod { None } } - /// Returns this [`OnField`] argument's name, if it represents the one. - #[must_use] - pub(crate) fn name(&self) -> Option<&str> { - self.as_regular().map(|a| a.name.as_str()) - } /// Returns [`syn::Type`] of this [`OnMethod::Context`], if it represents /// the one. @@ -377,7 +372,11 @@ impl OnMethod { /// /// Returns [`None`] if parsing fails and emits parsing errors into the /// given `scope`. - pub(crate) fn parse(argument: &mut syn::PatType, scope: &GraphQLScope) -> Option { + pub(crate) fn parse( + argument: &mut syn::PatType, + renaming: &RenameRule, + scope: &GraphQLScope, + ) -> Option { let orig_attrs = argument.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. @@ -398,8 +397,10 @@ impl OnMethod { } if let syn::Pat::Ident(name) = &*argument.pat { let arg = match name.ident.unraw().to_string().as_str() { - "context" | "ctx" => Some(Self::Context(argument.ty.unreferenced().clone())), - "executor" => Some(Self::Executor), + "context" | "ctx" | "_context" | "_ctx" => { + Some(Self::Context(argument.ty.unreferenced().clone())) + } + "executor" | "_executor" => Some(Self::Executor), _ => None, }; if arg.is_some() { @@ -413,7 +414,7 @@ impl OnMethod { let name = if let Some(name) = attr.name.as_ref() { name.as_ref().value() } else if let syn::Pat::Ident(name) = &*argument.pat { - to_camel_case(&name.ident.unraw().to_string()) + renaming.apply(&name.ident.unraw().to_string()) } else { scope .custom( diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index c3ec54f7c..eb7bfa260 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -95,19 +95,25 @@ impl<'a> ParseBufferExt for ParseBuffer<'a> { /// Extension of [`syn::Type`] providing common function widely used by this crate for parsing. pub(crate) trait TypeExt { - /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested - /// [`syn::TypeParen`]s asap). + /// Retrieves the innermost non-parenthesized [`syn::Type`] from the given + /// one (unwraps nested [`syn::TypeParen`]s asap). #[must_use] fn unparenthesized(&self) -> &Self; - /// Retrieves the inner [`syn::Type`] from the given reference type, or just returns "as is" if - /// the type is not a reference. + /// Retrieves the inner [`syn::Type`] from the given reference type, or just + /// returns "as is" if the type is not a reference. /// /// Also, makes the type [`TypeExt::unparenthesized`], if possible. #[must_use] fn unreferenced(&self) -> &Self; + /// Anonymises all the lifetime parameters of this [`syn::Type`] making it + /// suitable for using in contexts with inferring. fn lifetimes_anonymized(&mut self); + + /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any. + #[must_use] + fn topmost_ident(&self) -> Option<&syn::Ident>; } impl TypeExt for syn::Type { @@ -202,6 +208,24 @@ 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::Path(p) => Some(&p.path), + syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() { + syn::TypeParamBound::Trait(b) => Some(&b.path), + _ => None, + }, + _ => None, + }, + _ => None, + }? + .segments + .last() + .map(|s| &s.ident) + } } /// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing. diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 88e1222c1..fff1534d3 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -136,8 +136,8 @@ pub fn expand_on_trait( let context = meta .context - .as_ref() - .map(|c| c.as_ref().clone()) + .as_deref() + .cloned() .or_else(|| { fields.iter().find_map(|f| { f.arguments.as_ref().and_then(|f| { @@ -240,12 +240,11 @@ pub fn expand_on_trait( Ok(quote! { #ast - #generated_code }) } -/// Expands `#[graphql_interface]` macro placed on trait implementation block. +/// Expands `#[graphql_interface]` macro placed on a trait implementation block. pub fn expand_on_impl( attrs: Vec, mut ast: syn::ItemImpl, @@ -467,7 +466,7 @@ impl TraitMethod { args_iter .filter_map(|arg| match arg { syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, &ERR), + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), }) .collect() }; @@ -500,34 +499,29 @@ impl TraitMethod { /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`. #[must_use] fn err_invalid_method_receiver(span: &S) -> Option { - ERR.custom( + ERR.emit_custom( span.span(), "trait method receiver can only be a shared reference `&self`", - ) - .emit(); - + ); None } /// Emits "no trait method receiver" [`syn::Error`] pointing to the given `span`. #[must_use] fn err_no_method_receiver(span: &S) -> Option { - ERR.custom( + ERR.emit_custom( span.span(), "trait method should have a shared reference receiver `&self`", - ) - .emit(); - + ); None } /// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given `span`. fn err_only_implementer_downcast(span: &S) { - ERR.custom( + ERR.emit_custom( span.span(), "downcasting is possible only to interface implementers", - ) - .emit(); + ); } /// Emits "duplicate downcast" [`syn::Error`] for the given `method` and `external` diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index b75a36a0b..e6c3ecbc3 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -1,6 +1,231 @@ //! Code generation for `#[graphql_object]` macro. -use crate::result::GraphQLScope; +use std::mem; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; + +use crate::{ + common::{ + field, + parse::{self, TypeExt as _}, + ScalarValueType, + }, + result::GraphQLScope, + util::{path_eq_single, span_container::SpanContainer, RenameRule}, +}; + +use super::{Attr, Definition}; /// [`GraphQLScope`] of errors for `#[graphql_object]` macro. const ERR: GraphQLScope = GraphQLScope::ObjectAttr; + +/// Expands `#[graphql_object]` macro into generated code. +pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { + if let Ok(mut ast) = syn::parse2::(body) { + if ast.trait_.is_none() { + let impl_attrs = parse::attr::unite(("graphql_object", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_object", ast.attrs); + return expand_on_impl(impl_attrs, ast); + } + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_object] attribute is applicable to non-trait `impl` blocks only", + )) +} + +/// Expands `#[graphql_object]` macro placed on an implementation block. +pub fn expand_on_impl( + attrs: Vec, + mut ast: syn::ItemImpl, +) -> syn::Result { + let attr = Attr::from_attrs("graphql_object", &attrs)?; + + let type_span = ast.self_ty.span(); + let type_ident = ast.self_ty.topmost_ident().ok_or_else(|| { + ERR.custom_error(type_span, "could not determine ident for the `impl` type") + })?; + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| type_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(|| type_ident.span()), + ); + } + + let scalar = ScalarValueType::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: Vec<_> = ast + .items + .iter_mut() + .filter_map(|item| { + if let syn::ImplItem::Method(m) = item { + parse_field(m, &renaming) + } else { + None + } + }) + .collect(); + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(type_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(type_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() + }) + }) + }); + + let generated_code = Definition { + name, + ty: parse_quote! { #type_ident }, + generics: ast.generics.clone(), + description: attr.description.map(SpanContainer::into_inner), + context, + scalar, + fields, + interfaces: attr + .interfaces + .iter() + .map(|ty| ty.as_ref().clone()) + .collect(), + }; + + Ok(quote! { + #ast + #generated_code + }) +} + +/// Parses a [`field::Definition`] from the given Rust [`syn::ImplItemMethod`]. +/// +/// Returns [`None`] if parsing fails, or the method field is ignored. +#[must_use] +fn parse_field( + method: &mut syn::ImplItemMethod, + renaming: &RenameRule, +) -> Option { + let method_attrs = method.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + method.attrs = mem::take(&mut method.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); + + let attr = field::Attr::from_attrs("graphql", &method_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; + + if attr.ignore.is_some() { + return None; + } + + let 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 let Some(arg) = method.sig.inputs.first() { + match arg { + 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); + } + } + } + } + } + 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! { () }, + 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 method receiver" [`syn::Error`] pointing to the given `span`. +#[must_use] +fn err_invalid_method_receiver(span: &S) -> Option { + ERR.emit_custom( + span.span(), + "method should have a shared reference receiver `&self`, or no receiver at all", + ); + None +} diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index b8326701a..a4d95bb35 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -68,8 +68,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { .filter_map(|f| parse_field(f, &renaming)) .collect(); } else { - ERR.custom(struct_span, "only named fields are allowed") - .emit(); + ERR.emit_custom(struct_span, "only named fields are allowed"); } } diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 9de0a7551..b7bdf3a30 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -8,15 +8,6 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ext::IdentExt, spanned::Spanned}; -/// Generate code for the juniper::graphql_object macro. -pub fn build_object(args: TokenStream, body: TokenStream, error: GraphQLScope) -> TokenStream { - let definition = match create(args, body, error) { - Ok(definition) => definition, - Err(err) => return err.to_compile_error(), - }; - definition.into_tokens() -} - /// Generate code for the juniper::graphql_subscription macro. pub fn build_subscription( args: TokenStream, diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index c3d314f70..21034f786 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -143,15 +143,6 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream { } } -// TODO -#[proc_macro_error] -#[proc_macro_derive(GraphQLObject, attributes(graphql))] -pub fn derive_object(input: TokenStream) -> TokenStream { - self::graphql_object::derive::expand(input.into()) - .unwrap_or_abort() - .into() -} - /// This custom derive macro implements the #[derive(GraphQLScalarValue)] /// derive. /// @@ -205,6 +196,16 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { } } +// TODO +#[proc_macro_error] +#[proc_macro_derive(GraphQLObject, attributes(graphql))] +pub fn derive_object(body: TokenStream) -> TokenStream { + self::graphql_object::derive::expand(body.into()) + .unwrap_or_abort() + .into() +} + +// TODO /** The `object` proc macro is the primary way of defining GraphQL resolvers that can not be implemented with the GraphQLObject derive. @@ -462,14 +463,10 @@ impl User { */ #[proc_macro_error] #[proc_macro_attribute] -pub fn graphql_object(args: TokenStream, input: TokenStream) -> TokenStream { - let args = proc_macro2::TokenStream::from(args); - let input = proc_macro2::TokenStream::from(input); - TokenStream::from(impl_object::build_object( - args, - input, - GraphQLScope::ObjectAttr, - )) +pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_object::attr::expand(attr.into(), body.into()) + .unwrap_or_abort() + .into() } /// Expose GraphQL scalars @@ -1213,8 +1210,8 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html #[proc_macro_error] #[proc_macro_derive(GraphQLUnion, attributes(graphql))] -pub fn derive_union(input: TokenStream) -> TokenStream { - self::graphql_union::derive::expand(input.into()) +pub fn derive_union(body: TokenStream) -> TokenStream { + self::graphql_union::derive::expand(body.into()) .unwrap_or_abort() .into() } diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index eed57f0be..2d97791e2 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -738,395 +738,6 @@ impl GraphQLTypeDefiniton { self.fields.iter().any(|field| field.is_async) } - pub fn into_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 field_definitions = self.fields.iter().map(|field| { - let args = field.args.iter().map(|arg| { - let arg_type = &arg._type; - let arg_name = &arg.name; - - let description = match arg.description.as_ref() { - Some(value) => quote!( .description( #value ) ), - None => quote!(), - }; - - // Code. - match arg.default.as_ref() { - Some(value) => quote!( - .argument( - registry.arg_with_default::<#arg_type>(#arg_name, &#value, info) - #description - ) - ), - None => quote!( - .argument( - registry.arg::<#arg_type>(#arg_name, info) - #description - ) - ), - } - }); - - let description = match field.description.as_ref() { - Some(description) => quote!( .description(#description) ), - None => quote!(), - }; - - let deprecation = match field.deprecation.as_ref() { - Some(deprecation) => { - if let Some(reason) = deprecation.reason.as_ref() { - quote!( .deprecated(Some(#reason)) ) - } else { - quote!( .deprecated(None) ) - } - } - None => quote!(), - }; - - let field_name = &field.name; - - let _type = &field._type; - quote! { - registry - .field_convert::<#_type, _, Self::Context>(#field_name, info) - #(#args)* - #description - #deprecation - } - }); - - 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 resolve_matches = self.fields.iter().map(|field| { - let name = &field.name; - let code = &field.resolver_code; - - if field.is_async { - quote!( - #name => { - panic!("Tried to resolve async field {} on type {:?} with a sync resolver", - #name, - >::name(_info) - ); - }, - ) - } else { - let _type = if field.is_type_inferred { - quote!() - } else { - let _type = &field._type; - quote!(: #_type) - }; - quote!( - #name => { - let res #_type = (|| { #code })(); - ::juniper::IntoResolvable::into( - res, - executor.context() - ) - .and_then(|res| { - match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - None => Ok(::juniper::Value::null()), - } - }) - }, - ) - } - }); - - let description = self - .description - .as_ref() - .map(|description| quote!( .description(#description) )); - - let interfaces = if !self.interfaces.is_empty() { - let interfaces_ty = &self.interfaces; - - Some(quote!( - .interfaces(&[ - #( registry.get_type::<#interfaces_ty>(&()) ,)* - ]) - )) - } else { - None - }; - - // Preserve the original type_generics before modification, - // since alteration makes them invalid if self.generic_scalar - // is specified. - let (_, type_generics, _) = self.generics.split_for_impl(); - - 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)); - generics - .make_where_clause() - .predicates - .push(parse_quote!(__S: ::juniper::ScalarValue)); - } - - let type_generics_tokens = if self.include_type_generics { - Some(type_generics) - } else { - None - }; - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - let resolve_field_async = { - let resolve_matches_async = self.fields.iter().map(|field| { - let name = &field.name; - let code = &field.resolver_code; - let _type = if field.is_type_inferred { - quote!() - } else { - let _type = &field._type; - quote!(: #_type) - }; - - if field.is_async { - quote!( - #name => { - let f = async move { - let res #_type = async move { #code }.await; - - let inner_res = ::juniper::IntoResolvable::into( - res, - executor.context() - ); - match inner_res { - Ok(Some((ctx, r))) => { - let subexec = executor - .replaced_context(ctx); - subexec.resolve_with_ctx_async(&(), &r) - .await - }, - Ok(None) => Ok(::juniper::Value::null()), - Err(e) => Err(e), - } - }; - Box::pin(f) - }, - ) - } else { - let inner = if !self.no_async { - quote!( - let f = async move { - match res2 { - Ok(Some((ctx, r))) => { - let sub = executor.replaced_context(ctx); - sub.resolve_with_ctx_async(&(), &r).await - }, - Ok(None) => Ok(::juniper::Value::null()), - Err(e) => Err(e), - } - }; - use ::juniper::futures::future; - future::FutureExt::boxed(f) - ) - } else { - quote!( - let v = match res2 { - Ok(Some((ctx, r))) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), - Ok(None) => Ok(::juniper::Value::null()), - Err(e) => Err(e), - }; - use ::juniper::futures::future; - Box::pin(future::ready(v)) - ) - }; - - quote!( - #name => { - let res #_type = (||{ #code })(); - let res2 = ::juniper::IntoResolvable::into( - res, - executor.context() - ); - #inner - }, - ) - } - }); - - 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 as_dyn_value = if !self.interfaces.is_empty() { - Some(quote! { - #[automatically_derived] - impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens - #where_async - { - type Context = >::Context; - type TypeInfo = >::TypeInfo; - - #[inline] - fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> { - self - } - - #[inline] - fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<#scalar, Self::Context, Self::TypeInfo> { - self - } - } - }) - } else { - None - }; - - quote!( - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #type_generics_tokens - #where_async - { - 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>> - where #scalar: Send + Sync, - { - use ::juniper::futures::future; - use ::juniper::GraphQLType; - match field { - #( #resolve_matches_async )* - _ => { - panic!("Field {} not found on type {:?}", - field, - >::name(info) - ); - } - } - } - } - - #as_dyn_value - ) - }; - - let marks = self.fields.iter().map(|field| { - let field_marks = field.args.iter().map(|arg| { - let arg_ty = &arg._type; - quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } - }); - - let field_ty = &field._type; - let resolved_ty = quote! { - <#field_ty as ::juniper::IntoResolvable< - '_, #scalar, _, >::Context, - >>::Type - }; - - quote! { - #( #field_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - } - }); - - let output = quote!( - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause { - fn mark() { - #( #marks )* - } - } - - impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #type_generics_tokens #where_clause - { } - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens - #where_clause - { - fn name(_: &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::meta::MetaType<'r, #scalar> - where #scalar : 'r, - { - let fields = [ - #( #field_definitions ),* - ]; - let meta = registry.build_object_type::<#ty>(info, &fields) - #description - #interfaces; - meta.into_meta() - } - } - - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #type_generics_tokens - #where_clause - { - type Context = #context; - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - #[allow(unused_variables)] - #[allow(unused_mut)] - fn resolve_field( - &self, - _info: &(), - field: &str, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - match field { - #( #resolve_matches )* - _ => { - panic!("Field {} not found on type {:?}", - field, - >::name(_info) - ); - } - } - } - - - fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String { - #name.to_string() - } - - } - - #resolve_field_async - ); - output - } - pub fn into_subscription_tokens(self) -> TokenStream { let name = &self.name; let ty = &self._type; From 0ea37a30a6be3bf002dc9c287bb56a02607a5e46 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 28 Jul 2021 18:48:04 +0300 Subject: [PATCH 10/39] Impl #[graphql_object], vol.2 --- juniper/src/macros/tests/impl_object.rs | 15 +- juniper/src/schema/meta.rs | 32 ++--- juniper/src/schema/schema.rs | 132 +++++++++--------- juniper/src/tests/fixtures/starwars/schema.rs | 6 +- juniper_codegen/src/graphql_interface/mod.rs | 2 + juniper_codegen/src/graphql_object/mod.rs | 28 +++- 6 files changed, 111 insertions(+), 104 deletions(-) diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 336371e7f..3a63bf20f 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -1,5 +1,6 @@ use crate::{ - graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, RootNode, + graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, + RootNode, }; use super::util; @@ -15,7 +16,7 @@ struct WithLifetime<'a> { value: &'a str, } -#[crate::graphql_object(Context=Context)] +#[graphql_object(context = Context)] impl<'a> WithLifetime<'a> { fn value(&'a self) -> &'a str { self.value @@ -24,7 +25,7 @@ impl<'a> WithLifetime<'a> { struct WithContext; -#[crate::graphql_object(Context=Context)] +#[graphql_object(context = Context)] impl WithContext { fn ctx(ctx: &Context) -> bool { ctx.flag1 @@ -36,7 +37,7 @@ struct Query { b: bool, } -#[crate::graphql_object( +#[graphql_object( name = "Query", scalar = DefaultScalarValue, context = Context, @@ -114,7 +115,7 @@ impl Query { #[derive(Default)] struct Mutation; -#[crate::graphql_object(context = Context)] +#[graphql_object(context = Context)] impl Mutation { fn empty() -> bool { true @@ -124,7 +125,7 @@ impl Mutation { #[derive(Default)] struct Subscription; -#[crate::graphql_object(context = Context)] +#[graphql_object(context = Context)] impl Subscription { fn empty() -> bool { true @@ -136,7 +137,7 @@ async fn object_introspect() { let res = util::run_info_query::("Query").await; assert_eq!( res, - crate::graphql_value!({ + graphql_value!({ "name": "Query", "description": "Query Description.", "fields": [ diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index beb8e0ac7..0f2f965db 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -32,10 +32,10 @@ impl DeprecationStatus { } /// An optional reason for the deprecation, or none if `Current`. - pub fn reason(&self) -> Option<&String> { + pub fn reason(&self) -> Option<&str> { match self { DeprecationStatus::Current => None, - DeprecationStatus::Deprecated(ref reason) => reason.as_ref(), + DeprecationStatus::Deprecated(rsn) => rsn.as_deref(), } } } @@ -236,26 +236,14 @@ impl<'a, S> MetaType<'a, S> { /// Access the description of the type, if applicable /// /// Lists, nullable wrappers, and placeholders don't have names. - pub fn description(&self) -> Option<&String> { - match *self { - MetaType::Scalar(ScalarMeta { - ref description, .. - }) - | MetaType::Object(ObjectMeta { - ref description, .. - }) - | MetaType::Enum(EnumMeta { - ref description, .. - }) - | MetaType::Interface(InterfaceMeta { - ref description, .. - }) - | MetaType::Union(UnionMeta { - ref description, .. - }) - | MetaType::InputObject(InputObjectMeta { - ref description, .. - }) => description.as_ref(), + pub fn description(&self) -> Option<&str> { + match self { + MetaType::Scalar(ScalarMeta { description, .. }) + | MetaType::Object(ObjectMeta { description, .. }) + | MetaType::Enum(EnumMeta { description, .. }) + | MetaType::Interface(InterfaceMeta { description, .. }) + | MetaType::Union(UnionMeta { description, .. }) + | MetaType::InputObject(InputObjectMeta { description, .. }) => description.as_deref(), _ => None, } } diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index ae30223b9..f514ffedc 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -1,6 +1,7 @@ use crate::{ ast::Selection, executor::{ExecutionResult, Executor, Registry}, + graphql_object, types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, @@ -129,16 +130,13 @@ where } } -#[crate::graphql_object( +#[graphql_object( name = "__Schema" context = SchemaType<'a, S>, scalar = S, internal, )] -impl<'a, S> SchemaType<'a, S> -where - S: crate::ScalarValue + 'a, -{ +impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> { fn types(&self) -> Vec> { self.type_list() .into_iter() @@ -150,18 +148,21 @@ where }) .unwrap_or(false) }) - .collect::>() + .collect() } - fn query_type(&self) -> TypeType { + #[graphql(name = "query_type")] + fn query_type_(&self) -> TypeType { self.query_type() } - fn mutation_type(&self) -> Option> { + #[graphql(name = "mutation_type")] + fn mutation_type_(&self) -> Option> { self.mutation_type() } - fn subscription_type(&self) -> Option> { + #[graphql(name = "subscription_type")] + fn subscription_type_(&self) -> Option> { self.subscription_type() } @@ -170,32 +171,29 @@ where } } -#[crate::graphql_object( +#[graphql_object( name = "__Type" context = SchemaType<'a, S>, scalar = S, internal, )] -impl<'a, S> TypeType<'a, S> -where - S: crate::ScalarValue + 'a, -{ +impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn name(&self) -> Option<&str> { - match *self { + match self { TypeType::Concrete(t) => t.name(), _ => None, } } - fn description(&self) -> Option<&String> { - match *self { + fn description(&self) -> Option<&str> { + match self { TypeType::Concrete(t) => t.description(), _ => None, } } fn kind(&self) -> TypeKind { - match *self { + match self { TypeType::Concrete(t) => t.type_kind(), TypeType::List(..) => TypeKind::List, TypeType::NonNull(_) => TypeKind::NonNull, @@ -206,7 +204,7 @@ where &self, #[graphql(default = false)] include_deprecated: bool, ) -> Option>> { - match *self { + match self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( fields @@ -219,53 +217,53 @@ where } } - fn of_type(&self) -> Option<&Box>> { - match *self { + fn of_type(&self) -> Option<&TypeType> { + match self { TypeType::Concrete(_) => None, - TypeType::List(ref l, _) | TypeType::NonNull(ref l) => Some(l), + TypeType::List(l, _) | TypeType::NonNull(l) => Some(&*l), } } - fn input_fields(&self) -> Option<&Vec>> { - match *self { + fn input_fields(&self) -> Option<&[Argument]> { + match self { TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { ref input_fields, .. - })) => Some(input_fields), + })) => Some(input_fields.as_slice()), _ => None, } } - fn interfaces(&self, schema: &SchemaType<'a, S>) -> Option>> { - match *self { + fn interfaces<'s>(&self, context: &'s SchemaType<'a, S>) -> Option>> { + match self { TypeType::Concrete(&MetaType::Object(ObjectMeta { ref interface_names, .. })) => Some( interface_names .iter() - .filter_map(|n| schema.type_by_name(n)) + .filter_map(|n| context.type_by_name(n)) .collect(), ), _ => None, } } - fn possible_types(&self, schema: &SchemaType<'a, S>) -> Option>> { - match *self { + fn possible_types<'s>(&self, context: &'s SchemaType<'a, S>) -> Option>> { + match self { TypeType::Concrete(&MetaType::Union(UnionMeta { ref of_type_names, .. })) => Some( of_type_names .iter() - .filter_map(|tn| schema.type_by_name(tn)) + .filter_map(|tn| context.type_by_name(tn)) .collect(), ), TypeType::Concrete(&MetaType::Interface(InterfaceMeta { name: ref iface_name, .. })) => Some( - schema + context .concrete_type_list() .iter() .filter_map(|&ct| { @@ -276,7 +274,7 @@ where }) = *ct { if interface_names.contains(&iface_name.to_string()) { - schema.type_by_name(name) + context.type_by_name(name) } else { None } @@ -294,7 +292,7 @@ where &self, #[graphql(default = false)] include_deprecated: bool, ) -> Option> { - match *self { + match self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values .iter() @@ -306,22 +304,20 @@ where } } -#[crate::graphql_object( +#[graphql_object( name = "__Field", context = SchemaType<'a, S>, scalar = S, internal, )] -impl<'a, S> Field<'a, S> -where - S: crate::ScalarValue + 'a, -{ +impl<'a, S: ScalarValue + 'a> Field<'a, S> { fn name(&self) -> String { self.name.clone().into() } - fn description(&self) -> &Option { - &self.description + #[graphql(name = "description")] + fn description_(&self) -> Option<&str> { + self.description.as_deref() } fn args(&self) -> Vec<&Argument> { @@ -331,7 +327,7 @@ where } #[graphql(name = "type")] - fn _type(&self, context: &SchemaType<'a, S>) -> TypeType { + fn type_<'s>(&self, context: &'s SchemaType<'a, S>) -> TypeType<'s, S> { context.make_type(&self.field_type) } @@ -339,81 +335,79 @@ where self.deprecation_status.is_deprecated() } - fn deprecation_reason(&self) -> Option<&String> { + fn deprecation_reason(&self) -> Option<&str> { self.deprecation_status.reason() } } -#[crate::graphql_object( +#[graphql_object( name = "__InputValue", context = SchemaType<'a, S>, scalar = S, internal, )] -impl<'a, S> Argument<'a, S> -where - S: crate::ScalarValue + 'a, -{ - fn name(&self) -> &String { +impl<'a, S: ScalarValue + 'a> Argument<'a, S> { + fn name(&self) -> &str { &self.name } - fn description(&self) -> &Option { - &self.description + #[graphql(name = "description")] + fn description_(&self) -> Option<&str> { + self.description.as_deref() } #[graphql(name = "type")] - fn _type(&self, context: &SchemaType<'a, S>) -> TypeType { + fn type_<'s>(&self, context: &'s SchemaType<'a, S>) -> TypeType<'s, S> { context.make_type(&self.arg_type) } - fn default_value(&self) -> Option { - self.default_value.as_ref().map(|v| format!("{}", v)) + #[graphql(name = "default_value")] + fn default_value_(&self) -> Option { + self.default_value.as_ref().map(ToString::to_string) } } -#[crate::graphql_object(name = "__EnumValue", internal)] +#[graphql_object(name = "__EnumValue", internal)] impl EnumValue { - fn name(&self) -> &String { + fn name(&self) -> &str { &self.name } - fn description(&self) -> &Option { - &self.description + #[graphql(name = "description")] + fn description_(&self) -> Option<&str> { + self.description.as_deref() } fn is_deprecated(&self) -> bool { self.deprecation_status.is_deprecated() } - fn deprecation_reason(&self) -> Option<&String> { + fn deprecation_reason(&self) -> Option<&str> { self.deprecation_status.reason() } } -#[crate::graphql_object( +#[graphql_object( name = "__Directive", context = SchemaType<'a, S>, scalar = S, internal, )] -impl<'a, S> DirectiveType<'a, S> -where - S: crate::ScalarValue + 'a, -{ - fn name(&self) -> &String { +impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> { + fn name(&self) -> &str { &self.name } - fn description(&self) -> &Option { - &self.description + #[graphql(name = "description")] + fn description_(&self) -> Option<&str> { + self.description.as_deref() } - fn locations(&self) -> &Vec { + fn locations(&self) -> &[DirectiveLocation] { &self.locations } - fn args(&self) -> &Vec> { + fn args(&self) -> &[Argument] { &self.arguments } diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 28f7bd979..3b220bdd5 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -10,21 +10,21 @@ pub struct Query; /// The root query object of the schema impl Query { fn human( - database: &Database, + #[graphql(context)] database: &Database, #[graphql(description = "id of the human")] id: String, ) -> Option<&Human> { database.get_human(&id) } fn droid( - database: &Database, + #[graphql(context)] database: &Database, #[graphql(description = "id of the droid")] id: String, ) -> Option<&Droid> { database.get_droid(&id) } fn hero( - database: &Database, + #[graphql(context)] database: &Database, #[graphql(description = "If omitted, returns the hero of the whole saga. \ If provided, returns the hero of that particular episode")] episode: Option, diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index a7c4e9b62..6ca667861 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -613,6 +613,7 @@ impl Definition { let regular_downcast = self.ty.method_resolve_into_type_tokens(); quote! { + #[allow(deprecated)] #[automatically_derived] impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { @@ -697,6 +698,7 @@ impl Definition { let regular_downcast = self.ty.method_resolve_into_type_async_tokens(); quote! { + #[allow(deprecated)] #[automatically_derived] impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 2d941c1ee..a7ce7c282 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -8,7 +8,7 @@ pub mod derive; use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, parse_quote, @@ -27,6 +27,7 @@ use crate::{ }, util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; +use syn::ext::IdentExt; /// Available arguments behind `#[graphql]` (or `#[graphql_object]`) attribute /// when generating code for [GraphQL object][1] type. @@ -300,10 +301,28 @@ impl Definition { } if for_async { + let self_ty = if 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.ty; + 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() @@ -433,7 +452,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_object_type::<#ty>(info, &fields) + registry.build_object_type::<#ty#ty_generics>(info, &fields) #description #interfaces .into_meta() @@ -474,6 +493,7 @@ impl Definition { let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); quote! { + #[allow(deprecated)] #[automatically_derived] impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics #where_clause { @@ -528,6 +548,7 @@ impl Definition { let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); quote! { + #[allow(deprecated, non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics #where_clause { @@ -564,6 +585,7 @@ impl Definition { let ty = &self.ty; Some(quote! { + #[allow(non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty#ty_generics #where_clause { From 29433795d845d1ac62a711d6c60ab4a2d0160b30 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 28 Jul 2021 22:35:39 +0300 Subject: [PATCH 11/39] Impl #[graphql_object], vol.3 --- juniper/src/executor_tests/executor.rs | 36 +- .../introspection/input_object.rs | 410 +++++------------- juniper/src/executor_tests/variables.rs | 4 +- juniper/src/integrations/chrono.rs | 72 ++- juniper/src/macros/tests/args.rs | 4 +- juniper/src/macros/tests/impl_object.rs | 2 +- juniper/src/macros/tests/impl_subscription.rs | 12 +- juniper/src/macros/tests/object.rs | 1 + juniper/src/schema/meta.rs | 2 +- juniper/src/schema/schema.rs | 4 +- juniper_codegen/src/common/parse/mod.rs | 69 +-- 11 files changed, 213 insertions(+), 403 deletions(-) diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index d8aa33b95..3d6f098dd 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -111,7 +111,7 @@ mod field_execution { "d": "Donut", "e": "Egg", "f": "Fish", - "pic": "ic of size: 100", + "pic": "Pic of size: 100", "deep": { "a": "Already Been Done", "b": "Boring", @@ -199,6 +199,7 @@ mod merge_parallel_fragments { }, "c": "Cherry", }, + "c": "Cherry", }), ); } @@ -305,6 +306,7 @@ mod merge_parallel_inline_fragments { "b": "Banana", "c": "Cherry", }, + }, { "deepest": { "b": "Banana", "c": "Cherry", @@ -599,7 +601,7 @@ mod dynamic_context_switching { assert_eq!( errs, vec![ExecutionError::new( - SourcePosition::new(25, 2, 12), + SourcePosition::new(14, 1, 12), &["missing"], FieldError::new("Could not find key 2", Value::null()), )] @@ -1072,18 +1074,18 @@ mod propagates_errors_to_nullable_fields { mod named_operations { use crate::{ + graphql_object, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::Value, GraphQLError, }; struct Schema; - #[crate::graphql_object] + #[graphql_object] impl Schema { fn a(p: Option) -> &'static str { - let _ = p; + drop(p); "b" } } @@ -1099,16 +1101,12 @@ mod named_operations { let vars = vec![].into_iter().collect(); - let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) + let (res, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); - - assert_eq!( - result, - Value::object(vec![("a", Value::scalar("b"))].into_iter().collect()) - ); + assert_eq!(res, graphql_value!({"a": "b"})); } #[tokio::test] @@ -1122,16 +1120,12 @@ mod named_operations { let vars = vec![].into_iter().collect(); - let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) + let (res, errs) = crate::execute(doc, None, &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); - - assert_eq!( - result, - Value::object(vec![("a", Value::scalar("b"))].into_iter().collect()) - ); + assert_eq!(res, graphql_value!({"a": "b"})); } #[tokio::test] @@ -1146,16 +1140,12 @@ mod named_operations { let vars = vec![].into_iter().collect(); - let (result, errs) = crate::execute(doc, Some("OtherExample"), &schema, &vars, &()) + let (res, errs) = crate::execute(doc, Some("OtherExample"), &schema, &vars, &()) .await .expect("Execution failed"); assert_eq!(errs, []); - - assert_eq!( - result, - Value::object(vec![("second", Value::scalar("b"))].into_iter().collect()) - ); + assert_eq!(res, graphql_value!({"second": "b"})); } #[tokio::test] diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs index 8e86553e8..5a37cdf35 100644 --- a/juniper/src/executor_tests/introspection/input_object.rs +++ b/juniper/src/executor_tests/introspection/input_object.rs @@ -3,6 +3,7 @@ use crate::{ ast::{FromInputValue, InputValue}, executor::Variables, + graphql_object, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, value::{DefaultScalarValue, Object, Value}, @@ -76,13 +77,13 @@ struct FieldDescription { #[derive(GraphQLInputObject, Debug)] struct FieldWithDefaults { - #[graphql(default = "123")] + #[graphql(default = 123)] field_one: i32, - #[graphql(default = "456", description = "The second field")] + #[graphql(default = 456, description = "The second field")] field_two: i32, } -#[crate::graphql_object] +#[graphql_object] impl Root { fn test_field( a1: DefaultName, @@ -149,8 +150,7 @@ where #[tokio::test] async fn default_name_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "DefaultName") { name description @@ -165,70 +165,35 @@ async fn default_name_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("DefaultName")) + Some(&graphql_value!("DefaultName")), ); assert_eq!( type_info.get_field_value("description"), - Some(&Value::null()) + Some(&graphql_value!(None)), ); assert_eq!(fields.len(), 2); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldTwo")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldTwo", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); }) .await; } @@ -256,8 +221,7 @@ fn default_name_input_value() { #[tokio::test] async fn no_trailing_comma_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "NoTrailingComma") { name description @@ -272,78 +236,42 @@ async fn no_trailing_comma_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("NoTrailingComma")) + Some(&graphql_value!("NoTrailingComma")), ); assert_eq!( type_info.get_field_value("description"), - Some(&Value::null()) + Some(&graphql_value!(None)), ); assert_eq!(fields.len(), 2); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldTwo")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldTwo", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); }) .await; } #[tokio::test] async fn derive_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "Derive") { name description @@ -358,45 +286,27 @@ async fn derive_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("Derive")) + Some(&graphql_value!("Derive")), ); assert_eq!( type_info.get_field_value("description"), - Some(&Value::null()) + Some(&graphql_value!(None)), ); assert_eq!(fields.len(), 1); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); }) .await; } @@ -408,7 +318,7 @@ fn derive_derived() { "{:?}", Derive { field_one: "test".to_owned(), - } + }, ), "Derive { field_one: \"test\" }" ); @@ -416,8 +326,7 @@ fn derive_derived() { #[tokio::test] async fn named_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "ANamedInputObject") { name description @@ -432,53 +341,34 @@ async fn named_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("ANamedInputObject")) + Some(&graphql_value!("ANamedInputObject")) ); assert_eq!( type_info.get_field_value("description"), - Some(&Value::null()) + Some(&graphql_value!(None)) ); assert_eq!(fields.len(), 1); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); }) .await; } #[tokio::test] async fn description_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "Description") { name description @@ -493,53 +383,34 @@ async fn description_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("Description")) + Some(&graphql_value!("Description")), ); assert_eq!( type_info.get_field_value("description"), - Some(&Value::scalar("Description for the input object")) + Some(&graphql_value!("Description for the input object")), ); assert_eq!(fields.len(), 1); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ("description", Value::null()), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "description": None, + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); }) .await; } #[tokio::test] async fn field_description_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "FieldDescription") { name description @@ -554,78 +425,42 @@ async fn field_description_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("FieldDescription")) + Some(&graphql_value!("FieldDescription")), ); assert_eq!( type_info.get_field_value("description"), - Some(&Value::null()) + Some(&graphql_value!(None)), ); assert_eq!(fields.len(), 2); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ("description", Value::scalar("The first field")), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldTwo")), - ("description", Value::scalar("The second field")), - ( - "type", - Value::object( - vec![( - "ofType", - Value::object( - vec![("name", Value::scalar("String"))] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ("defaultValue", Value::null()), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "description": "The first field", + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldTwo", + "description": "The second field", + "type": { + "ofType": {"name": "String"}, + }, + "defaultValue": None, + }))); }) .await; } #[tokio::test] async fn field_with_defaults_introspection() { - let doc = r#" - { + let doc = r#"{ __type(name: "FieldWithDefaults") { name inputFields { @@ -636,42 +471,25 @@ async fn field_with_defaults_introspection() { defaultValue } } - } - "#; + }"#; run_type_info_query(doc, |type_info, fields| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar("FieldWithDefaults")) + Some(&graphql_value!("FieldWithDefaults")), ); assert_eq!(fields.len(), 2); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldOne")), - ( - "type", - Value::object(vec![("name", Value::scalar("Int"))].into_iter().collect()), - ), - ("defaultValue", Value::scalar("123")), - ] - .into_iter() - .collect(), - ))); - - assert!(fields.contains(&Value::object( - vec![ - ("name", Value::scalar("fieldTwo")), - ( - "type", - Value::object(vec![("name", Value::scalar("Int"))].into_iter().collect()), - ), - ("defaultValue", Value::scalar("456")), - ] - .into_iter() - .collect(), - ))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldOne", + "type": {"name": "Int"}, + "defaultValue": "123", + }))); + assert!(fields.contains(&graphql_value!({ + "name": "fieldTwo", + "type": {"name": "Int"}, + "defaultValue": "456", + }))); }) .await; } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 3b810aa36..256892b6c 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -57,7 +57,7 @@ struct ExampleInputObject { #[derive(GraphQLInputObject, Debug)] struct InputWithDefaults { - #[graphql(default = "123")] + #[graphql(default = 123)] a: i32, } @@ -78,7 +78,7 @@ impl TestType { } fn field_with_default_argument_value( - #[graphql(default = "Hello World".to_string())] input: String, + #[graphql(default = "Hello World")] input: String, ) -> String { format!("{:?}", input) } diff --git a/juniper/src/integrations/chrono.rs b/juniper/src/integrations/chrono.rs index 03be9dda7..56613fdd6 100644 --- a/juniper/src/integrations/chrono.rs +++ b/juniper/src/integrations/chrono.rs @@ -253,72 +253,68 @@ mod integration_test { use crate::{ executor::Variables, + graphql_object, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, - value::Value, }; #[tokio::test] async fn test_serialization() { struct Root; - #[crate::graphql_object] + #[graphql_object] #[cfg(feature = "scalar-naivetime")] impl Root { - fn exampleNaiveDate() -> NaiveDate { + fn example_naive_date() -> NaiveDate { NaiveDate::from_ymd(2015, 3, 14) } - fn exampleNaiveDateTime() -> NaiveDateTime { + fn example_naive_date_time() -> NaiveDateTime { NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11) } - fn exampleNaiveTime() -> NaiveTime { + fn example_naive_time() -> NaiveTime { NaiveTime::from_hms(16, 7, 8) } - fn exampleDateTimeFixedOffset() -> DateTime { + fn example_date_time_fixed_offset() -> DateTime { DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() } - fn exampleDateTimeUtc() -> DateTime { + fn example_date_time_utc() -> DateTime { Utc.timestamp(61, 0) } } - #[crate::graphql_object] + #[graphql_object] #[cfg(not(feature = "scalar-naivetime"))] impl Root { - fn exampleNaiveDate() -> NaiveDate { + fn example_naive_date() -> NaiveDate { NaiveDate::from_ymd(2015, 3, 14) } - fn exampleNaiveDateTime() -> NaiveDateTime { + fn example_naive_date_time() -> NaiveDateTime { NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11) } - fn exampleDateTimeFixedOffset() -> DateTime { + fn example_date_time_fixed_offset() -> DateTime { DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap() } - fn exampleDateTimeUtc() -> DateTime { + fn example_date_time_utc() -> DateTime { Utc.timestamp(61, 0) } } #[cfg(feature = "scalar-naivetime")] - let doc = r#" - { + let doc = r#"{ exampleNaiveDate, exampleNaiveDateTime, exampleNaiveTime, exampleDateTimeFixedOffset, exampleDateTimeUtc, - } - "#; + }"#; #[cfg(not(feature = "scalar-naivetime"))] - let doc = r#" - { + let doc = r#"{ exampleNaiveDate, exampleNaiveDateTime, exampleDateTimeFixedOffset, exampleDateTimeUtc, - } - "#; + }"#; let schema = RootNode::new( Root, @@ -332,26 +328,26 @@ mod integration_test { 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, - Value::object( - vec![ - ("exampleNaiveDate", Value::scalar("2015-03-14")), - ("exampleNaiveDateTime", Value::scalar(1_467_969_011.0)), - #[cfg(feature = "scalar-naivetime")] - ("exampleNaiveTime", Value::scalar("16:07:08")), - ( - "exampleDateTimeFixedOffset", - Value::scalar("1996-12-19T16:39:57-08:00"), - ), - ( - "exampleDateTimeUtc", - Value::scalar("1970-01-01T00:01:01+00:00"), - ), - ] - .into_iter() - .collect() - ) + 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", + }), ); } } diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index e274f121d..d98fb807e 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -60,7 +60,7 @@ impl Root { r#arg1 + r#arg2 } - fn attr_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { + fn attr_arg_descr(#[graphql(description = "The arg")] _arg: i32) -> i32 { 0 } @@ -101,7 +101,7 @@ impl Root { fn args_with_complex_default( #[graphql( - default = "test".to_string(), + default = "test", description = "A string default argument", )] arg1: String, diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 3a63bf20f..9d2e1aebb 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -18,7 +18,7 @@ struct WithLifetime<'a> { #[graphql_object(context = Context)] impl<'a> WithLifetime<'a> { - fn value(&'a self) -> &'a str { + fn value(&self) -> &str { self.value } } diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs index de56473c2..e1670c0aa 100644 --- a/juniper/src/macros/tests/impl_subscription.rs +++ b/juniper/src/macros/tests/impl_subscription.rs @@ -2,7 +2,7 @@ use std::pin::Pin; use futures::StreamExt as _; -use crate::{graphql_value, EmptyMutation, RootNode, Value}; +use crate::{graphql_object, graphql_value, EmptyMutation, RootNode, Value}; use super::util; @@ -17,16 +17,16 @@ struct WithLifetime<'a> { value: &'a str, } -#[crate::graphql_object(Context = Context)] +#[graphql_object(Context = Context)] impl<'a> WithLifetime<'a> { - fn value(&'a self) -> &'a str { + fn value(&self) -> &str { self.value } } struct WithContext; -#[crate::graphql_object(Context = Context)] +#[graphql_object(Context = Context)] impl WithContext { fn ctx(ctx: &Context) -> bool { ctx.flag1 @@ -36,7 +36,7 @@ impl WithContext { #[derive(Default)] struct Query; -#[crate::graphql_object( +#[graphql_object( Context = Context, )] impl Query { @@ -48,7 +48,7 @@ impl Query { #[derive(Default)] struct Mutation; -#[crate::graphql_object(context = Context)] +#[graphql_object(context = Context)] impl Mutation { fn empty() -> bool { true diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs index 8a5137276..a7e279ca9 100644 --- a/juniper/src/macros/tests/object.rs +++ b/juniper/src/macros/tests/object.rs @@ -41,6 +41,7 @@ impl<'a> WithLifetime<'a> { } struct WithGenerics { + #[allow(dead_code)] data: T, } diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 0f2f965db..f5ac2c0f7 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -658,7 +658,7 @@ impl<'a, S> Field<'a, S> { impl<'a, S> Argument<'a, S> { #[doc(hidden)] pub fn new(name: &str, arg_type: Type<'a>) -> Self { - Argument { + Self { name: name.to_owned(), description: None, arg_type, diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index f514ffedc..8a2f5424b 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -202,7 +202,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn fields( &self, - #[graphql(default = false)] include_deprecated: bool, + #[graphql(default)] include_deprecated: bool, ) -> Option>> { match self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) @@ -290,7 +290,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { fn enum_values( &self, - #[graphql(default = false)] include_deprecated: bool, + #[graphql(default)] include_deprecated: bool, ) -> Option> { match self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index eb7bfa260..5b110ed8f 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -134,6 +134,34 @@ impl TypeExt for syn::Type { fn lifetimes_anonymized(&mut self) { use syn::{GenericArgument as GA, Type as T}; + fn anonymize_path(path: &mut syn::Path) { + for seg in path.segments.iter_mut() { + match &mut seg.arguments { + syn::PathArguments::AngleBracketed(angle) => { + for arg in angle.args.iter_mut() { + match arg { + GA::Lifetime(lt) => { + lt.ident = syn::Ident::new("_", Span::call_site()) + } + GA::Type(ty) => ty.lifetimes_anonymized(), + GA::Binding(b) => b.ty.lifetimes_anonymized(), + GA::Constraint(_) | GA::Const(_) => {} + } + } + } + syn::PathArguments::Parenthesized(args) => { + for ty in args.inputs.iter_mut() { + ty.lifetimes_anonymized() + } + if let syn::ReturnType::Type(_, ty) = &mut args.output { + (&mut *ty).lifetimes_anonymized() + } + } + syn::PathArguments::None => {} + } + } + } + match self { T::Array(syn::TypeArray { elem, .. }) | T::Group(syn::TypeGroup { elem, .. }) @@ -143,7 +171,7 @@ impl TypeExt for syn::Type { T::Tuple(syn::TypeTuple { elems, .. }) => { for ty in elems.iter_mut() { - ty.lifetimes_anonymized(); + ty.lifetimes_anonymized() } } @@ -154,8 +182,11 @@ impl TypeExt for syn::Type { syn::TypeParamBound::Lifetime(lt) => { lt.ident = syn::Ident::new("_", Span::call_site()) } - syn::TypeParamBound::Trait(_) => { - todo!("Anonymizing lifetimes in trait is not yet supported") + syn::TypeParamBound::Trait(bound) => { + if bound.lifetimes.is_some() { + todo!("Anonymizing HRTB lifetimes in trait is not yet supported") + } + anonymize_path(&mut bound.path) } } } @@ -165,41 +196,15 @@ impl TypeExt for syn::Type { if let Some(lt) = ref_ty.lifetime.as_mut() { lt.ident = syn::Ident::new("_", Span::call_site()); } - (&mut *ref_ty.elem).lifetimes_anonymized(); + (&mut *ref_ty.elem).lifetimes_anonymized() } - T::Path(ty) => { - for seg in ty.path.segments.iter_mut() { - match &mut seg.arguments { - syn::PathArguments::AngleBracketed(angle) => { - for arg in angle.args.iter_mut() { - match arg { - GA::Lifetime(lt) => { - lt.ident = syn::Ident::new("_", Span::call_site()); - } - GA::Type(ty) => ty.lifetimes_anonymized(), - GA::Binding(b) => b.ty.lifetimes_anonymized(), - GA::Constraint(_) | GA::Const(_) => {} - } - } - } - syn::PathArguments::Parenthesized(args) => { - for ty in args.inputs.iter_mut() { - ty.lifetimes_anonymized(); - } - if let syn::ReturnType::Type(_, ty) = &mut args.output { - (&mut *ty).lifetimes_anonymized(); - } - } - syn::PathArguments::None => {} - } - } - } + T::Path(ty) => anonymize_path(&mut ty.path), // 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 + // 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!(), From 4cf8d1b92c95f6f6353cbd83b1d50264e70157b0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 29 Jul 2021 17:14:14 +0300 Subject: [PATCH 12/39] Impl #[graphql_object], vol.4 --- integration_tests/juniper_tests/src/array.rs | 3 +- .../juniper_tests/src/codegen/derive_enum.rs | 11 +- .../src/codegen/derive_input_object.rs | 11 +- .../src/codegen/derive_object.rs | 266 +++++++----------- .../juniper_tests/src/codegen/impl_object.rs | 8 +- .../src/codegen/interface_attr.rs | 90 +++--- .../src/codegen/scalar_value_transparent.rs | 7 +- .../juniper_tests/src/explicit_null.rs | 63 +++-- .../juniper_tests/src/issue_371.rs | 117 +++----- .../juniper_tests/src/issue_398.rs | 8 +- .../juniper_tests/src/issue_500.rs | 16 +- juniper/src/ast.rs | 11 +- .../introspection/input_object.rs | 4 +- juniper/src/executor_tests/variables.rs | 2 +- juniper/src/macros/tests/args.rs | 6 +- juniper/src/schema/schema.rs | 18 +- juniper/src/types/base.rs | 2 +- juniper/src/types/nullable.rs | 4 +- juniper_codegen/src/common/field/arg.rs | 12 +- juniper_codegen/src/common/field/mod.rs | 18 +- juniper_codegen/src/common/mod.rs | 111 +------- juniper_codegen/src/common/scalar.rs | 173 ++++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 28 +- juniper_codegen/src/graphql_interface/mod.rs | 64 +++-- juniper_codegen/src/graphql_object/attr.rs | 4 +- juniper_codegen/src/graphql_object/derive.rs | 4 +- juniper_codegen/src/graphql_object/mod.rs | 24 +- juniper_codegen/src/graphql_union/mod.rs | 4 +- juniper_codegen/src/impl_object.rs | 3 +- juniper_codegen/src/util/mod.rs | 12 +- 30 files changed, 522 insertions(+), 582 deletions(-) create mode 100644 juniper_codegen/src/common/scalar.rs diff --git a/integration_tests/juniper_tests/src/array.rs b/integration_tests/juniper_tests/src/array.rs index 43cbd8ea4..aa1606511 100644 --- a/integration_tests/juniper_tests/src/array.rs +++ b/integration_tests/juniper_tests/src/array.rs @@ -145,8 +145,7 @@ mod as_input_argument { input[0] } - #[graphql(arguments(input(default = [true, false, false])))] - fn third(input: [bool; 3]) -> bool { + fn third(#[graphql(default = [true, false, false])] input: [bool; 3]) -> bool { input[2] } } diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index 47329b9b0..26b0f2e1f 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -69,7 +69,7 @@ fn test_derived_enum() { let meta = SomeEnum::meta(&(), &mut registry); assert_eq!(meta.name(), Some("Some")); - assert_eq!(meta.description(), Some(&"enum descr".to_string())); + assert_eq!(meta.description(), Some("enum descr")); // Test no rename variant. assert_eq!( @@ -102,24 +102,21 @@ fn test_derived_enum() { fn test_doc_comment() { let mut registry: Registry = Registry::new(FnvHashMap::default()); let meta = DocEnum::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"Enum doc.".to_string())); + assert_eq!(meta.description(), Some("Enum doc.")); } #[test] fn test_multi_doc_comment() { 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.".to_string()) - ); + 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 meta = OverrideDocEnum::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"enum override".to_string())); + assert_eq!(meta.description(), Some("enum override")); } fn test_context(_t: T) diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index cc1595f5e..9b4c72909 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -115,7 +115,7 @@ fn test_derived_input_object() { 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".to_string())); + assert_eq!(meta.description(), Some("input descr")); // Test default value injection. @@ -173,22 +173,19 @@ fn test_derived_input_object() { fn test_doc_comment() { let mut registry: Registry = Registry::new(FnvHashMap::default()); let meta = DocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"Object comment.".to_string())); + assert_eq!(meta.description(), Some("Object comment.")); } #[test] fn test_multi_doc_comment() { 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.".to_string()) - ); + 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 meta = OverrideDocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"obj override".to_string())); + assert_eq!(meta.description(), Some("obj override")); } diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index 9f3a65017..a5015449d 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -1,7 +1,7 @@ use fnv::FnvHashMap; use juniper::{ - execute, graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, - GraphQLType, Object, Registry, RootNode, Value, Variables, + execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, + GraphQLObject, GraphQLType, Object, Registry, RootNode, Value, Variables, }; #[derive(GraphQLObject, Debug, PartialEq)] @@ -131,7 +131,7 @@ impl Query { struct NoRenameQuery; -#[graphql_object(rename = "none")] +#[graphql_object(rename_all = "none")] impl NoRenameQuery { fn obj() -> Obj { Obj { @@ -152,13 +152,13 @@ impl NoRenameQuery { async fn test_doc_comment_simple() { let mut registry: Registry = Registry::new(FnvHashMap::default()); let meta = DocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"Object comment.".to_string())); + assert_eq!(meta.description(), Some("Object comment.")); check_descriptions( "DocComment", - &Value::scalar("Object comment."), + &graphql_value!("Object comment."), "regularField", - &Value::scalar("Field comment."), + &graphql_value!("Field comment."), ) .await; } @@ -167,16 +167,13 @@ async fn test_doc_comment_simple() { async fn test_multi_doc_comment() { 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.".to_string()) - ); + assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4.")); check_descriptions( "MultiDocComment", - &Value::scalar("Doc 1. Doc 2.\n\nDoc 4."), + &graphql_value!("Doc 1. Doc 2.\n\nDoc 4."), "regularField", - &Value::scalar("Field 1.\nField 2."), + &graphql_value!("Field 1.\nField 2."), ) .await; } @@ -185,13 +182,13 @@ async fn test_multi_doc_comment() { async fn test_doc_comment_override() { let mut registry: Registry = Registry::new(FnvHashMap::default()); let meta = OverrideDocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"obj override".to_string())); + assert_eq!(meta.description(), Some("obj override")); check_descriptions( "OverrideDocComment", - &Value::scalar("obj override"), + &graphql_value!("obj override"), "regularField", - &Value::scalar("field override"), + &graphql_value!("field override"), ) .await; } @@ -208,15 +205,14 @@ async fn test_derived_object() { let meta = Obj::meta(&(), &mut registry); assert_eq!(meta.name(), Some("MyObj")); - assert_eq!(meta.description(), Some(&"obj descr".to_string())); + assert_eq!(meta.description(), Some("obj descr")); - let doc = r#" - { - obj { - regularField - renamedField - } - }"#; + let doc = r#"{ + obj { + regularField + renamedField + } + }"#; let schema = RootNode::new( Query, @@ -227,35 +223,25 @@ async fn test_derived_object() { assert_eq!( execute(doc, None, &schema, &Variables::new(), &()).await, Ok(( - Value::object( - vec![( - "obj", - Value::object( - vec![ - ("regularField", Value::scalar(true)), - ("renamedField", Value::scalar(22)), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect() - ), - vec![] - )) + graphql_value!({ + "obj": { + "regularField": true, + "renamedField": 22, + }, + }), + vec![], + )), ); } #[tokio::test] #[should_panic] async fn test_cannot_query_skipped_field() { - let doc = r#" - { - skippedFieldObj { - skippedField - } - }"#; + let doc = r#"{ + skippedFieldObj { + skippedField + } + }"#; let schema = RootNode::new( Query, EmptyMutation::<()>::new(), @@ -268,12 +254,11 @@ async fn test_cannot_query_skipped_field() { #[tokio::test] async fn test_skipped_field_siblings_unaffected() { - let doc = r#" - { - skippedFieldObj { - regularField - } - }"#; + let doc = r#"{ + skippedFieldObj { + regularField + } + }"#; let schema = RootNode::new( Query, EmptyMutation::<()>::new(), @@ -286,15 +271,14 @@ async fn test_skipped_field_siblings_unaffected() { #[tokio::test] async fn test_derived_object_nested() { - let doc = r#" - { - nested { - obj { - regularField - renamedField - } + let doc = r#"{ + nested { + obj { + regularField + renamedField } - }"#; + } + }"#; let schema = RootNode::new( Query, @@ -305,46 +289,30 @@ async fn test_derived_object_nested() { assert_eq!( execute(doc, None, &schema, &Variables::new(), &()).await, Ok(( - Value::object( - vec![( - "nested", - Value::object( - vec![( - "obj", - Value::object( - vec![ - ("regularField", Value::scalar(false)), - ("renamedField", Value::scalar(333)), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect() - ), - vec![] - )) + graphql_value!({ + "nested": { + "obj": { + "regularField": false, + "renamedField": 333, + }, + }, + }), + vec![], + )), ); } #[tokio::test] async fn test_no_rename_root() { - let doc = r#" - { - no_rename_obj { - one_field - another_field - } - - obj { - regularField - } - }"#; + let doc = r#"{ + no_rename_obj { + one_field + another_field + } + obj { + regularField + } + }"#; let schema = RootNode::new( NoRenameQuery, @@ -355,45 +323,28 @@ async fn test_no_rename_root() { assert_eq!( execute(doc, None, &schema, &Variables::new(), &()).await, Ok(( - Value::object( - vec![ - ( - "no_rename_obj", - Value::object( - vec![ - ("one_field", Value::scalar(true)), - ("another_field", Value::scalar(146)), - ] - .into_iter() - .collect(), - ), - ), - ( - "obj", - Value::object( - vec![("regularField", Value::scalar(false)),] - .into_iter() - .collect(), - ), - ) - ] - .into_iter() - .collect() - ), - vec![] - )) + graphql_value!({ + "no_rename_obj": { + "one_field": true, + "another_field": 146, + }, + "obj": { + "regularField": false, + }, + }), + vec![], + )), ); } #[tokio::test] async fn test_no_rename_obj() { - let doc = r#" - { - noRenameObj { - one_field - another_field - } - }"#; + let doc = r#"{ + noRenameObj { + one_field + another_field + } + }"#; let schema = RootNode::new( Query, @@ -404,23 +355,14 @@ async fn test_no_rename_obj() { assert_eq!( execute(doc, None, &schema, &Variables::new(), &()).await, Ok(( - Value::object( - vec![( - "noRenameObj", - Value::object( - vec![ - ("one_field", Value::scalar(true)), - ("another_field", Value::scalar(146)), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect() - ), - vec![] - )) + graphql_value!({ + "noRenameObj": { + "one_field": true, + "another_field": 146, + }, + }), + vec![], + )), ); } @@ -431,37 +373,31 @@ async fn check_descriptions( field_value: &Value, ) { let doc = format!( - r#" - {{ - __type(name: "{}") {{ - name, - description, - fields {{ + r#"{{ + __type(name: "{}") {{ name description + fields {{ + name + description + }} }} - }} - }} - "#, - object_name + }}"#, + object_name, ); let _result = run_type_info_query(&doc, |(type_info, values)| { assert_eq!( type_info.get_field_value("name"), - Some(&Value::scalar(object_name)) + Some(&graphql_value!(object_name)) ); assert_eq!( type_info.get_field_value("description"), Some(object_description) ); - assert!(values.contains(&Value::object( - vec![ - ("name", Value::scalar(field_name)), - ("description", field_value.clone()), - ] - .into_iter() - .collect(), - ))); + assert!(values.contains(&graphql_value!({ + "name": (field_name.clone()), + "description": (field_value.clone()), + }))); }) .await; } diff --git a/integration_tests/juniper_tests/src/codegen/impl_object.rs b/integration_tests/juniper_tests/src/codegen/impl_object.rs index 7a7245ff6..474d7cc96 100644 --- a/integration_tests/juniper_tests/src/codegen/impl_object.rs +++ b/integration_tests/juniper_tests/src/codegen/impl_object.rs @@ -7,8 +7,7 @@ pub struct MyObject; #[graphql_object] impl MyObject { - #[graphql(arguments(arg(name = "test")))] - fn test(&self, arg: String) -> String { + fn test(&self, #[graphql(name = "test")] arg: String) -> String { arg } } @@ -104,9 +103,8 @@ mod raw_argument { #[graphql_object] impl Obj { - #[graphql(arguments(r#arg(description = "The only argument")))] - fn test(&self, arg: String) -> String { - arg + fn test(&self, #[graphql(desc = "The only argument")] r#arg: String) -> String { + r#arg } } diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index b85f4f195..29643499f 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -46,13 +46,13 @@ mod no_implers { struct QueryRoot; - #[graphql_object] + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] impl QueryRoot { fn character(&self) -> CharacterValue { unimplemented!() } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { unimplemented!() } } @@ -183,8 +183,8 @@ mod trivial { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -200,7 +200,7 @@ mod trivial { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -705,8 +705,8 @@ mod trivial_async { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -722,7 +722,7 @@ mod trivial_async { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -1068,8 +1068,8 @@ mod explicit_async { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -1085,7 +1085,7 @@ mod explicit_async { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -1318,8 +1318,8 @@ mod fallible_field { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -1335,7 +1335,7 @@ mod fallible_field { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -1584,8 +1584,8 @@ mod generic { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -1601,7 +1601,7 @@ mod generic { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -1828,8 +1828,8 @@ mod generic_async { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -1845,7 +1845,7 @@ mod generic_async { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -2072,8 +2072,8 @@ mod generic_lifetime_async { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue<'_, ()> { match self { Self::Human => Human { @@ -2089,7 +2089,7 @@ mod generic_lifetime_async { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -2299,8 +2299,8 @@ mod argument { struct QueryRoot; - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { Human { id: "human-32".to_string(), @@ -2309,7 +2309,7 @@ mod argument { .into() } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { Box::new(Human { id: "human-32".to_string(), home_planet: "earth".to_string(), @@ -3430,9 +3430,9 @@ mod explicit_generic_scalar { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { - fn character(&self) -> CharacterValue { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { match self { Self::Human => Human { id: "human-32".to_string(), @@ -3447,7 +3447,7 @@ mod explicit_generic_scalar { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -3698,8 +3698,8 @@ mod explicit_custom_context { Droid, } - #[graphql_object(context = CustomContext, scalar = S)] - impl QueryRoot { + #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -3715,7 +3715,7 @@ mod explicit_custom_context { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -3944,8 +3944,8 @@ mod inferred_custom_context_from_field { Droid, } - #[graphql_object(context = CustomContext, scalar = S)] - impl QueryRoot { + #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -3961,7 +3961,7 @@ mod inferred_custom_context_from_field { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -4196,8 +4196,8 @@ mod inferred_custom_context_from_downcast { Droid, } - #[graphql_object(context = Database, scalar = S)] - impl QueryRoot { + #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -4213,7 +4213,7 @@ mod inferred_custom_context_from_downcast { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -4806,8 +4806,8 @@ mod downcast_method { Droid, } - #[graphql_object(scalar = S)] - impl QueryRoot { + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -4823,7 +4823,7 @@ mod downcast_method { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), @@ -5071,8 +5071,8 @@ mod external_downcast { Droid, } - #[graphql_object(context = Database, scalar = S)] - impl QueryRoot { + #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { fn character(&self) -> CharacterValue { match self { Self::Human => Human { @@ -5088,7 +5088,7 @@ mod external_downcast { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), 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 087bc91c2..613d6394f 100644 --- a/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs +++ b/integration_tests/juniper_tests/src/codegen/scalar_value_transparent.rs @@ -63,10 +63,7 @@ fn test_scalar_value_custom() { let mut registry: Registry = Registry::new(FnvHashMap::default()); let meta = CustomUserId::meta(&(), &mut registry); assert_eq!(meta.name(), Some("MyUserId")); - assert_eq!( - meta.description(), - Some(&"custom description...".to_string()) - ); + assert_eq!(meta.description(), Some("custom description...")); let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap(); let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap(); @@ -81,5 +78,5 @@ fn test_scalar_value_custom() { fn test_scalar_value_doc_comment() { let mut registry: Registry = Registry::new(FnvHashMap::default()); let meta = IdWithDocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some(&"The doc comment...".to_string())); + assert_eq!(meta.description(), Some("The doc comment...")); } diff --git a/integration_tests/juniper_tests/src/explicit_null.rs b/integration_tests/juniper_tests/src/explicit_null.rs index 3ade4b528..0349f2621 100644 --- a/integration_tests/juniper_tests/src/explicit_null.rs +++ b/integration_tests/juniper_tests/src/explicit_null.rs @@ -1,4 +1,7 @@ -use juniper::*; +use juniper::{ + graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLInputObject, + InputValue, Nullable, +}; pub struct Context; @@ -6,12 +9,12 @@ impl juniper::Context for Context {} pub struct Query; -#[derive(juniper::GraphQLInputObject)] +#[derive(GraphQLInputObject)] struct ObjectInput { field: Nullable, } -#[graphql_object(Context=Context)] +#[graphql_object(context = Context)] impl Query { fn is_explicit_null(arg: Nullable) -> bool { arg.is_explicit_null() @@ -26,40 +29,38 @@ type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySub #[tokio::test] async fn explicit_null() { - let ctx = Context; - let query = r#" - query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) { - literalOneIsExplicitNull: isExplicitNull(arg: 1) - literalNullIsExplicitNull: isExplicitNull(arg: null) - noArgIsExplicitNull: isExplicitNull - literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1}) - literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null}) - noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {}) - emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj) - literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj) - } + query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) { + literalOneIsExplicitNull: isExplicitNull(arg: 1) + literalNullIsExplicitNull: isExplicitNull(arg: null) + noArgIsExplicitNull: isExplicitNull + literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1}) + literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null}) + noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {}) + emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj) + literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj) + } "#; + let schema = &Schema::new( + Query, + EmptyMutation::::new(), + EmptySubscription::::new(), + ); + let vars = [ + ("emptyObj".to_string(), InputValue::Object(vec![])), + ( + "literalNullObj".to_string(), + InputValue::object(vec![("field", InputValue::null())].into_iter().collect()), + ), + ]; + let (data, errors) = juniper::execute( query, None, - &Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ), - &[ - ("emptyObj".to_string(), InputValue::Object(vec![])), - ( - "literalNullObj".to_string(), - InputValue::object(vec![("field", InputValue::null())].into_iter().collect()), - ), - ] - .iter() - .cloned() - .collect(), - &ctx, + &schema, + &vars.iter().cloned().collect(), + &Context, ) .await .unwrap(); diff --git a/integration_tests/juniper_tests/src/issue_371.rs b/integration_tests/juniper_tests/src/issue_371.rs index d4085e60d..f8abb271e 100644 --- a/integration_tests/juniper_tests/src/issue_371.rs +++ b/integration_tests/juniper_tests/src/issue_371.rs @@ -1,7 +1,8 @@ // Original author of this test is . use juniper::{ - graphql_object, EmptyMutation, EmptySubscription, LookAheadMethods as _, RootNode, Variables, + graphql_object, EmptyMutation, EmptySubscription, Executor, LookAheadMethods as _, RootNode, + ScalarValue, Variables, }; pub struct Context; @@ -12,14 +13,14 @@ pub struct Query; #[graphql_object(context = Context)] impl Query { - fn users(exec: &Executor) -> Vec { - let lh = exec.look_ahead(); + fn users<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec { + let lh = executor.look_ahead(); assert_eq!(lh.field_name(), "users"); vec![User] } - fn countries(exec: &Executor) -> Vec { - let lh = exec.look_ahead(); + fn countries<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec { + let lh = executor.look_ahead(); assert_eq!(lh.field_name(), "countries"); vec![Country] } @@ -49,98 +50,66 @@ type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription #[tokio::test] async fn users() { - let ctx = Context; - - let query = r#"{ users { id } }"#; - - let (_, errors) = juniper::execute( - query, - None, - &Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ), - &juniper::Variables::new(), - &ctx, - ) - .await - .unwrap(); + let query = "{ users { id } }"; + + let schema = Schema::new( + Query, + EmptyMutation::::new(), + EmptySubscription::::new(), + ); + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) + .await + .unwrap(); assert_eq!(errors.len(), 0); } #[tokio::test] async fn countries() { - let ctx = Context; - - let query = r#"{ countries { id } }"#; + let query = "{ countries { id } }"; - let (_, errors) = juniper::execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &juniper::Variables::new(), - &ctx, - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) + .await + .unwrap(); assert_eq!(errors.len(), 0); } #[tokio::test] async fn both() { - let ctx = Context; - - let query = r#" - { + let query = "{ countries { id } users { id } - } - "#; - - let (_, errors) = juniper::execute( - query, - None, - &Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ), - &Variables::new(), - &ctx, - ) - .await - .unwrap(); + }"; + + let schema = Schema::new( + Query, + EmptyMutation::::new(), + EmptySubscription::::new(), + ); + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) + .await + .unwrap(); assert_eq!(errors.len(), 0); } #[tokio::test] async fn both_in_different_order() { - let ctx = Context; - - let query = r#" - { + let query = "{ users { id } countries { id } - } - "#; - - let (_, errors) = juniper::execute( - query, - None, - &Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ), - &Variables::new(), - &ctx, - ) - .await - .unwrap(); + }"; + + let schema = Schema::new( + Query, + EmptyMutation::::new(), + EmptySubscription::::new(), + ); + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) + .await + .unwrap(); assert_eq!(errors.len(), 0); } diff --git a/integration_tests/juniper_tests/src/issue_398.rs b/integration_tests/juniper_tests/src/issue_398.rs index 5a42ac238..87d6dd713 100644 --- a/integration_tests/juniper_tests/src/issue_398.rs +++ b/integration_tests/juniper_tests/src/issue_398.rs @@ -1,12 +1,14 @@ // Original author of this test is . -use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode, Variables}; +use juniper::{ + graphql_object, EmptyMutation, EmptySubscription, Executor, RootNode, ScalarValue, Variables, +}; struct Query; #[graphql_object] impl Query { - fn users(executor: &Executor) -> Vec { + fn users(executor: &Executor<'_, '_, (), S>) -> Vec { // This doesn't cause a panic executor.look_ahead(); @@ -22,7 +24,7 @@ struct User { #[graphql_object] impl User { - fn country(&self, executor: &Executor) -> &Country { + fn country(&self, executor: &Executor<'_, '_, (), S>) -> &Country { // This panics! executor.look_ahead(); diff --git a/integration_tests/juniper_tests/src/issue_500.rs b/integration_tests/juniper_tests/src/issue_500.rs index 7dbb595fd..06799a2a4 100644 --- a/integration_tests/juniper_tests/src/issue_500.rs +++ b/integration_tests/juniper_tests/src/issue_500.rs @@ -1,10 +1,10 @@ -use juniper::*; +use juniper::{graphql_object, EmptyMutation, EmptySubscription, Executor, ScalarValue, Variables}; struct Query; -#[juniper::graphql_object] +#[graphql_object] impl Query { - fn users(executor: &Executor) -> Vec { + fn users(executor: &Executor<'_, '_, (), S>) -> Vec { executor.look_ahead(); vec![User { @@ -19,9 +19,9 @@ struct User { city: City, } -#[juniper::graphql_object] +#[graphql_object] impl User { - fn city(&self, executor: &Executor) -> &City { + fn city(&self, executor: &Executor<'_, '_, (), S>) -> &City { executor.look_ahead(); &self.city } @@ -31,9 +31,9 @@ struct City { country: Country, } -#[juniper::graphql_object] +#[graphql_object] impl City { - fn country(&self, executor: &Executor) -> &Country { + fn country(&self, executor: &Executor<'_, '_, (), S>) -> &Country { executor.look_ahead(); &self.country } @@ -43,7 +43,7 @@ struct Country { id: i32, } -#[juniper::graphql_object] +#[graphql_object] impl Country { fn id(&self) -> i32 { self.id diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index 07aed950b..06ac627d3 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -160,12 +160,13 @@ pub trait FromInputValue: Sized { /// Performs the conversion. fn from_input_value(v: &InputValue) -> Option; - /// Performs the conversion from an absent value (e.g. to distinguish between implicit and - /// explicit null). The default implementation just uses `from_input_value` as if an explicit - /// null were provided. This conversion must not fail. - fn from_implicit_null() -> Self { + /// Performs the conversion from an absent value (e.g. to distinguish between + /// implicit and explicit null). The default implementation just uses + /// `from_input_value` as if an explicit null were provided. + /// + /// This conversion must not fail. + fn from_implicit_null() -> Option { Self::from_input_value(&InputValue::::Null) - .expect("input value conversion from null must not fail") } } diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs index 5a37cdf35..d9c48bda5 100644 --- a/juniper/src/executor_tests/introspection/input_object.rs +++ b/juniper/src/executor_tests/introspection/input_object.rs @@ -77,9 +77,9 @@ struct FieldDescription { #[derive(GraphQLInputObject, Debug)] struct FieldWithDefaults { - #[graphql(default = 123)] + #[graphql(default = "123")] field_one: i32, - #[graphql(default = 456, description = "The second field")] + #[graphql(default = "456", description = "The second field")] field_two: i32, } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 256892b6c..1cefcb83f 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -57,7 +57,7 @@ struct ExampleInputObject { #[derive(GraphQLInputObject, Debug)] struct InputWithDefaults { - #[graphql(default = 123)] + #[graphql(default = "123")] a: i32, } diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index d98fb807e..496424881 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -100,11 +100,7 @@ impl Root { } fn args_with_complex_default( - #[graphql( - default = "test", - description = "A string default argument", - )] - arg1: String, + #[graphql(default = "test", description = "A string default argument")] arg1: String, #[graphql( default = Point { x: 1 }, description = "An input object default argument", diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 8a2f5424b..5651ccff5 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -151,17 +151,17 @@ impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> { .collect() } - #[graphql(name = "query_type")] + #[graphql(name = "queryType")] fn query_type_(&self) -> TypeType { self.query_type() } - #[graphql(name = "mutation_type")] + #[graphql(name = "mutationType")] fn mutation_type_(&self) -> Option> { self.mutation_type() } - #[graphql(name = "subscription_type")] + #[graphql(name = "subscriptionType")] fn subscription_type_(&self) -> Option> { self.subscription_type() } @@ -200,10 +200,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn fields( - &self, - #[graphql(default)] include_deprecated: bool, - ) -> Option>> { + fn fields(&self, #[graphql(default)] include_deprecated: bool) -> Option>> { match self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( @@ -288,10 +285,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn enum_values( - &self, - #[graphql(default)] include_deprecated: bool, - ) -> Option> { + fn enum_values(&self, #[graphql(default)] include_deprecated: bool) -> Option> { match self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values @@ -361,7 +355,7 @@ impl<'a, S: ScalarValue + 'a> Argument<'a, S> { context.make_type(&self.arg_type) } - #[graphql(name = "default_value")] + #[graphql(name = "defaultValue")] fn default_value_(&self) -> Option { self.default_value.as_ref().map(ToString::to_string) } diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 1b351a215..1e02ea6ad 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -105,7 +105,7 @@ where /// the `InputValue` will be converted into the type `T`. /// /// Returns `Some` if the argument is present _and_ type conversion - /// succeeeds. + /// succeeds. pub fn get(&self, key: &str) -> Option where T: FromInputValue, diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 0e3659fce..e463f74d2 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -290,8 +290,8 @@ where } } - fn from_implicit_null() -> Self { - Self::ImplicitNull + fn from_implicit_null() -> Option { + Some(Self::ImplicitNull) } } diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 8acce515a..7525ba5b4 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -18,7 +18,7 @@ use crate::{ attr::{err, OptionExt as _}, ParseBufferExt as _, TypeExt as _, }, - ScalarValueType, + scalar, }, result::GraphQLScope, util::{filter_attrs, path_eq_single, span_container::SpanContainer, RenameRule}, @@ -82,7 +82,7 @@ pub(crate) struct Attr { } impl Parse for Attr { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; @@ -303,7 +303,7 @@ impl OnMethod { /// /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark #[must_use] - pub(crate) fn method_mark_tokens(&self, scalar: &ScalarValueType) -> Option { + pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> Option { let ty = &self.as_regular()?.ty; Some(quote! { <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); @@ -346,7 +346,7 @@ impl OnMethod { /// /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field #[must_use] - pub(crate) fn method_resolve_field_tokens(&self) -> TokenStream { + pub(crate) fn method_resolve_field_tokens(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); @@ -355,7 +355,9 @@ impl OnMethod { &name, ); quote! { - args.get::<#ty>(#name).expect(#err_text) + args.get::<#ty>(#name) + .or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null) + .expect(#err_text) } } diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 3d41eb3cd..0fd64d06d 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -19,7 +19,7 @@ use crate::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, - ScalarValueType, + scalar, }, util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer}, }; @@ -80,7 +80,7 @@ pub(crate) struct Attr { } impl Parse for Attr { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; @@ -272,9 +272,7 @@ impl Definition { /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] - pub(crate) fn method_resolve_field_panic_no_field_tokens( - scalar: &ScalarValueType, - ) -> TokenStream { + pub(crate) fn method_resolve_field_panic_no_field_tokens(scalar: &scalar::Type) -> TokenStream { quote! { panic!( "Field `{}` not found on type `{}`", @@ -293,7 +291,7 @@ impl Definition { #[must_use] pub(crate) fn method_resolve_field_panic_async_field_tokens( field_names: &[&str], - scalar: &ScalarValueType, + scalar: &scalar::Type, ) -> TokenStream { quote! { #( #field_names )|* => panic!( @@ -310,7 +308,7 @@ impl Definition { /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] - pub(crate) fn method_mark_tokens(&self, scalar: &ScalarValueType) -> TokenStream { + pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> TokenStream { let args_marks = self .arguments .iter() @@ -375,6 +373,7 @@ impl Definition { #[must_use] pub(crate) fn method_resolve_field_tokens( &self, + scalar: &scalar::Type, trait_ty: Option<&syn::Type>, ) -> Option { if self.is_async { @@ -389,7 +388,7 @@ impl Definition { .as_ref() .unwrap() .iter() - .map(MethodArgument::method_resolve_field_tokens); + .map(|arg| arg.method_resolve_field_tokens(scalar)); let rcv = self.has_receiver.then(|| { quote! { self, } @@ -424,6 +423,7 @@ impl Definition { #[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); @@ -434,7 +434,7 @@ impl Definition { .as_ref() .unwrap() .iter() - .map(MethodArgument::method_resolve_field_tokens); + .map(|arg| arg.method_resolve_field_tokens(scalar)); let rcv = self.has_receiver.then(|| { quote! { self, } diff --git a/juniper_codegen/src/common/mod.rs b/juniper_codegen/src/common/mod.rs index 9485ea5f0..fd16d954d 100644 --- a/juniper_codegen/src/common/mod.rs +++ b/juniper_codegen/src/common/mod.rs @@ -3,113 +3,4 @@ pub(crate) mod field; pub(crate) mod gen; pub(crate) mod parse; - -use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::parse_quote; - -/// [`ScalarValue`] parametrization of the code generation. -/// -/// [`ScalarValue`]: juniper::ScalarValue -#[derive(Clone, Debug)] -pub(crate) enum ScalarValueType { - /// Concrete Rust type is specified as [`ScalarValue`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - Concrete(syn::Type), - - /// One of type parameters of the original type is specified as [`ScalarValue`]. - /// - /// The original type is the type that the code is generated for. - /// - /// [`ScalarValue`]: juniper::ScalarValue - ExplicitGeneric(syn::Ident), - - /// [`ScalarValue`] parametrization is assumed to be a generic and is not specified explicitly. - /// - /// [`ScalarValue`]: juniper::ScalarValue - ImplicitGeneric, -} - -impl ToTokens for ScalarValueType { - fn to_tokens(&self, into: &mut TokenStream) { - self.ty().to_tokens(into) - } -} - -impl ScalarValueType { - /// Indicates whether this [`ScalarValueType`] is generic. - #[must_use] - pub(crate) fn is_generic(&self) -> bool { - matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric) - } - - /// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ExplicitGeneric`]. - #[must_use] - pub(crate) fn is_explicit_generic(&self) -> bool { - matches!(self, Self::ExplicitGeneric(_)) - } - - /// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ImplicitGeneric`]. - #[must_use] - pub(crate) fn is_implicit_generic(&self) -> bool { - matches!(self, Self::ImplicitGeneric) - } - - /// Returns a type identifier which represents this [`ScalarValueType`]. - #[must_use] - pub(crate) fn ty(&self) -> syn::Type { - match self { - Self::Concrete(ty) => ty.clone(), - Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, - Self::ImplicitGeneric => parse_quote! { __S }, - } - } - - /// Returns a type parameter identifier that suits this [`ScalarValueType`]. - #[must_use] - pub(crate) fn generic_ty(&self) -> syn::Type { - match self { - Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, - Self::ImplicitGeneric | Self::Concrete(_) => parse_quote! { __S }, - } - } - - /// Returns a default [`ScalarValue`] type that is compatible with this [`ScalarValueType`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - #[must_use] - pub(crate) fn default_ty(&self) -> syn::Type { - match self { - Self::Concrete(ty) => ty.clone(), - Self::ExplicitGeneric(_) | Self::ImplicitGeneric => { - parse_quote! { ::juniper::DefaultScalarValue } - } - } - } - - /// Parses [`ScalarValueType`] from the given `explicit` type definition (if - /// any), checking whether it contains in the giving `generics`. - #[must_use] - pub(crate) fn parse(explicit: Option<&syn::Type>, generics: &syn::Generics) -> Self { - if let Some(scalar_ty) = explicit { - generics - .params - .iter() - .find_map(|p| { - if let syn::GenericParam::Type(tp) = p { - let ident = &tp.ident; - let ty: syn::Type = parse_quote! { #ident }; - if &ty == scalar_ty { - return Some(&tp.ident); - } - } - None - }) - .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) - .unwrap_or_else(|| ScalarValueType::Concrete(scalar_ty.clone())) - } else { - ScalarValueType::ImplicitGeneric - } - } -} +pub(crate) mod scalar; diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs new file mode 100644 index 000000000..1569a5391 --- /dev/null +++ b/juniper_codegen/src/common/scalar.rs @@ -0,0 +1,173 @@ +//! Common functions, definitions and extensions for parsing and code generation +//! related to [`ScalarValue`]. +//! +//! [`ScalarValue`]: juniper::ScalarValue + +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned, +}; + +/// Possible values of `#[graphql(scalar = ...)]` attribute. +#[derive(Clone, Debug)] +pub(crate) enum AttrValue { + /// Concrete Rust type (like `DefaultScalarValue`). + /// + /// [`ScalarValue`]: juniper::ScalarValue + Concrete(syn::Type), + + /// Generic Rust type parameter with a bound predicate + /// (like `S: ScalarValue + Send + Sync`). + /// + /// [`ScalarValue`]: juniper::ScalarValue + Generic(syn::PredicateType), +} + +impl Parse for AttrValue { + fn parse(input: ParseStream<'_>) -> syn::Result { + if input.fork().parse::().is_ok() { + let pred = input.parse().unwrap(); + if let syn::WherePredicate::Type(p) = pred { + Ok(Self::Generic(p)) + } else { + Err(syn::Error::new( + pred.span(), + "only type predicates are allowed here", + )) + } + } else { + input.parse::().map(Self::Concrete) + } + } +} + +impl Spanned for AttrValue { + fn span(&self) -> Span { + match self { + Self::Concrete(ty) => ty.span(), + Self::Generic(pred) => pred.span(), + } + } +} + +/// [`ScalarValue`] parametrization of the code generation. +/// +/// [`ScalarValue`]: juniper::ScalarValue +#[derive(Clone, Debug)] +pub(crate) enum Type { + /// Concrete Rust type is specified as [`ScalarValue`]. + /// + /// [`ScalarValue`]: juniper::ScalarValue + Concrete(syn::Type), + + /// One of type parameters of the original type is specified as [`ScalarValue`]. + /// + /// The original type is the type that the code is generated for. + /// + /// [`ScalarValue`]: juniper::ScalarValue + ExplicitGeneric(syn::Ident), + + /// [`ScalarValue`] parametrization is assumed to be generic and is not specified + /// explicitly, or specified as bound predicate (like `S: ScalarValue + Send + Sync`). + /// + /// [`ScalarValue`]: juniper::ScalarValue + ImplicitGeneric(Option), +} + +impl ToTokens for Type { + fn to_tokens(&self, into: &mut TokenStream) { + self.ty().to_tokens(into) + } +} + +impl Type { + /// Indicates whether this [`Type`] is generic. + #[must_use] + pub(crate) fn is_generic(&self) -> bool { + 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 { + matches!(self, Self::ImplicitGeneric(_)) + } + + /// Returns additional trait bounds behind this [`Type`], if any. + #[must_use] + pub(crate) fn bounds(&self) -> Option { + if let Self::ImplicitGeneric(Some(pred)) = self { + Some(syn::WherePredicate::Type(pred.clone())) + } else { + None + } + } + + /// Returns a type identifier which represents this [`Type`]. + #[must_use] + pub(crate) fn ty(&self) -> syn::Type { + match self { + Self::Concrete(ty) => ty.clone(), + Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, + Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(), + Self::ImplicitGeneric(None) => parse_quote! { __S }, + } + } + + /// 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 + #[must_use] + pub(crate) fn default_ty(&self) -> syn::Type { + match self { + Self::Concrete(ty) => ty.clone(), + Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_) => { + parse_quote! { ::juniper::DefaultScalarValue } + } + } + } + + /// Parses [`Type`] from the given `explicit` [`AttrValue`] (if any), + /// checking whether it's contained in the giving `generics`. + #[must_use] + pub(crate) fn parse(explicit: Option<&AttrValue>, generics: &syn::Generics) -> Self { + match explicit { + Some(AttrValue::Concrete(scalar_ty)) => generics + .params + .iter() + .find_map(|p| { + if let syn::GenericParam::Type(tp) = p { + let ident = &tp.ident; + let ty: syn::Type = parse_quote! { #ident }; + if &ty == scalar_ty { + return Some(&tp.ident); + } + } + None + }) + .map(|ident| Self::ExplicitGeneric(ident.clone())) + .unwrap_or_else(|| Self::Concrete(scalar_ty.clone())), + Some(AttrValue::Generic(pred)) => Self::ImplicitGeneric(Some(pred.clone())), + None => Self::ImplicitGeneric(None), + } + } +} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index fff1534d3..7e1eff169 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -10,7 +10,7 @@ use crate::{ common::{ field, parse::{self, TypeExt as _}, - ScalarValueType, + scalar, }, result::GraphQLScope, util::{path_eq_single, span_container::SpanContainer, RenameRule}, @@ -69,7 +69,7 @@ pub fn expand_on_trait( ); } - let scalar = ScalarValueType::parse(meta.scalar.as_deref(), &ast.generics); + let scalar = scalar::Type::parse(meta.scalar.as_deref(), &ast.generics); let mut implementers: Vec<_> = meta .implementers @@ -264,27 +264,7 @@ pub fn expand_on_impl( let is_trait_object = meta.r#dyn.is_some(); if is_trait_object { - let scalar = meta - .scalar - .as_ref() - .map(|sc| { - ast.generics - .params - .iter() - .find_map(|p| { - if let syn::GenericParam::Type(tp) = p { - let ident = &tp.ident; - let ty: syn::Type = parse_quote! { #ident }; - if &ty == sc.as_ref() { - return Some(&tp.ident); - } - } - None - }) - .map(|ident| ScalarValueType::ExplicitGeneric(ident.clone())) - .unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone())) - }) - .unwrap_or_else(|| ScalarValueType::ImplicitGeneric); + let scalar = scalar::Type::parse(meta.scalar.as_deref(), &ast.generics); ast.attrs.push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] @@ -413,7 +393,7 @@ impl TraitMethod { ty, downcast: Some(downcast), context_ty, - scalar: ScalarValueType::ImplicitGeneric, + scalar: scalar::Type::ImplicitGeneric(None), }) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 6ca667861..fc5616ab0 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -25,7 +25,7 @@ use crate::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, }, - ScalarValueType, + scalar, }, util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; @@ -85,18 +85,19 @@ struct TraitMeta { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces context: Option>, - /// Explicitly specified type of [`ScalarValue`] to use for resolving this - /// [GraphQL interface][1] type with. + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to resolve this [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 one of the implementers implements - /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. + /// If absent, then generated code will be generic over any [`ScalarValue`] + /// type, which, in turn, requires all [interface][1] implementers to be + /// generic over any [`ScalarValue`] type too. That's why this type should + /// be specified only if one of the implementers implements [`GraphQLType`] + /// in a non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - scalar: Option>, + scalar: Option>, /// Explicitly specified marker indicating that the Rust trait should be transformed into /// [`async_trait`]. @@ -133,7 +134,7 @@ struct TraitMeta { } impl Parse for TraitMeta { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { @@ -173,7 +174,7 @@ impl Parse for TraitMeta { } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; - let scl = input.parse::()?; + let scl = input.parse::()?; output .scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) @@ -301,18 +302,19 @@ impl TraitMeta { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] struct ImplMeta { - /// Explicitly specified type of [`ScalarValue`] to use for implementing the - /// [GraphQL interface][1] type. + /// 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. + /// If absent, then generated code will be generic over any [`ScalarValue`] + /// type, which, in turn, requires all [interface][1] implementers to be + /// generic over any [`ScalarValue`] type too. That's why this type should + /// be specified only if the implementer itself implements [`GraphQLType`] + /// in a non-generic way over [`ScalarValue`] type. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - scalar: Option>, + scalar: Option>, /// Explicitly specified marker indicating that the trait implementation block should be /// transformed with applying [`async_trait`]. @@ -333,7 +335,7 @@ struct ImplMeta { } impl Parse for ImplMeta { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { @@ -341,7 +343,7 @@ impl Parse for ImplMeta { match ident.to_string().as_str() { "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; - let scl = input.parse::()?; + let scl = input.parse::()?; output .scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) @@ -420,13 +422,13 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces context: Option, - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this - /// [GraphQL interface][1]. + /// [`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 - scalar: ScalarValueType, + scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. /// @@ -587,7 +589,7 @@ impl Definition { let fields_resolvers = self .fields .iter() - .filter_map(|f| f.method_resolve_field_tokens(Some(&trait_ty))); + .filter_map(|f| f.method_resolve_field_tokens(scalar, Some(&trait_ty))); let async_fields_panic = { let names = self .fields @@ -688,7 +690,7 @@ impl Definition { let fields_resolvers = self .fields .iter() - .map(|f| f.method_resolve_field_async_tokens(Some(&trait_ty))); + .map(|f| f.method_resolve_field_async_tokens(scalar, Some(&trait_ty))); let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); let custom_downcasts = self @@ -784,7 +786,7 @@ struct Implementer { /// [`ScalarValue`] parametrization of this [`Implementer`]. /// /// [`ScalarValue`]: juniper::ScalarValue - scalar: ScalarValueType, + scalar: scalar::Type, } impl Implementer { @@ -938,12 +940,12 @@ struct EnumType { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces trait_methods: Vec, - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation with for this - /// [`EnumType`]. + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation + /// with for this [`EnumType`]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue - scalar: ScalarValueType, + scalar: scalar::Type, } impl EnumType { @@ -953,7 +955,7 @@ impl EnumType { r#trait: &syn::ItemTrait, meta: &TraitMeta, implers: &[Implementer], - scalar: ScalarValueType, + scalar: scalar::Type, ) -> Self { Self { ident: meta @@ -1379,7 +1381,7 @@ struct TraitObjectType { /// [`ScalarValue`] parametrization of this [`TraitObjectType`] to generate it with. /// /// [`ScalarValue`]: juniper::ScalarValue - scalar: ScalarValueType, + scalar: scalar::Type, /// Rust type of [`Context`] to generate this [`TraitObjectType`] with. /// @@ -1395,7 +1397,7 @@ impl TraitObjectType { fn new( r#trait: &syn::ItemTrait, meta: &TraitMeta, - scalar: ScalarValueType, + scalar: scalar::Type, context: Option, ) -> Self { Self { diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index e6c3ecbc3..4027f3db7 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -10,7 +10,7 @@ use crate::{ common::{ field, parse::{self, TypeExt as _}, - ScalarValueType, + scalar, }, result::GraphQLScope, util::{path_eq_single, span_container::SpanContainer, RenameRule}, @@ -63,7 +63,7 @@ pub fn expand_on_impl( ); } - let scalar = ScalarValueType::parse(attr.scalar.as_deref(), &ast.generics); + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); proc_macro_error::abort_if_dirty(); diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index a4d95bb35..6caa76c9d 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -6,7 +6,7 @@ use quote::ToTokens; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ - common::{field, parse::TypeExt as _, ScalarValueType}, + common::{field, parse::TypeExt as _, scalar}, result::GraphQLScope, util::{span_container::SpanContainer, RenameRule}, }; @@ -49,7 +49,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { ); } - let scalar = ScalarValueType::parse(attr.scalar.as_deref(), &ast.generics); + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); proc_macro_error::abort_if_dirty(); diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index a7ce7c282..f622a4617 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -23,7 +23,7 @@ use crate::{ attr::{err, OptionExt as _}, ParseBufferExt as _, }, - ScalarValueType, + scalar, }, util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; @@ -59,8 +59,9 @@ struct Attr { /// [1]: https://spec.graphql.org/June2018/#sec-Objects context: Option>, - /// Explicitly specified type of `juniper::ScalarValue` to use for resolving - /// this [GraphQL object][1] type with. + /// Explicitly specified type (or type parameter with its bounds) of + /// `juniper::ScalarValue` to use for resolving this [GraphQL object][1] + /// type with. /// /// If [`None`], then generated code will be generic over any /// `juniper::ScalarValue` type, which, in turn, requires all [object][1] @@ -70,7 +71,7 @@ struct Attr { /// type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - scalar: Option>, + scalar: Option>, /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] /// type implements. @@ -93,7 +94,7 @@ struct Attr { } impl Parse for Attr { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { @@ -133,7 +134,7 @@ impl Parse for Attr { } "scalar" | "Scalar" | "ScalarValue" => { input.parse::()?; - let scl = input.parse::()?; + let scl = input.parse::()?; output .scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) @@ -245,7 +246,7 @@ struct Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Objects - scalar: ScalarValueType, + scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL object][1]. /// @@ -290,7 +291,7 @@ impl Definition { let mut generics = self.generics.clone(); let scalar = &self.scalar; - if self.scalar.is_implicit_generic() { + if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } if scalar.is_generic() { @@ -299,6 +300,9 @@ impl Definition { .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 generics.lifetimes().next().is_some() { @@ -479,7 +483,7 @@ impl Definition { let fields_resolvers = self .fields .iter() - .filter_map(|f| f.method_resolve_field_tokens(None)); + .filter_map(|f| f.method_resolve_field_tokens(scalar, None)); let async_fields_panic = { let names = self .fields @@ -544,7 +548,7 @@ impl Definition { let fields_resolvers = self .fields .iter() - .map(|f| f.method_resolve_field_async_tokens(None)); + .map(|f| f.method_resolve_field_async_tokens(scalar, None)); let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); quote! { diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 3be2a7c96..e1ca08587 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -83,7 +83,7 @@ struct UnionMeta { } impl Parse for UnionMeta { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { @@ -207,7 +207,7 @@ struct UnionVariantMeta { } impl Parse for UnionVariantMeta { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index b7bdf3a30..b9d9c15da 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -103,7 +103,8 @@ fn create( let resolver = quote!( let #mut_modifier #arg_ident = args .get::<#ty>(#final_name) - .unwrap_or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null); + .or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null) + .unwrap(); ); let field_type = util::GraphQLTypeDefinitionFieldArg { diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 2d97791e2..3660e0e89 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -337,7 +337,7 @@ impl TryFrom for RenameRule { } impl Parse for RenameRule { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { Self::try_from(input.parse::()?) } } @@ -355,7 +355,7 @@ pub struct ObjectAttributes { } impl Parse for ObjectAttributes { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let mut output = Self::default(); while !input.is_empty() { @@ -458,7 +458,7 @@ pub struct FieldAttributeArgument { } impl Parse for FieldAttributeArgument { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let name = input.parse::()?.unraw(); let mut arg = Self { @@ -512,7 +512,7 @@ enum FieldAttribute { } impl Parse for FieldAttribute { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let ident = input.parse::()?; match ident.to_string().as_str() { @@ -602,7 +602,7 @@ pub struct FieldAttributes { } impl Parse for FieldAttributes { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let items = Punctuated::::parse_terminated(&input)?; let mut output = Self::default(); @@ -1352,7 +1352,7 @@ impl GraphQLTypeDefiniton { match obj.get(#field_name) { #from_input_default Some(ref v) => ::juniper::FromInputValue::from_input_value(v)?, - None => ::juniper::FromInputValue::<#scalar>::from_implicit_null(), + None => ::juniper::FromInputValue::<#scalar>::from_implicit_null()?, } }, ) From d385ca726f21fe7fe41668e1b03e65a5232676e2 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 29 Jul 2021 19:29:15 +0300 Subject: [PATCH 13/39] Improve #[graphql_interface] with bounded scalar --- .../src/codegen/interface_attr.rs | 223 ++++++++++++++++++ juniper/src/macros/tests/impl_object.rs | 189 +++++++-------- juniper_codegen/src/graphql_interface/mod.rs | 123 +++++++--- juniper_codegen/src/graphql_object/mod.rs | 3 +- 4 files changed, 392 insertions(+), 146 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 29643499f..104bcd63b 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3592,6 +3592,229 @@ mod explicit_generic_scalar { } } +mod bounded_generic_scalar { + use super::*; + + #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue)] + trait Character { + fn id(&self) -> &str; + } + + #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S: ScalarValue)] + trait Hero { + async fn info(&self) -> &str; + } + + #[derive(GraphQLObject)] + #[graphql(impl = [CharacterValue, DynHero], scalar = S: ScalarValue)] + struct Human { + id: String, + home_planet: String, + } + + #[graphql_interface(scalar = S: ScalarValue)] + impl Character for Human { + fn id(&self) -> &str { + &self.id + } + } + + #[graphql_interface(dyn, scalar = S: ScalarValue)] + impl Hero for Human { + async fn info(&self) -> &str { + &self.home_planet + } + } + + #[derive(GraphQLObject)] + #[graphql(impl = [CharacterValue, DynHero<__S>])] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_interface(scalar = S: ScalarValue)] + impl Character for Droid { + fn id(&self) -> &str { + &self.id + } + } + + #[graphql_interface(dyn, scalar = S: ScalarValue)] + impl Hero for Droid { + async fn info(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + + fn hero(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_human() { + const DOC: &str = r#"{ + hero { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn dyn_resolves_droid() { + const DOC: &str = r#"{ + hero { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } + + #[tokio::test] + async fn dyn_resolves_info_field() { + const DOC: &str = r#"{ + hero { + info + } + }"#; + + for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + let schema = schema(*root); + + let expected_info: &str = *expected_info; + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + ); + } + } +} + mod explicit_custom_context { use super::*; diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 9d2e1aebb..c62e45710 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -21,6 +21,10 @@ impl<'a> WithLifetime<'a> { fn value(&self) -> &str { self.value } + + async fn value_async(&self) -> &str { + self.value + } } struct WithContext; @@ -140,115 +144,83 @@ async fn object_introspect() { graphql_value!({ "name": "Query", "description": "Query Description.", - "fields": [ - { - "name": "withSelf", - "description": "With Self Description", - "args": [], - }, - { - "name": "independent", - "description": None, - "args": [], - }, - { - "name": "withExecutor", - "description": None, - "args": [], - }, - { - "name": "withExecutorAndSelf", - "description": None, - "args": [], - }, - { - "name": "withContext", - "description": None, - "args": [], - }, - { - "name": "withContextAndSelf", - "description": None, - "args": [], - }, - { - "name": "renamed", - "description": None, - "args": [], - }, - { - "name": "hasDescriptionAttr", - "description": "attr", - "args": [], - }, - { - "name": "hasDescriptionDocComment", - "description": "Doc description", - "args": [], - }, - { - "name": "hasArgument", - "description": None, - "args": [ - { - "name": "arg1", - "description": None, - "type": { - "name": None, - }, - } - ], - }, - { - "name": "defaultArgument", - "description": None, - "args": [ - { - "name": "defaultArg", - "description": None, - "type": { - "name": "Boolean", - }, - } - ], - }, - { - "name": "argWithDescription", - "description": None, - "args": [ - { - "name": "arg", - "description": "my argument description", - "type": { - "name": None - }, - } - ], - }, - { - "name": "withContextChild", + "fields": [{ + "name": "withSelf", + "description": "With Self Description", + "args": [], + }, { + "name": "independent", + "description": None, + "args": [], + }, { + "name": "withExecutor", + "description": None, + "args": [], + }, { + "name": "withExecutorAndSelf", + "description": None, + "args": [], + }, { + "name": "withContext", + "description": None, + "args": [], + }, { + "name": "withContextAndSelf", + "description": None, + "args": [], + }, { + "name": "renamed", + "description": None, + "args": [], + }, { + "name": "hasDescriptionAttr", + "description": "attr", + "args": [], + }, { + "name": "hasDescriptionDocComment", + "description": "Doc description", + "args": [], + }, { + "name": "hasArgument", + "description": None, + "args": [{ + "name": "arg1", "description": None, - "args": [], - }, - { - "name": "withLifetimeChild", + "type": {"name": None}, + }], + }, { + "name": "defaultArgument", + "description": None, + "args": [{ + "name": "defaultArg", "description": None, - "args": [], - }, - { - "name": "withMutArg", + "type": {"name": "Boolean"}, + }], + }, { + "name": "argWithDescription", + "description": None, + "args": [{ + "name": "arg", + "description": "my argument description", + "type": {"name": None}, + }], + }, { + "name": "withContextChild", + "description": None, + "args": [], + }, { + "name": "withLifetimeChild", + "description": None, + "args": [], + }, { + "name": "withMutArg", + "description": None, + "args": [{ + "name": "arg", "description": None, - "args": [ - { - "name": "arg", - "description": None, - "type": { - "name": None, - }, - } - ], - }, - ] + "type": {"name": None}, + }], + }], }) ); } @@ -272,6 +244,7 @@ async fn object_query() { } withLifetimeChild { value + valueAsync } withMutArg(arg: true) } @@ -300,8 +273,8 @@ async fn object_query() { "hasArgument": true, "defaultArgument": true, "argWithDescription": true, - "withContextChild": { "ctx": true }, - "withLifetimeChild": { "value": "blub" }, + "withContextChild": {"ctx": true}, + "withLifetimeChild": {"value": "blub", "valueAsync": "blub"}, "withMutArg": false, }) ); diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index fc5616ab0..0fbdfb51b 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -12,6 +12,7 @@ use std::{ 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 _, @@ -463,8 +464,7 @@ impl Definition { fn impl_graphql_interface_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); @@ -493,8 +493,7 @@ impl Definition { fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); @@ -521,8 +520,7 @@ impl Definition { fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); let name = &self.name; @@ -579,9 +577,7 @@ impl Definition { fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - + let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); let trait_ty = self.ty.trait_ty(); let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); @@ -672,18 +668,7 @@ impl Definition { fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let generics = self.ty.impl_generics(); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let mut where_clause = where_clause - .cloned() - .unwrap_or_else(|| parse_quote! { where }); - where_clause.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_generic() { - where_clause - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - + let (impl_generics, where_clause) = self.ty.impl_generics(true); let ty = self.ty.ty_tokens(); let trait_ty = self.ty.trait_ty(); @@ -700,7 +685,7 @@ impl Definition { let regular_downcast = self.ty.method_resolve_into_type_async_tokens(); quote! { - #[allow(deprecated)] + #[allow(deprecated, non_snake_case)] #[automatically_derived] impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { @@ -1036,24 +1021,62 @@ impl EnumType { } } - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation - /// for this [`EnumType`]. + /// 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) -> syn::Generics { + fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.trait_generics.clone(); let scalar = &self.scalar; - if self.scalar.is_implicit_generic() { + if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); } - if self.scalar.is_generic() { + 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 } @@ -1410,12 +1433,16 @@ impl TraitObjectType { } } - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation - /// for this [`TraitObjectType`]. + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this [`TraitObjectType`]. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType #[must_use] - fn impl_generics(&self) -> syn::Generics { + fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.trait_generics.clone(); generics.params.push(parse_quote! { '__obj }); @@ -1430,6 +1457,22 @@ impl TraitObjectType { .predicates .push(parse_quote! { #scalar: ::juniper::ScalarValue }); } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Sync }); + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } generics } @@ -1582,16 +1625,22 @@ enum Type { } impl Type { - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and similar) implementation - /// for this [`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) -> syn::Generics { - match self { - Self::Enum(e) => e.impl_generics(), - Self::TraitObject(o) => o.impl_generics(), - } + 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 diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index f622a4617..fc59c1dc9 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -279,6 +279,7 @@ impl Definition { /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] @@ -305,7 +306,7 @@ impl Definition { } if for_async { - let self_ty = if 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.generics.clone(); From c67f3ef12550458b8f8df1179aa7b0784eb7ea00 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 29 Jul 2021 20:08:07 +0300 Subject: [PATCH 14/39] Refactor integration tests of issues --- .../juniper_tests/src/issue_371.rs | 24 +++----- .../juniper_tests/src/issue_372.rs | 3 + .../juniper_tests/src/issue_398.rs | 9 ++- .../juniper_tests/src/issue_407.rs | 57 +++++++------------ .../juniper_tests/src/issue_500.rs | 18 +++--- .../juniper_tests/src/issue_798.rs | 57 +++++++------------ .../juniper_tests/src/issue_914.rs | 31 ++++------ .../juniper_tests/src/issue_922.rs | 48 +++++++--------- .../juniper_tests/src/issue_925.rs | 31 +++++----- .../juniper_tests/src/issue_945.rs | 40 ++++++------- juniper_codegen/src/util/mod.rs | 1 - 11 files changed, 133 insertions(+), 186 deletions(-) diff --git a/integration_tests/juniper_tests/src/issue_371.rs b/integration_tests/juniper_tests/src/issue_371.rs index f8abb271e..571a1587e 100644 --- a/integration_tests/juniper_tests/src/issue_371.rs +++ b/integration_tests/juniper_tests/src/issue_371.rs @@ -1,4 +1,8 @@ -// Original author of this test is . +//! Checks that `executor.look_ahead().field_name()` is correct in presence of +//! multiple query fields. +//! See [#371](https://github.com/graphql-rust/juniper/issues/371) for details. +//! +//! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn). use juniper::{ graphql_object, EmptyMutation, EmptySubscription, Executor, LookAheadMethods as _, RootNode, @@ -52,11 +56,7 @@ type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription async fn users() { let query = "{ users { id } }"; - let schema = Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) .await .unwrap(); @@ -83,11 +83,7 @@ async fn both() { users { id } }"; - let schema = Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) .await .unwrap(); @@ -102,11 +98,7 @@ async fn both_in_different_order() { countries { id } }"; - let schema = Schema::new( - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context) .await .unwrap(); diff --git a/integration_tests/juniper_tests/src/issue_372.rs b/integration_tests/juniper_tests/src/issue_372.rs index a78cf0d1a..661c90db6 100644 --- a/integration_tests/juniper_tests/src/issue_372.rs +++ b/integration_tests/juniper_tests/src/issue_372.rs @@ -1,3 +1,6 @@ +//! Checks that `__typename` field queries okay on root types. +//! See [#372](https://github.com/graphql-rust/juniper/issues/372) for details. + use futures::{stream, StreamExt as _}; use juniper::{graphql_object, graphql_subscription, graphql_value, RootNode, Value, Variables}; diff --git a/integration_tests/juniper_tests/src/issue_398.rs b/integration_tests/juniper_tests/src/issue_398.rs index 87d6dd713..0d5557411 100644 --- a/integration_tests/juniper_tests/src/issue_398.rs +++ b/integration_tests/juniper_tests/src/issue_398.rs @@ -1,4 +1,7 @@ -// Original author of this test is . +//! Checks that `executor.look_ahead()` on a fragment with nested type works okay. +//! See [#398](https://github.com/graphql-rust/juniper/issues/398) for details. +//! +//! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn). use juniper::{ graphql_object, EmptyMutation, EmptySubscription, Executor, RootNode, ScalarValue, Variables, @@ -9,7 +12,7 @@ struct Query; #[graphql_object] impl Query { fn users(executor: &Executor<'_, '_, (), S>) -> Vec { - // This doesn't cause a panic + // This doesn't cause a panic. executor.look_ahead(); vec![User { @@ -46,7 +49,7 @@ impl Country { type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; #[tokio::test] -async fn test_lookahead_from_fragment_with_nested_type() { +async fn lookahead_from_fragment_with_nested_type() { let _ = juniper::execute( r#" query Query { diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/integration_tests/juniper_tests/src/issue_407.rs index 6b4e79fb2..11c6075c9 100644 --- a/integration_tests/juniper_tests/src/issue_407.rs +++ b/integration_tests/juniper_tests/src/issue_407.rs @@ -1,4 +1,9 @@ -use juniper::*; +//! Checks that using a fragment of an implementation in an interface works okay. +//! See [#407](https://github.com/graphql-rust/juniper/issues/407) for details. + +use juniper::{ + graphql_interface, graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, Variables, +}; struct Query; @@ -53,7 +58,7 @@ impl Query { type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] -async fn test_fragments_in_interface() { +async fn fragments_in_interface() { let query = r#" query Query { characters { @@ -71,30 +76,19 @@ async fn test_fragments_in_interface() { } "#; - let (_, errors) = juniper::execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); - let (_, errors) = juniper::execute_sync( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); } #[tokio::test] -async fn test_inline_fragments_in_interface() { +async fn inline_fragments_in_interface() { let query = r#" query Query { characters { @@ -116,24 +110,13 @@ async fn test_inline_fragments_in_interface() { } "#; - let (_, errors) = juniper::execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); - let (_, errors) = juniper::execute_sync( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); } diff --git a/integration_tests/juniper_tests/src/issue_500.rs b/integration_tests/juniper_tests/src/issue_500.rs index 06799a2a4..221b1720e 100644 --- a/integration_tests/juniper_tests/src/issue_500.rs +++ b/integration_tests/juniper_tests/src/issue_500.rs @@ -1,3 +1,6 @@ +//! Checks that using nested fragments works okay. +//! See [#500](https://github.com/graphql-rust/juniper/issues/500) for details. + use juniper::{graphql_object, EmptyMutation, EmptySubscription, Executor, ScalarValue, Variables}; struct Query; @@ -53,7 +56,7 @@ impl Country { type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; #[tokio::test] -async fn test_nested_fragments() { +async fn nested_fragments() { let query = r#" query Query { users { @@ -78,15 +81,10 @@ async fn test_nested_fragments() { } "#; - let (_, errors) = juniper::execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); } diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/integration_tests/juniper_tests/src/issue_798.rs index f3ec4d700..a8f7ec0de 100644 --- a/integration_tests/juniper_tests/src/issue_798.rs +++ b/integration_tests/juniper_tests/src/issue_798.rs @@ -1,3 +1,6 @@ +//! Checks that interface field resolves okay on a union. +//! See [#798](https://github.com/graphql-rust/juniper/issues/798) for details. + use juniper::{ graphql_interface, graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLUnion, RootNode, Variables, @@ -64,10 +67,10 @@ impl Query { } } -type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; +type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] -async fn test_interface_inline_fragment_on_union() { +async fn interface_inline_fragment_on_union() { let query = r#" query Query { field { @@ -85,15 +88,10 @@ async fn test_interface_inline_fragment_on_union() { } "#; - let (res, errors) = juniper::execute( - query, - None, - &Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()); + let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); assert_eq!( @@ -107,14 +105,9 @@ async fn test_interface_inline_fragment_on_union() { }), ); - let (res, errors) = juniper::execute_sync( - query, - None, - &Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let schema = Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()); + let (res, errors) = + juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); assert_eq!( @@ -130,7 +123,7 @@ async fn test_interface_inline_fragment_on_union() { } #[tokio::test] -async fn test_interface_fragment_on_union() { +async fn interface_fragment_on_union() { let query = r#" query Query { field { @@ -150,15 +143,10 @@ async fn test_interface_fragment_on_union() { } "#; - let (res, errors) = juniper::execute( - query, - None, - &Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query::Human, EmptyMutation::new(), EmptySubscription::new()); + let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); assert_eq!( @@ -172,14 +160,9 @@ async fn test_interface_fragment_on_union() { }), ); - let (res, errors) = juniper::execute_sync( - query, - None, - &Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let schema = Schema::new(Query::Droid, EmptyMutation::new(), EmptySubscription::new()); + let (res, errors) = + juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); assert_eq!( diff --git a/integration_tests/juniper_tests/src/issue_914.rs b/integration_tests/juniper_tests/src/issue_914.rs index be15514fb..aa9b86c38 100644 --- a/integration_tests/juniper_tests/src/issue_914.rs +++ b/integration_tests/juniper_tests/src/issue_914.rs @@ -1,4 +1,7 @@ -use juniper::*; +//! Checks that multiple fragments on sub types don't override each other. +//! See [#914](https://github.com/graphql-rust/juniper/issues/914) for details. + +use juniper::{graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, Variables}; struct Query; @@ -32,7 +35,7 @@ impl Query { type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] -async fn test_fragments_with_nested_objects_dont_override_previous_selections() { +async fn fragments_with_nested_objects_dont_override_previous_selections() { let query = r#" query Query { foo { @@ -72,25 +75,15 @@ async fn test_fragments_with_nested_objects_dont_override_previous_selections() } "#; - let (async_value, errors) = juniper::execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + + let (async_value, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); - let (sync_value, errors) = juniper::execute_sync( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let (sync_value, errors) = + juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); assert_eq!(async_value, sync_value); diff --git a/integration_tests/juniper_tests/src/issue_922.rs b/integration_tests/juniper_tests/src/issue_922.rs index 35de87faf..b99803fb3 100644 --- a/integration_tests/juniper_tests/src/issue_922.rs +++ b/integration_tests/juniper_tests/src/issue_922.rs @@ -1,8 +1,14 @@ -use juniper::*; +//! Checks that fields on interface fragment spreads resolve okay. +//! See [#922](https://github.com/graphql-rust/juniper/issues/922) for details. + +use juniper::{ + graphql_interface, graphql_object, graphql_value, EmptyMutation, EmptySubscription, + GraphQLObject, Variables, +}; struct Query; -#[juniper::graphql_object] +#[graphql_object] impl Query { fn characters() -> Vec { vec![ @@ -18,21 +24,21 @@ impl Query { } } -#[juniper::graphql_interface(for = [Human, Droid])] +#[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> i32; fn name(&self) -> String; } -#[derive(juniper::GraphQLObject)] +#[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Human { pub id: i32, pub name: String, } -#[juniper::graphql_interface] +#[graphql_interface] impl Character for Human { fn id(&self) -> i32 { self.id @@ -43,14 +49,14 @@ impl Character for Human { } } -#[derive(juniper::GraphQLObject)] +#[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { pub id: i32, pub name: String, } -#[juniper::graphql_interface] +#[graphql_interface] impl Character for Droid { fn id(&self) -> i32 { self.id @@ -61,10 +67,10 @@ impl Character for Droid { } } -type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; +type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] -async fn test_fragment_on_interface() { +async fn fragment_on_interface() { let query = r#" query Query { characters { @@ -85,15 +91,11 @@ async fn test_fragment_on_interface() { } "#; - let (res, errors) = juniper::execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + + let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); assert_eq!( @@ -106,14 +108,8 @@ async fn test_fragment_on_interface() { }), ); - let (res, errors) = juniper::execute_sync( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let (res, errors) = + juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); assert_eq!( diff --git a/integration_tests/juniper_tests/src/issue_925.rs b/integration_tests/juniper_tests/src/issue_925.rs index 45180262e..435cb2a7b 100644 --- a/integration_tests/juniper_tests/src/issue_925.rs +++ b/integration_tests/juniper_tests/src/issue_925.rs @@ -1,8 +1,14 @@ -use juniper::*; +//! Checks that `FieldError` doesn't lose its extensions while being implicitly +//! converted from user defined subscription errors. +//! See [#925](https://github.com/graphql-rust/juniper/issues/925) for details. use futures::stream::BoxStream; +use juniper::{ + graphql_object, graphql_subscription, graphql_value, EmptyMutation, FieldError, GraphQLObject, + IntoFieldError, Object, ScalarValue, Value, Variables, +}; -#[derive(juniper::GraphQLObject)] +#[derive(GraphQLObject)] struct User { name: String, } @@ -33,17 +39,17 @@ fn users_stream() -> Result, Error> { struct Query; -#[juniper::graphql_object] +#[graphql_object] impl Query { fn users() -> Vec { vec![] } } -type Schema = juniper::RootNode<'static, Query, EmptyMutation<()>, SubscriptionsRoot>; +type Schema = juniper::RootNode<'static, Query, EmptyMutation, SubscriptionsRoot>; #[tokio::test] -async fn test_error_extensions() { +async fn error_extensions() { let sub = r#" subscription Users { users { @@ -52,18 +58,13 @@ async fn test_error_extensions() { } "#; - let (_, errors) = juniper::resolve_into_stream( - sub, - None, - &Schema::new(Query, EmptyMutation::new(), SubscriptionsRoot), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), SubscriptionsRoot); + let (_, errors) = juniper::resolve_into_stream(sub, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!( errors.first().unwrap().error().extensions(), - &graphql_value!({ "a": 42 }) + &graphql_value!({ "a": 42 }), ); } diff --git a/integration_tests/juniper_tests/src/issue_945.rs b/integration_tests/juniper_tests/src/issue_945.rs index 763fcd69a..9d8c0db85 100644 --- a/integration_tests/juniper_tests/src/issue_945.rs +++ b/integration_tests/juniper_tests/src/issue_945.rs @@ -1,4 +1,10 @@ -use juniper::*; +//! Checks that spreading untyped union fragment work okay. +//! See [#945](https://github.com/graphql-rust/juniper/issues/945) for details. + +use juniper::{ + graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLUnion, + Variables, +}; struct Query; @@ -34,10 +40,10 @@ struct Droid { pub sensor_color: String, } -type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; +type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] -async fn test_fragment_on_interface() { +async fn fragment_on_union() { let query = r#" query Query { artoo { @@ -58,38 +64,28 @@ async fn test_fragment_on_interface() { } "#; - let (res, errors) = execute( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .await - .unwrap(); + let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()); + + let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &()) + .await + .unwrap(); assert_eq!(errors.len(), 0); assert_eq!( res, graphql_value!({ - "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"} + "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"}, }), ); - let (res, errors) = execute_sync( - query, - None, - &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), - &Variables::new(), - &(), - ) - .unwrap(); + let (res, errors) = + juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap(); assert_eq!(errors.len(), 0); assert_eq!( res, graphql_value!({ - "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"} + "artoo": {"__typename": "Droid", "id": 1, "sensorColor": "red"}, }), ); } diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 3660e0e89..c36b302db 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1348,7 +1348,6 @@ impl GraphQLTypeDefiniton { quote!( #field_ident: { - // TODO: investigate the unwraps here, they seem dangerous! match obj.get(#field_name) { #from_input_default Some(ref v) => ::juniper::FromInputValue::from_input_value(v)?, From f5097acf8ec018a1777dfaa40f04bf85ffe0948e Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 29 Jul 2021 22:55:21 +0300 Subject: [PATCH 15/39] Refactor new macros code to be consistent --- juniper_codegen/src/graphql_interface/attr.rs | 70 +- juniper_codegen/src/graphql_interface/mod.rs | 430 ++++++----- juniper_codegen/src/graphql_object/mod.rs | 59 +- juniper_codegen/src/graphql_union/attr.rs | 94 +-- juniper_codegen/src/graphql_union/derive.rs | 79 +- juniper_codegen/src/graphql_union/mod.rs | 707 +++++++++++------- 6 files changed, 800 insertions(+), 639 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 7e1eff169..90750c851 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -17,8 +17,8 @@ use crate::{ }; use super::{ - inject_async_trait, Definition, EnumType, ImplMeta, Implementer, ImplementerDowncast, - TraitMeta, TraitObjectType, Type, + inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, + TraitAttr, TraitObjectType, Type, }; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. @@ -50,28 +50,28 @@ pub fn expand_on_trait( attrs: Vec, mut ast: syn::ItemTrait, ) -> syn::Result { - let meta = TraitMeta::from_attrs("graphql_interface", &attrs)?; + let attr = TraitAttr::from_attrs("graphql_interface", &attrs)?; let trait_ident = &ast.ident; let trait_span = ast.span(); - let name = meta + let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| trait_ident.unraw().to_string()); - if !meta.is_internal && name.starts_with("__") { + if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( - meta.name + attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| trait_ident.span()), ); } - let scalar = scalar::Type::parse(meta.scalar.as_deref(), &ast.generics); + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - let mut implementers: Vec<_> = meta + let mut implementers: Vec<_> = attr .implementers .iter() .map(|ty| Implementer { @@ -81,7 +81,7 @@ pub fn expand_on_trait( scalar: scalar.clone(), }) .collect(); - for (ty, downcast) in &meta.external_downcasts { + for (ty, downcast) in &attr.external_downcasts { match implementers.iter_mut().find(|i| &i.ty == ty) { Some(impler) => { impler.downcast = Some(ImplementerDowncast::External { @@ -94,7 +94,7 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); - let renaming = meta + let renaming = attr .rename_fields .as_deref() .copied() @@ -134,7 +134,7 @@ pub fn expand_on_trait( proc_macro_error::abort_if_dirty(); - let context = meta + let context = attr .context .as_deref() .cloned() @@ -154,9 +154,9 @@ pub fn expand_on_trait( .cloned() }); - let is_trait_object = meta.r#dyn.is_some(); + let is_trait_object = attr.r#dyn.is_some(); - let is_async_trait = meta.asyncness.is_some() + let is_async_trait = attr.asyncness.is_some() || ast .items .iter() @@ -173,14 +173,14 @@ pub fn expand_on_trait( let ty = if is_trait_object { Type::TraitObject(Box::new(TraitObjectType::new( &ast, - &meta, + &attr, scalar.clone(), context.clone(), ))) } else { Type::Enum(Box::new(EnumType::new( &ast, - &meta, + &attr, &implementers, scalar.clone(), ))) @@ -190,7 +190,7 @@ pub fn expand_on_trait( ty, name, - description: meta.description.map(SpanContainer::into_inner), + description: attr.description.map(SpanContainer::into_inner), context, scalar: scalar.clone(), @@ -249,9 +249,9 @@ pub fn expand_on_impl( attrs: Vec, mut ast: syn::ItemImpl, ) -> syn::Result { - let meta = ImplMeta::from_attrs("graphql_interface", &attrs)?; + let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?; - let is_async_trait = meta.asyncness.is_some() + let is_async_trait = attr.asyncness.is_some() || ast .items .iter() @@ -261,10 +261,10 @@ pub fn expand_on_impl( }) .is_some(); - let is_trait_object = meta.r#dyn.is_some(); + let is_trait_object = attr.r#dyn.is_some(); if is_trait_object { - let scalar = scalar::Type::parse(meta.scalar.as_deref(), &ast.generics); + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); ast.attrs.push(parse_quote! { #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] @@ -338,19 +338,19 @@ impl TraitMethod { .filter(|attr| !path_eq_single(&attr.path, "graphql")) .collect(); - let meta = field::Attr::from_attrs("graphql", &method_attrs) + let attr = field::Attr::from_attrs("graphql", &method_attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; - if meta.ignore.is_some() { + if attr.ignore.is_some() { return None; } - if meta.downcast.is_some() { + if attr.downcast.is_some() { return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?))); } - Some(Self::Field(Self::parse_field(method, meta, renaming)?)) + Some(Self::Field(Self::parse_field(method, attr, renaming)?)) } /// Parses [`TraitMethod::Downcast`] from the given trait method definition. @@ -476,7 +476,8 @@ impl TraitMethod { } } -/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`. +/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given +/// `span`. #[must_use] fn err_invalid_method_receiver(span: &S) -> Option { ERR.emit_custom( @@ -486,7 +487,8 @@ fn err_invalid_method_receiver(span: &S) -> Option { None } -/// Emits "no trait method receiver" [`syn::Error`] pointing to the given `span`. +/// Emits "no trait method receiver" [`syn::Error`] pointing to the given +/// `span`. #[must_use] fn err_no_method_receiver(span: &S) -> Option { ERR.emit_custom( @@ -496,7 +498,8 @@ fn err_no_method_receiver(span: &S) -> Option { None } -/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given `span`. +/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given +/// `span`. fn err_only_implementer_downcast(span: &S) { ERR.emit_custom( span.span(), @@ -504,8 +507,8 @@ fn err_only_implementer_downcast(span: &S) { ); } -/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and `external` -/// [`ImplementerDowncast`] function. +/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and +/// `external` [`ImplementerDowncast`] function. fn err_duplicate_downcast( method: &syn::TraitItemMethod, external: &ImplementerDowncast, @@ -519,16 +522,17 @@ fn err_duplicate_downcast( ERR.custom( method.span(), format!( - "trait method `{}` conflicts with the external downcast function `{}` declared on the \ - trait to downcast into the implementer type `{}`", + "trait method `{}` conflicts with the external downcast function \ + `{}` declared on the trait to downcast into the implementer type \ + `{}`", method.sig.ident, external.to_token_stream(), impler_ty.to_token_stream(), ), ) .note(String::from( - "use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface \ - implementers downcasting", + "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 0fbdfb51b..d16ef2f70 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -31,56 +31,58 @@ use crate::{ util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, }; -/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait -/// definition, when generating code for [GraphQL interface][1] type. +/// 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)] -struct TraitMeta { +struct TraitAttr { /// Explicitly specified name of [GraphQL interface][1] type. /// - /// If absent, then Rust trait name is used by default. + /// If [`None`], then Rust trait name is used by default. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces name: Option>, /// Explicitly specified [description][2] of [GraphQL interface][1] type. /// - /// If absent, then Rust doc comment is used as [description][2], if any. + /// 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 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 enum Rust type behind the trait, + /// being an actual implementation of a [GraphQL interface][1] type. /// - /// If absent, then `{trait_name}Value` identifier will be used. + /// 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. + /// Explicitly specified identifier of the Rust type alias of the + /// [trait object][2], being an actual implementation of a + /// [GraphQL interface][1] type. /// - /// Effectively makes code generation to use a [trait object][2] as a [GraphQL interface][1] - /// type rather than an enum. If absent, then enum is used by default. + /// 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. + /// 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>, - /// Explicitly specified type of [`Context`] to use for resolving this [GraphQL interface][1] - /// type with. + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL interface][1] type with. /// - /// If absent, then unit type `()` is assumed as type of [`Context`]. + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -89,33 +91,35 @@ struct TraitMeta { /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to resolve this [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 one of the implementers implements [`GraphQLType`] - /// in a non-generic way over [`ScalarValue`] type. + /// 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 scalar: Option>, - /// Explicitly specified marker indicating that the Rust trait should be transformed into - /// [`async_trait`]. + /// Explicitly specified marker indicating that the Rust trait should be + /// transformed into [`async_trait`]. /// - /// If absent, then trait will be transformed into [`async_trait`] only if it contains async - /// methods. + /// If [`None`], then trait will be transformed into [`async_trait`] only if + /// it contains async methods. asyncness: Option>, - /// Explicitly specified external downcasting functions for [GraphQL interface][1] implementers. + /// Explicitly specified external downcasting functions for + /// [GraphQL interface][1] implementers. /// - /// If absent, then macro will downcast to the implementers via enum dispatch or dynamic - /// dispatch (if the one is chosen). That's why specifying an external resolver function has - /// sense, when some custom [interface][1] implementer resolving logic is involved. + /// 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. + /// 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 @@ -129,23 +133,21 @@ struct TraitMeta { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces rename_fields: Option>, - /// Indicator whether the generated code is intended to be used only inside the [`juniper`] - /// library. + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. is_internal: bool, } -impl Parse for TraitMeta { +impl Parse for TraitAttr { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut output = Self::default(); - + 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::()?; - output - .name + out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), @@ -156,8 +158,7 @@ impl Parse for TraitMeta { "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; - output - .description + out.description .replace(SpanContainer::new( ident.span(), Some(desc.span()), @@ -168,16 +169,14 @@ impl Parse for TraitMeta { "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; - output - .context + 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::()?; - output - .scalar + out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -187,7 +186,7 @@ impl Parse for TraitMeta { syn::Type, token::Bracket, token::Comma, >()? { let impler_span = impler.span(); - output + out .implementers .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) .none_or_else(|_| err::dup_arg(impler_span))?; @@ -196,23 +195,20 @@ impl Parse for TraitMeta { "dyn" => { input.parse::()?; let alias = input.parse::()?; - output - .r#dyn + 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::()?; - output - .r#enum + out.r#enum .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) .none_or_else(|_| err::dup_arg(&ident))? } "async" => { let span = ident.span(); - output - .asyncness + out.asyncness .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } @@ -222,16 +218,14 @@ impl Parse for TraitMeta { let dwncst = input.parse::()?; let dwncst_spanned = SpanContainer::new(ident.span(), Some(ty.span()), dwncst); let dwncst_span = dwncst_spanned.span_joined(); - output - .external_downcasts + out.external_downcasts .insert(ty, dwncst_spanned) .none_or_else(|_| err::dup_arg(dwncst_span))? } "rename_all" => { input.parse::()?; let val = input.parse::()?; - output - .rename_fields + out.rename_fields .replace(SpanContainer::new( ident.span(), Some(val.span()), @@ -240,7 +234,7 @@ impl Parse for TraitMeta { .none_or_else(|_| err::dup_arg(&ident))?; } "internal" => { - output.is_internal = true; + out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); @@ -248,13 +242,13 @@ impl Parse for TraitMeta { } input.try_parse::()?; } - - Ok(output) + Ok(out) } } -impl TraitMeta { - /// Tries to merge two [`TraitMeta`]s into a single one, reporting about duplicates, if any. +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), @@ -273,8 +267,8 @@ impl TraitMeta { }) } - /// Parses [`TraitMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait - /// definition. + /// Parses [`TraitAttr`] 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()) @@ -297,12 +291,13 @@ impl TraitMeta { } } -/// Available metadata (arguments) behind `#[graphql_interface]` attribute placed on a trait -/// implementation block, when generating code for [GraphQL interface][1] type. +/// Available arguments behind `#[graphql_interface]` attribute placed on a +/// trait implementation block, when generating code for [GraphQL interface][1] +/// type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Debug, Default)] -struct ImplMeta { +struct ImplAttr { /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to implementing the [GraphQL interface][1] type with. /// @@ -317,50 +312,47 @@ struct ImplMeta { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces scalar: Option>, - /// Explicitly specified marker indicating that the trait implementation block should be - /// transformed with applying [`async_trait`]. + /// 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. + /// If absent, then trait will be transformed with applying [`async_trait`] + /// only if it contains async methods. /// - /// This marker is especially useful when Rust trait contains async default methods, while the - /// implementation block doesn't. + /// This marker is especially useful when Rust trait contains async default + /// methods, while the implementation block doesn't. asyncness: Option>, - /// Explicitly specified marker indicating that the implemented [GraphQL interface][1] type is - /// represented as a [trait object][2] in Rust type system rather then an enum (default mode, - /// when the marker is absent). + /// Explicitly specified marker indicating that the implemented + /// [GraphQL interface][1] type is represented as a [trait object][2] in + /// Rust type system rather then an enum (default mode, when the marker is + /// absent). /// /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html r#dyn: Option>, } -impl Parse for ImplMeta { +impl Parse for ImplAttr { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut output = Self::default(); - + 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::()?; - output - .scalar + out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } "dyn" => { let span = ident.span(); - output - .r#dyn + out.r#dyn .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } "async" => { let span = ident.span(); - output - .asyncness + out.asyncness .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } @@ -370,13 +362,13 @@ impl Parse for ImplMeta { } input.try_parse::()?; } - - Ok(output) + Ok(out) } } -impl ImplMeta { - /// Tries to merge two [`ImplMeta`]s into a single one, reporting about duplicates, if any. +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), @@ -385,8 +377,8 @@ impl ImplMeta { }) } - /// Parses [`ImplMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a trait - /// implementation block. + /// 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()) @@ -413,8 +405,8 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces description: Option, - /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with for this - /// [GraphQL interface][1]. + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL interface][1]. /// /// If [`None`] then generated code will use unit type `()` as [`Context`]. /// @@ -423,8 +415,8 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces context: Option, - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] implementation - /// with for this [GraphQL interface][1]. + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue @@ -484,8 +476,8 @@ impl Definition { } } - /// Returns generated code implementing [`marker::IsOutputType`] trait for this - /// [GraphQL interface][1]. + /// 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 @@ -512,7 +504,8 @@ impl Definition { } } - /// Returns generated code implementing [`GraphQLType`] trait for this [GraphQL interface][1]. + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL interface][1]. /// /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -569,7 +562,8 @@ impl Definition { } } - /// Returns generated code implementing [`GraphQLValue`] trait for this [GraphQL interface][1]. + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL interface][1]. /// /// [`GraphQLValue`]: juniper::GraphQLValue /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -717,20 +711,22 @@ impl Definition { } } -/// Representation of custom downcast into an [`Implementer`] from a [GraphQL interface][1] type for -/// code generation. +/// Representation of custom downcast into an [`Implementer`] from a +/// [GraphQL interface][1] type for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Clone, Debug)] enum ImplementerDowncast { - /// Downcast is performed via a method of trait describing a [GraphQL interface][1]. + /// Downcast is performed via a method of trait describing a + /// [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces Method { /// Name of trait method which performs this [`ImplementerDowncast`]. name: syn::Ident, - /// Indicator whether the trait method accepts a [`Context`] as its second argument. + /// Indicator whether the trait method accepts a [`Context`] as its + /// second argument. /// /// [`Context`]: juniper::Context with_context: bool, @@ -748,21 +744,23 @@ enum ImplementerDowncast { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[derive(Clone, Debug)] struct Implementer { - /// Rust type that this [GraphQL interface][1] [`Implementer`] is represented by. + /// Rust type that this [GraphQL interface][1] [`Implementer`] is + /// represented by. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces ty: syn::Type, /// Custom [`ImplementerDowncast`] for this [`Implementer`]. /// - /// If absent, then [`Implementer`] is downcast from an enum variant or a trait object. + /// If absent, then [`Implementer`] is downcast from an enum variant or a + /// trait object. downcast: Option, - /// Rust type of [`Context`] that this [GraphQL interface][1] [`Implementer`] requires for - /// downcasting. + /// 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. + /// It's available only when code generation happens for Rust traits and a + /// trait method contains context argument. /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -805,8 +803,9 @@ impl Implementer { }) } - /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns - /// name of the GraphQL type represented by this [`Implementer`]. + /// 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`]. /// @@ -830,12 +829,13 @@ impl Implementer { }) } - /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts - /// the [GraphQL interface][1] type into this [`Implementer`] synchronously. + /// 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`]. /// - /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + /// [0]: juniper::GraphQLValue::resolve_into_type /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option { @@ -856,8 +856,10 @@ impl Implementer { }) } - /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, - /// which downcasts the [GraphQL interface][1] type into this [`Implementer`] asynchronously. + /// Returns generated code for the + /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which + /// downcasts the [GraphQL interface][1] type into this [`Implementer`] + /// asynchronously. /// /// Returns [`None`] if there is no custom [`Implementer::downcast`]. /// @@ -883,7 +885,8 @@ impl Implementer { } } -/// Representation of Rust enum implementing [GraphQL interface][1] type for code generation. +/// Representation of Rust enum implementing [GraphQL interface][1] type for +/// code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces struct EnumType { @@ -893,52 +896,62 @@ struct EnumType { /// [`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`]. + /// 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`]. + /// 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`]. + /// [`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`]. + /// 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`]. + /// 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`]. + /// 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`]. + /// [`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 new [`EnumType`] out of the given parameters. + /// Constructs a new [`EnumType`] out of the given parameters. #[must_use] fn new( r#trait: &syn::ItemTrait, - meta: &TraitMeta, + meta: &TraitAttr, implers: &[Implementer], scalar: scalar::Type, ) -> Self { @@ -990,8 +1003,8 @@ impl EnumType { } } - /// Returns name of a single variant of this [`EnumType`] by the given underlying [`syn::Type`] - /// of the variant. + /// 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 { @@ -1001,15 +1014,15 @@ impl EnumType { } } - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant to hold type - /// parameters. + /// 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 generate code for dispatching non-exhaustive phantom variant of + /// this [`EnumType`] in `match` expressions. /// /// Returns [`None`] if this [`EnumType`] is exhaustive. #[must_use] @@ -1081,8 +1094,8 @@ impl EnumType { generics } - /// Returns full type signature of the original trait describing the [GraphQL interface][1] for - /// this [`EnumType`]. + /// 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] @@ -1104,8 +1117,8 @@ impl EnumType { /// 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. + /// 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; @@ -1168,8 +1181,8 @@ impl EnumType { } } - /// Returns generated code implementing [`From`] trait for this [`EnumType`] from its - /// [`EnumType::variants`]. + /// 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(); @@ -1188,8 +1201,8 @@ impl EnumType { }) } - /// Returns generated code implementing the original trait describing the [GraphQL interface][1] - /// for this [`EnumType`]. + /// Returns generated code implementing the original trait describing the + /// [GraphQL interface][1] for this [`EnumType`]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] @@ -1288,10 +1301,11 @@ impl EnumType { impl_tokens } - /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns - /// name of the underlying [`Implementer`] GraphQL type contained in this [`EnumType`]. + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`EnumType`]. /// - /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + /// [0]: juniper::GraphQLValue::concrete_type_name #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -1315,10 +1329,11 @@ impl EnumType { } } - /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts - /// this [`EnumType`] into its underlying [`Implementer`] type synchronously. + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which downcasts this [`EnumType`] into its underlying + /// [`Implementer`] type synchronously. /// - /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + /// [0]: juniper::GraphQLValue::resolve_into_type #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); @@ -1340,8 +1355,10 @@ impl EnumType { } } - /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, - /// which downcasts this [`EnumType`] into its underlying [`Implementer`] type asynchronously. + /// 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] @@ -1369,16 +1386,8 @@ impl EnumType { } } -impl ToTokens for EnumType { - fn to_tokens(&self, into: &mut TokenStream) { - into.append_all(&[self.type_definition_tokens()]); - into.append_all(self.impl_from_tokens()); - into.append_all(&[self.impl_trait_tokens()]); - } -} - -/// Representation of Rust [trait object][2] implementing [GraphQL interface][1] type for code -/// generation. +/// 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 @@ -1389,19 +1398,20 @@ struct TraitObjectType { /// [`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`]. + /// 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`]. + /// [`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`] parametrization of this [`TraitObjectType`] to generate + /// it with. /// /// [`ScalarValue`]: juniper::ScalarValue scalar: scalar::Type, @@ -1415,11 +1425,11 @@ struct TraitObjectType { } impl TraitObjectType { - /// Constructs new [`TraitObjectType`] out of the given parameters. + /// Constructs a new [`TraitObjectType`] out of the given parameters. #[must_use] fn new( r#trait: &syn::ItemTrait, - meta: &TraitMeta, + meta: &TraitAttr, scalar: scalar::Type, context: Option, ) -> Self { @@ -1477,8 +1487,8 @@ impl TraitObjectType { generics } - /// Returns full type signature of the original trait describing the [GraphQL interface][1] for - /// this [`TraitObjectType`]. + /// 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] @@ -1495,7 +1505,8 @@ impl TraitObjectType { parse_quote! { #ty#generics } } - /// Returns generated code of the full type signature of this [`TraitObjectType`]. + /// Returns generated code of the full type signature of this + /// [`TraitObjectType`]. #[must_use] fn ty_tokens(&self) -> TokenStream { let ty = &self.trait_ident; @@ -1516,10 +1527,11 @@ impl TraitObjectType { } } - /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns - /// name of the underlying [`Implementer`] GraphQL type contained in this [`TraitObjectType`]. + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`TraitObjectType`]. /// - /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + /// [0]: juniper::GraphQLValue::concrete_type_name #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { quote! { @@ -1527,10 +1539,11 @@ impl TraitObjectType { } } - /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts - /// this [`TraitObjectType`] into its underlying [`Implementer`] type synchronously. + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which downcasts this [`TraitObjectType`] into its underlying + /// [`Implementer`] type synchronously. /// - /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + /// [0]: juniper::GraphQLValue::resolve_into_type #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); @@ -1541,9 +1554,10 @@ impl TraitObjectType { } } - /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, - /// which downcasts this [`TraitObjectType`] into its underlying [`Implementer`] type - /// asynchronously. + /// 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] @@ -1607,8 +1621,8 @@ impl ToTokens for TraitObjectType { } } -/// Representation of possible Rust types implementing [GraphQL interface][1] type for code -/// generation. +/// Representation of possible Rust types implementing [GraphQL interface][1] +/// type for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces enum Type { @@ -1624,6 +1638,15 @@ enum Type { 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`]. @@ -1643,8 +1666,8 @@ impl Type { (quote! { #impl_generics }, where_clause.cloned()) } - /// Returns full type signature of the original trait describing the [GraphQL interface][1] for - /// this [`Type`]. + /// Returns full type signature of the original trait describing the + /// [GraphQL interface][1] for this [`Type`]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] @@ -1664,10 +1687,11 @@ impl Type { } } - /// Returns generated code for the [`GraphQLValue::concrete_type_name`] method, which returns - /// name of the underlying [`Implementer`] GraphQL type contained in this [`Type`]. + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`Type`]. /// - /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name + /// [0]: juniper::GraphQLValue::concrete_type_name #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { match self { @@ -1676,10 +1700,11 @@ impl Type { } } - /// Returns generated code for the [`GraphQLValue::resolve_into_type`] method, which downcasts - /// this [`Type`] into its underlying [`Implementer`] type synchronously. + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which downcasts this [`Type`] into its underlying + /// [`Implementer`] type synchronously. /// - /// [`GraphQLValue::resolve_into_type`]: juniper::GraphQLValue::resolve_into_type + /// [0]: juniper::GraphQLValue::resolve_into_type #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { match self { @@ -1688,8 +1713,10 @@ impl Type { } } - /// Returns generated code for the [`GraphQLValueAsync::resolve_into_type_async`][0] method, - /// which downcasts this [`Type`] into its underlying [`Implementer`] type asynchronously. + /// 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 { @@ -1700,18 +1727,9 @@ impl Type { } } -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), - } - } -} - -/// 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. +/// 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, diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index fc59c1dc9..fbfda8e33 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -50,26 +50,26 @@ struct Attr { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions description: Option>, - /// Explicitly specified type of `juniper::Context` to use for resolving - /// this [GraphQL object][1] type with. + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL object][1] type with. /// - /// If [`None`], then unit type `()` is assumed as a type of - /// `juniper::Context`. + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// + /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Objects context: Option>, /// Explicitly specified type (or type parameter with its bounds) of - /// `juniper::ScalarValue` to use for resolving this [GraphQL object][1] - /// type with. + /// [`ScalarValue`] to use for resolving this [GraphQL object][1] type with. /// /// If [`None`], then generated code will be generic over any - /// `juniper::ScalarValue` type, which, in turn, requires all [object][1] - /// fields to be generic over any `juniper::ScalarValue` type too. That's - /// why this type should be specified only if one of the variants implements - /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` - /// type. + /// [`ScalarValue`] type, which, in turn, requires all [object][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/June2018/#sec-Objects scalar: Option>, @@ -95,16 +95,14 @@ struct Attr { impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut output = Self::default(); - + 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::()?; - output - .name + out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), @@ -115,8 +113,7 @@ impl Parse for Attr { "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; - output - .description + out.description .replace(SpanContainer::new( ident.span(), Some(desc.span()), @@ -127,16 +124,14 @@ impl Parse for Attr { "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; - output - .context + 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::()?; - output - .scalar + out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -146,7 +141,7 @@ impl Parse for Attr { syn::Type, token::Bracket, token::Comma, >()? { let iface_span = iface.span(); - output + out .interfaces .replace(SpanContainer::new(ident.span(), Some(iface_span), iface)) .none_or_else(|_| err::dup_arg(iface_span))?; @@ -155,8 +150,7 @@ impl Parse for Attr { "rename_all" => { input.parse::()?; let val = input.parse::()?; - output - .rename_fields + out.rename_fields .replace(SpanContainer::new( ident.span(), Some(val.span()), @@ -165,7 +159,7 @@ impl Parse for Attr { .none_or_else(|_| err::dup_arg(&ident))?; } "internal" => { - output.is_internal = true; + out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); @@ -173,8 +167,7 @@ impl Parse for Attr { } input.try_parse::()?; } - - Ok(output) + Ok(out) } } @@ -208,6 +201,9 @@ impl Attr { } } +/// Definition of [GraphQL object][1] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Objects #[derive(Debug)] struct Definition { /// Name of this [GraphQL object][1] in GraphQL schema. @@ -231,12 +227,13 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Objects description: Option, - /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` - /// implementation with for this [GraphQL object][1]. + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL object][1]. /// - /// If [`None`] then generated code will use unit type `()` as - /// `juniper::Context`. + /// If [`None`] then generated code will use unit type `()` as [`Context`]. /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Objects context: Option, diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index c2617d1d2..938a48cae 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -7,14 +7,14 @@ use quote::{quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _}; use crate::{ - common::parse, + common::{parse, scalar}, result::GraphQLScope, util::{path_eq_single, span_container::SpanContainer}, }; use super::{ - all_variants_different, emerge_union_variants_from_meta, UnionDefinition, UnionMeta, - UnionVariantDefinition, UnionVariantMeta, + all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr, + VariantDefinition, }; /// [`GraphQLScope`] of errors for `#[graphql_union]` macro. @@ -22,28 +22,36 @@ const ERR: GraphQLScope = GraphQLScope::UnionAttr; /// Expands `#[graphql_union]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - let mut ast = syn::parse2::(body).map_err(|_| { - syn::Error::new( - Span::call_site(), - "#[graphql_union] attribute is applicable to trait definitions only", - ) - })?; - let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_union", ast.attrs); + if let Ok(mut ast) = syn::parse2::(body) { + let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_union", ast.attrs); + return expand_on_trait(trait_attrs, ast); + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_union] attribute is applicable to trait definitions only", + )) +} - let meta = UnionMeta::from_attrs("graphql_union", &trait_attrs)?; +/// Expands `#[graphql_union]` macro placed on a trait definition. +pub fn expand_on_trait( + attrs: Vec, + mut ast: syn::ItemTrait, +) -> syn::Result { + let attr = Attr::from_attrs("graphql_union", &attrs)?; let trait_span = ast.span(); let trait_ident = &ast.ident; - let name = meta + let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| trait_ident.unraw().to_string()); - if !meta.is_internal && name.starts_with("__") { + if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( - meta.name + attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| trait_ident.span()), @@ -54,14 +62,14 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result parse_variant_from_trait_method(m, trait_ident, &meta), + syn::TraitItem::Method(m) => parse_variant_from_trait_method(m, trait_ident, &attr), _ => None, }) .collect(); proc_macro_error::abort_if_dirty(); - emerge_union_variants_from_meta(&mut variants, meta.external_resolvers); + emerge_union_variants_from_attr(&mut variants, attr.external_resolvers); if variants.is_empty() { ERR.emit_custom(trait_span, "expects at least one union variant"); @@ -76,40 +84,39 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result Option { + trait_attr: &Attr, +) -> Option { let method_attrs = method.attrs.clone(); // Remove repeated attributes from the method, to omit incorrect expansion. @@ -118,22 +125,22 @@ fn parse_variant_from_trait_method( .filter(|attr| !path_eq_single(&attr.path, "graphql")) .collect(); - let meta = UnionVariantMeta::from_attrs("graphql", &method_attrs) + let attr = VariantAttr::from_attrs("graphql", &method_attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; - if let Some(rslvr) = meta.external_resolver { + if let Some(rslvr) = attr.external_resolver { ERR.custom( rslvr.span_ident(), "cannot use #[graphql(with = ...)] attribute on a trait method", ) .note(String::from( - "instead use #[graphql(ignore)] on the method with #[graphql_union(on ... = ...)] on \ - the trait itself", + "instead use #[graphql(ignore)] on the method with \ + #[graphql_union(on ... = ...)] on the trait itself", )) .emit() } - if meta.ignore.is_some() { + if attr.ignore.is_some() { return None; } @@ -165,21 +172,21 @@ fn parse_variant_from_trait_method( } let resolver_code = { - if let Some(other) = trait_meta.external_resolvers.get(&ty) { + if let Some(other) = trait_attr.external_resolvers.get(&ty) { ERR.custom( method_span, format!( - "trait method `{}` conflicts with the external resolver function `{}` declared \ - on the trait to resolve the variant type `{}`", + "trait method `{}` conflicts with the external resolver \ + function `{}` declared on the trait to resolve the \ + variant type `{}`", method_ident, other.to_token_stream(), ty.to_token_stream(), - ), ) .note(String::from( - "use `#[graphql(ignore)]` attribute to ignore this trait method for union \ - variants resolution", + "use `#[graphql(ignore)]` attribute to ignore this trait \ + method for union variants resolution", )) .emit(); } @@ -195,17 +202,18 @@ fn parse_variant_from_trait_method( } }; - // 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. + // 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. let resolver_check = parse_quote! { ({ #resolver_code } as ::std::option::Option<&#ty>).is_some() }; - Some(UnionVariantDefinition { + Some(VariantDefinition { ty, resolver_code, resolver_check, - context_ty: method_context_ty, + context: method_context_ty, }) } diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index 161ddf6a9..bcd6e2a0e 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -6,12 +6,14 @@ use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields}; use crate::{ - common::parse::TypeExt as _, result::GraphQLScope, util::span_container::SpanContainer, + common::{parse::TypeExt as _, scalar}, + result::GraphQLScope, + util::span_container::SpanContainer, }; use super::{ - all_variants_different, emerge_union_variants_from_meta, UnionDefinition, UnionMeta, - UnionVariantDefinition, UnionVariantMeta, + all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr, + VariantDefinition, }; /// [`GraphQLScope`] of errors for `#[derive(GraphQLUnion)]` macro. @@ -29,21 +31,22 @@ pub fn expand(input: TokenStream) -> syn::Result { .map(ToTokens::into_token_stream) } -/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a Rust enum. -fn expand_enum(ast: syn::DeriveInput) -> syn::Result { - let meta = UnionMeta::from_attrs("graphql", &ast.attrs)?; +/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a +/// Rust enum. +fn expand_enum(ast: syn::DeriveInput) -> syn::Result { + let attr = Attr::from_attrs("graphql", &ast.attrs)?; let enum_span = ast.span(); let enum_ident = ast.ident; - let name = meta + let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| enum_ident.unraw().to_string()); - if !meta.is_internal && name.starts_with("__") { + if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( - meta.name + attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| enum_ident.span()), @@ -55,12 +58,12 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { _ => unreachable!(), } .into_iter() - .filter_map(|var| parse_variant_from_enum_variant(var, &enum_ident, &meta)) + .filter_map(|var| parse_variant_from_enum_variant(var, &enum_ident, &attr)) .collect(); proc_macro_error::abort_if_dirty(); - emerge_union_variants_from_meta(&mut variants, meta.external_resolvers); + emerge_union_variants_from_attr(&mut variants, attr.external_resolvers); if variants.is_empty() { ERR.emit_custom(enum_span, "expects at least one union variant"); @@ -75,13 +78,13 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { proc_macro_error::abort_if_dirty(); - Ok(UnionDefinition { + Ok(Definition { name, ty: parse_quote! { #enum_ident }, is_trait_object: false, - description: meta.description.map(SpanContainer::into_inner), - context: meta.context.map(SpanContainer::into_inner), - scalar: meta.scalar.map(SpanContainer::into_inner), + description: attr.description.map(SpanContainer::into_inner), + context: attr.context.map(SpanContainer::into_inner), + scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics, variants, }) @@ -89,19 +92,19 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { /// Parses given Rust enum `var`iant as [GraphQL union][1] variant. /// -/// On failure returns [`None`] and internally fills up [`proc_macro_error`] with the corresponding -/// errors. +/// On failure returns [`None`] and internally fills up [`proc_macro_error`] +/// with the corresponding errors. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions fn parse_variant_from_enum_variant( var: syn::Variant, enum_ident: &syn::Ident, - enum_meta: &UnionMeta, -) -> Option { - let meta = UnionVariantMeta::from_attrs("graphql", &var.attrs) + enum_attr: &Attr, +) -> Option { + let attr = VariantAttr::from_attrs("graphql", &var.attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; - if meta.ignore.is_some() { + if attr.ignore.is_some() { return None; } @@ -129,12 +132,13 @@ fn parse_variant_from_enum_variant( let enum_path = quote! { #enum_ident::#var_ident }; - let resolver_code = if let Some(rslvr) = meta.external_resolver { - if let Some(other) = enum_meta.external_resolvers.get(&ty) { + let resolver_code = if let Some(rslvr) = attr.external_resolver { + if let Some(other) = enum_attr.external_resolvers.get(&ty) { ERR.emit_custom( rslvr.span_ident(), format!( - "variant `{}` already has external resolver function `{}` declared on the enum", + "variant `{}` already has external resolver function `{}` \ + declared on the enum", ty.to_token_stream(), other.to_token_stream(), ), @@ -156,29 +160,30 @@ fn parse_variant_from_enum_variant( matches!(self, #enum_path(_)) }; - Some(UnionVariantDefinition { + Some(VariantDefinition { ty, resolver_code, resolver_check, - context_ty: None, + context: None, }) } -/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a Rust struct. -fn expand_struct(ast: syn::DeriveInput) -> syn::Result { - let meta = UnionMeta::from_attrs("graphql", &ast.attrs)?; +/// Expands into generated code a `#[derive(GraphQLUnion)]` macro placed on a +/// Rust struct. +fn expand_struct(ast: syn::DeriveInput) -> syn::Result { + let attr = Attr::from_attrs("graphql", &ast.attrs)?; let struct_span = ast.span(); let struct_ident = ast.ident; - let name = meta + let name = attr .name .clone() .map(SpanContainer::into_inner) .unwrap_or_else(|| struct_ident.unraw().to_string()); - if !meta.is_internal && name.starts_with("__") { + if !attr.is_internal && name.starts_with("__") { ERR.no_double_underscore( - meta.name + attr.name .as_ref() .map(SpanContainer::span_ident) .unwrap_or_else(|| struct_ident.span()), @@ -186,7 +191,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { } let mut variants = vec![]; - emerge_union_variants_from_meta(&mut variants, meta.external_resolvers); + emerge_union_variants_from_attr(&mut variants, attr.external_resolvers); if variants.is_empty() { ERR.emit_custom(struct_span, "expects at least one union variant"); } @@ -200,13 +205,13 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { proc_macro_error::abort_if_dirty(); - Ok(UnionDefinition { + Ok(Definition { name, ty: parse_quote! { #struct_ident }, is_trait_object: false, - description: meta.description.map(SpanContainer::into_inner), - context: meta.context.map(SpanContainer::into_inner), - scalar: meta.scalar.map(SpanContainer::into_inner), + description: attr.description.map(SpanContainer::into_inner), + context: attr.context.map(SpanContainer::into_inner), + scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics, variants, }) diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index e1ca08587..26b7fe430 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -8,8 +8,9 @@ pub mod derive; use std::collections::HashMap; use proc_macro2::TokenStream; -use quote::{quote, ToTokens, TokenStreamExt as _}; +use quote::{format_ident, quote, ToTokens}; use syn::{ + ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, @@ -17,83 +18,90 @@ use syn::{ }; use crate::{ - common::parse::{ - attr::{err, OptionExt as _}, - ParseBufferExt as _, + common::{ + gen, + parse::{ + attr::{err, OptionExt as _}, + ParseBufferExt as _, + }, + scalar, }, util::{filter_attrs, get_doc_comment, span_container::SpanContainer}, }; -/// Helper alias for the type of [`UnionMeta::external_resolvers`] field. -type UnionMetaResolvers = HashMap>; +/// Helper alias for the type of [`Attr::external_resolvers`] field. +type AttrResolvers = HashMap>; -/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_union]`) attribute when -/// generating code for [GraphQL union][1] type. +/// Available arguments behind `#[graphql]` (or `#[graphql_union]`) attribute +/// when generating code for [GraphQL union][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[derive(Debug, Default)] -struct UnionMeta { +struct Attr { /// Explicitly specified name of [GraphQL union][1] type. /// - /// If absent, then Rust type name is used by default. + /// If [`None`], then Rust type name is used by default. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub name: Option>, + name: Option>, /// Explicitly specified [description][2] of [GraphQL union][1] type. /// - /// If absent, then Rust doc comment is used as [description][2], if any. + /// If [`None`], then Rust doc comment is used as [description][2], if any. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub description: Option>, + description: Option>, - /// Explicitly specified type of `juniper::Context` to use for resolving this [GraphQL union][1] - /// type with. + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL union][1] type with. /// - /// If absent, then unit type `()` is assumed as type of `juniper::Context`. + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. /// + /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub context: Option>, + context: Option>, - /// Explicitly specified type of `juniper::ScalarValue` to use for resolving this + /// Explicitly specified type of [`ScalarValue`] to use for resolving this /// [GraphQL union][1] type with. /// - /// If absent, then generated code will be generic over any `juniper::ScalarValue` type, which, - /// in turn, requires all [union][1] variants to be generic over any `juniper::ScalarValue` type - /// too. That's why this type should be specified only if one of the variants implements - /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` type. + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type, which, in turn, requires all [union][1] variants + /// 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/June2018/#sec-Unions - pub scalar: Option>, + scalar: Option>, - /// Explicitly specified external resolver functions for [GraphQL union][1] variants. + /// Explicitly specified external resolver functions for [GraphQL union][1] + /// variants. /// - /// If absent, then macro will try to auto-infer all the possible variants from the type - /// declaration, if possible. That's why specifying an external resolver function has sense, - /// when some custom [union][1] variant resolving logic is involved, or variants cannot be - /// inferred. + /// If [`None`], then macro will try to auto-infer all the possible variants + /// from the type declaration, if possible. That's why specifying an + /// external resolver function has sense, when some custom [union][1] + /// variant resolving logic is involved, or variants cannot be inferred. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub external_resolvers: UnionMetaResolvers, + external_resolvers: AttrResolvers, - /// Indicator whether the generated code is intended to be used only inside the `juniper` - /// library. - pub is_internal: bool, + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. + is_internal: bool, } -impl Parse for UnionMeta { +impl Parse for Attr { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut output = Self::default(); - + let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { "name" => { input.parse::()?; let name = input.parse::()?; - output - .name + out.name .replace(SpanContainer::new( ident.span(), Some(name.span()), @@ -104,8 +112,7 @@ impl Parse for UnionMeta { "desc" | "description" => { input.parse::()?; let desc = input.parse::()?; - output - .description + out.description .replace(SpanContainer::new( ident.span(), Some(desc.span()), @@ -116,16 +123,14 @@ impl Parse for UnionMeta { "ctx" | "context" | "Context" => { input.parse::()?; let ctx = input.parse::()?; - output - .context + 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::()?; - output - .scalar + let scl = input.parse::()?; + out.scalar .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -135,13 +140,12 @@ impl Parse for UnionMeta { let rslvr = input.parse::()?; let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr); let rslvr_span = rslvr_spanned.span_joined(); - output - .external_resolvers + out.external_resolvers .insert(ty, rslvr_spanned) .none_or_else(|_| err::dup_arg(rslvr_span))? } "internal" => { - output.is_internal = true; + out.is_internal = true; } name => { return Err(err::unknown_arg(&ident, name)); @@ -149,13 +153,13 @@ impl Parse for UnionMeta { } input.try_parse::()?; } - - Ok(output) + Ok(out) } } -impl UnionMeta { - /// Tries to merge two [`UnionMeta`]s into a single one, reporting about duplicates, if any. +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), @@ -169,9 +173,9 @@ impl UnionMeta { }) } - /// Parses [`UnionMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a type - /// definition. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a type definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut meta = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; @@ -184,17 +188,17 @@ impl UnionMeta { } } -/// Available metadata (arguments) behind `#[graphql]` (or `#[graphql_union]`) attribute when -/// generating code for [GraphQL union][1]'s variant. +/// Available arguments behind `#[graphql]` attribute when generating code for +/// [GraphQL union][1]'s variant. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[derive(Debug, Default)] -struct UnionVariantMeta { - /// Explicitly specified marker for the variant/field being ignored and not included into - /// [GraphQL union][1]. +struct VariantAttr { + /// Explicitly specified marker for the variant/field being ignored and not + /// included into [GraphQL union][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub ignore: Option>, + ignore: Option>, /// Explicitly specified external resolver function for this [GraphQL union][1] variant. /// @@ -203,25 +207,23 @@ struct UnionVariantMeta { /// logic is involved. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub external_resolver: Option>, + external_resolver: Option>, } -impl Parse for UnionVariantMeta { +impl Parse for VariantAttr { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut output = Self::default(); - + let mut out = Self::default(); while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { - "ignore" | "skip" => output + "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, "with" => { input.parse::()?; let rslvr = input.parse::()?; - output - .external_resolver + out.external_resolver .replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr)) .none_or_else(|_| err::dup_arg(&ident))? } @@ -231,14 +233,13 @@ impl Parse for UnionVariantMeta { } input.try_parse::()?; } - - Ok(output) + Ok(out) } } -impl UnionVariantMeta { - /// Tries to merge two [`UnionVariantMeta`]s into a single one, reporting about duplicates, if - /// any. +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 { ignore: try_merge_opt!(ignore: self, another), @@ -246,219 +247,240 @@ impl UnionVariantMeta { }) } - /// Parses [`UnionVariantMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a - /// variant/field/method definition. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + /// Parses [`VariantAttr`] from the given multiple `name`d + /// [`syn::Attribute`]s placed on a variant/field/method definition. + fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) } } -/// Definition of [GraphQL union][1] variant for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Unions -struct UnionVariantDefinition { - /// Rust type that this [GraphQL union][1] variant resolves into. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub ty: syn::Type, - - /// Rust code for value resolution of this [GraphQL union][1] variant. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub resolver_code: syn::Expr, - - /// Rust code for checking whether [GraphQL union][1] should be resolved into this variant. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub resolver_check: syn::Expr, - - /// Rust type of `juniper::Context` that this [GraphQL union][1] variant requires for - /// resolution. - /// - /// It's available only when code generation happens for Rust traits and a trait method contains - /// context argument. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub context_ty: Option, -} - /// Definition of [GraphQL union][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions -struct UnionDefinition { +struct Definition { /// Name of this [GraphQL union][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub name: String, + name: String, /// Rust type that this [GraphQL union][1] is represented with. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub ty: syn::Type, + ty: syn::Type, - /// Generics of the Rust type that this [GraphQL union][1] is implemented for. + /// Generics of the Rust type that this [GraphQL union][1] is implemented + /// for. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub generics: syn::Generics, + generics: syn::Generics, - /// Indicator whether code should be generated for a trait object, rather than for a regular - /// Rust type. - pub is_trait_object: bool, + /// Indicator whether code should be generated for a trait object, rather + /// than for a regular Rust type. + is_trait_object: bool, /// Description of this [GraphQL union][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub description: Option, + description: Option, - /// Rust type of `juniper::Context` to generate `juniper::GraphQLType` implementation with + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL union][1]. /// - /// If [`None`] then generated code will use unit type `()` as `juniper::Context`. + /// If [`None`] then generated code will use unit type `()` as [`Context`]. /// + /// [`Context`]: juniper::Context + /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub context: Option, + context: Option, - /// Rust type of `juniper::ScalarValue` to generate `juniper::GraphQLType` implementation with - /// for this [GraphQL union][1]. + /// Rust type of [`ScalarValue`] to generate [`GraphQLType`] implementation + /// with for this [GraphQL union][1]. /// - /// If [`None`] then generated code will be generic over any `juniper::ScalarValue` type, which, - /// in turn, requires all [union][1] variants to be generic over any `juniper::ScalarValue` type - /// too. That's why this type should be specified only if one of the variants implements - /// `juniper::GraphQLType` in a non-generic way over `juniper::ScalarValue` type. + /// If [`None`] then generated code will be generic over any [`ScalarValue`] + /// type, which, in turn, requires all [union][1] variants 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/June2018/#sec-Unions - pub scalar: Option, + scalar: scalar::Type, /// Variants definitions of this [GraphQL union][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions - pub variants: Vec, + variants: Vec, } -impl ToTokens for UnionDefinition { +impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { - let name = &self.name; - let ty = &self.ty; + self.impl_graphql_union_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); + } +} - let context = self - .context - .as_ref() - .map(|ctx| quote! { #ctx }) - .unwrap_or_else(|| quote! { () }); +impl Definition { + /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`] + /// trait (and similar) implementation of this [GraphQL union][1]. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + fn impl_generics( + &self, + for_async: bool, + ) -> (TokenStream, TokenStream, Option) { + let (_, ty_generics, _) = self.generics.split_for_impl(); + let ty = &self.ty; - let scalar = self - .scalar - .as_ref() - .map(|scl| quote! { #scl }) - .unwrap_or_else(|| quote! { __S }); + let mut ty_full = quote! { #ty#ty_generics }; + if self.is_trait_object { + ty_full = quote! { dyn #ty_full + '__obj + Send + Sync }; + } - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); + let mut generics = self.generics.clone(); - let var_types: Vec<_> = self.variants.iter().map(|var| &var.ty).collect(); + if self.is_trait_object { + generics.params.push(parse_quote! { '__obj }); + } - let all_variants_unique = if var_types.len() > 1 { - Some(quote! { ::juniper::sa::assert_type_ne_all!(#(#var_types),*); }) - } else { - None - }; + 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); + } - let match_names = self.variants.iter().map(|var| { - let var_ty = &var.ty; - let var_check = &var.resolver_check; - quote! { - if #var_check { - return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(info) - .unwrap().to_string(); + if for_async { + let self_ty = if !self.is_trait_object && 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.ty; + 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 }); } + } + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + ( + quote! { #impl_generics }, + quote! { #ty_full }, + where_clause.cloned(), + ) + } + + /// Returns generated code implementing [`GraphQLUnion`] trait for this + /// [GraphQL union][1]. + /// + /// [`GraphQLUnion`]: juniper::GraphQLUnion + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + fn impl_graphql_union_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_full, where_clause) = self.impl_generics(false); + + let variant_tys: Vec<_> = self.variants.iter().map(|var| &var.ty).collect(); + let all_variants_unique = (variant_tys.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #variant_tys ),*); } }); - let match_resolves: Vec<_> = self.variants.iter().map(|var| &var.resolver_code).collect(); - let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| { - let var_ty = &var.ty; - - let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)) }; - quote! { - if type_name == #get_name.unwrap() { - return ::juniper::IntoResolvable::into( - { #expr }, - executor.context(), - ) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }); + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full #where_clause + { + fn mark() { + #all_variants_unique + #( <#variant_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* } } - }); - let resolve_into_type_async = - self.variants - .iter() - .zip(match_resolves.iter()) - .map(|(var, expr)| { - let var_ty = &var.ty; - - let get_name = quote! { - (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)) - }; - quote! { - if type_name == #get_name.unwrap() { - let res = ::juniper::IntoResolvable::into( - { #expr }, - executor.context(), - ); - return Box::pin(async move { - match res? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - }, - None => Ok(::juniper::Value::null()), - } - }); - } - } - }); + } + } - let (_, ty_generics, _) = self.generics.split_for_impl(); + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL union][1]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; - let mut ext_generics = self.generics.clone(); - if self.is_trait_object { - ext_generics.params.push(parse_quote! { '__obj }); - } - if self.scalar.is_none() { - ext_generics.params.push(parse_quote! { #scalar }); - ext_generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - let (ext_impl_generics, _, where_clause) = ext_generics.split_for_impl(); - - let mut where_async = where_clause - .cloned() - .unwrap_or_else(|| parse_quote! { where }); - where_async.predicates.push(parse_quote! { Self: Sync }); - if self.scalar.is_none() { - where_async - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } + let (impl_generics, ty_full, where_clause) = self.impl_generics(false); - let mut ty_full = quote! { #ty#ty_generics }; - if self.is_trait_object { - ty_full = quote! { dyn #ty_full + '__obj + Send + Sync }; + let variant_tys = self.variants.iter().map(|var| &var.ty); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full #where_clause + { + fn mark() { + #( <#variant_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } + } } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL union][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + fn impl_graphql_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_full, where_clause) = self.impl_generics(false); - let type_impl = quote! { + let name = &self.name; + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + let variant_tys = self.variants.iter().map(|var| &var.ty); + + quote! { #[automatically_derived] - impl#ext_impl_generics ::juniper::GraphQLType<#scalar> for #ty_full - #where_clause + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty_full #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -471,19 +493,43 @@ impl ToTokens for UnionDefinition { where #scalar: 'r, { let types = [ - #( registry.get_type::<#var_types>(info), )* + #( registry.get_type::<#variant_tys>(info), )* ]; registry.build_union_type::<#ty_full>(info, &types) - #description - .into_meta() + #description + .into_meta() } } - }; + } + } - let value_impl = quote! { + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL union][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + + let (impl_generics, ty_full, where_clause) = self.impl_generics(false); + + let name = &self.name; + + let match_variant_names = self + .variants + .iter() + .map(|v| v.method_concrete_type_name_tokens(scalar)); + + let variant_resolvers = self + .variants + .iter() + .map(|v| v.method_resolve_into_type_tokens(scalar)); + + quote! { #[automatically_derived] - impl#ext_impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full - #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty_full #where_clause { type Context = #context; type TypeInfo = (); @@ -497,10 +543,10 @@ impl ToTokens for UnionDefinition { context: &Self::Context, info: &Self::TypeInfo, ) -> String { - #( #match_names )* + #( #match_variant_names )* panic!( - "GraphQL union {} cannot be resolved into any of its variants in its \ - current state", + "GraphQL union `{}` cannot be resolved into any of its \ + variants in its current state", #name, ); } @@ -513,19 +559,39 @@ impl ToTokens for UnionDefinition { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { let context = executor.context(); - #( #resolve_into_type )* + #( #variant_resolvers )* panic!( - "Concrete type {} is not handled by instance resolvers on GraphQL union {}", + "Concrete type `{}` is not handled by instance \ + resolvers on GraphQL union `{}`", type_name, #name, ); } } - }; + } + } - let value_async_impl = quote! { + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL union][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + #[must_use] + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_full, where_clause) = self.impl_generics(true); + + let name = &self.name; + + let variant_async_resolvers = self + .variants + .iter() + .map(|v| v.method_resolve_into_type_async_tokens(scalar)); + + quote! { + #[allow(non_snake_case)] #[automatically_derived] - impl#ext_impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full - #where_async + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty_full #where_clause { fn resolve_into_type_async<'b>( &'b self, @@ -535,58 +601,119 @@ impl ToTokens for UnionDefinition { executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { let context = executor.context(); - #( #resolve_into_type_async )* + #( #variant_async_resolvers )* panic!( - "Concrete type {} is not handled by instance resolvers on GraphQL union {}", + "Concrete type `{}` is not handled by instance \ + resolvers on GraphQL union `{}`", type_name, #name, ); } } - }; + } + } +} - let output_type_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty_full - #where_clause - { - fn mark() { - #( <#var_types as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* - } +/// Definition of [GraphQL union][1] variant for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Unions +struct VariantDefinition { + /// Rust type that this [GraphQL union][1] variant resolves into. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + ty: syn::Type, + + /// Rust code for value resolution of this [GraphQL union][1] variant. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + resolver_code: syn::Expr, + + /// Rust code for checking whether [GraphQL union][1] should be resolved + /// into this variant. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + resolver_check: syn::Expr, + + /// Rust type of [`Context`] that this [GraphQL union][1] variant requires + /// for resolution. + /// + /// It's available only when code generation happens for Rust traits and a + /// trait method contains context argument. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Unions + context: Option, +} + +impl VariantDefinition { + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying GraphQL type contained in + /// this [`VariantDefinition`]. + /// + /// [0]: juniper::GraphQLValue::concrete_type_name + #[must_use] + fn method_concrete_type_name_tokens(&self, scalar: &scalar::Type) -> TokenStream { + let ty = &self.ty; + let check = &self.resolver_check; + + quote! { + if #check { + return <#ty as ::juniper::GraphQLType<#scalar>>::name(info) + .unwrap() + .to_string(); } - }; + } + } - let union_impl = quote! { - #[automatically_derived] - impl#ext_impl_generics ::juniper::marker::GraphQLUnion<#scalar> for #ty_full - #where_clause - { - fn mark() { - #all_variants_unique + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which resolves the underlying GraphQL type contained in this + /// [`VariantDefinition`] synchronously. + /// + /// [0]: juniper::GraphQLValue::resolve_into_type + #[must_use] + fn method_resolve_into_type_tokens(&self, scalar: &scalar::Type) -> TokenStream { + let ty = &self.ty; + let expr = &self.resolver_code; + let resolving_code = gen::sync_resolving_code(); - #( <#var_types as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* - } + quote! { + if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { + let res = { #expr }; + return #resolving_code; } - }; + } + } - into.append_all(&[ - union_impl, - output_type_impl, - type_impl, - value_impl, - value_async_impl, - ]); + /// Returns generated code for the + /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which + /// resolves the underlying GraphQL type contained in this + /// [`VariantDefinition`] asynchronously. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + #[must_use] + fn method_resolve_into_type_async_tokens(&self, scalar: &scalar::Type) -> TokenStream { + let ty = &self.ty; + let expr = &self.resolver_code; + let resolving_code = gen::async_resolving_code(None); + + quote! { + if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap() { + let fut = ::juniper::futures::future::ready({ #expr }); + return #resolving_code; + } + } } } -/// Emerges [`UnionMeta::external_resolvers`] into the given [GraphQL union][1] `variants`. +/// Emerges [`Attr::external_resolvers`] into the given [GraphQL union][1] +/// `variants`. /// /// If duplication happens, then resolving code is overwritten with the one from /// `external_resolvers`. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions -fn emerge_union_variants_from_meta( - variants: &mut Vec, - external_resolvers: UnionMetaResolvers, +fn emerge_union_variants_from_attr( + variants: &mut Vec, + external_resolvers: AttrResolvers, ) { if external_resolvers.is_empty() { return; @@ -597,9 +724,10 @@ fn emerge_union_variants_from_meta( let resolver_code = parse_quote! { #resolver_fn(self, ::juniper::FromContext::from(context)) }; - // Doing this may be quite an expensive, because resolving may contain some heavy - // computation, so we're preforming it twice. Unfortunately, we have no other options here, - // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. + // 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. let resolver_check = parse_quote! { ({ #resolver_code } as ::std::option::Option<&#ty>).is_some() }; @@ -608,29 +736,30 @@ fn emerge_union_variants_from_meta( var.resolver_code = resolver_code; var.resolver_check = resolver_check; } else { - variants.push(UnionVariantDefinition { + variants.push(VariantDefinition { ty, resolver_code, resolver_check, - context_ty: None, + context: None, }) } } } -/// Checks whether all [GraphQL union][1] `variants` represent a different Rust type. +/// Checks whether all [GraphQL union][1] `variants` represent a different Rust +/// type. /// /// # Notice /// -/// This is not an optimal implementation, as it's possible to bypass this check by using a full -/// qualified path instead (`crate::Test` vs `Test`). Since this requirement is mandatory, the -/// static assertion [`assert_type_ne_all!`][2] is used to enforce this requirement in the generated -/// code. However, due to the bad error message this implementation should stay and provide -/// guidance. +/// This is not an optimal implementation, as it's possible to bypass this check +/// by using a full qualified path instead (`crate::Test` vs `Test`). Since this +/// requirement is mandatory, the static assertion [`assert_type_ne_all!`][2] is +/// used to enforce this requirement in the generated code. However, due to the +/// bad error message this implementation should stay and provide guidance. /// /// [1]: https://spec.graphql.org/June2018/#sec-Unions -/// [2]: https://docs.rs/static_assertions/latest/static_assertions/macro.assert_type_ne_all.html -fn all_variants_different(variants: &[UnionVariantDefinition]) -> bool { +/// [2]: juniper::sa::assert_type_ne_all +fn all_variants_different(variants: &[VariantDefinition]) -> bool { let mut types: Vec<_> = variants.iter().map(|var| &var.ty).collect(); types.dedup(); types.len() == variants.len() From 9afeb5dc073b5990a6a85d23b11031d7aad780a0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 29 Jul 2021 23:18:52 +0300 Subject: [PATCH 16/39] Cover exotic scalars in integration tests for unions --- .../src/codegen/interface_attr.rs | 18 +- .../juniper_tests/src/codegen/union_attr.rs | 178 ++++++++++++++++++ .../juniper_tests/src/codegen/union_derive.rs | 146 ++++++++++++++ 3 files changed, 333 insertions(+), 9 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 104bcd63b..64e590336 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3595,31 +3595,31 @@ mod explicit_generic_scalar { mod bounded_generic_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue)] + #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] trait Character { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S: ScalarValue)] + #[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)] + #[graphql(impl = [CharacterValue, DynHero], scalar = S: ScalarValue + Clone)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S: ScalarValue)] + #[graphql_interface(scalar = S: ScalarValue + Clone)] impl Character for Human { fn id(&self) -> &str { &self.id } } - #[graphql_interface(dyn, scalar = S: ScalarValue)] + #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] impl Hero for Human { async fn info(&self) -> &str { &self.home_planet @@ -3633,14 +3633,14 @@ mod bounded_generic_scalar { primary_function: String, } - #[graphql_interface(scalar = S: ScalarValue)] + #[graphql_interface(scalar = S: ScalarValue + Clone)] impl Character for Droid { fn id(&self) -> &str { &self.id } } - #[graphql_interface(dyn, scalar = S: ScalarValue)] + #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] impl Hero for Droid { async fn info(&self) -> &str { &self.primary_function @@ -3653,7 +3653,7 @@ mod bounded_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(scalar = S: ScalarValue + Clone + Send + Sync)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3670,7 +3670,7 @@ mod bounded_generic_scalar { } } - fn hero(&self) -> Box> { + fn hero(&self) -> Box> { let ch: Box> = match self { Self::Human => Box::new(Human { id: "human-32".to_string(), diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index 52820fb6a..4d831ba9c 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -660,6 +660,184 @@ mod custom_scalar { } } +mod explicit_generic_scalar { + use super::*; + + #[graphql_union(scalar = S)] + trait Character { + fn as_human(&self) -> Option<&Human> { + None + } + fn as_droid(&self) -> Option<&Droid> { + None + } + } + + impl Character for Human { + fn as_human(&self) -> Option<&Human> { + Some(&self) + } + } + + impl Character for Droid { + fn as_droid(&self) -> Option<&Droid> { + Some(&self) + } + } + + type DynCharacter<'a, S> = dyn Character + Send + Sync + 'a; + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character<__S: ScalarValue>(&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 + } + } + + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + #[graphql_union(scalar = S: ScalarValue + Clone)] + trait Character { + fn as_human(&self) -> Option<&Human> { + None + } + fn as_droid(&self) -> Option<&Droid> { + None + } + } + + impl Character for Human { + fn as_human(&self) -> Option<&Human> { + Some(&self) + } + } + + impl Character for Droid { + fn as_droid(&self) -> Option<&Droid> { + Some(&self) + } + } + + type DynCharacter<'a> = dyn Character + Send + Sync + 'a; + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + mod explicit_custom_context { use super::*; diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index ed90741bd..414bb5123 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -571,6 +571,150 @@ mod custom_scalar { } } +mod explicit_generic_scalar { + use super::*; + + #[derive(GraphQLUnion)] + #[graphql(scalar = S)] + enum Character { + A(Human), + B(Droid), + #[graphql(ignore)] + _P(PhantomData), + } + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character<__S: ScalarValue>(&self) -> Character<__S> { + match self { + Self::Human => Character::A(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Character::B(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + } + } + } + + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLUnion)] + #[graphql(scalar = S: ScalarValue + Clone)] + enum Character { + A(Human), + B(Droid), + } + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Character { + match self { + Self::Human => Character::A(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Character::B(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + } + } + } + + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + mod custom_context { use super::*; @@ -1473,6 +1617,8 @@ mod full_featured_struct { } } +/// Checks that union with boxed variants resolves okay. +/// See [#845](https://github.com/graphql-rust/juniper/issues/845) for details. mod issue_845 { use std::sync::Arc; From 727b5dac8cea58db5150bff43a7e3d29ccca2810 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 2 Aug 2021 15:10:04 +0300 Subject: [PATCH 17/39] Impl subscriptions, vol.1 --- juniper_codegen/src/common/field/mod.rs | 91 ++++- juniper_codegen/src/graphql_interface/attr.rs | 7 +- juniper_codegen/src/graphql_interface/mod.rs | 5 +- juniper_codegen/src/graphql_object/attr.rs | 22 +- juniper_codegen/src/graphql_object/derive.rs | 7 +- juniper_codegen/src/graphql_object/mod.rs | 150 ++++--- .../src/graphql_subscription/attr.rs | 29 ++ .../src/graphql_subscription/mod.rs | 139 +++++++ juniper_codegen/src/graphql_union/attr.rs | 2 +- juniper_codegen/src/impl_object.rs | 223 ----------- juniper_codegen/src/lib.rs | 15 +- juniper_codegen/src/util/mod.rs | 370 ------------------ juniper_codegen/src/util/parse_impl.rs | 186 --------- 13 files changed, 367 insertions(+), 879 deletions(-) create mode 100644 juniper_codegen/src/graphql_subscription/attr.rs create mode 100644 juniper_codegen/src/graphql_subscription/mod.rs delete mode 100644 juniper_codegen/src/impl_object.rs delete mode 100644 juniper_codegen/src/util/parse_impl.rs diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 0fd64d06d..e05a826eb 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -308,13 +308,23 @@ impl Definition { /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] - pub(crate) fn method_mark_tokens(&self, scalar: &scalar::Type) -> TokenStream { + pub(crate) fn method_mark_tokens( + &self, + coerce_result: bool, + scalar: &scalar::Type, + ) -> TokenStream { let args_marks = self .arguments .iter() .flat_map(|args| args.iter().filter_map(|a| a.method_mark_tokens(scalar))); let ty = &self.ty; + let mut ty = quote! { #ty }; + if coerce_result { + ty = quote! { + <#ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item + }; + } let resolved_ty = quote! { <#ty as ::juniper::IntoResolvable< '_, #scalar, _, >::Context, @@ -363,12 +373,12 @@ impl Definition { } } - /// Returns generated code for the [`GraphQLValue::resolve_field`] method, - /// which resolves this [GraphQL field][1] synchronously. + /// Returns generated code for the [`GraphQLValue::resolve_field`][0] + /// method, which resolves this [GraphQL field][1] synchronously. /// /// Returns [`None`] if this [`Definition::is_async`]. /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field + /// [0]: juniper::GraphQLValue::resolve_field /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] pub(crate) fn method_resolve_field_tokens( @@ -415,10 +425,10 @@ impl Definition { } /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_field_async`] method, which resolves this - /// [GraphQL field][1] asynchronously. + /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves + /// this [GraphQL field][1] asynchronously. /// - /// [`GraphQLValueAsync::resolve_field_async`]: juniper::GraphQLValueAsync::resolve_field_async + /// [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( @@ -462,6 +472,73 @@ impl Definition { } } } + + /// Returns generated code for the + /// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which + /// resolves this [GraphQL field][1] as [subscription][2]. + /// + /// [0]: juniper::GraphQLSubscriptionValue::resolve_field_into_stream + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields + /// [2]: https://spec.graphql.org/June2018/#sec-Subscription + #[must_use] + pub(crate) fn method_resolve_field_into_stream_tokens( + &self, + scalar: &scalar::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)); + + let rcv = self.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + if !self.is_async { + fut = quote! { ::juniper::futures::future::ready(#fut) }; + } + + quote! { + #name => { + ::juniper::futures::FutureExt::boxed(async move { + let res: #ty = #fut.await; + let res = ::juniper::IntoFieldResult::<_, #scalar>::into_result(res)?; + let executor = executor.as_owned_executor(); + let stream = ::juniper::futures::StreamExt::then(res, move |res| { + let executor = executor.clone(); + let res2: ::juniper::FieldResult<_, #scalar> = + ::juniper::IntoResolvable::into(res, executor.context()); + async move { + let ex = executor.as_executor(); + match res2 { + Ok(Some((ctx, r))) => { + let sub = ex.replaced_context(ctx); + sub.resolve_with_ctx_async(&(), &r) + .await + .map_err(|e| ex.new_error(e)) + } + Ok(None) => Ok(Value::null()), + Err(e) => Err(ex.new_error(e)), + } + } + }); + Ok(::juniper::Value::Scalar::< + ::juniper::ValuesStream::<#scalar> + >(Box::pin(stream))) + }) + } + } + } } /// Checks whether all [GraphQL fields][1] fields have different names. diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 90750c851..e64ad6a6a 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -46,7 +46,7 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, mut ast: syn::ItemTrait, ) -> syn::Result { @@ -245,10 +245,7 @@ pub fn expand_on_trait( } /// Expands `#[graphql_interface]` macro placed on a trait implementation block. -pub fn expand_on_impl( - attrs: Vec, - mut ast: syn::ItemImpl, -) -> syn::Result { +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() diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index d16ef2f70..9fa63a570 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -488,7 +488,10 @@ impl Definition { let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); - let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); + let fields_marks = self + .fields + .iter() + .map(|f| f.method_mark_tokens(false, scalar)); let impler_tys = self.implementers.iter().map(|impler| &impler.ty); diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index 4027f3db7..120d77c77 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -1,9 +1,9 @@ //! Code generation for `#[graphql_object]` macro. -use std::mem; +use std::{marker::PhantomData, mem}; use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -16,7 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{Attr, Definition}; +use super::{Attr, Definition, Query}; /// [`GraphQLScope`] of errors for `#[graphql_object]` macro. const ERR: GraphQLScope = GraphQLScope::ObjectAttr; @@ -27,7 +27,7 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result(Attr::from_attrs("graphql_object", &impl_attrs)?, ast); } } @@ -38,12 +38,13 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, +pub(crate) fn expand_on_impl( + attr: Attr, mut ast: syn::ItemImpl, -) -> syn::Result { - let attr = Attr::from_attrs("graphql_object", &attrs)?; - +) -> syn::Result +where + Definition: ToTokens, +{ let type_span = ast.self_ty.span(); let type_ident = ast.self_ty.topmost_ident().ok_or_else(|| { ERR.custom_error(type_span, "could not determine ident for the `impl` type") @@ -106,7 +107,7 @@ pub fn expand_on_impl( }) }); - let generated_code = Definition { + let generated_code = Definition:: { name, ty: parse_quote! { #type_ident }, generics: ast.generics.clone(), @@ -119,6 +120,7 @@ pub fn expand_on_impl( .iter() .map(|ty| ty.as_ref().clone()) .collect(), + _operation: PhantomData, }; Ok(quote! { diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index 6caa76c9d..e6f32f380 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -1,5 +1,7 @@ //! Code generation for `#[derive(GraphQLObject)]` macro. +use std::marker::PhantomData; + use proc_macro2::TokenStream; use proc_macro_error::ResultExt as _; use quote::ToTokens; @@ -11,7 +13,7 @@ use crate::{ util::{span_container::SpanContainer, RenameRule}, }; -use super::{Attr, Definition}; +use super::{Attr, Definition, Query}; /// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro. const ERR: GraphQLScope = GraphQLScope::ObjectDerive; @@ -29,7 +31,7 @@ pub fn expand(input: TokenStream) -> syn::Result { /// Expands into generated code a `#[derive(GraphQLObject)]` macro placed on a /// Rust struct. -fn expand_struct(ast: syn::DeriveInput) -> syn::Result { +fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { let attr = Attr::from_attrs("graphql", &ast.attrs)?; let struct_span = ast.span(); @@ -96,6 +98,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { .iter() .map(|ty| ty.as_ref().clone()) .collect(), + _operation: PhantomData, }) } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index fbfda8e33..3307fdff3 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -5,7 +5,7 @@ pub mod attr; pub mod derive; -use std::{collections::HashSet, convert::TryInto as _}; +use std::{any::TypeId, collections::HashSet, convert::TryInto as _, marker::PhantomData}; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; @@ -34,13 +34,13 @@ use syn::ext::IdentExt; /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[derive(Debug, Default)] -struct Attr { +pub(crate) struct Attr { /// Explicitly specified name of this [GraphQL object][1] type. /// /// If [`None`], then Rust type name is used by default. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - name: Option>, + pub(crate) name: Option>, /// Explicitly specified [description][2] of this [GraphQL object][1] type. /// @@ -48,7 +48,7 @@ struct Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - description: Option>, + pub(crate) description: Option>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL object][1] type with. @@ -57,7 +57,7 @@ struct Attr { /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Objects - context: Option>, + pub(crate) context: Option>, /// Explicitly specified type (or type parameter with its bounds) of /// [`ScalarValue`] to use for resolving this [GraphQL object][1] type with. @@ -71,14 +71,14 @@ struct Attr { /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Objects - scalar: Option>, + pub(crate) scalar: Option>, /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] /// type implements. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces - interfaces: HashSet>, + pub(crate) interfaces: HashSet>, /// Explicitly specified [`RenameRule`] for all fields of this /// [GraphQL object][1] type. @@ -86,11 +86,11 @@ struct Attr { /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - rename_fields: Option>, + pub(crate) rename_fields: Option>, /// Indicator whether the generated code is intended to be used only inside /// the [`juniper`] library. - is_internal: bool, + pub(crate) is_internal: bool, } impl Parse for Attr { @@ -188,7 +188,7 @@ impl Attr { /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a struct or impl block definition. - fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + 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?))?; @@ -205,27 +205,27 @@ impl Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[derive(Debug)] -struct Definition { +pub(crate) struct Definition { /// Name of this [GraphQL object][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - name: String, + pub(crate) name: String, /// Rust type that this [GraphQL object][1] is represented with. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - ty: syn::Type, + pub(crate) ty: syn::Type, /// Generics of the Rust type that this [GraphQL object][1] is implemented /// for. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - generics: syn::Generics, + pub(crate) generics: syn::Generics, /// Description of this [GraphQL object][1] to put into GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects - description: Option, + pub(crate) description: Option, /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL object][1]. @@ -235,7 +235,7 @@ struct Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Objects - context: Option, + pub(crate) context: Option, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL object][1]. @@ -243,33 +243,31 @@ struct Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Objects - scalar: scalar::Type, + pub(crate) scalar: scalar::Type, /// Defined [GraphQL fields][2] of this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - fields: Vec, + pub(crate) fields: Vec, /// [GraphQL interfaces][2] implemented by this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects /// [2]: https://spec.graphql.org/June2018/#sec-Interfaces - interfaces: HashSet, -} + pub(crate) interfaces: HashSet, -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - self.impl_graphql_object_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_as_dyn_graphql_value_tokens().to_tokens(into); - } + /// [GraphQL operation][1] this [`Definition`] should generate code for. + /// + /// Either [GraphQL query][2] or [GraphQL subscription][3]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Language.Operations + /// [2]: https://spec.graphql.org/June2018/#sec-Query + /// [3]: https://spec.graphql.org/June2018/#sec-Subscription + pub(crate) _operation: PhantomData>, } -impl Definition { +impl Definition { /// Returns prepared [`syn::Generics::split_for_impl`] for [`GraphQLType`] /// trait (and similar) implementation of this [GraphQL object][1]. /// @@ -280,7 +278,7 @@ impl Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - fn impl_generics( + pub(crate) fn impl_generics( &self, for_async: bool, ) -> (TokenStream, TokenStream, Option) { @@ -341,50 +339,23 @@ impl Definition { ) } - /// Returns generated code implementing [`GraphQLObject`] trait for this - /// [GraphQL object][1]. - /// - /// [`GraphQLObject`]: juniper::GraphQLObject - /// [1]: https://spec.graphql.org/June2018/#sec-Objects - #[must_use] - fn impl_graphql_object_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); - let ty = &self.ty; - - let interface_tys = self.interfaces.iter(); - // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, - // but considering generics. - //let interface_tys: Vec<_> = self.interfaces.iter().collect(); - //let all_interfaces_unique = (interface_tys.len() > 1).then(|| { - // quote! { ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); } - //}); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause - { - fn mark() { - #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* - } - } - } - } - /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL object][1]. /// /// [`marker::IsOutputType`]: juniper::marker::IsOutputType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - fn impl_output_type_tokens(&self) -> TokenStream { + pub(crate) fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let fields_marks = self.fields.iter().map(|f| f.method_mark_tokens(scalar)); + let coerce_result = TypeId::of::() != TypeId::of::(); + let fields_marks = self + .fields + .iter() + .map(|f| f.method_mark_tokens(coerce_result, scalar)); let interface_tys = self.interfaces.iter(); @@ -406,7 +377,7 @@ impl Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - fn impl_graphql_type_tokens(&self) -> TokenStream { + pub(crate) fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); @@ -462,6 +433,55 @@ impl Definition { } } } +} + +/// [GraphQL query operation][2] of the [`Definition`] to generate code for. +/// +/// [2]: https://spec.graphql.org/June2018/#sec-Query +struct Query; + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.impl_graphql_object_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_as_dyn_graphql_value_tokens().to_tokens(into); + } +} + +impl Definition { + /// Returns generated code implementing [`GraphQLObject`] trait for this + /// [GraphQL object][1]. + /// + /// [`GraphQLObject`]: juniper::GraphQLObject + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_graphql_object_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let ty = &self.ty; + + let interface_tys = self.interfaces.iter(); + // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, + // but considering generics. + //let interface_tys: Vec<_> = self.interfaces.iter().collect(); + //let all_interfaces_unique = (interface_tys.len() > 1).then(|| { + // quote! { ::juniper::sa::assert_type_ne_all!(#( #interface_tys ),*); } + //}); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause + { + fn mark() { + #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + } + } + } + } /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL object][1]. diff --git a/juniper_codegen/src/graphql_subscription/attr.rs b/juniper_codegen/src/graphql_subscription/attr.rs new file mode 100644 index 000000000..288baca0a --- /dev/null +++ b/juniper_codegen/src/graphql_subscription/attr.rs @@ -0,0 +1,29 @@ +//! Code generation for `#[graphql_subscription]` macro. + +use proc_macro2::{Span, TokenStream}; + +use crate::{ + common::parse, + graphql_object::{attr::expand_on_impl, Attr}, +}; + +use super::Subscription; + +/// Expands `#[graphql_subscription]` macro into generated code. +pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { + if let Ok(mut ast) = syn::parse2::(body) { + if ast.trait_.is_none() { + let impl_attrs = parse::attr::unite(("graphql_subscription", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_subscription", ast.attrs); + return expand_on_impl::( + Attr::from_attrs("graphql_subscription", &impl_attrs)?, + ast, + ); + } + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_subscription] attribute is applicable to non-trait `impl` blocks only", + )) +} diff --git a/juniper_codegen/src/graphql_subscription/mod.rs b/juniper_codegen/src/graphql_subscription/mod.rs new file mode 100644 index 000000000..57f57624e --- /dev/null +++ b/juniper_codegen/src/graphql_subscription/mod.rs @@ -0,0 +1,139 @@ +//! Code generation for [GraphQL subscription][1]. +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Subscription + +pub mod attr; + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse_quote; + +use crate::{common::field, graphql_object::Definition}; + +/// [GraphQL subscription operation][2] of the [`Definition`] to generate code +/// for. +/// +/// [2]: https://spec.graphql.org/June2018/#sec-Subscription +struct Subscription; + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + 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_subscription_value_tokens() + .to_tokens(into); + } +} + +impl Definition { + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL subscription][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/June2018/#sec-Subscription + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + + let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let ty = &self.ty; + + let name = &self.name; + + quote! { + #[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, + _: &Self::TypeInfo, + _: &str, + _: &::juniper::Arguments<#scalar>, + _: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + panic!("Called `resolve_field` on subscription object"); + } + + fn concrete_type_name( + &self, + _: &Self::Context, + _: &Self::TypeInfo, + ) -> String { + #name.to_string() + } + } + } + } + + /// Returns generated code implementing [`GraphQLSubscriptionValue`] trait + /// for this [GraphQL subscription][1]. + /// + /// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue + /// [1]: https://spec.graphql.org/June2018/#sec-Subscription + #[must_use] + fn impl_graphql_subscription_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + // We use `for_async = false` here as `GraphQLSubscriptionValue` requires + // simpler and less `Send`/`Sync` bounds than `GraphQLValueAsync`. + let (impl_generics, ty_generics, mut where_clause) = self.impl_generics(false); + if scalar.is_generic() { + where_clause = Some(where_clause.unwrap_or_else(|| parse_quote! { where })); + where_clause + .as_mut() + .unwrap() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + let ty = &self.ty; + + let fields_resolvers = self + .fields + .iter() + .map(|f| f.method_resolve_field_into_stream_tokens(scalar)); + let no_field_panic = field::Definition::method_resolve_field_panic_no_field_tokens(scalar); + + quote! { + #[allow(deprecated)] + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty#ty_generics #where_clause + { + fn resolve_field_into_stream< + 's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f, + >( + &'s self, + info: &'i Self::TypeInfo, + field: &'fi str, + args: ::juniper::Arguments<'args, #scalar>, + executor: &'ref_e ::juniper::Executor<'ref_e, 'e, Self::Context, #scalar>, + ) -> ::juniper::BoxFuture<'f, std::result::Result< + ::juniper::Value<::juniper::ValuesStream<'res, #scalar>>, + ::juniper::FieldError<#scalar>, + >> + where + 's: 'f, + 'fi: 'f, + 'args: 'f, + 'ref_e: 'f, + 'res: 'f, + 'i: 'res, + 'e: 'res, + { + match field { + #( #fields_resolvers )* + _ => #no_field_panic, + } + } + } + } + } +} diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index 938a48cae..ec8734d05 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -35,7 +35,7 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result, mut ast: syn::ItemTrait, ) -> syn::Result { diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs deleted file mode 100644 index b9d9c15da..000000000 --- a/juniper_codegen/src/impl_object.rs +++ /dev/null @@ -1,223 +0,0 @@ -#![allow(clippy::collapsible_if)] - -use crate::{ - result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer, RenameRule}, -}; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ext::IdentExt, spanned::Spanned}; - -/// Generate code for the juniper::graphql_subscription macro. -pub fn build_subscription( - args: TokenStream, - body: TokenStream, - error: GraphQLScope, -) -> TokenStream { - let definition = match create(args, body, error) { - Ok(definition) => definition, - Err(err) => return err.to_compile_error(), - }; - definition.into_subscription_tokens() -} - -fn create( - args: TokenStream, - body: TokenStream, - error: GraphQLScope, -) -> syn::Result { - let body_span = body.span(); - let _impl = util::parse_impl::ImplBlock::parse(args, body)?; - let name = _impl - .attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| _impl.type_ident.unraw().to_string()); - - let top_attrs = &_impl.attrs; - - let scalar = _impl - .attrs - .scalar - .as_ref() - .map(|s| quote!( #s )) - .unwrap_or_else(|| quote!(::juniper::DefaultScalarValue)); - - let fields = _impl - .methods - .iter() - .filter_map(|method| { - let span = method.span(); - let _type = match method.sig.output { - syn::ReturnType::Type(_, ref t) => *t.clone(), - syn::ReturnType::Default => { - error.emit_custom(method.sig.span(), "return value required"); - return None; - } - }; - - let is_async = method.sig.asyncness.is_some(); - - let attrs = match util::FieldAttributes::from_attrs( - &method.attrs, - util::FieldAttributeParseMode::Impl, - ) { - Ok(attrs) => attrs, - Err(err) => { - proc_macro_error::emit_error!(err); - return None; - } - }; - - let parse_method = - _impl.parse_method(&method, true, |captured, arg_ident, is_mut: bool| { - let arg_name = arg_ident.unraw().to_string(); - let ty = &captured.ty; - - let final_name = attrs - .argument(&arg_name) - .and_then(|attrs| attrs.rename.clone().map(|ident| ident.value())) - .unwrap_or_else(|| { - top_attrs - .rename - .unwrap_or(RenameRule::CamelCase) - .apply(&arg_name) - }); - - let mut_modifier = if is_mut { quote!(mut) } else { quote!() }; - - if final_name.starts_with("__") { - error.no_double_underscore( - if let Some(name) = attrs - .argument(&arg_name) - .and_then(|attrs| attrs.rename.as_ref()) - { - name.span_ident() - } else { - arg_ident.span() - }, - ); - } - - let resolver = quote!( - let #mut_modifier #arg_ident = args - .get::<#ty>(#final_name) - .or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null) - .unwrap(); - ); - - let field_type = util::GraphQLTypeDefinitionFieldArg { - description: attrs - .argument(&arg_name) - .and_then(|arg| arg.description.as_ref().map(|d| d.value())), - default: attrs - .argument(&arg_name) - .and_then(|arg| arg.default.clone()), - _type: ty.clone(), - name: final_name, - }; - Ok((resolver, field_type)) - }); - - let (resolve_parts, args) = match parse_method { - Ok((resolve_parts, args)) => (resolve_parts, args), - Err(err) => { - proc_macro_error::emit_error!(err); - return None; - } - }; - - let body = &method.block; - let resolver_code = quote!( - #( #resolve_parts )* - #body - ); - - let ident = &method.sig.ident; - let name = attrs - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| { - top_attrs - .rename - .unwrap_or(RenameRule::CamelCase) - .apply(&ident.unraw().to_string()) - }); - - if name.starts_with("__") { - error.no_double_underscore(if let Some(name) = attrs.name { - name.span_ident() - } else { - ident.span() - }); - } - - if let Some(default) = attrs.default { - error.unsupported_attribute_within( - default.span_ident(), - UnsupportedAttribute::Default, - ); - } - - Some(util::GraphQLTypeDefinitionField { - name, - _type, - args, - description: attrs.description.map(SpanContainer::into_inner), - deprecation: attrs.deprecation.map(SpanContainer::into_inner), - resolver_code, - is_type_inferred: false, - is_async, - default: None, - span, - }) - }) - .collect::>(); - - // Early abort after checking all fields - proc_macro_error::abort_if_dirty(); - - if let Some(duplicates) = - crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name) - { - error.duplicate(duplicates.iter()) - } - - if !_impl.attrs.is_internal && name.starts_with("__") { - error.no_double_underscore(if let Some(name) = _impl.attrs.name { - name.span_ident() - } else { - _impl.type_ident.span() - }); - } - - if fields.is_empty() { - error.not_empty(body_span); - } - - // Early abort after GraphQL properties - proc_macro_error::abort_if_dirty(); - - let definition = util::GraphQLTypeDefiniton { - name, - _type: *_impl.target_type.clone(), - scalar: _impl.attrs.scalar.map(SpanContainer::into_inner), - context: _impl.attrs.context.map(SpanContainer::into_inner), - description: _impl.description, - fields, - generics: _impl.generics.clone(), - interfaces: _impl - .attrs - .interfaces - .into_iter() - .map(SpanContainer::into_inner) - .collect(), - include_type_generics: false, - generic_scalar: true, - no_async: _impl.attrs.no_async.is_some(), - }; - - Ok(definition) -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 21034f786..4aea0e81c 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -109,12 +109,12 @@ macro_rules! try_merge_hashset { mod derive_enum; mod derive_input_object; mod derive_scalar_value; -mod impl_object; mod impl_scalar; mod common; mod graphql_interface; mod graphql_object; +mod graphql_subscription; mod graphql_union; use proc_macro::TokenStream; @@ -530,17 +530,14 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { } } +// TODO /// A proc macro for defining a GraphQL subscription. #[proc_macro_error] #[proc_macro_attribute] -pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStream { - let args = proc_macro2::TokenStream::from(args); - let input = proc_macro2::TokenStream::from(input); - TokenStream::from(impl_object::build_subscription( - args, - input, - GraphQLScope::ObjectAttr, - )) +pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_subscription::attr::expand(attr.into(), body.into()) + .unwrap_or_abort() + .into() } /// `#[graphql_interface]` macro for generating a [GraphQL interface][1] implementation for traits diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index c36b302db..5648b8c30 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1,7 +1,6 @@ #![allow(clippy::single_match)] pub mod duplicate; -pub mod parse_impl; pub mod span_container; use std::{collections::HashMap, convert::TryFrom, str::FromStr}; @@ -21,61 +20,12 @@ use syn::{ use crate::common::parse::ParseBufferExt as _; -/// Returns the name of a type. -/// If the type does not end in a simple ident, `None` is returned. -pub fn name_of_type(ty: &syn::Type) -> Option { - let path_opt = match ty { - syn::Type::Path(ref type_path) => Some(&type_path.path), - syn::Type::Reference(ref reference) => match &*reference.elem { - syn::Type::Path(ref type_path) => Some(&type_path.path), - syn::Type::TraitObject(ref trait_obj) => { - match trait_obj.bounds.iter().next().unwrap() { - syn::TypeParamBound::Trait(ref trait_bound) => Some(&trait_bound.path), - _ => None, - } - } - _ => None, - }, - _ => None, - }; - let path = path_opt?; - - path.segments - .iter() - .last() - .map(|segment| segment.ident.clone()) -} - /// Compares a path to a one-segment string value, /// return true if equal. pub fn path_eq_single(path: &syn::Path, value: &str) -> bool { path.segments.len() == 1 && path.segments[0].ident == value } -/// Check if a type is a reference to another type. -pub fn type_is_ref_of(ty: &syn::Type, target: &syn::Type) -> bool { - match ty { - syn::Type::Reference(_ref) => &*_ref.elem == target, - _ => false, - } -} - -/// Check if a Type is a simple identifier. -pub fn type_is_identifier(ty: &syn::Type, name: &str) -> bool { - match ty { - syn::Type::Path(ref type_path) => path_eq_single(&type_path.path, name), - _ => false, - } -} - -/// Check if a Type is a reference to a given identifier. -pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool { - match ty { - syn::Type::Reference(_ref) => type_is_identifier(&*_ref.elem, name), - _ => false, - } -} - #[derive(Debug)] pub struct DeprecationAttr { pub reason: Option, @@ -499,7 +449,6 @@ impl Parse for FieldAttributeArgument { #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum FieldAttributeParseMode { Object, - Impl, } enum FieldAttribute { @@ -663,10 +612,6 @@ impl FieldAttributes { Ok(output) } - - pub fn argument(&self, name: &str) -> Option<&FieldAttributeArgument> { - self.arguments.get(name) - } } #[derive(Debug)] @@ -738,321 +683,6 @@ impl GraphQLTypeDefiniton { self.fields.iter().any(|field| field.is_async) } - pub fn into_subscription_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 field_definitions = self.fields.iter().map(|field| { - let args = field.args.iter().map(|arg| { - let arg_type = &arg._type; - let arg_name = &arg.name; - - let description = match arg.description.as_ref() { - Some(value) => quote!( .description( #value ) ), - None => quote!(), - }; - - match arg.default.as_ref() { - Some(value) => quote!( - .argument( - registry.arg_with_default::<#arg_type>(#arg_name, &#value, info) - #description - ) - ), - None => quote!( - .argument( - registry.arg::<#arg_type>(#arg_name, info) - #description - ) - ), - } - }); - - let description = match field.description.as_ref() { - Some(description) => quote!( .description(#description) ), - None => quote!(), - }; - - let deprecation = match field.deprecation.as_ref() { - Some(deprecation) => { - if let Some(reason) = deprecation.reason.as_ref() { - quote!( .deprecated(Some(#reason)) ) - } else { - quote!( .deprecated(None) ) - } - } - None => quote!(), - }; - - let field_name = &field.name; - - let type_name = &field._type; - - let _type; - - if field.is_async { - _type = quote!(<#type_name as ::juniper::ExtractTypeFromStream<_, #scalar>>::Item); - } else { - panic!("Synchronous resolvers are not supported. Specify that this function is async: 'async fn foo()'") - } - - quote! { - registry - .field_convert::<#_type, _, Self::Context>(#field_name, info) - #(#args)* - #description - #deprecation - } - }); - - let description = self - .description - .as_ref() - .map(|description| quote!( .description(#description) )); - - let interfaces = if !self.interfaces.is_empty() { - let interfaces_ty = &self.interfaces; - - Some(quote!( - .interfaces(&[ - #( registry.get_type::<#interfaces_ty>(&()) ,)* - ]) - )) - } else { - None - }; - - // Preserve the original type_generics before modification, - // since alteration makes them invalid if self.generic_scalar - // is specified. - let (_, type_generics, _) = self.generics.split_for_impl(); - - 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. - - // Insert ScalarValue constraint. - generics.params.push(parse_quote!(__S)); - generics - .make_where_clause() - .predicates - .push(parse_quote!(__S: ::juniper::ScalarValue)); - } - - let type_generics_tokens = if self.include_type_generics { - Some(type_generics) - } else { - None - }; - let (impl_generics, _, where_clause) = generics.split_for_impl(); - - let mut generics_with_send_sync = generics.clone(); - if self.scalar.is_none() && self.generic_scalar { - generics_with_send_sync - .make_where_clause() - .predicates - .push(parse_quote!(__S: Send + Sync)); - } - let (_, _, where_clause_with_send_sync) = generics_with_send_sync.split_for_impl(); - - let resolve_matches_async = self.fields.iter().filter(|field| field.is_async).map( - |field| { - let name = &field.name; - let code = &field.resolver_code; - - let _type; - if field.is_type_inferred { - _type = quote!(); - } else { - let _type_name = &field._type; - _type = quote!(: #_type_name); - }; - quote!( - #name => { - ::juniper::futures::FutureExt::boxed(async move { - let res #_type = async { #code }.await; - let res = ::juniper::IntoFieldResult::<_, #scalar>::into_result(res)?; - let executor= executor.as_owned_executor(); - let f = res.then(move |res| { - let executor = executor.clone(); - let res2: ::juniper::FieldResult<_, #scalar> = - ::juniper::IntoResolvable::into(res, executor.context()); - async move { - let ex = executor.as_executor(); - match res2 { - Ok(Some((ctx, r))) => { - let sub = ex.replaced_context(ctx); - sub.resolve_with_ctx_async(&(), &r) - .await - .map_err(|e| ex.new_error(e)) - } - Ok(None) => Ok(Value::null()), - Err(e) => Err(ex.new_error(e)), - } - } - }); - Ok( - ::juniper::Value::Scalar::< - ::juniper::ValuesStream::<#scalar> - >(Box::pin(f)) - ) - }) - } - ) - }, - ); - - let marks = self.fields.iter().map(|field| { - let field_marks = field.args.iter().map(|arg| { - let arg_ty = &arg._type; - quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); } - }); - - let field_ty = &field._type; - let stream_item_ty = quote! { - <#field_ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item - }; - let resolved_ty = quote! { - <#stream_item_ty as ::juniper::IntoResolvable< - '_, #scalar, _, >::Context, - >>::Type - }; - - quote! { - #( #field_marks )* - <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); - } - }); - - let graphql_implementation = quote!( - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens - #where_clause - { - fn mark() { - #( #marks )* - } - } - - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens - #where_clause - { - 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 fields = [ - #( #field_definitions ),* - ]; - let meta = registry.build_object_type::<#ty>(info, &fields) - #description - #interfaces; - meta.into_meta() - } - } - - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #type_generics_tokens - #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, - _: &(), - _: &str, - _: &::juniper::Arguments<#scalar>, - _: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - panic!("Called `resolve_field` on subscription object"); - } - - - fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String { - #name.to_string() - } - } - ); - - let subscription_implementation = quote!( - impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #type_generics_tokens - #where_clause_with_send_sync - { - #[allow(unused_variables)] - fn resolve_field_into_stream< - 's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f, - >( - &'s self, - info: &'i Self::TypeInfo, - field_name: &'fi str, - args: ::juniper::Arguments<'args, #scalar>, - executor: &'ref_e ::juniper::Executor<'ref_e, 'e, Self::Context, #scalar>, - ) -> std::pin::Pin>, - ::juniper::FieldError<#scalar> - > - > + Send + 'f - >> - where - 's: 'f, - 'i: 'res, - 'fi: 'f, - 'e: 'res, - 'args: 'f, - 'ref_e: 'f, - 'res: 'f, - { - use ::juniper::Value; - use ::juniper::futures::stream::StreamExt as _; - - match field_name { - #( #resolve_matches_async )* - _ => { - panic!("Field {} not found on type {}", field_name, "GraphQLSubscriptionValue"); - } - } - } - } - ); - - quote!( - #graphql_implementation - #subscription_implementation - ) - } - pub fn into_enum_tokens(self) -> TokenStream { let name = &self.name; let ty = &self._type; diff --git a/juniper_codegen/src/util/parse_impl.rs b/juniper_codegen/src/util/parse_impl.rs deleted file mode 100644 index db29d9e16..000000000 --- a/juniper_codegen/src/util/parse_impl.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Parse impl blocks. -#![allow(clippy::or_fun_call)] - -use crate::util::{self, span_container::SpanContainer}; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use syn::{spanned::Spanned, PatType}; - -pub struct ImplBlock { - pub attrs: util::ObjectAttributes, - pub target_trait: Option<(String, syn::Path)>, - pub target_type: Box, - pub type_ident: syn::Ident, - pub generics: syn::Generics, - // _impl: syn::ItemImpl, - pub methods: Vec, - pub description: Option, -} - -impl ImplBlock { - /// Parse a `fn () -> ` method declaration found in - /// objects. - pub fn parse_method< - F: Fn( - &PatType, - &Ident, - bool, - ) -> syn::Result<(TokenStream, util::GraphQLTypeDefinitionFieldArg)>, - >( - &self, - method: &syn::ImplItemMethod, - is_self_optional: bool, - f: F, - ) -> syn::Result<(Vec, Vec)> { - let mut arguments = method.sig.inputs.iter().peekable(); - - // Verify `&self` argument. - match arguments.peek() { - Some(syn::FnArg::Receiver(rec)) => { - let _consume = arguments.next(); - if rec.reference.is_none() || rec.mutability.is_some() { - return Err(syn::Error::new( - rec.span(), - "invalid argument: did you mean `&self`?", - )); - } - } - _ => { - if !is_self_optional { - return Err(syn::Error::new( - method.sig.span(), - "expected a `&self` argument", - )); - } - } - } - - let mut resolve_parts = Vec::new(); - let mut additional_arguments = Vec::new(); - - for arg in arguments { - match arg { - syn::FnArg::Receiver(_) => { - if !is_self_optional { - return Err(syn::Error::new( - method.sig.ident.span(), - "self receiver must be the first argument", - )); - } - } - syn::FnArg::Typed(captured) => { - let (arg_ident, is_mut) = match &*captured.pat { - syn::Pat::Ident(ref pat_ident) => { - (&pat_ident.ident, pat_ident.mutability.is_some()) - } - _ => { - return Err(syn::Error::new( - captured.pat.span(), - "expected identifier for function argument", - )); - } - }; - let context_type = self.attrs.context.as_ref(); - - // Check for executor arguments. - if util::type_is_identifier_ref(&captured.ty, "Executor") { - resolve_parts.push(quote!(let #arg_ident = executor;)); - } - // Make sure executor is specified as a reference. - else if util::type_is_identifier(&captured.ty, "Executor") { - return Err(syn::Error::new( - captured.ty.span(), - "to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?" - )); - } - // Check for context arg. - else if context_type - .clone() - .map(|ctx| util::type_is_ref_of(&captured.ty, ctx)) - .unwrap_or(false) - { - resolve_parts.push(quote!( let #arg_ident = executor.context(); )); - } - // Make sure the user does not specify the Context - // without a reference. (&Context) - else if context_type - .clone() - .map(|ctx| ctx.inner() == &*captured.ty) - .unwrap_or(false) - { - return Err(syn::Error::new( - captured.ty.span(), - format!("to access the context, you need to specify the type as a reference.\nDid you mean &{}?", quote!(captured.ty)), - )); - } else { - let (tokens, ty) = f(captured, arg_ident, is_mut)?; - resolve_parts.push(tokens); - additional_arguments.push(ty); - } - } - } - } - - Ok((resolve_parts, additional_arguments)) - } - - pub fn parse(attr_tokens: TokenStream, body: TokenStream) -> syn::Result { - let attrs = syn::parse2::(attr_tokens)?; - let mut _impl = syn::parse2::(body)?; - - let target_trait = match _impl.clone().trait_ { - Some((_, path, _)) => { - let name = path - .segments - .iter() - .map(|segment| segment.ident.to_string()) - .collect::>() - .join("."); - Some((name, path)) - } - None => None, - }; - - let type_ident = if let Some(ident) = util::name_of_type(&*_impl.self_ty) { - ident - } else { - return Err(syn::Error::new( - _impl.self_ty.span(), - "could not determine a name for the impl type", - )); - }; - - let target_type = _impl.self_ty.clone(); - - let description = attrs - .description - .clone() - .or_else(|| util::get_doc_comment(&_impl.attrs.clone())); - - let mut methods = Vec::new(); - - for item in _impl.items { - match item { - syn::ImplItem::Method(method) => { - methods.push(method); - } - _ => { - return Err(syn::Error::new( - item.span(), - "only type declarations and methods are allowed", - )); - } - } - } - - Ok(Self { - attrs, - type_ident, - target_trait, - target_type, - generics: _impl.generics, - description: description.map(SpanContainer::into_inner), - methods, - }) - } -} From 4b664d2d65a9bfca5917fea3303b716788cfcd86 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 2 Aug 2021 17:59:17 +0300 Subject: [PATCH 18/39] Impl subscriptions, vol.2 --- examples/actix_subscriptions/src/main.rs | 2 +- examples/basic_subscriptions/Cargo.toml | 2 - examples/basic_subscriptions/src/main.rs | 2 +- examples/warp_subscriptions/Cargo.toml | 1 + examples/warp_subscriptions/src/main.rs | 2 +- juniper/src/integrations/chrono_tz.rs | 1 - juniper/src/macros/tests/impl_subscription.rs | 270 ++++++++---------- juniper/src/tests/subscriptions.rs | 20 +- juniper/src/types/base.rs | 2 + juniper_codegen/src/common/field/arg.rs | 4 +- juniper_codegen/src/common/field/mod.rs | 23 +- juniper_codegen/src/graphql_interface/mod.rs | 5 +- juniper_codegen/src/graphql_object/attr.rs | 23 +- juniper_codegen/src/graphql_object/mod.rs | 3 +- juniper_codegen/src/lib.rs | 32 ++- 15 files changed, 195 insertions(+), 197 deletions(-) diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 6464a71eb..c5021ae6a 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -11,7 +11,7 @@ use actix_web::{ use juniper::{ graphql_object, graphql_subscription, tests::fixtures::starwars::schema::{Character as _, Database, Query}, - DefaultScalarValue, EmptyMutation, FieldError, RootNode, + DefaultScalarValue, EmptyMutation, FieldError, RootNode, Value, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; use juniper_graphql_ws::ConnectionConfig; diff --git a/examples/basic_subscriptions/Cargo.toml b/examples/basic_subscriptions/Cargo.toml index ccb294db8..8367cd167 100644 --- a/examples/basic_subscriptions/Cargo.toml +++ b/examples/basic_subscriptions/Cargo.toml @@ -5,8 +5,6 @@ edition = "2018" publish = false authors = ["Jordao Rosario "] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] futures = "0.3" serde = { version = "1.0", features = ["derive"] } diff --git a/examples/basic_subscriptions/src/main.rs b/examples/basic_subscriptions/src/main.rs index a8e3736c3..82d4b46fb 100644 --- a/examples/basic_subscriptions/src/main.rs +++ b/examples/basic_subscriptions/src/main.rs @@ -24,7 +24,7 @@ pub struct Query; #[graphql_object(context = Database)] impl Query { - fn hello_world() -> &str { + fn hello_world() -> &'static str { "Hello World!" } } diff --git a/examples/warp_subscriptions/Cargo.toml b/examples/warp_subscriptions/Cargo.toml index 5e0e76cfe..4c0b0025d 100644 --- a/examples/warp_subscriptions/Cargo.toml +++ b/examples/warp_subscriptions/Cargo.toml @@ -13,6 +13,7 @@ serde_json = "1.0" tokio = { version = "1", 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/src/main.rs b/examples/warp_subscriptions/src/main.rs index 21e2668bb..c7a6f855a 100644 --- a/examples/warp_subscriptions/src/main.rs +++ b/examples/warp_subscriptions/src/main.rs @@ -5,7 +5,7 @@ use std::{env, pin::Pin, sync::Arc, time::Duration}; use futures::{FutureExt as _, Stream}; use juniper::{ graphql_object, graphql_subscription, DefaultScalarValue, EmptyMutation, FieldError, - GraphQLEnum, RootNode, + GraphQLEnum, RootNode, Value, }; use juniper_graphql_ws::ConnectionConfig; use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws}; diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 8f5becda1..3000a1f46 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -1,6 +1,5 @@ //! [`Tz`] (timezone) scalar implementation, represented by its [IANA database][1] name. //! -//! [`Tz`]: chrono_tz::Tz //! [1]: http://www.iana.org/time-zones use chrono_tz::Tz; diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs index e1670c0aa..bdec72bec 100644 --- a/juniper/src/macros/tests/impl_subscription.rs +++ b/juniper/src/macros/tests/impl_subscription.rs @@ -1,8 +1,11 @@ -use std::pin::Pin; +use std::{collections::HashMap, pin::Pin}; -use futures::StreamExt as _; +use futures::{stream, StreamExt as _}; -use crate::{graphql_object, graphql_value, EmptyMutation, RootNode, Value}; +use crate::{ + graphql_object, graphql_subscription, graphql_value, resolve_into_stream, DefaultScalarValue, + EmptyMutation, Executor, RootNode, Value, +}; use super::util; @@ -17,7 +20,7 @@ struct WithLifetime<'a> { value: &'a str, } -#[graphql_object(Context = Context)] +#[graphql_object(context = Context)] impl<'a> WithLifetime<'a> { fn value(&self) -> &str { self.value @@ -26,7 +29,7 @@ impl<'a> WithLifetime<'a> { struct WithContext; -#[graphql_object(Context = Context)] +#[graphql_object(context = Context)] impl WithContext { fn ctx(ctx: &Context) -> bool { ctx.flag1 @@ -36,9 +39,7 @@ impl WithContext { #[derive(Default)] struct Query; -#[graphql_object( - Context = Context, -)] +#[graphql_object(context = Context)] impl Query { fn empty() -> bool { true @@ -62,76 +63,74 @@ struct Subscription { b: bool, } -#[crate::graphql_subscription( - scalar = crate::DefaultScalarValue, +#[graphql_subscription( name = "Subscription", context = Context, + scalar = DefaultScalarValue, )] /// Subscription Description. impl Subscription { #[graphql(description = "With Self Description")] async fn with_self(&self) -> Stream { let b = self.b; - Box::pin(futures::stream::once(async move { b })) + Box::pin(stream::once(async move { b })) } async fn independent() -> Stream { - Box::pin(futures::stream::once(async { 100 })) + Box::pin(stream::once(async { 100 })) } - async fn with_executor(_exec: &Executor) -> Stream { - Box::pin(futures::stream::once(async { true })) + async fn with_executor(_executor: &Executor<'_, '_, Context>) -> Stream { + Box::pin(stream::once(async { true })) } - async fn with_executor_and_self(&self, _exec: &Executor) -> Stream { - Box::pin(futures::stream::once(async { true })) + async fn with_executor_and_self(&self, _executor: &Executor<'_, '_, Context>) -> Stream { + Box::pin(stream::once(async { true })) } async fn with_context(_context: &Context) -> Stream { - Box::pin(futures::stream::once(async { true })) + Box::pin(stream::once(async { true })) } async fn with_context_and_self(&self, _context: &Context) -> Stream { - Box::pin(futures::stream::once(async { true })) + Box::pin(stream::once(async { true })) } #[graphql(name = "renamed")] async fn has_custom_name() -> Stream { - Box::pin(futures::stream::once(async { true })) + Box::pin(stream::once(async { true })) } #[graphql(description = "attr")] async fn has_description_attr() -> Stream { - Box::pin(futures::stream::once(async { true })) + Box::pin(stream::once(async { true })) } /// Doc description async fn has_description_doc_comment() -> Stream { - Box::pin(futures::stream::once(async { true })) + Box::pin(stream::once(async { true })) } async fn has_argument(arg1: bool) -> Stream { - Box::pin(futures::stream::once(async move { arg1 })) + Box::pin(stream::once(async move { arg1 })) } - #[graphql(arguments(default_arg(default = true)))] - async fn default_argument(default_arg: bool) -> Stream { - Box::pin(futures::stream::once(async move { default_arg })) + async fn default_argument(#[graphql(default = true)] default_arg: bool) -> Stream { + Box::pin(stream::once(async move { default_arg })) } - #[graphql(arguments(arg(description = "my argument description")))] - async fn arg_with_description(arg: bool) -> Stream { - Box::pin(futures::stream::once(async move { arg })) + async fn arg_with_description( + #[graphql(description = "my argument description")] arg: bool, + ) -> Stream { + Box::pin(stream::once(async move { arg })) } async fn with_context_child(&self) -> Stream { - Box::pin(futures::stream::once(async { WithContext })) + Box::pin(stream::once(async { WithContext })) } - async fn with_implicit_lifetime_child(&self) -> Stream> { - Box::pin(futures::stream::once(async { - WithLifetime { value: "blub" } - })) + async fn with_implicit_lifetime_child(&self) -> Stream> { + Box::pin(stream::once(async { WithLifetime { value: "blub" } })) } async fn with_mut_arg(mut arg: bool) -> Stream { @@ -139,11 +138,11 @@ impl Subscription { arg = !arg; } - Box::pin(futures::stream::once(async move { arg })) + Box::pin(stream::once(async move { arg })) } - async fn without_type_alias() -> Pin + Send>> { - Box::pin(futures::stream::once(async { "abc" })) + async fn without_type_alias() -> Pin + Send>> { + Box::pin(stream::once(async { "abc" })) } } @@ -152,123 +151,90 @@ async fn object_introspect() { let res = util::run_info_query::("Subscription").await; assert_eq!( res, - crate::graphql_value!({ + graphql_value!({ "name": "Subscription", "description": "Subscription Description.", - "fields": [ - { - "name": "withSelf", - "description": "With Self Description", - "args": [], - }, - { - "name": "independent", - "description": None, - "args": [], - }, - { - "name": "withExecutor", - "description": None, - "args": [], - }, - { - "name": "withExecutorAndSelf", - "description": None, - "args": [], - }, - { - "name": "withContext", - "description": None, - "args": [], - }, - { - "name": "withContextAndSelf", - "description": None, - "args": [], - }, - { - "name": "renamed", - "description": None, - "args": [], - }, - { - "name": "hasDescriptionAttr", - "description": "attr", - "args": [], - }, - { - "name": "hasDescriptionDocComment", - "description": "Doc description", - "args": [], - }, - { - "name": "hasArgument", - "description": None, - "args": [ - { - "name": "arg1", - "description": None, - "type": { - "name": None, - }, - } - ], - }, - { - "name": "defaultArgument", - "description": None, - "args": [ - { - "name": "defaultArg", - "description": None, - "type": { - "name": "Boolean", - }, - } - ], - }, - { - "name": "argWithDescription", - "description": None, - "args": [ - { - "name": "arg", - "description": "my argument description", - "type": { - "name": None - }, - } - ], - }, - { - "name": "withContextChild", - "description": None, - "args": [], - }, - { - "name": "withImplicitLifetimeChild", + "fields": [{ + "name": "withSelf", + "description": "With Self Description", + "args": [], + }, { + "name": "independent", + "description": None, + "args": [], + }, { + "name": "withExecutor", + "description": None, + "args": [], + }, { + "name": "withExecutorAndSelf", + "description": None, + "args": [], + }, { + "name": "withContext", + "description": None, + "args": [], + }, { + "name": "withContextAndSelf", + "description": None, + "args": [], + }, { + "name": "renamed", + "description": None, + "args": [], + }, { + "name": "hasDescriptionAttr", + "description": "attr", + "args": [], + }, { + "name": "hasDescriptionDocComment", + "description": "Doc description", + "args": [], + }, { + "name": "hasArgument", + "description": None, + "args": [{ + "name": "arg1", "description": None, - "args": [], - }, - { - "name": "withMutArg", + "type": {"name": None}, + }], + }, { + "name": "defaultArgument", + "description": None, + "args": [{ + "name": "defaultArg", "description": None, - "args": [ - { - "name": "arg", - "description": None, - "type": { - "name": None, - }, - } - ], - }, - { - "name": "withoutTypeAlias", + "type": {"name": "Boolean"}, + }], + }, { + "name": "argWithDescription", + "description": None, + "args": [{ + "name": "arg", + "description": "my argument description", + "type": {"name": None}, + }], + }, { + "name": "withContextChild", + "description": None, + "args": [], + }, { + "name": "withImplicitLifetimeChild", + "description": None, + "args": [], + }, { + "name": "withMutArg", + "description": None, + "args": [{ + "name": "arg", "description": None, - "args": [], - } - ] + "type": {"name": None}, + }], + }, { + "name": "withoutTypeAlias", + "description": None, + "args": [], + }], }) ); } @@ -302,10 +268,10 @@ async fn object_query() { EmptyMutation::::new(), Subscription { b: true }, ); - let vars = std::collections::HashMap::new(); + let vars = HashMap::new(); let (stream_val, errs) = - crate::resolve_into_stream(doc, None, &schema, &vars, &Context { flag1: true }) + resolve_into_stream(doc, None, &schema, &vars, &Context { flag1: true }) .await .expect("Execution failed"); @@ -342,11 +308,11 @@ async fn object_query() { ("argWithDescription".to_string(), graphql_value!(true)), ( "withContextChild".to_string(), - graphql_value!({"ctx": true}) + graphql_value!({"ctx": true}), ), ( "withImplicitLifetimeChild".to_string(), - graphql_value!({ "value": "blub" }) + graphql_value!({"value": "blub"}), ), ("withMutArg".to_string(), graphql_value!(false)), ("withoutTypeAlias".to_string(), graphql_value!("abc")), diff --git a/juniper/src/tests/subscriptions.rs b/juniper/src/tests/subscriptions.rs index 66d118fcb..ac61d504e 100644 --- a/juniper/src/tests/subscriptions.rs +++ b/juniper/src/tests/subscriptions.rs @@ -1,10 +1,10 @@ use std::{iter, iter::FromIterator as _, pin::Pin}; -use futures::{self, StreamExt as _}; +use futures::{stream, StreamExt as _}; use crate::{ - http::GraphQLRequest, Context, DefaultScalarValue, EmptyMutation, ExecutionError, FieldError, - GraphQLObject, Object, RootNode, Value, + graphql_object, graphql_subscription, http::GraphQLRequest, Context, DefaultScalarValue, + EmptyMutation, ExecutionError, FieldError, GraphQLObject, Object, RootNode, Value, }; #[derive(Debug, Clone)] @@ -22,7 +22,7 @@ struct Human { struct MyQuery; -#[crate::graphql_object(context = MyContext)] +#[graphql_object(context = MyContext)] impl MyQuery { fn test(&self) -> i32 { 0 // NOTICE: does not serve a purpose @@ -42,10 +42,10 @@ type HumanStream = Pin + Send>>; struct MySubscription; -#[crate::graphql_subscription(context = MyContext)] +#[graphql_subscription(context = MyContext)] impl MySubscription { async fn async_human() -> HumanStream { - Box::pin(futures::stream::once(async { + Box::pin(stream::once(async { Human { id: "stream id".to_string(), name: "stream name".to_string(), @@ -61,9 +61,9 @@ impl MySubscription { )) } - async fn human_with_context(ctxt: &MyContext) -> HumanStream { - let context_val = ctxt.0.clone(); - Box::pin(futures::stream::once(async move { + async fn human_with_context(context: &MyContext) -> HumanStream { + let context_val = context.0.clone(); + Box::pin(stream::once(async move { Human { id: context_val.to_string(), name: context_val.to_string(), @@ -73,7 +73,7 @@ impl MySubscription { } async fn human_with_args(id: String, name: String) -> HumanStream { - Box::pin(futures::stream::once(async { + Box::pin(stream::once(async { Human { id, name, diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 1e02ea6ad..60979a409 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -384,6 +384,8 @@ pub type DynGraphQLValue = /// } /// } /// ``` +/// +/// [3]: https://spec.graphql.org/June2018/#sec-Objects pub trait GraphQLType: GraphQLValue where S: ScalarValue, diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 7525ba5b4..0c6d16a94 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -1,5 +1,7 @@ //! Common functions, definitions and extensions for parsing and code generation -//! of [GraphQL arguments][1]. +//! of [GraphQL arguments][1] +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Language.Arguments. use std::mem; diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index e05a826eb..a627e444b 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -1,5 +1,7 @@ //! Common functions, definitions and extensions for parsing and code generation -//! of [GraphQL fields][1]. +//! of [GraphQL fields][1] +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Language.Fields. pub(crate) mod arg; @@ -310,7 +312,7 @@ impl Definition { #[must_use] pub(crate) fn method_mark_tokens( &self, - coerce_result: bool, + infer_result: bool, scalar: &scalar::Type, ) -> TokenStream { let args_marks = self @@ -320,7 +322,7 @@ impl Definition { let ty = &self.ty; let mut ty = quote! { #ty }; - if coerce_result { + if infer_result { ty = quote! { <#ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item }; @@ -344,8 +346,17 @@ impl Definition { /// [`Registry`]: juniper::Registry /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields #[must_use] - pub(crate) fn method_meta_tokens(&self) -> TokenStream { + pub(crate) fn method_meta_tokens( + &self, + extract_stream_type: Option<&scalar::Type>, + ) -> TokenStream { let (name, ty) = (&self.name, &self.ty); + let mut ty = quote! { #ty }; + if let Some(scalar) = extract_stream_type { + ty = quote! { + <#ty as ::juniper::ExtractTypeFromStream<_, #scalar>>::Item + }; + } let description = self .description @@ -527,14 +538,14 @@ impl Definition { .await .map_err(|e| ex.new_error(e)) } - Ok(None) => Ok(Value::null()), + Ok(None) => Ok(::juniper::Value::null()), Err(e) => Err(ex.new_error(e)), } } }); Ok(::juniper::Value::Scalar::< ::juniper::ValuesStream::<#scalar> - >(Box::pin(stream))) + >(::juniper::futures::StreamExt::boxed(stream))) }) } } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 9fa63a570..550f4c6e1 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -532,10 +532,7 @@ impl Definition { a.cmp(&b) }); - let fields_meta = self - .fields - .iter() - .map(field::Definition::method_meta_tokens); + let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None)); quote! { #[automatically_derived] diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index 120d77c77..bb3586978 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -1,6 +1,6 @@ //! Code generation for `#[graphql_object]` macro. -use std::{marker::PhantomData, mem}; +use std::{any::TypeId, marker::PhantomData, mem}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; @@ -44,6 +44,7 @@ pub(crate) fn expand_on_impl( ) -> syn::Result where Definition: ToTokens, + Operation: 'static, { let type_span = ast.self_ty.span(); let type_ident = ast.self_ty.topmost_ident().ok_or_else(|| { @@ -74,12 +75,13 @@ where .copied() .unwrap_or(RenameRule::CamelCase); + let async_only = TypeId::of::() != TypeId::of::(); let fields: Vec<_> = ast .items .iter_mut() .filter_map(|item| { if let syn::ImplItem::Method(m) = item { - parse_field(m, &renaming) + parse_field(m, async_only, &renaming) } else { None } @@ -135,6 +137,7 @@ where #[must_use] fn parse_field( method: &mut syn::ImplItemMethod, + async_only: bool, renaming: &RenameRule, ) -> Option { let method_attrs = method.attrs.clone(); @@ -153,6 +156,10 @@ fn parse_field( return None; } + if async_only && method.sig.asyncness.is_none() { + return err_no_sync_resolvers(&method.sig); + } + let method_ident = &method.sig.ident; let name = attr @@ -222,7 +229,7 @@ fn parse_field( }) } -/// Emits "invalid method receiver" [`syn::Error`] pointing to the given `span`. +/// Emits "invalid method receiver" [`syn::Error`] pointing to the given `span`. #[must_use] fn err_invalid_method_receiver(span: &S) -> Option { ERR.emit_custom( @@ -231,3 +238,13 @@ fn err_invalid_method_receiver(span: &S) -> Option { ); None } + +/// Emits "synchronous resolvers are not supported" [`syn::Error`] pointing to +/// the given `span`. +#[must_use] +fn err_no_sync_resolvers(span: &S) -> Option { + ERR.custom(span.span(), "synchronous resolvers are not supported") + .note("Specify that this function is async: `async fn foo()`".into()) + .emit(); + None +} diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 3307fdff3..3c90b4dbc 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -389,10 +389,11 @@ impl Definition { .as_ref() .map(|desc| quote! { .description(#desc) }); + let extract_stream_type = TypeId::of::() != TypeId::of::(); let fields_meta = self .fields .iter() - .map(field::Definition::method_meta_tokens); + .map(|f| f.method_meta_tokens(extract_stream_type.then(|| scalar))); // Sorting is required to preserve/guarantee the order of interfaces registered in schema. let mut interface_tys: Vec<_> = self.interfaces.iter().collect(); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 4aea0e81c..fb3cdb3aa 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -540,23 +540,27 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream .into() } -/// `#[graphql_interface]` macro for generating a [GraphQL interface][1] implementation for traits -/// and its implementers. +/// `#[graphql_interface]` macro for generating a [GraphQL interface][1] +/// implementation for traits and its implementers. /// -/// Specifying multiple `#[graphql_interface]` attributes on the same definition is totally okay. +/// 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 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]. +/// The main 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]. /// /// ``` /// use juniper::{graphql_interface, GraphQLObject}; From 72c00a7102b613f4fb686ea108a5440a590fae5c Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 2 Aug 2021 18:25:34 +0300 Subject: [PATCH 19/39] Impl subscriptions, vol.3 --- juniper/src/integrations/bson.rs | 2 ++ juniper/src/integrations/chrono_tz.rs | 1 + juniper/src/integrations/mod.rs | 18 ++++-------------- juniper/src/integrations/url.rs | 2 ++ juniper/src/integrations/uuid.rs | 2 ++ juniper/src/types/subscriptions.rs | 2 ++ juniper_actix/examples/actix_server.rs | 13 ++++++++----- juniper_graphql_ws/src/lib.rs | 6 ++++-- juniper_hyper/Cargo.toml | 8 ++++---- juniper_iron/src/lib.rs | 6 +++--- juniper_warp/src/lib.rs | 1 + 11 files changed, 33 insertions(+), 28 deletions(-) diff --git a/juniper/src/integrations/bson.rs b/juniper/src/integrations/bson.rs index 15d66c949..95d83382c 100644 --- a/juniper/src/integrations/bson.rs +++ b/juniper/src/integrations/bson.rs @@ -1,3 +1,5 @@ +//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types. + use bson::{oid::ObjectId, DateTime as UtcDateTime}; use chrono::prelude::*; diff --git a/juniper/src/integrations/chrono_tz.rs b/juniper/src/integrations/chrono_tz.rs index 3000a1f46..8f5becda1 100644 --- a/juniper/src/integrations/chrono_tz.rs +++ b/juniper/src/integrations/chrono_tz.rs @@ -1,5 +1,6 @@ //! [`Tz`] (timezone) scalar implementation, represented by its [IANA database][1] name. //! +//! [`Tz`]: chrono_tz::Tz //! [1]: http://www.iana.org/time-zones use chrono_tz::Tz; diff --git a/juniper/src/integrations/mod.rs b/juniper/src/integrations/mod.rs index ad4e29e3c..76ac28ba6 100644 --- a/juniper/src/integrations/mod.rs +++ b/juniper/src/integrations/mod.rs @@ -1,24 +1,14 @@ //! Provides GraphQLType implementations for some external types -#[doc(hidden)] -pub mod serde; - +#[cfg(feature = "bson")] +pub mod bson; #[cfg(feature = "chrono")] -/// GraphQL support for [chrono](https://github.com/chronotope/chrono) types. pub mod chrono; - #[cfg(feature = "chrono-tz")] -/// GraphQL support for [chrono-tz](https://github.com/chronotope/chrono-tz) types. pub mod chrono_tz; - +#[doc(hidden)] +pub mod serde; #[cfg(feature = "url")] -/// GraphQL support for [url](https://github.com/servo/rust-url) types. pub mod url; - #[cfg(feature = "uuid")] -/// GraphQL support for [uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) types. pub mod uuid; - -#[cfg(feature = "bson")] -/// GraphQL support for [bson](https://github.com/mongodb/bson-rust) types. -pub mod bson; diff --git a/juniper/src/integrations/url.rs b/juniper/src/integrations/url.rs index 649787987..492626bd9 100644 --- a/juniper/src/integrations/url.rs +++ b/juniper/src/integrations/url.rs @@ -1,3 +1,5 @@ +//! GraphQL support for [url](https://github.com/servo/rust-url) types. + use url::Url; use crate::{ diff --git a/juniper/src/integrations/uuid.rs b/juniper/src/integrations/uuid.rs index 5a949cf7b..1f677d65d 100644 --- a/juniper/src/integrations/uuid.rs +++ b/juniper/src/integrations/uuid.rs @@ -1,3 +1,5 @@ +//! GraphQL support for [uuid](https://doc.rust-lang.org/uuid/uuid/struct.Uuid.html) types. + #![allow(clippy::needless_lifetimes)] use uuid::Uuid; diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs index 2b5981e4d..a6c28fa75 100644 --- a/juniper/src/types/subscriptions.rs +++ b/juniper/src/types/subscriptions.rs @@ -82,6 +82,8 @@ where /// /// It can be treated as [`futures::Stream`] yielding [`GraphQLResponse`]s in /// server integration crates. +/// +/// [`GraphQLResponse`]: crate::http::GraphQLResponse pub trait SubscriptionConnection: futures::Stream> {} /// Extension of [`GraphQLValue`] trait with asynchronous [subscription][1] execution logic. diff --git a/juniper_actix/examples/actix_server.rs b/juniper_actix/examples/actix_server.rs index 7a9638075..38981a666 100644 --- a/juniper_actix/examples/actix_server.rs +++ b/juniper_actix/examples/actix_server.rs @@ -71,12 +71,15 @@ impl juniper::Context for Database {} struct Query; #[graphql_object(context = Database)] impl Query { - fn apiVersion() -> String { - "1.0".to_string() + fn api_version() -> &'static str { + "1.0" } - #[graphql(arguments(id(description = "id of the user")))] - fn user(database: &Database, id: i32) -> Option<&User> { - database.get_user(&id) + + fn user( + context: &Database, + #[graphql(description = "id of the user")] id: i32, + ) -> Option<&User> { + context.get_user(&id) } } diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs index 6ad25029a..f5627596e 100644 --- a/juniper_graphql_ws/src/lib.rs +++ b/juniper_graphql_ws/src/lib.rs @@ -642,6 +642,8 @@ mod test { struct Context(i32); + impl juniper::Context for Context {} + struct Query; #[graphql_object(context = Context)] @@ -657,7 +659,7 @@ mod test { #[graphql_subscription(context = Context)] impl Subscription { /// never never emits anything. - async fn never(context: &Context) -> BoxStream<'static, FieldResult> { + async fn never(_context: &Context) -> BoxStream<'static, FieldResult> { tokio::time::sleep(Duration::from_secs(10000)) .map(|_| unreachable!()) .into_stream() @@ -676,7 +678,7 @@ mod test { } /// error emits an error once, then never emits anything else. - async fn error(context: &Context) -> BoxStream<'static, FieldResult> { + async fn error(_context: &Context) -> BoxStream<'static, FieldResult> { stream::once(future::ready(Err(FieldError::new( "field error", Value::null(), diff --git a/juniper_hyper/Cargo.toml b/juniper_hyper/Cargo.toml index 11f034431..53aa080e6 100644 --- a/juniper_hyper/Cargo.toml +++ b/juniper_hyper/Cargo.toml @@ -11,13 +11,13 @@ repository = "https://github.com/graphql-rust/juniper" [dependencies] futures = "0.3.1" juniper = { version = "0.15.7", path = "../juniper", default-features = false } -hyper = {version = "0.14", features = ["server", "runtime"]} +hyper = { version = "0.14", features = ["server", "runtime"] } serde_json = "1.0" -tokio = "1" -url = "2" +tokio = "1.0" +url = "2.0" [dev-dependencies] juniper = { version = "0.15.7", path = "../juniper", features = ["expose-test-schema"] } pretty_env_logger = "0.4" reqwest = { version = "0.11", features = ["blocking", "rustls-tls"] } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index b58bcd7aa..118c64862 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -44,9 +44,9 @@ use juniper::{Context, EmptyMutation, EmptySubscription}; # Ok(&self.name) # } # -# fn friends(&self, context: &Database) -> FieldResult> { +# fn friends<'c>(&self, context: &'c Database) -> FieldResult> { # Ok(self.friend_ids.iter() -# .filter_map(|id| executor.context().users.get(id)) +# .filter_map(|id| context.users.get(id)) # .collect()) # } # } @@ -54,7 +54,7 @@ use juniper::{Context, EmptyMutation, EmptySubscription}; # #[juniper::graphql_object(context = Database, scalar = juniper::DefaultScalarValue)] # impl QueryRoot { # fn user(context: &Database, id: String) -> FieldResult> { -# Ok(executor.context().users.get(&id)) +# Ok(context.users.get(&id)) # } # } diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 64180d700..4c683e92f 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -70,6 +70,7 @@ use warp::{body, filters::BoxedFilter, http, hyper::body::Bytes, query, Filter}; /// # #[derive(Debug)] /// struct AppState(Vec); /// struct ExampleContext(Arc, UserId); +/// # impl juniper::Context for ExampleContext {} /// /// struct QueryRoot; /// From 4156d239ef0de8aaebe13ffdfea2b0cb4b0b318c Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 2 Aug 2021 18:43:39 +0300 Subject: [PATCH 20/39] Fix codegen failure tests --- .../implementer_non_object_type.stderr | 6 +++--- .../fail/object/derive_fields_unique.stderr | 12 +++++++----- .../fail/object/derive_no_fields.stderr | 2 +- .../fail/object/impl_argument_no_object.stderr | 13 ------------- .../object/impl_argument_wrong_default_array.rs | 3 +-- .../impl_argument_wrong_default_array.stderr | 9 +++++++-- .../fail/object/impl_fields_unique.stderr | 17 +++++++---------- .../fail/object/impl_no_argument_underscore.rs | 3 +-- .../object/impl_no_argument_underscore.stderr | 6 +++--- .../fail/object/impl_no_fields.stderr | 6 +++--- .../fail/union/enum_non_object_variant.stderr | 6 +++--- .../fail/union/struct_non_object_variant.stderr | 6 +++--- .../fail/union/trait_non_object_variant.stderr | 6 +++--- 13 files changed, 42 insertions(+), 53 deletions(-) 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 index e4e09b66a..597171164 100644 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr @@ -1,10 +1,10 @@ -error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied +error[E0277]: the trait bound `ObjA: GraphQLObject<__S>` is not satisfied --> $DIR/implementer_non_object_type.rs:15:1 | 15 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `ObjA` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA` | - = note: required by `juniper::marker::GraphQLObjectType::mark` + = note: required by `juniper::GraphQLObject::mark` = 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 diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr b/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr index 999b01168..66474ee1f 100644 --- a/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr +++ b/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr @@ -1,9 +1,11 @@ -error: GraphQL object does not allow fields with the same name - --> $DIR/derive_fields_unique.rs:4:5 +error: GraphQL object must have a different name for each field + --> $DIR/derive_fields_unique.rs:2:1 | -4 | / #[graphql(name = "test")] +2 | / struct Object { +3 | | test: String, +4 | | #[graphql(name = "test")] 5 | | test2: String, - | |_________________^ +6 | | } + | |_^ | - = help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr b/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr index 183cd4304..523fe8acc 100644 --- a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr +++ b/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr @@ -1,4 +1,4 @@ -error: GraphQL object expects at least one field +error: GraphQL object must have at least one field --> $DIR/derive_no_fields.rs:2:1 | 2 | struct Object {} diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr index 384686082..0a4e56ffd 100644 --- a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr +++ b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr @@ -14,16 +14,3 @@ error[E0277]: the trait bound `Obj: FromInputValue<__S>` is not satisfied | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `Obj` | = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Obj: FromInputValue` is not satisfied - --> $DIR/impl_argument_no_object.rs:8:1 - | -8 | #[juniper::graphql_object] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue` is not implemented for `Obj` - | - ::: $WORKSPACE/juniper/src/ast.rs - | - | pub trait FromInputValue: Sized { - | ------------------------------------------------------- required by this bound in `FromInputValue` - | - = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs index 740400c4d..6b6c10e1c 100644 --- a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs +++ b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs @@ -2,8 +2,7 @@ struct Object; #[juniper::graphql_object] impl Object { - #[graphql(arguments(input(default = [true, false, false])))] - fn wrong(input: [bool; 2]) -> bool { + fn wrong(#[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { input[0] } } diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr index eb85ff256..dc3736f9f 100644 --- a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr @@ -1,7 +1,12 @@ -error[E0308]: mismatched types +error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied --> $DIR/impl_argument_wrong_default_array.rs:3:1 | 3 | #[juniper::graphql_object] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 2 elements, found one with 3 elements + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 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>> + = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr b/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr index 49649a2ac..f3555ea6a 100644 --- a/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr +++ b/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr @@ -1,10 +1,7 @@ -error: GraphQL object does not allow fields with the same name - --> $DIR/impl_fields_unique.rs:9:5 - | -9 | / fn test(&self) -> String { -10 | | String::new() -11 | | } - | |_____^ - | - = help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute - = note: https://spec.graphql.org/June2018/#sec-Objects +error: GraphQL object must have a different name for each field + --> $DIR/impl_fields_unique.rs:4:6 + | +4 | impl Object { + | ^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs b/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs index 02fec848e..3eb2de5ed 100644 --- a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs +++ b/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs @@ -2,8 +2,7 @@ struct Object {} #[juniper::graphql_object] impl Object { - #[graphql(arguments(arg(name = "__arg")))] - fn test(&self, arg: String) -> String { + fn test(&self, #[graphql(name = "__arg")] arg: String) -> String { arg } } diff --git a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr b/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr index 40eea80dc..9c783fcd5 100644 --- a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr +++ b/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.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. - --> $DIR/impl_no_argument_underscore.rs:5:29 + --> $DIR/impl_no_argument_underscore.rs:5:30 | -5 | #[graphql(arguments(arg(name = "__arg")))] - | ^^^^ +5 | fn test(&self, #[graphql(name = "__arg")] arg: String) -> String { + | ^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr b/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr index 798a7db20..ea6ed2e0f 100644 --- a/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr +++ b/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr @@ -1,7 +1,7 @@ -error: GraphQL object expects at least one field - --> $DIR/impl_no_fields.rs:4:1 +error: GraphQL object must have at least one field + --> $DIR/impl_no_fields.rs:4:6 | 4 | impl Object {} - | ^^^^^^^^^^^^^^ + | ^^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Objects 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 index 3cb23c268..fc7f13fa1 100644 --- a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr +++ b/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr @@ -1,8 +1,8 @@ -error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied +error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied --> $DIR/enum_non_object_variant.rs:9:10 | 9 | #[derive(GraphQLUnion)] - | ^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test` + | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` | - = note: required by `juniper::marker::GraphQLObjectType::mark` + = note: required by `juniper::GraphQLObject::mark` = 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 index 8981bacfc..200aeecdf 100644 --- a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr +++ b/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr @@ -1,8 +1,8 @@ -error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied +error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied --> $DIR/struct_non_object_variant.rs:9:10 | 9 | #[derive(GraphQLUnion)] - | ^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test` + | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` | - = note: required by `juniper::marker::GraphQLObjectType::mark` + = note: required by `juniper::GraphQLObject::mark` = 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_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr index 95cb302b5..e9ac7cdb7 100644 --- a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr +++ b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr @@ -1,8 +1,8 @@ -error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied +error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied --> $DIR/trait_non_object_variant.rs:9:1 | 9 | #[graphql_union] - | ^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test` + | ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test` | - = note: required by `juniper::marker::GraphQLObjectType::mark` + = note: required by `juniper::GraphQLObject::mark` = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) From 051c356586dd9a237fdc4f175f98c80fb3298c9f Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 2 Aug 2021 19:07:37 +0300 Subject: [PATCH 21/39] Fix book, vol.1 --- .../advanced/implicit_and_explicit_null.md | 3 +- docs/book/content/advanced/subscriptions.md | 8 ++---- docs/book/content/quickstart.md | 11 ++++---- .../content/types/objects/complex_fields.md | 28 +++++++++---------- .../content/types/objects/error_handling.md | 21 ++++++-------- .../content/types/objects/using_contexts.md | 4 +-- 6 files changed, 33 insertions(+), 42 deletions(-) diff --git a/docs/book/content/advanced/implicit_and_explicit_null.md b/docs/book/content/advanced/implicit_and_explicit_null.md index 014249a19..746cb7ee3 100644 --- a/docs/book/content/advanced/implicit_and_explicit_null.md +++ b/docs/book/content/advanced/implicit_and_explicit_null.md @@ -99,10 +99,11 @@ impl Into for UserPatchInput { struct Context { session: Session, } +impl juniper::Context for Context {} struct Mutation; -#[juniper::graphql_object(Context=Context)] +#[juniper::graphql_object(context = Context)] impl Mutation { fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult { ctx.session.patch_user(patch.into())?; diff --git a/docs/book/content/advanced/subscriptions.md b/docs/book/content/advanced/subscriptions.md index 95b26513a..05e01d64b 100644 --- a/docs/book/content/advanced/subscriptions.md +++ b/docs/book/content/advanced/subscriptions.md @@ -25,10 +25,6 @@ This example shows a subscription operation that returns two events, the strings sequentially: ```rust -# extern crate futures; -# extern crate juniper; -# extern crate juniper_subscriptions; -# extern crate tokio; # use juniper::{graphql_object, graphql_subscription, FieldError}; # use futures::Stream; # use std::pin::Pin; @@ -40,7 +36,7 @@ sequentially: # pub struct Query; # #[graphql_object(context = Database)] # impl Query { -# fn hello_world() -> &str { +# fn hello_world() -> &'static str { # "Hello World!" # } # } @@ -110,7 +106,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati # # #[graphql_object(context = Database)] # impl Query { -# fn hello_world() -> &str { +# fn hello_world() -> &'static str { # "Hello World!" # } # } diff --git a/docs/book/content/quickstart.md b/docs/book/content/quickstart.md index 7af0bb02e..b33f33444 100644 --- a/docs/book/content/quickstart.md +++ b/docs/book/content/quickstart.md @@ -89,7 +89,7 @@ struct Query; context = Context, )] impl Query { - fn apiVersion() -> &str { + fn apiVersion() -> &'static str { "1.0" } @@ -114,14 +114,13 @@ struct Mutation; #[graphql_object( context = Context, - // If we need to use `ScalarValue` parametrization explicitly somewhere - // in the object definition (like here in `FieldResult`), we should + // in the object definition (like here in `FieldResult`), we could // declare an explicit type parameter for that, and specify it. - scalar = S, + scalar = S: ScalarValue + Display, )] -impl Mutation { - fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult { +impl Mutation { + fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult { let db = context.pool.get_connection().map_err(|e| e.map_scalar_value())?; let human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?; Ok(human) diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md index 75867184c..60dce4ae3 100644 --- a/docs/book/content/types/objects/complex_fields.md +++ b/docs/book/content/types/objects/complex_fields.md @@ -144,21 +144,19 @@ struct Person {} #[graphql_object] impl Person { - #[graphql( - arguments( - arg1( - // Set a default value which will be injected if not present. - // The default can be any valid Rust expression, including a function call, etc. - default = true, - // Set a description. - description = "The first argument..." - ), - arg2( - default = 0, - ) - ) - )] - fn field1(&self, arg1: bool, arg2: i32) -> String { + fn field1( + &self, + #[graphql( + // Set a default value which will be injected if not present. + // The default can be any valid Rust expression, including a function call, etc. + default = true, + // Set a description. + description = "The first argument..." + )] + arg1: bool, + #[graphql(default = 0)] + arg2: i32, + ) -> String { format!("{} {}", arg1, arg2) } } diff --git a/docs/book/content/types/objects/error_handling.md b/docs/book/content/types/objects/error_handling.md index ea64e7642..3a96a70f7 100644 --- a/docs/book/content/types/objects/error_handling.md +++ b/docs/book/content/types/objects/error_handling.md @@ -36,7 +36,7 @@ struct Example { #[graphql_object] impl Example { - fn contents() -> FieldResult { + fn contents(&self) -> FieldResult { let mut file = File::open(&self.filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; @@ -44,13 +44,10 @@ impl Example { } fn foo() -> FieldResult> { - // Some invalid bytes. - let invalid = vec![128, 223]; + // Some invalid bytes. + let invalid = vec![128, 223]; - match str::from_utf8(&invalid) { - Ok(s) => Ok(Some(s.to_string())), - Err(e) => Err(e)?, - } + Ok(Some(str::from_utf8(&invalid)?.to_string())) } } # @@ -162,11 +159,11 @@ struct Example { #[graphql_object] impl Example { - fn whatever() -> Result { - if let Some(value) = self.whatever { - return Ok(value); - } - Err(CustomError::WhateverNotSet) + fn whatever(&self) -> Result { + if let Some(value) = self.whatever { + return Ok(value); + } + Err(CustomError::WhateverNotSet) } } # diff --git a/docs/book/content/types/objects/using_contexts.md b/docs/book/content/types/objects/using_contexts.md index 0576a6644..10164e466 100644 --- a/docs/book/content/types/objects/using_contexts.md +++ b/docs/book/content/types/objects/using_contexts.md @@ -63,8 +63,8 @@ impl User { // with the context type. // Note: // - the type must be a reference - // - the name of the argument SHOULD be context - fn friends(&self, context: &Database) -> Vec<&User> { + // - the name of the argument SHOULD be `context` + fn friends<'db>(&self, context: &'db Database) -> Vec<&'db User> { // 5. Use the database to lookup users self.friend_ids.iter() From 95ad9bd57cef569c0572108003c1c6ffadae613b Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 2 Aug 2021 23:17:10 +0300 Subject: [PATCH 22/39] Fix tests --- juniper_codegen/src/lib.rs | 40 +++++++++++++------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index fb3cdb3aa..db5389b98 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -304,7 +304,6 @@ specifying an argument with the same type as the context (but as a reference). ``` - # #[derive(juniper::GraphQLObject)] struct User { id: i32 } # struct DbPool; # impl DbPool { fn user(&self, id: i32) -> Option { unimplemented!() } } @@ -318,10 +317,8 @@ impl juniper::Context for Context {} struct Query; -#[juniper::graphql_object( - // Here we specify the context type for this object. - Context = Context, -)] +// Here we specify the context type for this object. +#[juniper::graphql_object(context = Context)] impl Query { // Context is injected by specifying a argument // as a reference to the Context. @@ -331,7 +328,9 @@ impl Query { // You can also gain access to the executor, which // allows you to do look aheads. - fn with_executor(executor: &Executor) -> bool { + fn with_executor<__S: juniper::ScalarValue>( + executor: &juniper::Executor<'_, '_, Context, __S>, + ) -> bool { let info = executor.look_ahead(); // ... true @@ -376,26 +375,15 @@ impl InternalQuery { )] fn deprecated_field_simple() -> bool { true } - - // Customizing field arguments is a little awkward right now. - // This will improve once [RFC 2564](https://github.com/rust-lang/rust/issues/60406) - // is implemented, which will allow attributes on function parameters. - - #[graphql( - arguments( - arg1( - // You can specify default values. - // A default can be any valid expression that yields the right type. - default = true, - description = "Argument description....", - ), - arg2( - default = false, - description = "arg2 description...", - ), - ), - )] - fn args(arg1: bool, arg2: bool) -> bool { + fn args( + // You can specify default values. + // A default can be any valid expression that yields the right type. + #[graphql(default = true, description = "Argument description....")] + arg1: bool, + // If expression is not specified then `Default::default()` is used. + #[graphql(default, description = "arg2 description...")] + arg2: bool, + ) -> bool { arg1 && arg2 } } From 4c026e8e8c22fbe6e598ab1600d74dbbfd30eff9 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 3 Aug 2021 01:51:24 +0300 Subject: [PATCH 23/39] Fix type params handling --- .../juniper_tests/src/codegen/mod.rs | 1 + .../juniper_tests/src/codegen/object_attr.rs | 21 ++++++ juniper_codegen/src/common/parse/mod.rs | 46 ++++++------ juniper_codegen/src/graphql_interface/attr.rs | 11 +-- juniper_codegen/src/graphql_interface/mod.rs | 18 ++--- juniper_codegen/src/graphql_object/attr.rs | 21 +++--- juniper_codegen/src/graphql_object/derive.rs | 10 ++- juniper_codegen/src/graphql_object/mod.rs | 70 ++++++++----------- .../src/graphql_subscription/mod.rs | 10 +-- juniper_codegen/src/graphql_union/attr.rs | 3 +- juniper_codegen/src/graphql_union/derive.rs | 10 ++- juniper_codegen/src/graphql_union/mod.rs | 6 +- juniper_codegen/src/lib.rs | 1 - 13 files changed, 129 insertions(+), 99 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/object_attr.rs diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 0b45de8d3..88a726665 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -6,6 +6,7 @@ mod derive_scalar; mod impl_object; mod impl_scalar; mod interface_attr; +mod object_attr; mod scalar_value_transparent; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs new file mode 100644 index 000000000..8815aaf88 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -0,0 +1,21 @@ +use juniper::graphql_object; + +mod two_objects_on_generic { + use super::*; + + struct Generic(T); + + #[graphql_object(name = "BooleanGeneric")] + impl Generic { + fn boolean(&self) -> bool { + self.0 + } + } + + #[graphql_object(name = "IntGeneric")] + impl Generic { + fn num(&self) -> i32 { + self.0 + } + } +} diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 5b110ed8f..4681de1a8 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -107,7 +107,11 @@ pub(crate) trait TypeExt { #[must_use] fn unreferenced(&self) -> &Self; - /// Anonymises all the lifetime parameters of this [`syn::Type`] making it + /// Iterates mutably over all the lifetime parameters of this [`syn::Type`] + /// with the given `func`tion. + fn lifetimes_iter_mut(&mut self, func: &mut F); + + /// Anonymizes all the lifetime parameters of this [`syn::Type`] making it /// suitable for using in contexts with inferring. fn lifetimes_anonymized(&mut self); @@ -131,30 +135,28 @@ impl TypeExt for syn::Type { } } - fn lifetimes_anonymized(&mut self) { + fn lifetimes_iter_mut(&mut self, func: &mut F) { use syn::{GenericArgument as GA, Type as T}; - fn anonymize_path(path: &mut syn::Path) { + fn iter_path(path: &mut syn::Path, func: &mut F) { for seg in path.segments.iter_mut() { match &mut seg.arguments { syn::PathArguments::AngleBracketed(angle) => { for arg in angle.args.iter_mut() { match arg { - GA::Lifetime(lt) => { - lt.ident = syn::Ident::new("_", Span::call_site()) - } - GA::Type(ty) => ty.lifetimes_anonymized(), - GA::Binding(b) => b.ty.lifetimes_anonymized(), + GA::Lifetime(lt) => func(lt), + GA::Type(ty) => ty.lifetimes_iter_mut(func), + GA::Binding(b) => b.ty.lifetimes_iter_mut(func), GA::Constraint(_) | GA::Const(_) => {} } } } syn::PathArguments::Parenthesized(args) => { for ty in args.inputs.iter_mut() { - ty.lifetimes_anonymized() + ty.lifetimes_iter_mut(func) } if let syn::ReturnType::Type(_, ty) = &mut args.output { - (&mut *ty).lifetimes_anonymized() + (&mut *ty).lifetimes_iter_mut(func) } } syn::PathArguments::None => {} @@ -167,11 +169,11 @@ 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_anonymized(), + | T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_iter_mut(func), T::Tuple(syn::TypeTuple { elems, .. }) => { for ty in elems.iter_mut() { - ty.lifetimes_anonymized() + ty.lifetimes_iter_mut(func) } } @@ -179,14 +181,12 @@ impl TypeExt for syn::Type { | T::TraitObject(syn::TypeTraitObject { bounds, .. }) => { for bound in bounds.iter_mut() { match bound { - syn::TypeParamBound::Lifetime(lt) => { - lt.ident = syn::Ident::new("_", Span::call_site()) - } + syn::TypeParamBound::Lifetime(lt) => func(lt), syn::TypeParamBound::Trait(bound) => { if bound.lifetimes.is_some() { - todo!("Anonymizing HRTB lifetimes in trait is not yet supported") + todo!("Iterating over HRTB lifetimes in trait is not yet supported") } - anonymize_path(&mut bound.path) + iter_path(&mut bound.path, func) } } } @@ -194,12 +194,12 @@ impl TypeExt for syn::Type { T::Reference(ref_ty) => { if let Some(lt) = ref_ty.lifetime.as_mut() { - lt.ident = syn::Ident::new("_", Span::call_site()); + func(lt) } - (&mut *ref_ty.elem).lifetimes_anonymized() + (&mut *ref_ty.elem).lifetimes_iter_mut(func) } - T::Path(ty) => anonymize_path(&mut ty.path), + T::Path(ty) => iter_path(&mut ty.path, func), // These types unlikely will be used as GraphQL types. T::BareFn(_) | T::Infer(_) | T::Macro(_) | T::Never(_) | T::Verbatim(_) => {} @@ -214,6 +214,12 @@ impl TypeExt for syn::Type { } } + fn lifetimes_anonymized(&mut self) { + self.lifetimes_iter_mut(&mut |lt| { + lt.ident = syn::Ident::new("_", Span::call_site()); + }); + } + fn topmost_ident(&self) -> Option<&syn::Ident> { match self.unparenthesized() { syn::Type::Path(p) => Some(&p.path), diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index e64ad6a6a..2aedbb2ad 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -77,7 +77,7 @@ fn expand_on_trait( .map(|ty| Implementer { ty: ty.as_ref().clone(), downcast: None, - context_ty: None, + context: None, scalar: scalar.clone(), }) .collect(); @@ -112,7 +112,7 @@ fn expand_on_trait( err_duplicate_downcast(m, external, &impler.ty); } else { impler.downcast = d.downcast; - impler.context_ty = d.context_ty; + impler.context = d.context; } } None => err_only_implementer_downcast(&m.sig), @@ -150,9 +150,10 @@ fn expand_on_trait( .or_else(|| { implementers .iter() - .find_map(|impler| impler.context_ty.as_ref()) + .find_map(|impler| impler.context.as_ref()) .cloned() - }); + }) + .unwrap_or_else(|| parse_quote! { () }); let is_trait_object = attr.r#dyn.is_some(); @@ -389,7 +390,7 @@ impl TraitMethod { Some(Implementer { ty, downcast: Some(downcast), - context_ty, + context: context_ty, scalar: scalar::Type::ImplicitGeneric(None), }) } diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 550f4c6e1..23e9bc441 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -408,12 +408,10 @@ struct Definition { /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL interface][1]. /// - /// If [`None`] then generated code will use unit type `()` as [`Context`]. - /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - context: Option, + context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL interface][1]. @@ -570,11 +568,11 @@ impl Definition { #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { 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 trait_ty = self.ty.trait_ty(); - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); let fields_resolvers = self .fields @@ -764,7 +762,7 @@ struct Implementer { /// /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - context_ty: Option, + context: Option, /// [`ScalarValue`] parametrization of this [`Implementer`]. /// @@ -1418,10 +1416,8 @@ struct TraitObjectType { /// Rust type of [`Context`] to generate this [`TraitObjectType`] with. /// - /// If [`None`] then generated code will use unit type `()` as [`Context`]. - /// /// [`Context`]: juniper::Context - context: Option, + context: syn::Type, } impl TraitObjectType { @@ -1431,7 +1427,7 @@ impl TraitObjectType { r#trait: &syn::ItemTrait, meta: &TraitAttr, scalar: scalar::Type, - context: Option, + context: syn::Type, ) -> Self { Self { ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(), @@ -1520,7 +1516,7 @@ impl TraitObjectType { } let ty_params = &generics.params; - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = &self.context; quote! { dyn #ty<#ty_params, Context = #context, TypeInfo = ()> + '__obj + Send + Sync @@ -1607,7 +1603,7 @@ impl ToTokens for TraitObjectType { ty_params_right = Some(quote! { #params, }); }; - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = &self.context; let dyn_alias = quote! { #[automatically_derived] diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index bb3586978..34a70c211 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -99,19 +99,24 @@ where 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() + 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 generated_code = Definition:: { name, - ty: parse_quote! { #type_ident }, + ty: ast.self_ty.unparenthesized().clone(), generics: ast.generics.clone(), description: attr.description.map(SpanContainer::into_inner), context, diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index e6f32f380..7cbe961b8 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -37,6 +37,9 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { let struct_span = ast.span(); let struct_ident = ast.ident; + let (_, struct_generics, _) = ast.generics.split_for_impl(); + let ty = parse_quote! { #struct_ident#struct_generics }; + let name = attr .name .clone() @@ -87,10 +90,13 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result> { Ok(Definition { name, - ty: parse_quote! { #struct_ident }, + ty, generics: ast.generics, description: attr.description.map(SpanContainer::into_inner), - context: attr.context.map(SpanContainer::into_inner), + context: attr + .context + .map(SpanContainer::into_inner) + .unwrap_or_else(|| parse_quote! { () }), scalar, fields, interfaces: attr diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 3c90b4dbc..267fde795 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -21,7 +21,7 @@ use crate::{ field, parse::{ attr::{err, OptionExt as _}, - ParseBufferExt as _, + ParseBufferExt as _, TypeExt, }, scalar, }, @@ -213,6 +213,8 @@ pub(crate) struct Definition { /// Rust type that this [GraphQL object][1] is represented with. /// + /// It should contain all its generics, if any. + /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects pub(crate) ty: syn::Type, @@ -230,12 +232,10 @@ pub(crate) struct Definition { /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL object][1]. /// - /// If [`None`] then generated code will use unit type `()` as [`Context`]. - /// /// [`GraphQLType`]: juniper::GraphQLType /// [`Context`]: juniper::Context /// [1]: https://spec.graphql.org/June2018/#sec-Objects - pub(crate) context: Option, + pub(crate) context: syn::Type, /// [`ScalarValue`] parametrization to generate [`GraphQLType`] /// implementation with for this [GraphQL object][1]. @@ -278,12 +278,7 @@ impl Definition { /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - pub(crate) fn impl_generics( - &self, - for_async: bool, - ) -> (TokenStream, TokenStream, Option) { - let (_, ty_generics, _) = self.generics.split_for_impl(); - + pub(crate) fn impl_generics(&self, for_async: bool) -> (TokenStream, Option) { let mut generics = self.generics.clone(); let scalar = &self.scalar; @@ -302,19 +297,18 @@ impl Definition { if for_async { let self_ty = if self.generics.lifetimes().next().is_some() { + let mut lifetimes = vec![]; + // 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.ty; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } + let mut ty = self.ty.clone(); + ty.lifetimes_iter_mut(&mut |lt| { + let ident = lt.ident.unraw(); + lt.ident = format_ident!("__fa__{}", ident); + lifetimes.push(lt.clone()); + }); + + quote! { for<#( #lifetimes ),*> #ty } } else { quote! { Self } }; @@ -332,11 +326,7 @@ impl Definition { } let (impl_generics, _, where_clause) = generics.split_for_impl(); - ( - quote! { #impl_generics }, - quote! { #ty_generics }, - where_clause.cloned(), - ) + (quote! { #impl_generics }, where_clause.cloned()) } /// Returns generated code implementing [`marker::IsOutputType`] trait for @@ -348,7 +338,7 @@ impl Definition { pub(crate) fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let coerce_result = TypeId::of::() != TypeId::of::(); @@ -361,7 +351,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause { fn mark() { #( #fields_marks )* @@ -380,7 +370,7 @@ impl Definition { pub(crate) fn impl_graphql_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; @@ -411,7 +401,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -426,7 +416,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_object_type::<#ty#ty_generics>(info, &fields) + registry.build_object_type::<#ty>(info, &fields) #description #interfaces .into_meta() @@ -462,7 +452,7 @@ impl Definition { fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let interface_tys = self.interfaces.iter(); @@ -475,7 +465,7 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::marker::GraphQLObject<#scalar> for #ty #where_clause { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* @@ -492,9 +482,9 @@ impl Definition { #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = &self.context; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; @@ -518,7 +508,7 @@ impl Definition { quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); @@ -561,7 +551,7 @@ impl Definition { fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(true); + let (impl_generics, where_clause) = self.impl_generics(true); let ty = &self.ty; let fields_resolvers = self @@ -573,7 +563,7 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause { fn resolve_field_async<'b>( &'b self, @@ -604,13 +594,13 @@ impl Definition { let scalar = &self.scalar; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(true); + 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#ty_generics #where_clause + impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #where_clause { type Context = >::Context; type TypeInfo = >::TypeInfo; diff --git a/juniper_codegen/src/graphql_subscription/mod.rs b/juniper_codegen/src/graphql_subscription/mod.rs index 57f57624e..8f6b0e06f 100644 --- a/juniper_codegen/src/graphql_subscription/mod.rs +++ b/juniper_codegen/src/graphql_subscription/mod.rs @@ -35,16 +35,16 @@ impl Definition { #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = &self.context; - let (impl_generics, ty_generics, where_clause) = self.impl_generics(false); + let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let name = &self.name; quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); @@ -85,7 +85,7 @@ impl Definition { // We use `for_async = false` here as `GraphQLSubscriptionValue` requires // simpler and less `Send`/`Sync` bounds than `GraphQLValueAsync`. - let (impl_generics, ty_generics, mut where_clause) = self.impl_generics(false); + let (impl_generics, mut where_clause) = self.impl_generics(false); if scalar.is_generic() { where_clause = Some(where_clause.unwrap_or_else(|| parse_quote! { where })); where_clause @@ -105,7 +105,7 @@ impl Definition { quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty#ty_generics #where_clause + impl#impl_generics ::juniper::GraphQLSubscriptionValue<#scalar> for #ty #where_clause { fn resolve_field_into_stream< 's, 'i, 'fi, 'args, 'e, 'ref_e, 'res, 'f, diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index ec8734d05..ae2be4db4 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -87,7 +87,8 @@ fn expand_on_trait( let context = attr .context .map(SpanContainer::into_inner) - .or_else(|| variants.iter().find_map(|v| v.context.as_ref()).cloned()); + .or_else(|| variants.iter().find_map(|v| v.context.as_ref()).cloned()) + .unwrap_or_else(|| parse_quote! { () }); let generated_code = Definition { name, diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index bcd6e2a0e..1c1bc5c1d 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -83,7 +83,10 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { ty: parse_quote! { #enum_ident }, is_trait_object: false, description: attr.description.map(SpanContainer::into_inner), - context: attr.context.map(SpanContainer::into_inner), + context: attr + .context + .map(SpanContainer::into_inner) + .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics, variants, @@ -210,7 +213,10 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { ty: parse_quote! { #struct_ident }, is_trait_object: false, description: attr.description.map(SpanContainer::into_inner), - context: attr.context.map(SpanContainer::into_inner), + context: attr + .context + .map(SpanContainer::into_inner) + .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), generics: ast.generics, variants, diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 26b7fe430..c882c24ba 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -288,12 +288,10 @@ struct Definition { /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with /// for this [GraphQL union][1]. /// - /// If [`None`] then generated code will use unit type `()` as [`Context`]. - /// /// [`Context`]: juniper::Context /// [`GraphQLType`]: juniper::GraphQLType /// [1]: https://spec.graphql.org/June2018/#sec-Unions - context: Option, + context: syn::Type, /// Rust type of [`ScalarValue`] to generate [`GraphQLType`] implementation /// with for this [GraphQL union][1]. @@ -511,7 +509,7 @@ impl Definition { #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let context = self.context.clone().unwrap_or_else(|| parse_quote! { () }); + let context = &self.context; let (impl_generics, ty_full, where_clause) = self.impl_generics(false); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index db5389b98..133ea98dd 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -336,7 +336,6 @@ impl Query { true } } - ``` ## Customization (Documentation, Renaming, ...) From 210ed932291c3efdeb914c6507617575b34f2316 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 4 Aug 2021 19:03:25 +0300 Subject: [PATCH 24/39] Integration tests for objects, vol.1 --- .../juniper_tests/src/codegen/impl_object.rs | 137 -- .../src/codegen/interface_attr.rs | 18 +- .../juniper_tests/src/codegen/mod.rs | 1 - .../juniper_tests/src/codegen/object_attr.rs | 1311 ++++++++++++++++- juniper_codegen/src/common/parse/mod.rs | 9 +- 5 files changed, 1315 insertions(+), 161 deletions(-) delete mode 100644 integration_tests/juniper_tests/src/codegen/impl_object.rs diff --git a/integration_tests/juniper_tests/src/codegen/impl_object.rs b/integration_tests/juniper_tests/src/codegen/impl_object.rs deleted file mode 100644 index 474d7cc96..000000000 --- a/integration_tests/juniper_tests/src/codegen/impl_object.rs +++ /dev/null @@ -1,137 +0,0 @@ -use juniper::{ - execute, graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, Object, - RootNode, Value, Variables, -}; - -pub struct MyObject; - -#[graphql_object] -impl MyObject { - fn test(&self, #[graphql(name = "test")] arg: String) -> String { - arg - } -} - -#[tokio::test] -async fn check_argument_rename() { - let doc = format!( - r#" - {{ - __type(name: "{}") {{ - name, - description, - fields {{ - name - description - }} - }} - }} - "#, - "MyObject" - ); - - run_type_info_query(&doc, |(_, values)| { - assert_eq!( - *values, - vec![Value::object( - vec![ - ("name", Value::scalar("test")), - ("description", Value::null()), - ] - .into_iter() - .collect(), - )] - ); - }) - .await; -} - -async fn run_type_info_query(doc: &str, f: F) -where - F: Fn((&Object, &Vec)) -> (), -{ - let schema = RootNode::new( - MyObject, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - let (result, errs) = execute(doc, None, &schema, &Variables::new(), &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 fields = type_info - .get_field_value("fields") - .expect("fields field missing") - .as_list_value() - .expect("fields not a list"); - - f((type_info, fields)); -} - -mod fallible { - use juniper::{graphql_object, FieldError}; - - struct Obj; - - #[graphql_object] - impl Obj { - fn test(&self, arg: String) -> Result { - Ok(arg) - } - } -} - -mod raw_argument { - use juniper::{ - graphql_object, graphql_value, EmptyMutation, EmptySubscription, RootNode, Variables, - }; - - struct Obj; - - #[graphql_object] - impl Obj { - fn test(&self, #[graphql(desc = "The only argument")] r#arg: String) -> String { - r#arg - } - } - - #[tokio::test] - async fn named_correctly() { - let doc = r#"{ - __type(name: "Obj") { - fields { - args { - name - } - } - } - }"#; - - let schema = RootNode::new( - Obj, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - assert_eq!( - juniper::execute(&doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{"args": [{"name": "arg"}]}]}}), - vec![], - )), - ); - } -} diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 64e590336..03267926f 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1725,6 +1725,7 @@ mod generic { ); } } + #[tokio::test] async fn dyn_resolves_info_field() { const DOC: &str = r#"{ @@ -1969,6 +1970,7 @@ mod generic_async { ); } } + #[tokio::test] async fn dyn_resolves_info_field() { const DOC: &str = r#"{ @@ -2213,6 +2215,7 @@ mod generic_lifetime_async { ); } } + #[tokio::test] async fn dyn_resolves_info_field() { const DOC: &str = r#"{ @@ -2408,7 +2411,7 @@ mod argument { assert_eq!( execute(&doc, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": { "fields": [{"args": [{"description": None}]}]}}), + graphql_value!({"__type": {"fields": [{"args": [{"description": None}]}]}}), vec![], )), ); @@ -2436,7 +2439,7 @@ mod argument { assert_eq!( execute(&doc, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": { "fields": [{"args": [{"defaultValue": None}]}]}}), + graphql_value!({"__type": {"fields": [{"args": [{"defaultValue": None}]}]}}), vec![], )), ); @@ -2591,7 +2594,8 @@ mod description_from_doc_comment { execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( graphql_value!({"__type": { - "description": "Rust docs.", "fields": [{"description": "Rust `id` docs."}], + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs."}], }}), vec![], )), @@ -2600,8 +2604,6 @@ mod description_from_doc_comment { } mod deprecation_from_attr { - #![allow(deprecated)] - use super::*; #[graphql_interface(for = Human)] @@ -2675,7 +2677,7 @@ mod deprecation_from_attr { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![],)), + Ok((graphql_value!({"character": {"a": "a", "b": "b"}}), vec![])), ); } @@ -2733,8 +2735,6 @@ mod deprecation_from_attr { } mod explicit_name_description_and_deprecation { - #![allow(deprecated)] - use super::*; /// Rust docs. @@ -2745,7 +2745,7 @@ mod explicit_name_description_and_deprecation { #[deprecated(note = "Should be omitted.")] fn id( &self, - #[graphql(name = "myName", desc = "My argument.", default)] n: Option, + #[graphql(name = "myName", desc = "My argument.")] n: Option, ) -> &str; #[graphql(deprecated)] diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 88a726665..9d07b3b34 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -3,7 +3,6 @@ mod derive_input_object; mod derive_object; mod derive_object_with_raw_idents; mod derive_scalar; -mod impl_object; mod impl_scalar; mod interface_attr; mod object_attr; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 8815aaf88..407b69937 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -1,21 +1,1310 @@ -use juniper::graphql_object; +//! Tests for `#[graphql_object]` macro. -mod two_objects_on_generic { +use juniper::{ + execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, + Executor, FieldError, GraphQLType, IntoFieldError, RootNode, ScalarValue, Variables, +}; + +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(), + ) +} + +mod trivial { + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + fn id() -> &'static str { + "human-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + const DOC: &str = r#"{ + human { + id + } + }"#; + + #[tokio::test] + async fn resolves() { + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_object() { + const DOC: &str = r#"{ + __type(name: "Human") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +mod trivial_async { + use futures::future; + + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + async fn id() -> &'static str { + future::ready("human-32").await + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + const DOC: &str = r#"{ + human { + id + } + }"#; + + #[tokio::test] + async fn resolves() { + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_object() { + const DOC: &str = r#"{ + __type(name: "Human") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +mod raw_field { + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + fn r#my_id() -> &'static str { + "human-32" + } + + async fn r#async() -> &'static str { + "async-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + const DOC: &str = r#"{ + human { + myId + async + } + }"#; + + #[tokio::test] + async fn resolves() { + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"myId": "human-32", "async": "async-32"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_correct_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + kind + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Human", + "kind": "OBJECT", + "fields": [{"name": "myId"}, {"name": "async"}], + }}), + 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"})) + } + } + + struct Human { + id: String, + } + + #[graphql_object] + impl Human { + fn id(&self) -> Result<&str, CustomError> { + Ok(&self.id) + } + } + + #[derive(Clone, Copy)] + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { + id: "human-32".to_string(), + } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_correct_graphql_type() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Human", + "kind": "OBJECT", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }] + }}), + vec![], + )), + ); + } +} + +mod generic { + use super::*; + + struct Human { + id: A, + _home_planet: B, + } + + #[graphql_object] + impl Human { + fn id(&self) -> i32 { + self.id + } + } + + #[graphql_object(name = "HumanString")] + impl Human { + fn id(&self) -> &str { + self.id.as_str() + } + } + + #[derive(Clone, Copy)] + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human(&self) -> Human { + Human { + id: 32, + _home_planet: (), + } + } + + fn human_string(&self) -> Human { + Human { + id: "human-32".to_owned(), + _home_planet: (), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": 32}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_human_string() { + const DOC: &str = r#"{ + humanString { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"humanString": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + +mod generic_async { + use super::*; + + struct Human { + id: A, + _home_planet: B, + } + + #[graphql_object] + impl Human { + async fn id(&self) -> i32 { + self.id + } + } + + #[graphql_object(name = "HumanString")] + impl Human { + async fn id(&self) -> &str { + self.id.as_str() + } + } + + #[derive(Clone, Copy)] + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human(&self) -> Human { + Human { + id: 32, + _home_planet: (), + } + } + + fn human_string(&self) -> Human { + Human { + id: "human-32".to_owned(), + _home_planet: (), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": 32}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_human_string() { + const DOC: &str = r#"{ + humanString { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"humanString": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + +mod generic_lifetime_async { + use super::*; + + struct Human<'p, A = ()> { + id: A, + home_planet: &'p str, + } + + #[graphql_object] + impl<'p> Human<'p, i32> { + async fn id(&self) -> i32 { + self.id + } + + async fn planet(&self) -> &str { + self.home_planet + } + } + + #[graphql_object(name = "HumanString")] + impl<'id, 'p> Human<'p, &'id str> { + async fn id(&self) -> &str { + self.id + } + + async fn planet(&self) -> &str { + self.home_planet + } + } + + #[derive(Clone)] + struct QueryRoot(String); + + #[graphql_object] + impl QueryRoot { + fn human(&self) -> Human<'static, i32> { + Human { + id: 32, + home_planet: "earth", + } + } + + fn human_string(&self) -> Human<'_, &str> { + Human { + id: self.0.as_str(), + home_planet: self.0.as_str(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + human { + id + planet + } + }"#; + + let schema = schema(QueryRoot("mars".to_owned())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"id": 32, "planet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human_string() { + const DOC: &str = r#"{ + humanString { + id + planet + } + }"#; + + let schema = schema(QueryRoot("mars".to_owned())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"humanString": {"id": "mars", "planet": "mars"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot("mars".to_owned())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + +mod argument { use super::*; - struct Generic(T); + struct Human; - #[graphql_object(name = "BooleanGeneric")] - impl Generic { - fn boolean(&self) -> bool { - self.0 + #[graphql_object] + impl Human { + fn id(arg: String) -> String { + arg + } + + fn home_planet(r#raw_arg: String) -> String { + r#raw_arg } } - #[graphql_object(name = "IntGeneric")] - impl Generic { - fn num(&self) -> i32 { - self.0 + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human } } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id(arg: "human-32") + homePlanet(rawArg: "earth") + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_correct_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": [{"name": "arg"}]}, + {"name": "homePlanet", "args": [{"name": "rawArg"}]}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"description": None}]}, + {"args": [{"description": None}]}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_no_defaults() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + args { + defaultValue + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"defaultValue": None}]}, + {"args": [{"defaultValue": None}]}, + ]}}), + vec![], + )), + ); + } +} + +mod default_argument { + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + fn id( + #[graphql(default)] arg1: i32, + #[graphql(default = "second".to_string())] arg2: String, + #[graphql(default = true)] r#arg3: bool, + ) -> String { + format!("{}|{}&{}", arg1, arg2, r#arg3) + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves() { + let schema = schema(QueryRoot); + + for (input, expected) in &[ + ("{ human { id } }", "0|second&true"), + (r#"{ human { id(arg1: 1) } }"#, "1|second&true"), + (r#"{ human { id(arg2: "") } }"#, "0|&true"), + (r#"{ human { id(arg1: 2, arg2: "") } }"#, "2|&true"), + ( + r#"{ human { id(arg1: 1, arg2: "", arg3: false) } }"#, + "1|&false", + ), + ] { + let expected: &str = *expected; + + assert_eq!( + execute(*input, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": expected}}), vec![])), + ); + } + } + + #[tokio::test] + async fn has_defaults() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + args { + name + defaultValue + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"args": [ + {"name": "arg1", "defaultValue": "0"}, + {"name": "arg2", "defaultValue": r#""second""#}, + {"name": "arg3", "defaultValue": "true"}, + ]}]}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + struct Human; + + /// Rust docs. + #[graphql_object] + impl Human { + /// Rust `id` docs. + fn id() -> &'static str { + "human-32" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + fields { + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + fn id() -> &'static str { + "human-32" + } + + #[deprecated] + fn a(&self) -> &str { + "a" + } + + #[deprecated(note = "Use `id`.")] + fn b(&self) -> &str { + "b" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_fields() { + const DOC: &str = r#"{ + human { + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"a": "a", "b": "b"}}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": None}, + {"name": "a", "deprecationReason": None}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + struct Human; + + /// Rust docs. + #[graphql_object(name = "MyHuman", desc = "My human.")] + impl Human { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + fn id( + #[graphql(name = "myName", desc = "My argument.", default)] _n: String, + ) -> &'static str { + "human-32" + } + + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + fn a(&self) -> &str { + "a" + } + + fn b(&self) -> &str { + "b" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + myId + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"myId": "human-32", "a": "a", "b": "b"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + name + fields(includeDeprecated: true) { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyHuman", + "fields": [ + {"name": "myId", "args": [{"name": "myName"}]}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + description + fields(includeDeprecated: true) { + name + description + args { + description + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My human.", + "fields": [{ + "name": "myId", + "description": "My human ID.", + "args": [{"description": "My argument."}], + }, { + "name": "a", + "description": None, + "args": [], + }, { + "name": "b", + "description": None, + "args": [], + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": None, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": None, + }], + }}), + vec![], + )), + ); + } +} + +mod executor { + use juniper::LookAheadMethods as _; + + use super::*; + + struct Human; + + #[graphql_object(scalar = S: ScalarValue)] + impl Human { + async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> &'e str + where + S: ScalarValue, + { + executor.look_ahead().field_name() + } + + fn info<'b, S>(&'b self, #[graphql(executor)] _another: &Executor<'_, '_, (), S>) -> &'b str + where + S: ScalarValue, + { + "no info" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + info + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"id": "id", "info": "no info"}}), + vec![], + )), + ); + } +} + +mod ignored_method { + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + fn id() -> &'static str { + "human-32" + } + + #[allow(dead_code)] + #[graphql(ignore)] + fn planet() -> &'static str { + "earth" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![],)), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } } diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 4681de1a8..cae19a015 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -111,8 +111,9 @@ pub(crate) trait TypeExt { /// with the given `func`tion. fn lifetimes_iter_mut(&mut self, func: &mut F); - /// Anonymizes all the lifetime parameters of this [`syn::Type`] making it - /// suitable for using in contexts with inferring. + /// Anonymizes all the lifetime parameters of this [`syn::Type`] (except + /// the `'static` ones), making it suitable for using in contexts with + /// inferring. fn lifetimes_anonymized(&mut self); /// Returns the topmost [`syn::Ident`] of this [`syn::TypePath`], if any. @@ -216,7 +217,9 @@ impl TypeExt for syn::Type { fn lifetimes_anonymized(&mut self) { self.lifetimes_iter_mut(&mut |lt| { - lt.ident = syn::Ident::new("_", Span::call_site()); + if lt.ident != "_" && lt.ident != "static" { + lt.ident = syn::Ident::new("_", Span::call_site()); + } }); } From 26c1fa94d13a0524177ae1f850aa2f43119e733d Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 5 Aug 2021 22:00:45 +0300 Subject: [PATCH 25/39] Integration tests for objects, vol.2 --- .../src/codegen/interface_attr.rs | 9 +- .../juniper_tests/src/codegen/object_attr.rs | 288 +++++++++++++++++- juniper/src/macros/tests/impl_object.rs | 281 ----------------- juniper/src/macros/tests/mod.rs | 1 - 4 files changed, 290 insertions(+), 289 deletions(-) delete mode 100644 juniper/src/macros/tests/impl_object.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 03267926f..1dab9c774 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2743,10 +2743,7 @@ mod explicit_name_description_and_deprecation { /// Rust `id` docs. #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] #[deprecated(note = "Should be omitted.")] - fn id( - &self, - #[graphql(name = "myName", desc = "My argument.")] n: Option, - ) -> &str; + fn id(&self, #[graphql(name = "myName", desc = "My argument.")] n: Option) -> &str; #[graphql(deprecated)] #[deprecated(note = "Should be omitted.")] @@ -3818,7 +3815,7 @@ mod bounded_generic_scalar { mod explicit_custom_context { use super::*; - pub struct CustomContext; + struct CustomContext; impl juniper::Context for CustomContext {} @@ -4085,7 +4082,7 @@ mod explicit_custom_context { mod inferred_custom_context_from_field { use super::*; - pub struct CustomContext(String); + struct CustomContext(String); impl juniper::Context for CustomContext {} diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 407b69937..4f8e6a0ff 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -653,6 +653,160 @@ mod generic_lifetime_async { } } +mod nested_generic_lifetime_async { + use super::*; + + struct Droid<'p, A = ()> { + id: A, + primary_function: &'p str, + } + + #[graphql_object] + impl<'p> Droid<'p, i32> { + async fn id(&self) -> i32 { + self.id + } + + async fn primary_function(&self) -> &str { + self.primary_function + } + } + + #[graphql_object(name = "DroidString")] + impl<'id, 'p> Droid<'p, &'id str> { + async fn id(&self) -> &str { + self.id + } + + async fn primary_function(&self) -> &str { + self.primary_function + } + } + + struct Human<'p, A = ()> { + id: A, + home_planet: &'p str, + } + + #[graphql_object] + impl<'p> Human<'p, i32> { + async fn id(&self) -> i32 { + self.id + } + + async fn planet(&self) -> &str { + self.home_planet + } + + async fn droid(&self) -> Droid<'_, i32> { + Droid { + id: self.id, + primary_function: "run", + } + } + } + + #[graphql_object(name = "HumanString")] + impl<'id, 'p> Human<'p, &'id str> { + async fn id(&self) -> &str { + self.id + } + + async fn planet(&self) -> &str { + self.home_planet + } + + async fn droid(&self) -> Droid<'_, &str> { + Droid { + id: "none", + primary_function: self.home_planet, + } + } + } + + #[derive(Clone)] + struct QueryRoot(String); + + #[graphql_object] + impl QueryRoot { + fn human(&self) -> Human<'static, i32> { + Human { + id: 32, + home_planet: "earth", + } + } + + fn human_string(&self) -> Human<'_, &str> { + Human { + id: self.0.as_str(), + home_planet: self.0.as_str(), + } + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"{ + human { + id + planet + droid { + id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot("mars".to_owned())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": { + "id": 32, + "planet": "earth", + "droid": { + "id": 32, + "primaryFunction": "run", + }, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_human_string() { + const DOC: &str = r#"{ + humanString { + id + planet + droid { + id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot("mars".to_owned())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"humanString": { + "id": "mars", + "planet": "mars", + "droid": { + "id": "none", + "primaryFunction": "mars", + }, + }}), + vec![], + )), + ); + } +} + mod argument { use super::*; @@ -1190,6 +1344,126 @@ mod explicit_name_description_and_deprecation { } } +mod explicit_custom_context { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + struct Human; + + #[graphql_object(context = CustomContext)] + impl Human { + async fn id<'c>(&self, context: &'c CustomContext) -> &'c str { + context.0.as_str() + } + + async fn info(_ctx: &()) -> &'static str { + "human being" + } + + fn more(#[graphql(context)] custom: &CustomContext) -> &str { + custom.0.as_str() + } + } + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + info + more + } + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"human": { + "id": "ctx!", + "info": "human being", + "more": "ctx!", + }}), + vec![], + )), + ); + } +} + +mod inferred_custom_context_from_field { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + struct Human; + + #[graphql_object] + impl Human { + async fn id<'c>(&self, context: &'c CustomContext) -> &'c str { + context.0.as_str() + } + + async fn info(_ctx: &()) -> &'static str { + "human being" + } + + fn more(#[graphql(context)] custom: &CustomContext) -> &str { + custom.0.as_str() + } + } + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + info + more + } + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"human": { + "id": "ctx!", + "info": "human being", + "more": "ctx!", + }}), + vec![], + )), + ); + } +} + mod executor { use juniper::LookAheadMethods as _; @@ -1212,6 +1486,13 @@ mod executor { { "no info" } + + fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> &'e str + where + S: ScalarValue, + { + "no info" + } } struct QueryRoot; @@ -1229,6 +1510,7 @@ mod executor { human { id info + info2 } }"#; @@ -1237,7 +1519,11 @@ mod executor { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"human": {"id": "id", "info": "no info"}}), + graphql_value!({"human": { + "id": "id", + "info": "no info", + "info2": "no info", + }}), vec![], )), ); diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs deleted file mode 100644 index c62e45710..000000000 --- a/juniper/src/macros/tests/impl_object.rs +++ /dev/null @@ -1,281 +0,0 @@ -use crate::{ - graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, - RootNode, -}; - -use super::util; - -#[derive(Default)] -struct Context { - flag1: bool, -} - -impl crate::Context for Context {} - -struct WithLifetime<'a> { - value: &'a str, -} - -#[graphql_object(context = Context)] -impl<'a> WithLifetime<'a> { - fn value(&self) -> &str { - self.value - } - - async fn value_async(&self) -> &str { - self.value - } -} - -struct WithContext; - -#[graphql_object(context = Context)] -impl WithContext { - fn ctx(ctx: &Context) -> bool { - ctx.flag1 - } -} - -#[derive(Default)] -struct Query { - b: bool, -} - -#[graphql_object( - name = "Query", - scalar = DefaultScalarValue, - context = Context, -)] -/// Query Description. -impl Query { - #[graphql(description = "With Self Description")] - fn with_self(&self) -> bool { - self.b - } - - fn independent() -> i32 { - 100 - } - - fn with_executor(_executor: &Executor<'_, '_, Context>) -> bool { - true - } - - fn with_executor_and_self(&self, _executor: &Executor<'_, '_, Context>) -> bool { - true - } - - fn with_context(_context: &Context) -> bool { - true - } - - fn with_context_and_self(&self, _context: &Context) -> bool { - true - } - - #[graphql(name = "renamed")] - fn has_custom_name() -> bool { - true - } - - #[graphql(description = "attr")] - fn has_description_attr() -> bool { - true - } - - /// Doc description - fn has_description_doc_comment() -> bool { - true - } - - fn has_argument(arg1: bool) -> bool { - arg1 - } - - fn default_argument(#[graphql(default = true)] default_arg: bool) -> bool { - default_arg - } - - fn arg_with_description(#[graphql(description = "my argument description")] arg: bool) -> bool { - arg - } - - fn with_context_child(&self) -> WithContext { - WithContext - } - - fn with_lifetime_child(&self) -> WithLifetime<'static> { - WithLifetime { value: "blub" } - } - - fn with_mut_arg(mut arg: bool) -> bool { - if arg { - arg = !arg; - } - arg - } -} - -#[derive(Default)] -struct Mutation; - -#[graphql_object(context = Context)] -impl Mutation { - fn empty() -> bool { - true - } -} - -#[derive(Default)] -struct Subscription; - -#[graphql_object(context = Context)] -impl Subscription { - fn empty() -> bool { - true - } -} - -#[tokio::test] -async fn object_introspect() { - let res = util::run_info_query::("Query").await; - assert_eq!( - res, - graphql_value!({ - "name": "Query", - "description": "Query Description.", - "fields": [{ - "name": "withSelf", - "description": "With Self Description", - "args": [], - }, { - "name": "independent", - "description": None, - "args": [], - }, { - "name": "withExecutor", - "description": None, - "args": [], - }, { - "name": "withExecutorAndSelf", - "description": None, - "args": [], - }, { - "name": "withContext", - "description": None, - "args": [], - }, { - "name": "withContextAndSelf", - "description": None, - "args": [], - }, { - "name": "renamed", - "description": None, - "args": [], - }, { - "name": "hasDescriptionAttr", - "description": "attr", - "args": [], - }, { - "name": "hasDescriptionDocComment", - "description": "Doc description", - "args": [], - }, { - "name": "hasArgument", - "description": None, - "args": [{ - "name": "arg1", - "description": None, - "type": {"name": None}, - }], - }, { - "name": "defaultArgument", - "description": None, - "args": [{ - "name": "defaultArg", - "description": None, - "type": {"name": "Boolean"}, - }], - }, { - "name": "argWithDescription", - "description": None, - "args": [{ - "name": "arg", - "description": "my argument description", - "type": {"name": None}, - }], - }, { - "name": "withContextChild", - "description": None, - "args": [], - }, { - "name": "withLifetimeChild", - "description": None, - "args": [], - }, { - "name": "withMutArg", - "description": None, - "args": [{ - "name": "arg", - "description": None, - "type": {"name": None}, - }], - }], - }) - ); -} - -#[tokio::test] -async fn object_query() { - let doc = r#" - query { - withSelf - independent - withExecutor - withExecutorAndSelf - withContext - withContextAndSelf - renamed - hasArgument(arg1: true) - defaultArgument - argWithDescription(arg: true) - withContextChild { - ctx - } - withLifetimeChild { - value - valueAsync - } - withMutArg(arg: true) - } - "#; - let schema = RootNode::new( - Query { b: true }, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); - let vars = std::collections::HashMap::new(); - - let (result, errs) = crate::execute(doc, None, &schema, &vars, &Context { flag1: true }) - .await - .expect("Execution failed"); - assert_eq!(errs, []); - assert_eq!( - result, - graphql_value!({ - "withSelf": true, - "independent": 100, - "withExecutor": true, - "withExecutorAndSelf": true, - "withContext": true, - "withContextAndSelf": true, - "renamed": true, - "hasArgument": true, - "defaultArgument": true, - "argWithDescription": true, - "withContextChild": {"ctx": true}, - "withLifetimeChild": {"value": "blub", "valueAsync": "blub"}, - "withMutArg": false, - }) - ); -} diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index 795133254..b90ceee2f 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -1,6 +1,5 @@ mod args; mod field; -mod impl_object; mod impl_subscription; mod interface; mod object; From 1e9d1a784691ebf846b0d670fca708173ed826a1 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 6 Aug 2021 14:03:35 +0300 Subject: [PATCH 26/39] Integration tests for objects, vol.3 --- .../src/codegen/derive_object.rs | 345 ------- .../juniper_tests/src/codegen/mod.rs | 1 + .../juniper_tests/src/codegen/object_attr.rs | 404 ++++++-- .../src/codegen/object_derive.rs | 934 ++++++++++++++++++ .../juniper_tests/src/codegen/union_derive.rs | 52 +- 5 files changed, 1297 insertions(+), 439 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/object_derive.rs diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index a5015449d..3b2512201 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -1,65 +1,8 @@ -use fnv::FnvHashMap; use juniper::{ execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject, GraphQLType, Object, Registry, RootNode, Value, Variables, }; -#[derive(GraphQLObject, Debug, PartialEq)] -#[graphql(name = "MyObj", description = "obj descr")] -struct Obj { - regular_field: bool, - #[graphql( - name = "renamedField", - description = "descr", - deprecated = "field deprecation" - )] - c: i32, -} - -#[derive(GraphQLObject, Debug, PartialEq)] -struct Nested { - obj: Obj, -} - -/// Object comment. -#[derive(GraphQLObject, Debug, PartialEq)] -struct DocComment { - /// Field comment. - regular_field: bool, -} - -/// Doc 1.\ -/// Doc 2. -/// -/// Doc 4. -#[derive(GraphQLObject, Debug, PartialEq)] -struct MultiDocComment { - /// Field 1. - /// Field 2. - regular_field: bool, -} - -/// This is not used as the description. -#[derive(GraphQLObject, Debug, PartialEq)] -#[graphql(description = "obj override")] -struct OverrideDocComment { - /// This is not used as the description. - #[graphql(description = "field override")] - regular_field: bool, -} - -#[derive(GraphQLObject, Debug, PartialEq)] -struct WithLifetime<'a> { - regular_field: &'a i32, -} - -#[derive(GraphQLObject, Debug, PartialEq)] -struct SkippedFieldObj { - regular_field: bool, - #[graphql(skip)] - skipped: i32, -} - #[derive(GraphQLObject, Debug, PartialEq)] #[graphql(rename_all = "none")] struct NoRenameObj { @@ -67,60 +10,10 @@ struct NoRenameObj { another_field: i32, } -struct Context; -impl juniper::Context for Context {} - -#[derive(GraphQLObject, Debug)] -#[graphql(context = Context)] -struct WithCustomContext { - a: bool, -} - struct Query; #[graphql_object] impl Query { - fn obj() -> Obj { - Obj { - regular_field: true, - c: 22, - } - } - - fn nested() -> Nested { - Nested { - obj: Obj { - regular_field: false, - c: 333, - }, - } - } - - fn doc() -> DocComment { - DocComment { - regular_field: true, - } - } - - fn multi_doc() -> MultiDocComment { - MultiDocComment { - regular_field: true, - } - } - - fn override_doc() -> OverrideDocComment { - OverrideDocComment { - regular_field: true, - } - } - - fn skipped_field_obj() -> SkippedFieldObj { - SkippedFieldObj { - regular_field: false, - skipped: 42, - } - } - fn no_rename_obj() -> NoRenameObj { NoRenameObj { one_field: true, @@ -133,13 +26,6 @@ struct NoRenameQuery; #[graphql_object(rename_all = "none")] impl NoRenameQuery { - fn obj() -> Obj { - Obj { - regular_field: false, - c: 22, - } - } - fn no_rename_obj() -> NoRenameObj { NoRenameObj { one_field: true, @@ -148,160 +34,6 @@ impl NoRenameQuery { } } -#[tokio::test] -async fn test_doc_comment_simple() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); - let meta = DocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("Object comment.")); - - check_descriptions( - "DocComment", - &graphql_value!("Object comment."), - "regularField", - &graphql_value!("Field comment."), - ) - .await; -} - -#[tokio::test] -async fn test_multi_doc_comment() { - 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.")); - - check_descriptions( - "MultiDocComment", - &graphql_value!("Doc 1. Doc 2.\n\nDoc 4."), - "regularField", - &graphql_value!("Field 1.\nField 2."), - ) - .await; -} - -#[tokio::test] -async fn test_doc_comment_override() { - let mut registry: Registry = Registry::new(FnvHashMap::default()); - let meta = OverrideDocComment::meta(&(), &mut registry); - assert_eq!(meta.description(), Some("obj override")); - - check_descriptions( - "OverrideDocComment", - &graphql_value!("obj override"), - "regularField", - &graphql_value!("field override"), - ) - .await; -} - -#[tokio::test] -async fn test_derived_object() { - assert_eq!( - >::name(&()), - Some("MyObj") - ); - - // Verify meta info. - let mut registry: Registry = Registry::new(FnvHashMap::default()); - let meta = Obj::meta(&(), &mut registry); - - assert_eq!(meta.name(), Some("MyObj")); - assert_eq!(meta.description(), Some("obj descr")); - - let doc = r#"{ - obj { - regularField - renamedField - } - }"#; - - let schema = RootNode::new( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - assert_eq!( - execute(doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({ - "obj": { - "regularField": true, - "renamedField": 22, - }, - }), - vec![], - )), - ); -} - -#[tokio::test] -#[should_panic] -async fn test_cannot_query_skipped_field() { - let doc = r#"{ - skippedFieldObj { - skippedField - } - }"#; - let schema = RootNode::new( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - execute(doc, None, &schema, &Variables::new(), &()) - .await - .unwrap(); -} - -#[tokio::test] -async fn test_skipped_field_siblings_unaffected() { - let doc = r#"{ - skippedFieldObj { - regularField - } - }"#; - let schema = RootNode::new( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - execute(doc, None, &schema, &Variables::new(), &()) - .await - .unwrap(); -} - -#[tokio::test] -async fn test_derived_object_nested() { - let doc = r#"{ - nested { - obj { - regularField - renamedField - } - } - }"#; - - let schema = RootNode::new( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - assert_eq!( - execute(doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({ - "nested": { - "obj": { - "regularField": false, - "renamedField": 333, - }, - }, - }), - vec![], - )), - ); -} - #[tokio::test] async fn test_no_rename_root() { let doc = r#"{ @@ -309,9 +41,6 @@ async fn test_no_rename_root() { one_field another_field } - obj { - regularField - } }"#; let schema = RootNode::new( @@ -328,9 +57,6 @@ async fn test_no_rename_root() { "one_field": true, "another_field": 146, }, - "obj": { - "regularField": false, - }, }), vec![], )), @@ -365,74 +91,3 @@ async fn test_no_rename_obj() { )), ); } - -async fn check_descriptions( - object_name: &str, - object_description: &Value, - field_name: &str, - field_value: &Value, -) { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - description - fields {{ - name - description - }} - }} - }}"#, - object_name, - ); - let _result = run_type_info_query(&doc, |(type_info, values)| { - assert_eq!( - type_info.get_field_value("name"), - Some(&graphql_value!(object_name)) - ); - assert_eq!( - type_info.get_field_value("description"), - Some(object_description) - ); - assert!(values.contains(&graphql_value!({ - "name": (field_name.clone()), - "description": (field_value.clone()), - }))); - }) - .await; -} - -async fn run_type_info_query(doc: &str, f: F) -where - F: Fn((&Object, &Vec)) -> (), -{ - let schema = RootNode::new( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - let (result, errs) = execute(doc, None, &schema, &Variables::new(), &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 fields = type_info - .get_field_value("fields") - .expect("fields field missing") - .as_list_value() - .expect("fields not a list"); - - f((type_info, fields)); -} diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 9d07b3b34..1a555cf2b 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -6,6 +6,7 @@ mod derive_scalar; mod impl_scalar; mod interface_attr; mod object_attr; +mod object_derive; mod scalar_value_transparent; mod union_attr; mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 4f8e6a0ff..f66e8e7d2 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -51,14 +51,14 @@ mod trivial { } } - const DOC: &str = r#"{ - human { - id - } - }"#; - #[tokio::test] async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + let schema = schema(QueryRoot); assert_eq!( @@ -139,14 +139,14 @@ mod trivial_async { } } - const DOC: &str = r#"{ - human { - id - } - }"#; - #[tokio::test] async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + let schema = schema(QueryRoot); assert_eq!( @@ -204,7 +204,7 @@ mod trivial_async { } } -mod raw_field { +mod raw_method { use super::*; struct Human; @@ -229,15 +229,15 @@ mod raw_field { } } - const DOC: &str = r#"{ - human { - myId - async - } - }"#; - #[tokio::test] async fn resolves() { + const DOC: &str = r#"{ + human { + myId + async + } + }"#; + let schema = schema(QueryRoot); assert_eq!( @@ -277,6 +277,71 @@ mod raw_field { } } +mod ignored_method { + use super::*; + + struct Human; + + #[graphql_object] + impl Human { + fn id() -> &'static str { + "human-32" + } + + #[allow(dead_code)] + #[graphql(ignore)] + fn planet() -> &'static str { + "earth" + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + mod fallible_field { use super::*; @@ -400,7 +465,7 @@ mod generic { fn human_string(&self) -> Human { Human { - id: "human-32".to_owned(), + id: "human-32".into(), _home_planet: (), } } @@ -491,7 +556,7 @@ mod generic_async { fn human_string(&self) -> Human { Human { - id: "human-32".to_owned(), + id: "human-32".into(), _home_planet: (), } } @@ -605,7 +670,7 @@ mod generic_lifetime_async { } }"#; - let schema = schema(QueryRoot("mars".to_owned())); + let schema = schema(QueryRoot("mars".into())); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -625,7 +690,7 @@ mod generic_lifetime_async { } }"#; - let schema = schema(QueryRoot("mars".to_owned())); + let schema = schema(QueryRoot("mars".into())); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -644,7 +709,7 @@ mod generic_lifetime_async { } }"#; - let schema = schema(QueryRoot("mars".to_owned())); + let schema = schema(QueryRoot("mars".into())); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -757,7 +822,7 @@ mod nested_generic_lifetime_async { } }"#; - let schema = schema(QueryRoot("mars".to_owned())); + let schema = schema(QueryRoot("mars".into())); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -788,7 +853,7 @@ mod nested_generic_lifetime_async { } }"#; - let schema = schema(QueryRoot("mars".to_owned())); + let schema = schema(QueryRoot("mars".into())); assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, @@ -805,6 +870,28 @@ mod nested_generic_lifetime_async { )), ); } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + object, + ); + + let schema = schema(QueryRoot("mars".into())); + + let expected_name: &str = *object; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + ); + } + } } mod argument { @@ -1344,6 +1431,204 @@ mod explicit_name_description_and_deprecation { } } +mod explicit_scalar { + use super::*; + + struct Human; + + #[graphql_object(scalar = DefaultScalarValue)] + impl Human { + fn id(&self) -> &str { + "human-32" + } + + async fn home_planet() -> &'static str { + "earth" + } + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + homePlanet + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + struct Human; + + #[graphql_object(scalar = MyScalarValue)] + impl Human { + fn id() -> &'static str { + "human-32" + } + + async fn home_planet(&self) -> &str { + "earth" + } + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + homePlanet + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } +} + +mod explicit_generic_scalar { + use std::marker::PhantomData; + + use super::*; + + struct Human(PhantomData); + + #[graphql_object(scalar = S)] + impl Human { + fn id() -> &'static str { + "human-32" + } + + async fn another(&self, _executor: &Executor<'_, '_, (), S>) -> Human { + Human(PhantomData) + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human<__S>() -> Human<__S> { + Human(PhantomData) + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + another { + id + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": { + "id": "human-32", + "another": {"id": "human-32"}, + }}), + vec![], + )), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + struct Human; + + #[graphql_object(scalar = S: ScalarValue + Clone)] + impl Human { + fn id() -> &'static str { + "human-32" + } + + async fn another(&self, _executor: &Executor<'_, '_, (), S>) -> Human { + Human + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + another { + id + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": { + "id": "human-32", + "another": {"id": "human-32"}, + }}), + vec![], + )), + ); + } +} + mod explicit_custom_context { use super::*; @@ -1529,68 +1814,3 @@ mod executor { ); } } - -mod ignored_method { - use super::*; - - struct Human; - - #[graphql_object] - impl Human { - fn id() -> &'static str { - "human-32" - } - - #[allow(dead_code)] - #[graphql(ignore)] - fn planet() -> &'static str { - "earth" - } - } - - struct QueryRoot; - - #[graphql_object] - impl QueryRoot { - fn human() -> Human { - Human - } - } - - #[tokio::test] - async fn resolves() { - const DOC: &str = r#"{ - human { - id - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"human": {"id": "human-32"}}), vec![],)), - ); - } - - #[tokio::test] - async fn is_not_field() { - const DOC: &str = r#"{ - __type(name: "Human") { - fields { - name - } - } - }"#; - - let schema = schema(QueryRoot); - - assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{"name": "id"}]}}), - vec![], - )), - ); - } -} diff --git a/integration_tests/juniper_tests/src/codegen/object_derive.rs b/integration_tests/juniper_tests/src/codegen/object_derive.rs new file mode 100644 index 000000000..af1d48627 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/object_derive.rs @@ -0,0 +1,934 @@ +//! Tests for `#[derive(GraphQLObject)]` macro. + +use juniper::{ + execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, + GraphQLObject, GraphQLType, RootNode, ScalarValue, Variables, +}; + +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(), + ) +} + +mod trivial { + use super::*; + + #[derive(GraphQLObject)] + struct Human { + id: &'static str, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { id: "human-32" } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_object() { + const DOC: &str = r#"{ + __type(name: "Human") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +mod raw_field { + use super::*; + + #[derive(GraphQLObject)] + struct Human { + r#async: &'static str, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { + r#async: "human-32", + } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + async + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"async": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_correct_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + kind + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Human", + "kind": "OBJECT", + "fields": [{"name": "async"}], + }}), + vec![], + )), + ); + } +} + +mod ignored_field { + use super::*; + + #[derive(GraphQLObject)] + struct Human { + id: &'static str, + #[allow(dead_code)] + #[graphql(ignore)] + planet: &'static str, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { + id: "human-32", + planet: "earth", + } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + +mod generic { + use super::*; + + #[derive(GraphQLObject)] + struct Human { + id: &'static str, + #[graphql(ignore)] + _home_planet: B, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { + id: "human-32", + _home_planet: (), + } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + +mod generic_lifetime { + use super::*; + + #[derive(GraphQLObject)] + struct Human<'id, B: ?Sized = ()> { + id: &'id str, + #[graphql(ignore)] + _home_planet: B, + } + + struct QueryRoot(String); + + #[graphql_object] + impl QueryRoot { + fn human(&self) -> Human<'_, i32> { + Human { + id: self.0.as_str(), + _home_planet: 32, + } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot("mars".into())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "mars"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(QueryRoot("mars".into())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + +mod nested_generic_lifetime_async { + use super::*; + + #[derive(GraphQLObject)] + struct Droid<'p, A = ()> { + #[graphql(ignore)] + _id: A, + primary_function: &'p str, + } + + #[derive(GraphQLObject)] + struct Human<'d, A: Sync = ()> { + id: i32, + droid: Droid<'d, A>, + } + + struct QueryRoot(String); + + #[graphql_object] + impl QueryRoot { + fn human(&self) -> Human<'_, i8> { + Human { + id: 32, + droid: Droid { + _id: 12, + primary_function: self.0.as_str(), + }, + } + } + } + + #[tokio::test] + async fn resolves() { + const DOC: &str = r#"{ + human { + id + droid { + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot("mars".into())); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": { + "id": 32, + "droid": { + "primaryFunction": "mars", + }, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + for object in &["Human", "Droid"] { + let doc = format!( + r#"{{ + __type(name: "{}") {{ + name + }} + }}"#, + object, + ); + + let schema = schema(QueryRoot("mars".into())); + + let expected_name: &str = *object; + assert_eq!( + execute(&doc, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + ); + } + } +} + +mod description_from_doc_comment { + use super::*; + + /// Rust docs.\ + /// Here. + #[derive(GraphQLObject)] + struct Human { + /// Rust `id` docs. + /// Here. + id: String, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { + id: "human-32".into(), + } + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + fields { + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs. Here.", + "fields": [{"description": "Rust `id` docs.\nHere."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + #[derive(GraphQLObject)] + struct Human { + id: String, + #[deprecated] + a: &'static str, + #[deprecated(note = "Use `id`.")] + b: &'static str, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + #[allow(deprecated)] + fn human() -> Human { + Human { + id: "human-32".into(), + a: "a", + b: "b", + } + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_fields() { + const DOC: &str = r#"{ + human { + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"a": "a", "b": "b"}}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": None}, + {"name": "a", "deprecationReason": None}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + /// Rust docs. + #[derive(GraphQLObject)] + #[graphql(name = "MyHuman", desc = "My human.")] + struct Human { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + id: String, + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + a: &'static str, + b: &'static str, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + #[allow(deprecated)] + fn human() -> Human { + Human { + id: "human-32".into(), + a: "a", + b: "b", + } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + myId + a + b + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": {"myId": "human-32", "a": "a", "b": "b"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + name + fields(includeDeprecated: true) { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyHuman", + "fields": [ + {"name": "myId"}, + {"name": "a"}, + {"name": "b"}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + description + fields(includeDeprecated: true) { + name + description + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My human.", + "fields": [{ + "name": "myId", + "description": "My human ID.", + }, { + "name": "a", + "description": None, + }, { + "name": "b", + "description": None, + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": None, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": None, + }], + }}), + vec![], + )), + ); + } +} + +mod explicit_scalar { + use super::*; + + #[derive(GraphQLObject)] + #[graphql(scalar = DefaultScalarValue)] + struct Human { + id: &'static str, + } + + struct QueryRoot; + + #[graphql_object(scalar = DefaultScalarValue)] + impl QueryRoot { + fn human() -> Human { + Human { id: "human-32" } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[derive(GraphQLObject)] + #[graphql(scalar = MyScalarValue)] + struct Human { + id: &'static str, + } + + struct QueryRoot; + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn human() -> Human { + Human { id: "human-32" } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema_with_scalar::(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } +} + +mod explicit_generic_scalar { + use std::marker::PhantomData; + + use super::*; + + #[derive(GraphQLObject)] + #[graphql(scalar = S)] + struct Human { + id: &'static str, + #[graphql(ignore)] + _scalar: PhantomData, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human<__S: Clone>() -> Human<__S> { + Human { + id: "human-32", + _scalar: PhantomData, + } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + #[derive(GraphQLObject)] + #[graphql(scalar = S: ScalarValue + Clone)] + struct Human { + id: &'static str, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { id: "human-32" } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + ); + } +} + +mod explicit_custom_context { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + #[derive(GraphQLObject)] + #[graphql(context = CustomContext)] + struct Human<'s> { + id: &'s str, + } + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn human(ctx: &CustomContext) -> Human<'_> { + Human { + id: ctx.0.as_str(), + } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + } + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok((graphql_value!({"human": {"id": "ctx!"}}), vec![])), + ); + } +} diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index 414bb5123..e5c3ba0ea 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -1268,8 +1268,8 @@ mod trivial_struct { #[derive(GraphQLUnion)] #[graphql(context = Database)] #[graphql( - on Human = Character::as_human, - on Droid = Character::as_droid, + on Human = Character::as_human, + on Droid = Character::as_droid, )] struct Character { id: String, @@ -1371,6 +1371,54 @@ mod trivial_struct { )), ); } + + #[tokio::test] + async fn is_graphql_union() { + const DOC: &str = r#"{ + __type(name: "Character") { + kind + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "UNION"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Character") { + description + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } } mod generic_struct { From 5ac943d2350baaad529588d6228dd1d75129f8b3 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 6 Aug 2021 15:14:59 +0300 Subject: [PATCH 27/39] Integration tests for objects, vol.4 --- .../src/codegen/derive_object.rs | 93 ----- .../src/codegen/interface_attr.rs | 101 ++++++ .../juniper_tests/src/codegen/mod.rs | 1 - .../juniper_tests/src/codegen/object_attr.rs | 225 +++++++++++- .../src/codegen/object_derive.rs | 79 +++- .../juniper_tests/src/codegen/union_derive.rs | 27 +- juniper/src/macros/tests/interface.rs | 218 ----------- juniper/src/macros/tests/mod.rs | 2 - juniper/src/macros/tests/object.rs | 341 ------------------ juniper_codegen/src/graphql_interface/mod.rs | 1 + 10 files changed, 426 insertions(+), 662 deletions(-) delete mode 100644 integration_tests/juniper_tests/src/codegen/derive_object.rs delete mode 100644 juniper/src/macros/tests/interface.rs delete mode 100644 juniper/src/macros/tests/object.rs diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs deleted file mode 100644 index 3b2512201..000000000 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ /dev/null @@ -1,93 +0,0 @@ -use juniper::{ - execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, - GraphQLObject, GraphQLType, Object, Registry, RootNode, Value, Variables, -}; - -#[derive(GraphQLObject, Debug, PartialEq)] -#[graphql(rename_all = "none")] -struct NoRenameObj { - one_field: bool, - another_field: i32, -} - -struct Query; - -#[graphql_object] -impl Query { - fn no_rename_obj() -> NoRenameObj { - NoRenameObj { - one_field: true, - another_field: 146, - } - } -} - -struct NoRenameQuery; - -#[graphql_object(rename_all = "none")] -impl NoRenameQuery { - fn no_rename_obj() -> NoRenameObj { - NoRenameObj { - one_field: true, - another_field: 146, - } - } -} - -#[tokio::test] -async fn test_no_rename_root() { - let doc = r#"{ - no_rename_obj { - one_field - another_field - } - }"#; - - let schema = RootNode::new( - NoRenameQuery, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - assert_eq!( - execute(doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({ - "no_rename_obj": { - "one_field": true, - "another_field": 146, - }, - }), - vec![], - )), - ); -} - -#[tokio::test] -async fn test_no_rename_obj() { - let doc = r#"{ - noRenameObj { - one_field - another_field - } - }"#; - - let schema = RootNode::new( - Query, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - assert_eq!( - execute(doc, None, &schema, &Variables::new(), &()).await, - Ok(( - graphql_value!({ - "noRenameObj": { - "one_field": true, - "another_field": 146, - }, - }), - vec![], - )), - ); -} diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 1dab9c774..98dbb0efd 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2915,6 +2915,107 @@ mod explicit_name_description_and_deprecation { } } +mod renamed_all_fields_and_args { + use super::*; + + #[graphql_interface(rename_all = "none", for = Human)] + trait Character { + fn id(&self) -> &str { + "human-32" + } + + async fn home_planet(&self, planet_name: String) -> String { + planet_name + } + + async fn r#async_info(&self, r#my_num: i32) -> i32 { + r#my_num + } + } + + struct Human; + + #[graphql_object(rename_all = "none", impl = CharacterValue)] + impl Human { + fn id() -> &'static str { + "human-32" + } + + fn home_planet(planet_name: String) -> String { + planet_name + } + + fn r#async_info(r#my_num: i32) -> i32 { + r#my_num + } + } + + #[graphql_interface] + impl Character for Human {} + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> CharacterValue { + Human.into() + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + character { + id + home_planet(planet_name: "earth") + async_info(my_num: 3) + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": { + "id": "human-32", + "home_planet": "earth", + "async_info": 3, + }}), + 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, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "home_planet", "args": [{"name": "planet_name"}]}, + {"name": "async_info", "args": [{"name": "my_num"}]}, + ]}}), + vec![], + )), + ); + } +} + mod explicit_scalar { use super::*; diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 1a555cf2b..bd04d4dd8 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -1,6 +1,5 @@ mod derive_enum; mod derive_input_object; -mod derive_object; mod derive_object_with_raw_idents; mod derive_scalar; mod impl_scalar; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index f66e8e7d2..c35910bbe 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2,7 +2,8 @@ use juniper::{ execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, - Executor, FieldError, GraphQLType, IntoFieldError, RootNode, ScalarValue, Variables, + Executor, FieldError, FieldResult, GraphQLObject, GraphQLType, IntoFieldError, RootNode, + ScalarValue, Variables, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -1431,6 +1432,89 @@ mod explicit_name_description_and_deprecation { } } +mod renamed_all_fields_and_args { + use super::*; + + struct Human; + + #[graphql_object(rename_all = "none")] + impl Human { + fn id() -> &'static str { + "human-32" + } + + async fn home_planet(&self, planet_name: String) -> String { + planet_name + } + + async fn r#async_info(r#my_num: i32) -> i32 { + r#my_num + } + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + home_planet(planet_name: "earth") + async_info(my_num: 3) + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": { + "id": "human-32", + "home_planet": "earth", + "async_info": 3, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_and_args_names() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "home_planet", "args": [{"name": "planet_name"}]}, + {"name": "async_info", "args": [{"name": "my_num"}]}, + ]}}), + vec![], + )), + ); + } +} + mod explicit_scalar { use super::*; @@ -1814,3 +1898,142 @@ mod executor { ); } } + +mod switched_context { + use super::*; + + struct CustomContext; + + impl juniper::Context for CustomContext {} + + #[derive(GraphQLObject)] + #[graphql(context = CustomContext)] + struct Droid { + id: i32, + } + + struct Human; + + #[graphql_object(context = CustomContext)] + impl Human { + fn switch_always<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, CustomContext, S>, + ) -> (&'e CustomContext, Droid) { + (executor.context(), Droid { id: 0 }) + } + + async fn switch_opt<'e, S: ScalarValue>( + executor: &'e Executor<'_, '_, CustomContext, S>, + ) -> Option<(&'e CustomContext, Droid)> { + Some((executor.context(), Droid { id: 1 })) + } + + fn switch_res<'e, S: ScalarValue>( + &self, + executor: &'e Executor<'_, '_, CustomContext, S>, + ) -> FieldResult<(&'e CustomContext, Droid)> { + Ok((executor.context(), Droid { id: 2 })) + } + + async fn switch_res_opt<'e, S: ScalarValue>( + &self, + executor: &'e Executor<'_, '_, CustomContext, S>, + ) -> FieldResult> { + Ok(Some((executor.context(), Droid { id: 3 }))) + } + } + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn human() -> Human { + Human + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + switchAlways { id } + switchOpt { id } + switchRes { id } + switchResOpt { id } + } + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"human": { + "switchAlways": {"id": 0}, + "switchOpt": {"id": 1}, + "switchRes": {"id": 2}, + "switchResOpt": {"id": 3}, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_types() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + type { + kind + name + ofType { + name + } + } + } + } + }"#; + + let schema = schema(QueryRoot); + let ctx = CustomContext; + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &ctx).await, + Ok(( + graphql_value!({"__type": {"fields": [{ + "name": "switchAlways", + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": {"name": "Droid"}, + }, + }, { + "name": "switchOpt", + "type": { + "kind": "OBJECT", + "name": "Droid", + "ofType": None, + }, + }, { + "name": "switchRes", + "type": { + "kind": "NON_NULL", + "name": None, + "ofType": {"name": "Droid"}, + }, + }, { + "name": "switchResOpt", + "type": { + "kind": "OBJECT", + "name": "Droid", + "ofType": None, + }, + }]}}), + vec![], + )), + ); + } +} diff --git a/integration_tests/juniper_tests/src/codegen/object_derive.rs b/integration_tests/juniper_tests/src/codegen/object_derive.rs index af1d48627..5162f9d95 100644 --- a/integration_tests/juniper_tests/src/codegen/object_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/object_derive.rs @@ -742,6 +742,81 @@ mod explicit_name_description_and_deprecation { } } +mod renamed_all_fields { + use super::*; + + #[derive(GraphQLObject)] + #[graphql(rename_all = "none")] + struct Human { + id: &'static str, + home_planet: String, + r#async_info: i32, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn human() -> Human { + Human { + id: "human-32", + home_planet: "earth".into(), + r#async_info: 3, + } + } + } + + #[tokio::test] + async fn resolves_fields() { + const DOC: &str = r#"{ + human { + id + home_planet + async_info + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"human": { + "id": "human-32", + "home_planet": "earth", + "async_info": 3, + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_correct_fields_names() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id"}, + {"name": "home_planet"}, + {"name": "async_info"}, + ]}}), + vec![], + )), + ); + } +} + mod explicit_scalar { use super::*; @@ -909,9 +984,7 @@ mod explicit_custom_context { #[graphql_object(context = CustomContext)] impl QueryRoot { fn human(ctx: &CustomContext) -> Human<'_> { - Human { - id: ctx.0.as_str(), - } + Human { id: ctx.0.as_str() } } } diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index e5c3ba0ea..2f692a7d3 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -1381,9 +1381,16 @@ mod trivial_struct { }"#; let schema = schema(QueryRoot::Human); + let db = Database { + human: Some(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + droid: None, + }; assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, + execute(DOC, None, &schema, &Variables::new(), &db).await, Ok((graphql_value!({"__type": {"kind": "UNION"}}), vec![])), ); } @@ -1397,9 +1404,16 @@ mod trivial_struct { }"#; let schema = schema(QueryRoot::Human); + let db = Database { + human: Some(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + droid: None, + }; assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, + execute(DOC, None, &schema, &Variables::new(), &db).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1413,9 +1427,16 @@ mod trivial_struct { }"#; let schema = schema(QueryRoot::Human); + let db = Database { + human: Some(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + droid: None, + }; assert_eq!( - execute(DOC, None, &schema, &Variables::new(), &()).await, + execute(DOC, None, &schema, &Variables::new(), &db).await, Ok((graphql_value!({"__type": {"description": None}}), vec![])), ); } diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs deleted file mode 100644 index 0f2eb3a57..000000000 --- a/juniper/src/macros/tests/interface.rs +++ /dev/null @@ -1,218 +0,0 @@ -/* - -Syntax to validate: - -* Order of items: fields, description, instance resolvers -* Optional Generics/lifetimes -* Custom name vs. default name -* Optional commas between items -* Optional trailing commas on instance resolvers - -*/ - -use crate::{ - ast::InputValue, - graphql_interface, graphql_object, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, Value}, -}; - -struct Concrete; - -#[graphql_object(impl = [ - CustomNameValue, DescriptionValue, WithLifetimeValue<'_>, WithGenericsValue<()>, -])] -impl Concrete { - fn simple() -> i32 { - 0 - } -} - -#[graphql_interface(for = Concrete, name = "ACustomNamedInterface")] -trait CustomName { - fn simple(&self) -> i32; -} -#[graphql_interface] -impl CustomName for Concrete { - fn simple(&self) -> i32 { - 0 - } -} - -#[graphql_interface(for = Concrete)] -trait WithLifetime<'a> { - fn simple(&self) -> i32; -} -#[graphql_interface] -impl<'a> WithLifetime<'a> for Concrete { - fn simple(&self) -> i32 { - 0 - } -} - -#[graphql_interface(for = Concrete)] -trait WithGenerics { - fn simple(&self) -> i32; -} -#[graphql_interface] -impl WithGenerics for Concrete { - fn simple(&self) -> i32 { - 0 - } -} - -#[graphql_interface(for = Concrete, desc = "A description")] -trait Description { - fn simple(&self) -> i32; -} -#[graphql_interface] -impl Description for Concrete { - fn simple(&self) -> i32 { - 0 - } -} - -struct Root; - -#[graphql_object] -impl Root { - fn custom_name() -> CustomNameValue { - Concrete.into() - } - - fn with_lifetime() -> WithLifetimeValue<'static> { - Concrete.into() - } - fn with_generics() -> WithGenericsValue { - Concrete.into() - } - - fn description() -> DescriptionValue { - Concrete.into() - } -} - -async fn run_type_info_query(type_name: &str, f: F) -where - F: Fn(&Object, &Vec>) -> (), -{ - let doc = r#" - query ($typeName: String!) { - __type(name: $typeName) { - name - description - fields(includeDeprecated: true) { - name - } - } - } - "#; - let schema = RootNode::new( - Root {}, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))] - .into_iter() - .collect(); - - let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 fields = type_info - .get_field_value("fields") - .expect("fields field missing") - .as_list_value() - .expect("fields field not a list value"); - - f(type_info, fields); -} - -#[tokio::test] -async fn introspect_custom_name() { - run_type_info_query("ACustomNamedInterface", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("ACustomNamedInterface")) - ); - assert_eq!(object.get_field_value("description"), Some(&Value::null())); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_with_lifetime() { - run_type_info_query("WithLifetime", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("WithLifetime")) - ); - assert_eq!(object.get_field_value("description"), Some(&Value::null())); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_with_generics() { - run_type_info_query("WithGenerics", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("WithGenerics")) - ); - assert_eq!(object.get_field_value("description"), Some(&Value::null())); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_description_first() { - run_type_info_query("Description", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("Description")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(fields.contains(&Value::object( - vec![("name", Value::scalar("simple"))] - .into_iter() - .collect(), - ))); - }) - .await; -} diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index b90ceee2f..f97791cdd 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -1,7 +1,5 @@ mod args; mod field; mod impl_subscription; -mod interface; -mod object; mod union; mod util; diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs deleted file mode 100644 index a7e279ca9..000000000 --- a/juniper/src/macros/tests/object.rs +++ /dev/null @@ -1,341 +0,0 @@ -/* -Syntax to validate: - -* Order of items: fields, description, interfaces -* Optional Generics/lifetimes -* Custom name vs. default name -* Optional commas between items -* Nullable/fallible context switching -*/ - -use std::marker::PhantomData; - -use crate::{ - ast::InputValue, - executor::{Context, FieldResult}, - graphql_interface, graphql_object, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, Value}, - Executor, GraphQLObject, ScalarValue, -}; - -struct CustomName; - -#[graphql_object(name = "ACustomNamedType")] -impl CustomName { - fn simple() -> i32 { - 0 - } -} - -struct WithLifetime<'a> { - data: PhantomData<&'a i32>, -} - -#[graphql_object] -impl<'a> WithLifetime<'a> { - fn simple() -> i32 { - 0 - } -} - -struct WithGenerics { - #[allow(dead_code)] - data: T, -} - -#[graphql_object] -impl WithGenerics { - fn simple() -> i32 { - 0 - } -} - -#[graphql_interface(for = SimpleObject)] -trait Interface { - fn simple(&self) -> i32 { - 0 - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = InterfaceValue, description = "A description")] -struct SimpleObject { - simple: i32, -} - -#[graphql_interface] -impl Interface for SimpleObject {} - -struct InnerContext; -impl Context for InnerContext {} - -#[derive(GraphQLObject)] -#[graphql(context = InnerContext)] -struct InnerType { - a: i32, -} - -struct CtxSwitcher; - -#[graphql_object(context = InnerContext)] -impl CtxSwitcher { - fn ctx_switch_always<'e, S: ScalarValue>( - executor: &'e Executor<'_, '_, InnerContext, S>, - ) -> (&'e InnerContext, InnerType) { - (executor.context(), InnerType { a: 0 }) - } - - fn ctx_switch_opt<'e, S: ScalarValue>( - executor: &'e Executor<'_, '_, InnerContext, S>, - ) -> Option<(&'e InnerContext, InnerType)> { - Some((executor.context(), InnerType { a: 0 })) - } - - fn ctx_switch_res<'e, S: ScalarValue>( - executor: &'e Executor<'_, '_, InnerContext, S>, - ) -> FieldResult<(&'e InnerContext, InnerType)> { - Ok((executor.context(), InnerType { a: 0 })) - } - - fn ctx_switch_res_opt<'e, S: ScalarValue>( - executor: &'e Executor<'_, '_, InnerContext, S>, - ) -> FieldResult> { - Ok(Some((executor.context(), InnerType { a: 0 }))) - } -} - -struct Root; - -#[graphql_object(context = InnerContext)] -impl Root { - fn custom_name() -> CustomName { - CustomName {} - } - - fn with_lifetime() -> WithLifetime<'static> { - WithLifetime { data: PhantomData } - } - fn with_generics() -> WithGenerics { - WithGenerics { data: 123 } - } - - fn description_first() -> SimpleObject { - SimpleObject { simple: 0 } - } - fn interface() -> InterfaceValue { - SimpleObject { simple: 0 }.into() - } - - fn ctx_switcher() -> CtxSwitcher { - CtxSwitcher {} - } -} - -fn run_type_info_query(type_name: &str, f: F) -where - F: Fn(&Object, &Vec>) -> (), -{ - let doc = r#" - query ($typeName: String!) { - __type(name: $typeName) { - name - description - fields(includeDeprecated: true) { - name - type { - kind - name - ofType { - kind - name - } - } - } - interfaces { - name - kind - } - } - } - "#; - let schema = RootNode::new( - Root {}, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); - let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))] - .into_iter() - .collect(); - - let (result, errs) = - crate::execute_sync(doc, None, &schema, &vars, &InnerContext).expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 fields = type_info - .get_field_value("fields") - .expect("fields field missing") - .as_list_value() - .expect("fields field not a list value"); - - f(type_info, fields); -} - -#[test] -fn introspect_custom_name() { - run_type_info_query("ACustomNamedType", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("ACustomNamedType")) - ); - assert_eq!(object.get_field_value("description"), Some(&Value::null())); - assert_eq!( - object.get_field_value("interfaces"), - Some(&Value::list(vec![])) - ); - - assert!(fields.contains(&graphql_value!({ - "name": "simple", - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { "kind": "SCALAR", "name": "Int" } - } - }))); - }); -} - -#[test] -fn introspect_with_lifetime() { - run_type_info_query("WithLifetime", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("WithLifetime")) - ); - assert_eq!(object.get_field_value("description"), Some(&Value::null())); - assert_eq!( - object.get_field_value("interfaces"), - Some(&Value::list(vec![])) - ); - - assert!(fields.contains(&graphql_value!({ - "name": "simple", - "type": { - "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } - } - }))); - }); -} - -#[test] -fn introspect_with_generics() { - run_type_info_query("WithGenerics", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("WithGenerics")) - ); - assert_eq!(object.get_field_value("description"), Some(&Value::null())); - assert_eq!( - object.get_field_value("interfaces"), - Some(&Value::list(vec![])) - ); - - assert!(fields.contains(&graphql_value!({ - "name": "simple", - "type": { - "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } - } - }))); - }); -} - -#[test] -fn introspect_simple_object() { - run_type_info_query("SimpleObject", |object, fields| { - assert_eq!( - object.get_field_value("name"), - Some(&Value::scalar("SimpleObject")) - ); - assert_eq!( - object.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - assert_eq!( - object.get_field_value("interfaces"), - Some(&Value::list(vec![Value::object( - vec![ - ("name", Value::scalar("Interface")), - ("kind", Value::scalar("INTERFACE")), - ] - .into_iter() - .collect(), - )])) - ); - - assert!(fields.contains(&graphql_value!({ - "name": "simple", - "type": { - "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } - } - }))); - }); -} - -#[test] -fn introspect_ctx_switch() { - run_type_info_query("CtxSwitcher", |_, fields| { - assert!(fields.contains(&graphql_value!({ - "name": "ctxSwitchAlways", - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "InnerType", - } - } - }))); - - assert!(fields.contains(&graphql_value!({ - "name": "ctxSwitchOpt", - "type": { - "kind": "OBJECT", - "name": "InnerType", - "ofType": None - } - }))); - - assert!(fields.contains(&graphql_value!({ - "name": "ctxSwitchRes", - "type": { - "kind": "NON_NULL", - "name": None, - "ofType": { - "kind": "OBJECT", - "name": "InnerType", - } - } - }))); - - assert!(fields.contains(&graphql_value!({ - "name": "ctxSwitchResOpt", - "type": { - "kind": "OBJECT", - "name": "InnerType", - "ofType": None - } - }))); - }); -} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 23e9bc441..32d01cfeb 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -1270,6 +1270,7 @@ impl EnumType { }); let mut impl_tokens = quote! { + #[allow(deprecated)] #[automatically_derived] impl#impl_generics #trait_ident#generics for #enum_ty#generics #where_clause { #( #assoc_types )* From 2eede12bc9f9379849cf9dc2b12e63c6ce68b52d Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 6 Aug 2021 18:14:35 +0300 Subject: [PATCH 28/39] Integration tests for objects, vol.5 --- .../src/codegen/interface_attr.rs | 188 +++++- .../juniper_tests/src/codegen/object_attr.rs | 118 +++- juniper/src/macros/tests/args.rs | 553 ----------------- juniper/src/macros/tests/field.rs | 558 ------------------ juniper/src/macros/tests/mod.rs | 1 - juniper/src/macros/tests/union.rs | 95 --- 6 files changed, 265 insertions(+), 1248 deletions(-) delete mode 100644 juniper/src/macros/tests/args.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 98dbb0efd..c6012ed60 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2,8 +2,8 @@ use juniper::{ execute, graphql_interface, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, - EmptySubscription, Executor, FieldError, FieldResult, GraphQLObject, GraphQLType, - IntoFieldError, RootNode, ScalarValue, Variables, + EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, + GraphQLType, IntoFieldError, RootNode, ScalarValue, Variables, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -2264,11 +2264,15 @@ mod argument { #[graphql_interface(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)] @@ -2287,6 +2291,14 @@ mod argument { "none" } } + + async fn id_wide2(&self, is_number: bool, _: Option) -> &str { + if is_number { + &self.id + } else { + "none" + } + } } #[graphql_interface(dyn)] @@ -2298,6 +2310,14 @@ mod argument { &self.id } } + + async fn info_wide2(&self, is_planet: bool, _: Option) -> &str { + if is_planet { + &self.home_planet + } else { + &self.id + } + } } struct QueryRoot; @@ -2325,14 +2345,26 @@ mod argument { let schema = schema(QueryRoot); for (input, expected) in &[ - ("{ character { idWide(isNumber: true) } }", "human-32"), - ("{ character { idWide(isNumber: false) } }", "none"), + ( + "{ character { idWide(isNumber: true), idWide2(isNumber: true) } }", + "human-32", + ), + ( + "{ character { idWide(isNumber: false), idWide2(isNumber: false, async: 5) } }", + "none", + ), ] { let expected: &str = *expected; assert_eq!( execute(*input, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"character": {"idWide": expected}}), vec![])), + Ok(( + graphql_value!({"character": { + "idWide": expected, + "idWide2": expected, + }}), + vec![], + )), ); } } @@ -2342,14 +2374,26 @@ mod argument { let schema = schema(QueryRoot); for (input, expected) in &[ - ("{ hero { infoWide(isPlanet: true) } }", "earth"), - ("{ hero { infoWide(isPlanet: false) } }", "human-32"), + ( + "{ 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, &Variables::new(), &()).await, - Ok((graphql_value!({"hero": {"infoWide": expected}}), vec![])), + Ok(( + graphql_value!({"hero": { + "infoWide": expected, + "infoWide2": expected, + }}), + vec![], + )), ); } } @@ -2377,13 +2421,23 @@ mod argument { ); 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, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": {"fields": [ - {"name": expected_field_name, "args": [{"name": expected_arg_name}]}, - ]}}), + 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![], )), ); @@ -2411,7 +2465,10 @@ mod argument { assert_eq!( execute(&doc, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": {"fields": [{"args": [{"description": None}]}]}}), + graphql_value!({"__type": {"fields": [ + {"args": [{"description": None}]}, + {"args": [{"description": None}, {"description": None}]}, + ]}}), vec![], )), ); @@ -2439,7 +2496,10 @@ mod argument { assert_eq!( execute(&doc, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": {"fields": [{"args": [{"defaultValue": None}]}]}}), + graphql_value!({"__type": {"fields": [ + {"args": [{"defaultValue": None}]}, + {"args": [{"defaultValue": None}, {"defaultValue": None}]}, + ]}}), vec![], )), ); @@ -2450,6 +2510,11 @@ mod argument { mod default_argument { use super::*; + #[derive(GraphQLInputObject, Debug)] + struct Point { + x: i32, + } + #[graphql_interface(for = Human)] trait Character { async fn id( @@ -2458,12 +2523,17 @@ mod default_argument { #[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 + } } #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Human { id: String, + info: i32, } #[graphql_interface] @@ -2480,6 +2550,7 @@ mod default_argument { fn character(&self) -> CharacterValue { Human { id: "human-32".to_string(), + info: 0, } .into() } @@ -2511,6 +2582,23 @@ mod default_argument { } } + #[tokio::test] + async fn resolves_info_field() { + let schema = schema(QueryRoot); + + for (input, expected) in &[ + ("{ character { info } }", 1), + ("{ character { info(coord: {x: 2}) } }", 2), + ] { + let expected: i32 = *expected; + + assert_eq!( + execute(*input, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"character": {"info": expected}}), vec![])), + ); + } + } + #[tokio::test] async fn has_defaults() { const DOC: &str = r#"{ @@ -2519,6 +2607,12 @@ mod default_argument { args { name defaultValue + type { + name + ofType { + name + } + } } } } @@ -2529,11 +2623,27 @@ mod default_argument { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": {"fields": [{"args": [ - {"name": "first", "defaultValue": r#""""#}, - {"name": "second", "defaultValue": r#""second""#}, - {"name": "third", "defaultValue": r#""t""#}, - ]}]}}), + graphql_value!({"__type": {"fields": [{ + "args": [{ + "name": "first", + "defaultValue": r#""""#, + "type": {"name": "String", "ofType": None}, + }, { + "name": "second", + "defaultValue": r#""second""#, + "type": {"name": "String", "ofType": None}, + }, { + "name": "third", + "defaultValue": r#""t""#, + "type": {"name": "String", "ofType": None}, + }], + }, { + "args": [{ + "name": "coord", + "defaultValue": "{x: 1}", + "type": {"name": "Point", "ofType": None}, + }], + }]}}), vec![], )), ); @@ -4711,6 +4821,7 @@ mod executor { async fn info<'b>( &'b self, + arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, ) -> &'b str where @@ -4728,6 +4839,7 @@ mod executor { async fn info<'b>( &'b self, + arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, ) -> &'b str where @@ -4743,7 +4855,7 @@ mod executor { #[graphql_interface(scalar = S)] impl Character for Human { - async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str where S: Send + Sync, { @@ -4753,7 +4865,7 @@ mod executor { #[graphql_interface(dyn, scalar = S)] impl Hero for Human { - async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str where S: Send + Sync, { @@ -4770,7 +4882,7 @@ mod executor { #[graphql_interface(scalar = S)] impl Character for Droid { - async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str where S: Send + Sync, { @@ -4780,7 +4892,7 @@ mod executor { #[graphql_interface(dyn, scalar = S)] impl Hero for Droid { - async fn info<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str where S: Send + Sync, { @@ -4943,6 +5055,38 @@ mod executor { } } } + + #[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, &Variables::new(), &()).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/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index c35910bbe..97be3b5cb 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2,8 +2,8 @@ use juniper::{ execute, graphql_object, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, - Executor, FieldError, FieldResult, GraphQLObject, GraphQLType, IntoFieldError, RootNode, - ScalarValue, Variables, + Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLType, + IntoFieldError, RootNode, ScalarValue, Variables, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -906,8 +906,8 @@ mod argument { arg } - fn home_planet(r#raw_arg: String) -> String { - r#raw_arg + async fn home_planet(&self, r#raw_arg: String, r#async: Option) -> String { + format!("{},{:?}", r#raw_arg, r#async) } } @@ -934,7 +934,7 @@ mod argument { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}), + graphql_value!({"human": {"id": "human-32", "homePlanet": "earth,None"}}), vec![], )), ); @@ -960,7 +960,7 @@ mod argument { Ok(( graphql_value!({"__type": {"fields": [ {"name": "id", "args": [{"name": "arg"}]}, - {"name": "homePlanet", "args": [{"name": "rawArg"}]}, + {"name": "homePlanet", "args": [{"name": "rawArg"}, {"name": "async"}]}, ]}}), vec![], )), @@ -986,7 +986,7 @@ mod argument { Ok(( graphql_value!({"__type": {"fields": [ {"args": [{"description": None}]}, - {"args": [{"description": None}]}, + {"args": [{"description": None}, {"description": None}]}, ]}}), vec![], )), @@ -1012,7 +1012,7 @@ mod argument { Ok(( graphql_value!({"__type": {"fields": [ {"args": [{"defaultValue": None}]}, - {"args": [{"defaultValue": None}]}, + {"args": [{"defaultValue": None}, {"defaultValue": None}]}, ]}}), vec![], )), @@ -1023,6 +1023,11 @@ mod argument { mod default_argument { use super::*; + #[derive(GraphQLInputObject, Debug)] + struct Point { + x: i32, + } + struct Human; #[graphql_object] @@ -1034,6 +1039,10 @@ mod default_argument { ) -> String { format!("{}|{}&{}", arg1, arg2, r#arg3) } + + fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> i32 { + coord.x + } } struct QueryRoot; @@ -1046,12 +1055,12 @@ mod default_argument { } #[tokio::test] - async fn resolves() { + async fn resolves_id_field() { let schema = schema(QueryRoot); for (input, expected) in &[ ("{ human { id } }", "0|second&true"), - (r#"{ human { id(arg1: 1) } }"#, "1|second&true"), + ("{ human { id(arg1: 1) } }", "1|second&true"), (r#"{ human { id(arg2: "") } }"#, "0|&true"), (r#"{ human { id(arg1: 2, arg2: "") } }"#, "2|&true"), ( @@ -1068,6 +1077,23 @@ mod default_argument { } } + #[tokio::test] + async fn resolves_info_field() { + let schema = schema(QueryRoot); + + for (input, expected) in &[ + ("{ human { info } }", 1), + ("{ human { info(coord: { x: 2 }) } }", 2), + ] { + let expected: i32 = *expected; + + assert_eq!( + execute(*input, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"human": {"info": expected}}), vec![])), + ); + } + } + #[tokio::test] async fn has_defaults() { const DOC: &str = r#"{ @@ -1076,6 +1102,12 @@ mod default_argument { args { name defaultValue + type { + name + ofType { + name + } + } } } } @@ -1086,11 +1118,27 @@ mod default_argument { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, Ok(( - graphql_value!({"__type": {"fields": [{"args": [ - {"name": "arg1", "defaultValue": "0"}, - {"name": "arg2", "defaultValue": r#""second""#}, - {"name": "arg3", "defaultValue": "true"}, - ]}]}}), + graphql_value!({"__type": {"fields": [{ + "args": [{ + "name": "arg1", + "defaultValue": "0", + "type": {"name": "Int", "ofType": None}, + }, { + "name": "arg2", + "defaultValue": r#""second""#, + "type": {"name": "String", "ofType": None}, + }, { + "name": "arg3", + "defaultValue": "true", + "type": {"name": "Boolean", "ofType": None}, + }], + }, { + "args": [{ + "name": "coord", + "defaultValue": "{x: 1}", + "type": {"name": "Point", "ofType": None}, + }], + }]}}), vec![], )), ); @@ -1849,11 +1897,15 @@ mod executor { executor.look_ahead().field_name() } - fn info<'b, S>(&'b self, #[graphql(executor)] _another: &Executor<'_, '_, (), S>) -> &'b str + fn info( + &self, + arg: String, + #[graphql(executor)] _another: &Executor<'_, '_, (), S>, + ) -> String where S: ScalarValue, { - "no info" + arg } fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> &'e str @@ -1878,7 +1930,7 @@ mod executor { const DOC: &str = r#"{ human { id - info + info(arg: "input!") info2 } }"#; @@ -1890,13 +1942,41 @@ mod executor { Ok(( graphql_value!({"human": { "id": "id", - "info": "no info", + "info": "input!", "info2": "no info", }}), vec![], )), ); } + + #[tokio::test] + async fn not_arg() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "info", "args": [{"name": "arg"}]}, + {"name": "info2", "args": []}, + ]}}), + vec![], + )), + ); + } } mod switched_context { diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs deleted file mode 100644 index 496424881..000000000 --- a/juniper/src/macros/tests/args.rs +++ /dev/null @@ -1,553 +0,0 @@ -use crate::{ - executor::Variables, - graphql_object, graphql_value, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - DefaultScalarValue, Executor, GraphQLInputObject, ScalarValue, Value, -}; - -#[derive(GraphQLInputObject, Debug)] -struct Point { - x: i32, -} - -struct Root; - -#[graphql_object] -impl Root { - fn simple() -> i32 { - 0 - } - - fn exec_arg(_executor: &Executor<'_, '_, (), S>) -> i32 { - 0 - } - fn exec_arg_and_more(_executor: &Executor<'_, '_, (), S>, arg: i32) -> i32 { - arg - } - - fn single_arg(arg: i32) -> i32 { - arg - } - - fn multi_args(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 - } - - fn multi_args_trailing_comma(arg1: i32, arg2: i32) -> i32 { - arg1 + arg2 - } - - fn single_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { - arg - } - - fn single_arg_descr_raw_idents(#[graphql(description = "The arg")] r#arg: i32) -> i32 { - r#arg - } - - fn multi_args_descr( - #[graphql(description = "The first arg")] arg1: i32, - #[graphql(description = "The second arg")] arg2: i32, - ) -> i32 { - arg1 + arg2 - } - - fn multi_args_descr_raw_idents( - #[graphql(description = "The first arg")] r#arg1: i32, - #[graphql(description = "The second arg")] r#arg2: i32, - ) -> i32 { - r#arg1 + r#arg2 - } - - fn attr_arg_descr(#[graphql(description = "The arg")] _arg: i32) -> i32 { - 0 - } - - fn arg_with_default(#[graphql(default = 123)] arg: i32) -> i32 { - arg - } - - fn multi_args_with_default( - #[graphql(default = 123)] arg1: i32, - #[graphql(default = 456)] arg2: i32, - ) -> i32 { - arg1 + arg2 - } - - fn arg_with_default_descr(#[graphql(default = 123, description = "The arg")] arg: i32) -> i32 { - arg - } - - fn arg_with_default_descr_raw_ident( - #[graphql(default = 123, description = "The arg")] r#arg: i32, - ) -> i32 { - r#arg - } - - fn multi_args_with_default_descr( - #[graphql(default = 123, description = "The first arg")] arg1: i32, - #[graphql(default = 456, description = "The second arg")] arg2: i32, - ) -> i32 { - arg1 + arg2 - } - - fn multi_args_with_default_descr_raw_ident( - #[graphql(default = 123, description = "The first arg")] r#arg1: i32, - #[graphql(default = 456, description = "The second arg")] r#arg2: i32, - ) -> i32 { - r#arg1 + r#arg2 - } - - fn args_with_complex_default( - #[graphql(default = "test", description = "A string default argument")] arg1: String, - #[graphql( - default = Point { x: 1 }, - description = "An input object default argument", - )] - arg2: Point, - ) -> i32 { - let _ = arg1; - let _ = arg2; - 0 - } -} - -async fn run_args_info_query(field_name: &str, f: F) -where - F: Fn(&Vec>) -> (), -{ - let doc = r#"{ - __type(name: "Root") { - fields { - name - args { - name - description - defaultValue - type { - name - ofType { - name - } - } - } - } - } - }"#; - let schema = RootNode::new( - Root {}, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - - let (result, errs) = crate::execute(doc, None, &schema, &Variables::new(), &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 fields = type_info - .get_field_value("fields") - .expect("fields field missing") - .as_list_value() - .expect("fields not a list"); - - let field = fields - .into_iter() - .filter(|f| { - f.as_object_value() - .expect("Field not an object") - .get_field_value("name") - .expect("name field missing from field") - .as_scalar_value::() - .expect("name is not a string") - == field_name - }) - .next() - .expect("Field not found") - .as_object_value() - .expect("Field is not an object"); - - println!("Field: {:#?}", field); - - let args = field - .get_field_value("args") - .expect("args missing from field") - .as_list_value() - .expect("args is not a list"); - - println!("Args: {:#?}", args); - - f(args); -} - -#[tokio::test] -async fn introspect_field_simple() { - run_args_info_query("simple", |args| { - assert_eq!(args.len(), 0); - }) - .await -} - -#[tokio::test] -async fn introspect_field_exec_arg() { - run_args_info_query("execArg", |args| { - assert_eq!(args.len(), 0); - }) - .await -} - -#[tokio::test] -async fn introspect_field_exec_arg_and_more() { - run_args_info_query("execArgAndMore", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": None, - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_single_arg() { - run_args_info_query("singleArg", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": None, - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args() { - run_args_info_query("multiArgs", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": None, - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": None, - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args_trailing_comma() { - run_args_info_query("multiArgsTrailingComma", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": None, - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": None, - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_single_arg_descr() { - run_args_info_query("singleArgDescr", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": "The arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_single_arg_descr_raw_idents() { - run_args_info_query("singleArgDescrRawIdents", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": "The arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args_descr() { - run_args_info_query("multiArgsDescr", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": "The first arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": "The second arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args_descr_raw_idents() { - run_args_info_query("multiArgsDescrRawIdents", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": "The first arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": "The second arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_attr_arg_descr() { - run_args_info_query("attrArgDescr", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": "The arg", - "defaultValue": None, - "type": { - "name": None, - "ofType": { "name": "Int" }, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_arg_with_default() { - run_args_info_query("argWithDefault", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": None, - "defaultValue": "123", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args_with_default() { - run_args_info_query("multiArgsWithDefault", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": None, - "defaultValue": "123", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": None, - "defaultValue": "456", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_arg_with_default_descr() { - run_args_info_query("argWithDefaultDescr", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": "The arg", - "defaultValue": "123", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_arg_with_default_descr_raw_ident() { - run_args_info_query("argWithDefaultDescrRawIdent", |args| { - assert_eq!(args.len(), 1); - assert!(args.contains(&graphql_value!({ - "name": "arg", - "description": "The arg", - "defaultValue": "123", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args_with_default_descr() { - run_args_info_query("multiArgsWithDefaultDescr", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": "The first arg", - "defaultValue": "123", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": "The second arg", - "defaultValue": "456", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_multi_args_with_default_descr_raw_ident() { - run_args_info_query("multiArgsWithDefaultDescrRawIdent", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": "The first arg", - "defaultValue": "123", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": "The second arg", - "defaultValue": "456", - "type": { - "name": "Int", - "ofType": None, - }, - }))); - }) - .await -} - -#[tokio::test] -async fn introspect_field_args_with_complex_default() { - run_args_info_query("argsWithComplexDefault", |args| { - assert_eq!(args.len(), 2); - assert!(args.contains(&graphql_value!({ - "name": "arg1", - "description": "A string default argument", - "defaultValue": r#""test""#, - "type": { - "name": "String", - "ofType": None, - }, - }))); - assert!(args.contains(&graphql_value!({ - "name": "arg2", - "description": "An input object default argument", - "defaultValue": r#"{x: 1}"#, - "type": { - "name": "Point", - "ofType": None, - }, - }))); - }) - .await -} diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index e1a73039f..f0faa0575 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -26,46 +26,6 @@ struct Root; #[graphql_object(impl = InterfaceValue)] impl Root { - fn simple() -> i32 { - 0 - } - - /// Field description - fn description() -> i32 { - 0 - } - - #[deprecated] - fn deprecated_outer() -> bool { - true - } - - #[deprecated(note = "Deprecation Reason")] - fn deprecated_outer_with_reason() -> bool { - true - } - - #[graphql(deprecated = "Deprecation reason")] - fn deprecated() -> i32 { - 0 - } - - #[graphql(deprecated = "Deprecation reason", description = "Field description")] - fn deprecated_descr() -> i32 { - 0 - } - - /// Field description - fn attr_description() -> i32 { - 0 - } - - /// Field description - /// with `collapse_docs` behavior - fn attr_description_collapse() -> i32 { - 0 - } - /// Get the i32 representation of 0. /// /// - This comment is longer. @@ -75,30 +35,10 @@ impl Root { 0 } - #[graphql(deprecated)] - fn attr_deprecated() -> i32 { - 0 - } - - #[graphql(deprecated = "Deprecation reason")] - fn attr_deprecated_reason() -> i32 { - 0 - } - - /// Field description - #[graphql(deprecated = "Deprecation reason")] - fn attr_deprecated_descr() -> i32 { - 0 - } - fn with_field_result() -> FieldResult { Ok(0) } - fn with_return() -> i32 { - 0 - } - fn with_return_field_result() -> FieldResult { Ok(0) } @@ -106,82 +46,18 @@ impl Root { #[graphql_interface] impl Interface for Root { - fn simple(&self) -> i32 { - 0 - } - - fn description(&self) -> i32 { - 0 - } - - fn deprecated(&self) -> i32 { - 0 - } - - fn deprecated_descr(&self) -> i32 { - 0 - } - - fn attr_description(&self) -> i32 { - 0 - } - - fn attr_description_collapse(&self) -> i32 { - 0 - } - fn attr_description_long(&self) -> i32 { 0 } - - fn attr_deprecated(&self) -> i32 { - 0 - } - - fn attr_deprecated_reason(&self) -> i32 { - 0 - } - - fn attr_deprecated_descr(&self) -> i32 { - 0 - } } #[graphql_interface(for = Root)] trait Interface { - fn simple(&self) -> i32; - - #[graphql(desc = "Field description")] - fn description(&self) -> i32; - - #[graphql(deprecated = "Deprecation reason")] - fn deprecated(&self) -> i32; - - #[graphql(desc = "Field description", deprecated = "Deprecation reason")] - fn deprecated_descr(&self) -> i32; - - /// Field description - fn attr_description(&self) -> i32; - - /// Field description - /// with `collapse_docs` behavior - fn attr_description_collapse(&self) -> i32; - /// Get the i32 representation of 0. /// /// - This comment is longer. /// - These two lines are rendered as bullets by GraphiQL. fn attr_description_long(&self) -> i32; - - #[deprecated] - fn attr_deprecated(&self) -> i32; - - #[deprecated(note = "Deprecation reason")] - fn attr_deprecated_reason(&self) -> i32; - - /// Field description - #[deprecated(note = "Deprecation reason")] - fn attr_deprecated_descr(&self) -> i32; } async fn run_field_info_query(type_name: &str, field_name: &str, f: F) @@ -252,264 +128,6 @@ where f(field); } -#[tokio::test] -async fn introspect_object_field_simple() { - run_field_info_query("Root", "simple", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("simple")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_simple() { - run_field_info_query("Interface", "simple", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("simple")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_description() { - run_field_info_query("Root", "description", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("description")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_description() { - run_field_info_query("Interface", "description", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("description")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_deprecated_outer() { - run_field_info_query("Root", "deprecatedOuter", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("deprecatedOuter")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()), - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_deprecated_outer_with_reason() { - run_field_info_query("Root", "deprecatedOuterWithReason", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("deprecatedOuterWithReason")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation Reason")), - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_deprecated() { - run_field_info_query("Root", "deprecated", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("deprecated")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_deprecated() { - run_field_info_query("Interface", "deprecated", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("deprecated")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_deprecated_descr() { - run_field_info_query("Root", "deprecatedDescr", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("deprecatedDescr")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_deprecated_descr() { - run_field_info_query("Interface", "deprecatedDescr", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("deprecatedDescr")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_attr_description() { - run_field_info_query("Root", "attrDescription", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDescription")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_attr_description() { - run_field_info_query("Interface", "attrDescription", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDescription")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - #[tokio::test] async fn introspect_object_field_attr_description_long() { run_field_info_query("Root", "attrDescriptionLong", |field| { @@ -553,179 +171,3 @@ async fn introspect_interface_field_attr_description_long() { ); }).await; } - -#[tokio::test] -async fn introspect_object_field_attr_description_collapse() { - run_field_info_query("Root", "attrDescriptionCollapse", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDescriptionCollapse")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar( - "Field description\nwith `collapse_docs` behavior" - )) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_attr_description_collapse() { - run_field_info_query("Interface", "attrDescriptionCollapse", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDescriptionCollapse")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar( - "Field description\nwith `collapse_docs` behavior" - )) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_attr_deprecated() { - run_field_info_query("Root", "attrDeprecated", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDeprecated")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_attr_deprecated() { - run_field_info_query("Interface", "attrDeprecated", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDeprecated")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_attr_deprecated_reason() { - run_field_info_query("Root", "attrDeprecatedReason", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDeprecatedReason")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_attr_deprecated_reason() { - run_field_info_query("Interface", "attrDeprecatedReason", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDeprecatedReason")) - ); - assert_eq!(field.get_field_value("description"), Some(&Value::null())); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_object_field_attr_deprecated_descr() { - run_field_info_query("Root", "attrDeprecatedDescr", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDeprecatedDescr")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} - -#[tokio::test] -async fn introspect_interface_field_attr_deprecated_descr() { - run_field_info_query("Interface", "attrDeprecatedDescr", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDeprecatedDescr")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Field description")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(true)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::scalar("Deprecation reason")) - ); - }) - .await; -} diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index f97791cdd..757526a7d 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -1,4 +1,3 @@ -mod args; mod field; mod impl_subscription; mod union; diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs index f796ecd2c..90f306a83 100644 --- a/juniper/src/macros/tests/union.rs +++ b/juniper/src/macros/tests/union.rs @@ -30,12 +30,6 @@ impl Concrete { } } -#[derive(GraphQLUnion)] -#[graphql(name = "ACustomNamedUnion")] -enum CustomName { - Concrete(Concrete), -} - #[derive(GraphQLUnion)] #[graphql(on Concrete = WithLifetime::resolve)] enum WithLifetime<'a> { @@ -53,45 +47,13 @@ impl<'a> WithLifetime<'a> { } } -#[derive(GraphQLUnion)] -#[graphql(on Concrete = WithGenerics::resolve)] -enum WithGenerics { - #[graphql(ignore)] - Generic(T), -} - -impl WithGenerics { - fn resolve(&self, _: &()) -> Option<&Concrete> { - if matches!(self, Self::Generic(_)) { - Some(&Concrete) - } else { - None - } - } -} - -#[derive(GraphQLUnion)] -#[graphql(description = "A description")] -enum DescriptionFirst { - Concrete(Concrete), -} - struct Root; #[graphql_object] impl Root { - fn custom_name() -> CustomName { - CustomName::Concrete(Concrete) - } fn with_lifetime(&self) -> WithLifetime<'_> { WithLifetime::Int(PhantomData) } - fn with_generics() -> WithGenerics { - WithGenerics::Generic(123) - } - fn description_first() -> DescriptionFirst { - DescriptionFirst::Concrete(Concrete) - } } async fn run_type_info_query(type_name: &str, f: F) @@ -143,24 +105,6 @@ where f(type_info, possible_types); } -#[tokio::test] -async fn introspect_custom_name() { - run_type_info_query("ACustomNamedUnion", |union, possible_types| { - assert_eq!( - union.get_field_value("name"), - Some(&Value::scalar("ACustomNamedUnion")) - ); - assert_eq!(union.get_field_value("description"), Some(&Value::null())); - - assert!(possible_types.contains(&Value::object( - vec![("name", Value::scalar("Concrete"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - #[tokio::test] async fn introspect_with_lifetime() { run_type_info_query("WithLifetime", |union, possible_types| { @@ -178,42 +122,3 @@ async fn introspect_with_lifetime() { }) .await; } - -#[tokio::test] -async fn introspect_with_generics() { - run_type_info_query("WithGenerics", |union, possible_types| { - assert_eq!( - union.get_field_value("name"), - Some(&Value::scalar("WithGenerics")) - ); - assert_eq!(union.get_field_value("description"), Some(&Value::null())); - - assert!(possible_types.contains(&Value::object( - vec![("name", Value::scalar("Concrete"))] - .into_iter() - .collect(), - ))); - }) - .await; -} - -#[tokio::test] -async fn introspect_description_first() { - run_type_info_query("DescriptionFirst", |union, possible_types| { - assert_eq!( - union.get_field_value("name"), - Some(&Value::scalar("DescriptionFirst")) - ); - assert_eq!( - union.get_field_value("description"), - Some(&Value::scalar("A description")) - ); - - assert!(possible_types.contains(&Value::object( - vec![("name", Value::scalar("Concrete"))] - .into_iter() - .collect(), - ))); - }) - .await; -} From 908b388a74935a0f9dc56dbcd010ab15a33a65c2 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 9 Aug 2021 14:11:08 +0300 Subject: [PATCH 29/39] Integration tests for objects, vol.6 --- .../src/codegen/interface_attr.rs | 3 +- .../juniper_tests/src/codegen/object_attr.rs | 13 +- .../juniper_tests/src/codegen/union_derive.rs | 95 ++++++++++ juniper/src/macros/tests/field.rs | 173 ------------------ juniper/src/macros/tests/mod.rs | 2 - juniper/src/macros/tests/union.rs | 124 ------------- 6 files changed, 109 insertions(+), 301 deletions(-) delete mode 100644 juniper/src/macros/tests/field.rs delete mode 100644 juniper/src/macros/tests/union.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index c6012ed60..54d2e6e12 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2657,6 +2657,7 @@ mod description_from_doc_comment { #[graphql_interface(for = Human)] trait Character { /// Rust `id` docs. + /// Long. fn id(&self) -> &str; } @@ -2705,7 +2706,7 @@ mod description_from_doc_comment { Ok(( graphql_value!({"__type": { "description": "Rust docs.", - "fields": [{"description": "Rust `id` docs."}], + "fields": [{"description": "Rust `id` docs.\nLong."}], }}), vec![], )), diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 97be3b5cb..dffc107b9 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -363,6 +363,10 @@ mod fallible_field { fn id(&self) -> Result<&str, CustomError> { Ok(&self.id) } + + async fn home_planet<__S>() -> FieldResult<&'static str, __S> { + Ok("earth") + } } #[derive(Clone, Copy)] @@ -382,6 +386,7 @@ mod fallible_field { const DOC: &str = r#"{ human { id + homePlanet } }"#; @@ -389,7 +394,10 @@ mod fallible_field { assert_eq!( execute(DOC, None, &schema, &Variables::new(), &()).await, - Ok((graphql_value!({"human": {"id": "human-32"}}), vec![])), + Ok(( + graphql_value!({"human": {"id": "human-32", "homePlanet": "earth"}}), + vec![], + )), ); } @@ -422,6 +430,9 @@ mod fallible_field { "fields": [{ "name": "id", "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }, { + "name": "homePlanet", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, }] }}), vec![], diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index 2f692a7d3..6fd203649 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -284,6 +284,101 @@ mod generic_enum { } } +/* TODO: make it work +mod generic_lifetime_enum { + use super::*; + + #[derive(GraphQLObject)] + struct LifetimeHuman<'id> { + id: &'id str, + } + + #[derive(GraphQLObject)] + struct GenericDroid { + id: String, + #[graphql(ignore)] + _t: PhantomData, + } + + #[derive(GraphQLUnion)] + enum Character<'id, B = ()> { + A(LifetimeHuman<'id>), + B(GenericDroid), + } + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object] + impl QueryRoot { + fn character(&self) -> Character<'_> { + match self { + Self::Human => Character::A(LifetimeHuman { id: "human-32" }), + Self::Droid => Character::B(GenericDroid { + id: "droid-99".to_string(), + _t: PhantomData, + }), + } + } + } + + const DOC: &str = r#"{ + character { + ... on LifetimeHuman { + humanId: id + } + ... on GenericDroid { + droidId: id + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } +} +*/ + mod description_from_doc_comments { use super::*; diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs deleted file mode 100644 index f0faa0575..000000000 --- a/juniper/src/macros/tests/field.rs +++ /dev/null @@ -1,173 +0,0 @@ -/* - -Syntax to validate: - -* Object vs. interface -* Description vs. no description -* Deprecated vs. not deprecated -* FieldResult vs. object directly -* Return vs. implicit return - -*/ - -#![allow(deprecated)] - -use crate::{ - ast::InputValue, - executor::FieldResult, - graphql_interface, graphql_object, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, Value}, -}; - -#[derive(Debug)] -struct Root; - -#[graphql_object(impl = InterfaceValue)] -impl Root { - /// Get the i32 representation of 0. - /// - /// - This comment is longer. - /// - These two lines are rendered as bullets by GraphiQL. - /// - subsection - fn attr_description_long() -> i32 { - 0 - } - - fn with_field_result() -> FieldResult { - Ok(0) - } - - fn with_return_field_result() -> FieldResult { - Ok(0) - } -} - -#[graphql_interface] -impl Interface for Root { - fn attr_description_long(&self) -> i32 { - 0 - } -} - -#[graphql_interface(for = Root)] -trait Interface { - /// Get the i32 representation of 0. - /// - /// - This comment is longer. - /// - These two lines are rendered as bullets by GraphiQL. - fn attr_description_long(&self) -> i32; -} - -async fn run_field_info_query(type_name: &str, field_name: &str, f: F) -where - F: Fn(&Object) -> (), -{ - let doc = r#" - query ($typeName: String!) { - __type(name: $typeName) { - fields(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - } - } - "#; - let schema = RootNode::new( - Root {}, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))] - .into_iter() - .collect(); - - let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 fields = type_info - .get_field_value("fields") - .expect("fields field missing") - .as_list_value() - .expect("fields not a list"); - - let field = fields - .into_iter() - .filter(|f| { - f.as_object_value() - .expect("Field not an object") - .get_field_value("name") - .expect("name field missing from field") - .as_scalar_value::() - .expect("name is not a string") - == field_name - }) - .next() - .expect("Field not found") - .as_object_value() - .expect("Field is not an object"); - - println!("Field: {:#?}", field); - - f(field); -} - -#[tokio::test] -async fn introspect_object_field_attr_description_long() { - run_field_info_query("Root", "attrDescriptionLong", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDescriptionLong")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Get the i32 representation of 0.\n\n- This comment is longer.\n- These two lines are rendered as bullets by GraphiQL.\n - subsection")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }).await; -} - -#[tokio::test] -async fn introspect_interface_field_attr_description_long() { - run_field_info_query("Interface", "attrDescriptionLong", |field| { - assert_eq!( - field.get_field_value("name"), - Some(&Value::scalar("attrDescriptionLong")) - ); - assert_eq!( - field.get_field_value("description"), - Some(&Value::scalar("Get the i32 representation of 0.\n\n- This comment is longer.\n- These two lines are rendered as bullets by GraphiQL.")) - ); - assert_eq!( - field.get_field_value("isDeprecated"), - Some(&Value::scalar(false)) - ); - assert_eq!( - field.get_field_value("deprecationReason"), - Some(&Value::null()) - ); - }).await; -} diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index 757526a7d..b07a2d8f3 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -1,4 +1,2 @@ -mod field; mod impl_subscription; -mod union; mod util; diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs deleted file mode 100644 index 90f306a83..000000000 --- a/juniper/src/macros/tests/union.rs +++ /dev/null @@ -1,124 +0,0 @@ -/* - -Syntax to validate: - -* Order of items: description, instance resolvers -* Optional Generics/lifetimes -* Custom name vs. default name -* Optional commas between items -* Optional trailing commas on instance resolvers -* -*/ - -use std::marker::PhantomData; - -use crate::{ - ast::InputValue, - graphql_object, - schema::model::RootNode, - types::scalars::{EmptyMutation, EmptySubscription}, - value::{DefaultScalarValue, Object, Value}, - GraphQLUnion, -}; - -struct Concrete; - -#[graphql_object] -impl Concrete { - fn simple() -> i32 { - 123 - } -} - -#[derive(GraphQLUnion)] -#[graphql(on Concrete = WithLifetime::resolve)] -enum WithLifetime<'a> { - #[graphql(ignore)] - Int(PhantomData<&'a i32>), -} - -impl<'a> WithLifetime<'a> { - fn resolve(&self, _: &()) -> Option<&Concrete> { - if matches!(self, Self::Int(_)) { - Some(&Concrete) - } else { - None - } - } -} - -struct Root; - -#[graphql_object] -impl Root { - fn with_lifetime(&self) -> WithLifetime<'_> { - WithLifetime::Int(PhantomData) - } -} - -async fn run_type_info_query(type_name: &str, f: F) -where - F: Fn(&Object, &Vec>) -> (), -{ - let doc = r#" - query ($typeName: String!) { - __type(name: $typeName) { - name - description - possibleTypes { - name - } - } - } - "#; - let schema = RootNode::new( - Root {}, - EmptyMutation::<()>::new(), - EmptySubscription::<()>::new(), - ); - let vars = vec![("typeName".to_owned(), InputValue::scalar(type_name))] - .into_iter() - .collect(); - - let (result, errs) = crate::execute(doc, None, &schema, &vars, &()) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - - println!("Result: {:#?}", result); - - 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 possible_types = type_info - .get_field_value("possibleTypes") - .expect("possibleTypes field missing") - .as_list_value() - .expect("possibleTypes field not a list value"); - - f(type_info, possible_types); -} - -#[tokio::test] -async fn introspect_with_lifetime() { - run_type_info_query("WithLifetime", |union, possible_types| { - assert_eq!( - union.get_field_value("name"), - Some(&Value::scalar("WithLifetime")) - ); - assert_eq!(union.get_field_value("description"), Some(&Value::null())); - - assert!(possible_types.contains(&Value::object( - vec![("name", Value::scalar("Concrete"))] - .into_iter() - .collect(), - ))); - }) - .await; -} From 0564666ab537327ed34b07a4390575b20daa71a4 Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 9 Aug 2021 18:58:19 +0300 Subject: [PATCH 30/39] Integration tests for subscriptions, vol.1 --- .../juniper_tests/src/codegen/mod.rs | 11 +- .../juniper_tests/src/codegen/object_attr.rs | 10 +- .../src/codegen/subscription_attr.rs | 1287 +++++++++++++++++ juniper/src/macros/tests/impl_subscription.rs | 235 --- juniper/src/macros/tests/mod.rs | 1 - juniper/src/macros/tests/util.rs | 69 - 6 files changed, 1295 insertions(+), 318 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/subscription_attr.rs delete mode 100644 juniper/src/macros/tests/util.rs diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index bd04d4dd8..b407f0f81 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -3,9 +3,10 @@ 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 interface_attr; +//mod object_attr; +//mod object_derive; mod scalar_value_transparent; -mod union_attr; -mod union_derive; +mod subscription_attr; +//mod union_attr; +//mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index dffc107b9..c16d0a31e 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -1912,17 +1912,11 @@ mod executor { &self, arg: String, #[graphql(executor)] _another: &Executor<'_, '_, (), S>, - ) -> String - where - S: ScalarValue, - { + ) -> String { arg } - fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> &'e str - where - S: ScalarValue, - { + fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> &'e str { "no info" } } diff --git a/integration_tests/juniper_tests/src/codegen/subscription_attr.rs b/integration_tests/juniper_tests/src/codegen/subscription_attr.rs new file mode 100644 index 000000000..0e26ac41d --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/subscription_attr.rs @@ -0,0 +1,1287 @@ +//! Tests for `#[graphql_subscription]` macro. + +use std::pin::Pin; + +use futures::{future, stream, FutureExt as _, StreamExt as _}; +use juniper::{ + execute, graphql_object, graphql_subscription, graphql_value, resolve_into_stream, + DefaultScalarValue, EmptyMutation, ExecutionError, Executor, FieldError, FieldResult, + GraphQLError, GraphQLInputObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, Value, + ValuesStream, Variables, +}; + +struct Query; + +#[graphql_object] +impl Query { + fn empty() -> bool { + true + } +} + +fn schema<'q, C, Qry, Sub>( + query_root: Qry, + subscription_root: Sub, +) -> RootNode<'q, Qry, EmptyMutation, Sub> +where + Qry: GraphQLType + 'q, + Sub: GraphQLType + 'q, +{ + RootNode::new(query_root, EmptyMutation::::new(), subscription_root) +} + +fn schema_with_scalar<'q, S, C, Qry, Sub>( + query_root: Qry, + subscription_root: Sub, +) -> RootNode<'q, Qry, EmptyMutation, Sub, S> +where + Qry: GraphQLType + 'q, + Sub: GraphQLType + 'q, + S: ScalarValue + 'q, +{ + RootNode::new_with_scalar_value(query_root, EmptyMutation::::new(), subscription_root) +} + +type Stream<'a, I> = Pin + Send + 'a>>; + +async fn extract_next<'a, S: ScalarValue>( + input: Result<(Value>, Vec>), GraphQLError<'a>>, +) -> Result<(Value, Vec>), GraphQLError<'a>> { + let (stream, errs) = input?; + if !errs.is_empty() { + return Ok((Value::Null, errs)); + } + + if let Value::Object(obj) = stream { + for (name, mut val) in obj { + if let Value::Scalar(ref mut stream) = val { + return match stream.next().await { + Some(Ok(val)) => Ok((graphql_value!({ name: val }), vec![])), + Some(Err(e)) => Ok((Value::Null, vec![e])), + None => Ok((Value::Null, vec![])), + }; + } + } + } + + panic!("Expected to get Value::Object containing a Stream") +} + +mod trivial { + use super::*; + + struct Human; + + #[graphql_subscription] + impl Human { + async fn id() -> Stream<'static, String> { + Box::pin(stream::once(future::ready("human-32".to_owned()))) + } + + // TODO: Make work for `Stream<'_, String>`. + async fn home_planet(&self) -> Stream<'static, String> { + Box::pin(stream::once(future::ready("earth".to_owned()))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth"}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_object() { + const DOC: &str = r#"{ + __type(name: "Human") { + kind + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"kind": "OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"description": None}}), vec![])), + ); + } +} + +mod raw_method { + use super::*; + + struct Human; + + #[graphql_subscription] + impl Human { + async fn r#my_id() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + async fn r#async(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("async-32"))) + } + } + + #[tokio::test] + async fn resolves_my_id_field() { + const DOC: &str = r#"subscription { + myId + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"myId": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_async_field() { + const DOC: &str = r#"subscription { + async + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"async": "async-32"}), vec![])), + ); + } + + #[tokio::test] + async fn has_correct_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + kind + fields { + name + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Human", + "kind": "OBJECT", + "fields": [{"name": "myId"}, {"name": "async"}], + }}), + vec![], + )), + ); + } +} + +mod ignored_method { + use super::*; + + struct Human; + + #[graphql_subscription] + impl Human { + async fn id() -> Stream<'static, String> { + Box::pin(stream::once(future::ready("human-32".to_owned()))) + } + + #[allow(dead_code)] + #[graphql(ignore)] + fn planet() -> &'static str { + "earth" + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn is_not_field() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{"name": "id"}]}}), + vec![], + )), + ); + } +} + +mod argument { + use super::*; + + struct Human; + + #[graphql_subscription] + impl Human { + async fn id(arg: String) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(arg))) + } + + async fn home_planet( + &self, + r#raw_arg: String, + r#async: Option, + ) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(format!( + "{},{:?}", + r#raw_arg, r#async + )))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id(arg: "human-32") + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet(rawArg: "earth") + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth,None"}), vec![])), + ); + } + + #[tokio::test] + async fn has_correct_name() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": [{"name": "arg"}]}, + {"name": "homePlanet", "args": [{"name": "rawArg"}, {"name": "async"}]}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_no_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + args { + description + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"description": None}]}, + {"args": [{"description": None}, {"description": None}]}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn has_no_defaults() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + args { + defaultValue + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"defaultValue": None}]}, + {"args": [{"defaultValue": None}, {"defaultValue": None}]}, + ]}}), + vec![], + )), + ); + } +} + +mod default_argument { + use super::*; + + #[derive(GraphQLInputObject, Debug)] + struct Point { + x: i32, + } + + struct Human; + + #[graphql_subscription] + impl Human { + async fn id( + &self, + #[graphql(default)] arg1: i32, + #[graphql(default = "second".to_string())] arg2: String, + #[graphql(default = true)] r#arg3: bool, + ) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(format!( + "{}|{}&{}", + arg1, arg2, r#arg3 + )))) + } + + async fn info(#[graphql(default = Point { x: 1 })] coord: Point) -> Stream<'static, i32> { + Box::pin(stream::once(future::ready(coord.x))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + let schema = schema(Query, Human); + + for (input, expected) in &[ + ("subscription { id }", "0|second&true"), + ("subscription { id(arg1: 1) }", "1|second&true"), + (r#"subscription { id(arg2: "") }"#, "0|&true"), + (r#"subscription { id(arg1: 2, arg2: "") }"#, "2|&true"), + ( + r#"subscription { id(arg1: 1, arg2: "", arg3: false) }"#, + "1|&false", + ), + ] { + let expected: &str = *expected; + + assert_eq!( + resolve_into_stream(*input, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({ "id": expected }), vec![])), + ); + } + } + + #[tokio::test] + async fn resolves_info_field() { + let schema = schema(Query, Human); + + for (input, expected) in &[ + ("subscription { info }", 1), + ("subscription { info(coord: { x: 2 }) }", 2), + ] { + let expected: i32 = *expected; + + assert_eq!( + resolve_into_stream(*input, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({ "info": expected }), vec![])), + ); + } + } + + #[tokio::test] + async fn has_defaults() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + args { + name + defaultValue + type { + name + ofType { + name + } + } + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{ + "args": [{ + "name": "arg1", + "defaultValue": "0", + "type": {"name": "Int", "ofType": None}, + }, { + "name": "arg2", + "defaultValue": r#""second""#, + "type": {"name": "String", "ofType": None}, + }, { + "name": "arg3", + "defaultValue": "true", + "type": {"name": "Boolean", "ofType": None}, + }], + }, { + "args": [{ + "name": "coord", + "defaultValue": "{x: 1}", + "type": {"name": "Point", "ofType": None}, + }], + }]}}), + vec![], + )), + ); + } +} + +mod description_from_doc_comment { + use super::*; + + struct Human; + + /// Rust docs. + #[graphql_subscription] + impl Human { + /// Rust `id` docs. + /// Here. + async fn id() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + } + + #[tokio::test] + async fn uses_doc_comment_as_description() { + const DOC: &str = r#"{ + __type(name: "Human") { + description + fields { + description + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "Rust docs.", + "fields": [{"description": "Rust `id` docs.\nHere."}], + }}), + vec![], + )), + ); + } +} + +mod deprecation_from_attr { + use super::*; + + struct Human; + + #[graphql_subscription] + impl Human { + async fn id() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + #[deprecated] + async fn a(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("a"))) + } + + #[deprecated(note = "Use `id`.")] + async fn b(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("b"))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_a_field() { + const DOC: &str = r#"subscription { + a + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"a": "a"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_b_field() { + const DOC: &str = r#"subscription { + b + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"b": "b"}), vec![])), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "isDeprecated": false}, + {"name": "a", "isDeprecated": true}, + {"name": "b", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "deprecationReason": None}, + {"name": "a", "deprecationReason": None}, + {"name": "b", "deprecationReason": "Use `id`."}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_name_description_and_deprecation { + use super::*; + + struct Human; + + /// Rust docs. + #[graphql_subscription(name = "MyHuman", desc = "My human.")] + impl Human { + /// Rust `id` docs. + #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")] + #[deprecated(note = "Should be omitted.")] + async fn id( + #[graphql(name = "myName", desc = "My argument.", default)] _n: String, + ) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + async fn a(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("a"))) + } + + async fn b(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("b"))) + } + } + + #[tokio::test] + async fn resolves_deprecated_id_field() { + const DOC: &str = r#"subscription { + myId + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"myId": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_deprecated_a_field() { + const DOC: &str = r#"subscription { + a + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"a": "a"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_b_field() { + const DOC: &str = r#"subscription { + b + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"b": "b"}), vec![])), + ); + } + + #[tokio::test] + async fn uses_custom_name() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + name + fields(includeDeprecated: true) { + name + args { + name + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "MyHuman", + "fields": [ + {"name": "myId", "args": [{"name": "myName"}]}, + {"name": "a", "args": []}, + {"name": "b", "args": []}, + ], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_description() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + description + fields(includeDeprecated: true) { + name + description + args { + description + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "description": "My human.", + "fields": [{ + "name": "myId", + "description": "My human ID.", + "args": [{"description": "My argument."}], + }, { + "name": "a", + "description": None, + "args": [], + }, { + "name": "b", + "description": None, + "args": [], + }], + }}), + vec![], + )), + ); + } + + #[tokio::test] + async fn uses_custom_deprecation() { + const DOC: &str = r#"{ + __type(name: "MyHuman") { + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "fields": [{ + "name": "myId", + "isDeprecated": true, + "deprecationReason": "Not used.", + }, { + "name": "a", + "isDeprecated": true, + "deprecationReason": None, + }, { + "name": "b", + "isDeprecated": false, + "deprecationReason": None, + }], + }}), + vec![], + )), + ); + } +} + +mod renamed_all_fields_and_args { + use super::*; + + struct Human; + + #[graphql_subscription(rename_all = "none")] + impl Human { + async fn id() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + async fn home_planet(&self, planet_name: String) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(planet_name))) + } + + async fn r#async_info(r#my_num: i32) -> Stream<'static, i32> { + Box::pin(stream::once(future::ready(r#my_num))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + home_planet(planet_name: "earth") + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"home_planet": "earth"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_async_info_field() { + const DOC: &str = r#"subscription { + async_info(my_num: 3) + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"async_info": 3}), vec![])), + ); + } + + #[tokio::test] + async fn uses_correct_fields_and_args_names() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "home_planet", "args": [{"name": "planet_name"}]}, + {"name": "async_info", "args": [{"name": "my_num"}]}, + ]}}), + vec![], + )), + ); + } +} + +mod explicit_custom_context { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn empty() -> bool { + true + } + } + + struct Human; + + #[graphql_subscription(context = CustomContext)] + impl Human { + // TODO: Make work for `Stream<'c, String>`. + async fn id<'c>(&self, context: &'c CustomContext) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(context.0.clone()))) + } + + // TODO: Make work for `Stream<'_, String>`. + async fn info(_ctx: &()) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human being"))) + } + + async fn more(#[graphql(context)] custom: &CustomContext) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(custom.0.clone()))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(QueryRoot, Human); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "ctx!"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_info_field() { + const DOC: &str = r#"subscription { + info + }"#; + + let schema = schema(QueryRoot, Human); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"info": "human being"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_more_field() { + const DOC: &str = r#"subscription { + more + }"#; + + let schema = schema(QueryRoot, Human); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"more": "ctx!"}), vec![])), + ); + } +} + +mod inferred_custom_context_from_field { + use super::*; + + struct CustomContext(String); + + impl juniper::Context for CustomContext {} + + struct QueryRoot; + + #[graphql_object(context = CustomContext)] + impl QueryRoot { + fn empty() -> bool { + true + } + } + + struct Human; + + #[graphql_subscription] + impl Human { + // TODO: Make work for `Stream<'c, String>`. + async fn id<'c>(&self, context: &'c CustomContext) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(context.0.clone()))) + } + + // TODO: Make work for `Stream<'_, String>`. + async fn info(_ctx: &()) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human being"))) + } + + async fn more(#[graphql(context)] custom: &CustomContext) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(custom.0.clone()))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(QueryRoot, Human); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "ctx!"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_info_field() { + const DOC: &str = r#"subscription { + info + }"#; + + let schema = schema(QueryRoot, Human); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"info": "human being"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_more_field() { + const DOC: &str = r#"subscription { + more + }"#; + + let schema = schema(QueryRoot, Human); + let ctx = CustomContext("ctx!".into()); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &ctx) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"more": "ctx!"}), vec![])), + ); + } +} + +mod executor { + use juniper::LookAheadMethods as _; + + use super::*; + + struct Human; + + #[graphql_subscription(scalar = S: ScalarValue)] + impl Human { + async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> Stream<'e, &'e str> + where + S: ScalarValue, + { + Box::pin(stream::once(future::ready( + executor.look_ahead().field_name(), + ))) + } + + async fn info( + &self, + arg: String, + #[graphql(executor)] _another: &Executor<'_, '_, (), S>, + ) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(arg))) + } + + async fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> Stream<'e, &'e str> { + Box::pin(stream::once(future::ready("no info"))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "id"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_info_field() { + const DOC: &str = r#"subscription { + info(arg: "input!") + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"info": "input!"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_info2_field() { + const DOC: &str = r#"subscription { + info2 + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"info2": "no info"}), vec![])), + ); + } + + #[tokio::test] + async fn not_arg() { + const DOC: &str = r#"{ + __type(name: "Human") { + fields { + name + args { + name + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "info", "args": [{"name": "arg"}]}, + {"name": "info2", "args": []}, + ]}}), + vec![], + )), + ); + } +} diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs index bdec72bec..918629658 100644 --- a/juniper/src/macros/tests/impl_subscription.rs +++ b/juniper/src/macros/tests/impl_subscription.rs @@ -7,8 +7,6 @@ use crate::{ EmptyMutation, Executor, RootNode, Value, }; -use super::util; - #[derive(Default)] struct Context { flag1: bool, @@ -70,16 +68,6 @@ struct Subscription { )] /// Subscription Description. impl Subscription { - #[graphql(description = "With Self Description")] - async fn with_self(&self) -> Stream { - let b = self.b; - Box::pin(stream::once(async move { b })) - } - - async fn independent() -> Stream { - Box::pin(stream::once(async { 100 })) - } - async fn with_executor(_executor: &Executor<'_, '_, Context>) -> Stream { Box::pin(stream::once(async { true })) } @@ -88,43 +76,6 @@ impl Subscription { Box::pin(stream::once(async { true })) } - async fn with_context(_context: &Context) -> Stream { - Box::pin(stream::once(async { true })) - } - - async fn with_context_and_self(&self, _context: &Context) -> Stream { - Box::pin(stream::once(async { true })) - } - - #[graphql(name = "renamed")] - async fn has_custom_name() -> Stream { - Box::pin(stream::once(async { true })) - } - - #[graphql(description = "attr")] - async fn has_description_attr() -> Stream { - Box::pin(stream::once(async { true })) - } - - /// Doc description - async fn has_description_doc_comment() -> Stream { - Box::pin(stream::once(async { true })) - } - - async fn has_argument(arg1: bool) -> Stream { - Box::pin(stream::once(async move { arg1 })) - } - - async fn default_argument(#[graphql(default = true)] default_arg: bool) -> Stream { - Box::pin(stream::once(async move { default_arg })) - } - - async fn arg_with_description( - #[graphql(description = "my argument description")] arg: bool, - ) -> Stream { - Box::pin(stream::once(async move { arg })) - } - async fn with_context_child(&self) -> Stream { Box::pin(stream::once(async { WithContext })) } @@ -132,190 +83,4 @@ impl Subscription { async fn with_implicit_lifetime_child(&self) -> Stream> { Box::pin(stream::once(async { WithLifetime { value: "blub" } })) } - - async fn with_mut_arg(mut arg: bool) -> Stream { - if arg { - arg = !arg; - } - - Box::pin(stream::once(async move { arg })) - } - - async fn without_type_alias() -> Pin + Send>> { - Box::pin(stream::once(async { "abc" })) - } -} - -#[tokio::test] -async fn object_introspect() { - let res = util::run_info_query::("Subscription").await; - assert_eq!( - res, - graphql_value!({ - "name": "Subscription", - "description": "Subscription Description.", - "fields": [{ - "name": "withSelf", - "description": "With Self Description", - "args": [], - }, { - "name": "independent", - "description": None, - "args": [], - }, { - "name": "withExecutor", - "description": None, - "args": [], - }, { - "name": "withExecutorAndSelf", - "description": None, - "args": [], - }, { - "name": "withContext", - "description": None, - "args": [], - }, { - "name": "withContextAndSelf", - "description": None, - "args": [], - }, { - "name": "renamed", - "description": None, - "args": [], - }, { - "name": "hasDescriptionAttr", - "description": "attr", - "args": [], - }, { - "name": "hasDescriptionDocComment", - "description": "Doc description", - "args": [], - }, { - "name": "hasArgument", - "description": None, - "args": [{ - "name": "arg1", - "description": None, - "type": {"name": None}, - }], - }, { - "name": "defaultArgument", - "description": None, - "args": [{ - "name": "defaultArg", - "description": None, - "type": {"name": "Boolean"}, - }], - }, { - "name": "argWithDescription", - "description": None, - "args": [{ - "name": "arg", - "description": "my argument description", - "type": {"name": None}, - }], - }, { - "name": "withContextChild", - "description": None, - "args": [], - }, { - "name": "withImplicitLifetimeChild", - "description": None, - "args": [], - }, { - "name": "withMutArg", - "description": None, - "args": [{ - "name": "arg", - "description": None, - "type": {"name": None}, - }], - }, { - "name": "withoutTypeAlias", - "description": None, - "args": [], - }], - }) - ); -} - -#[tokio::test] -async fn object_query() { - let doc = r#" - subscription { - withSelf - independent - withExecutor - withExecutorAndSelf - withContext - withContextAndSelf - renamed - hasArgument(arg1: true) - defaultArgument - argWithDescription(arg: true) - withContextChild { - ctx - } - withImplicitLifetimeChild { - value - } - withMutArg(arg: true) - withoutTypeAlias - } - "#; - let schema = RootNode::new( - Query, - EmptyMutation::::new(), - Subscription { b: true }, - ); - let vars = HashMap::new(); - - let (stream_val, errs) = - resolve_into_stream(doc, None, &schema, &vars, &Context { flag1: true }) - .await - .expect("Execution failed"); - - let result = if let Value::Object(obj) = stream_val { - let mut result = Vec::new(); - for (name, mut val) in obj { - if let Value::Scalar(ref mut stream) = val { - let first = stream - .next() - .await - .expect("Stream does not have the first element") - .expect(&format!("Error resolving {} field", name)); - result.push((name, first)) - } - } - result - } else { - panic!("Expected to get Value::Object ") - }; - - assert_eq!(errs, []); - assert_eq!( - result, - vec![ - ("withSelf".to_string(), graphql_value!(true)), - ("independent".to_string(), graphql_value!(100)), - ("withExecutor".to_string(), graphql_value!(true)), - ("withExecutorAndSelf".to_string(), graphql_value!(true)), - ("withContext".to_string(), graphql_value!(true)), - ("withContextAndSelf".to_string(), graphql_value!(true)), - ("renamed".to_string(), graphql_value!(true)), - ("hasArgument".to_string(), graphql_value!(true)), - ("defaultArgument".to_string(), graphql_value!(true)), - ("argWithDescription".to_string(), graphql_value!(true)), - ( - "withContextChild".to_string(), - graphql_value!({"ctx": true}), - ), - ( - "withImplicitLifetimeChild".to_string(), - graphql_value!({"value": "blub"}), - ), - ("withMutArg".to_string(), graphql_value!(false)), - ("withoutTypeAlias".to_string(), graphql_value!("abc")), - ] - ); } diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index b07a2d8f3..6f5c65a7e 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -1,2 +1 @@ mod impl_subscription; -mod util; diff --git a/juniper/src/macros/tests/util.rs b/juniper/src/macros/tests/util.rs deleted file mode 100644 index 42ccadd4c..000000000 --- a/juniper/src/macros/tests/util.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::{DefaultScalarValue, GraphQLType, GraphQLTypeAsync, RootNode, Value, Variables}; - -pub async fn run_query(query: &str) -> Value -where - Query: GraphQLTypeAsync + Default, - Query::Context: Default + Sync, - Mutation: - GraphQLTypeAsync + Default, - Subscription: - GraphQLType + Default + Sync, -{ - let schema = RootNode::new( - Query::default(), - Mutation::default(), - Subscription::default(), - ); - let (result, errs) = crate::execute( - query, - None, - &schema, - &Variables::new(), - &Query::Context::default(), - ) - .await - .expect("Execution failed"); - - assert_eq!(errs, []); - result -} - -pub async fn run_info_query(type_name: &str) -> Value -where - Query: GraphQLTypeAsync + Default, - Query::Context: Default + Sync, - Mutation: - GraphQLTypeAsync + Default, - Subscription: - GraphQLType + Default + Sync, -{ - let query = format!( - r#" - {{ - __type(name: "{}") {{ - name, - description, - fields {{ - name - description - args {{ - name - description - type {{ - name - }} - }} - }} - }} - }} - "#, - type_name - ); - let result = run_query::(&query).await; - result - .as_object_value() - .expect("Result is not an object") - .get_field_value("__type") - .expect("__type field missing") - .clone() -} From 7b78efd7d8f0dcf0015b45ad96d9dbaadaaa8134 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Aug 2021 12:42:48 +0300 Subject: [PATCH 31/39] Integration tests for subscriptions, vol.2 --- .../juniper_tests/src/codegen/mod.rs | 10 +- .../juniper_tests/src/codegen/object_attr.rs | 2 +- .../src/codegen/subscription_attr.rs | 541 +++++++++++++++++- juniper/src/macros/mod.rs | 3 - juniper/src/macros/tests/impl_subscription.rs | 86 --- juniper/src/macros/tests/mod.rs | 1 - 6 files changed, 544 insertions(+), 99 deletions(-) delete mode 100644 juniper/src/macros/tests/impl_subscription.rs delete mode 100644 juniper/src/macros/tests/mod.rs diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index b407f0f81..6348a66c8 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -3,10 +3,10 @@ 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 interface_attr; +mod object_attr; +mod object_derive; mod scalar_value_transparent; mod subscription_attr; -//mod union_attr; -//mod union_derive; +mod union_attr; +mod union_derive; diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index c16d0a31e..797a87fc8 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -343,7 +343,7 @@ mod ignored_method { } } -mod fallible_field { +mod fallible_method { use super::*; struct CustomError; diff --git a/integration_tests/juniper_tests/src/codegen/subscription_attr.rs b/integration_tests/juniper_tests/src/codegen/subscription_attr.rs index 0e26ac41d..f2357ca1b 100644 --- a/integration_tests/juniper_tests/src/codegen/subscription_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/subscription_attr.rs @@ -297,6 +297,102 @@ mod ignored_method { } } +mod fallible_method { + use super::*; + + struct CustomError; + + impl IntoFieldError for CustomError { + fn into_field_error(self) -> FieldError { + juniper::FieldError::new("Whatever", graphql_value!({"code": "some"})) + } + } + + struct Human; + + #[graphql_subscription] + impl Human { + async fn id(&self) -> Result, CustomError> { + Ok(Box::pin(stream::once(future::ready("human-32".to_owned())))) + } + + async fn home_planet<__S>() -> FieldResult, __S> { + Ok(Box::pin(stream::once(future::ready("earth")))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth"}), vec![])), + ); + } + + #[tokio::test] + async fn has_correct_graphql_type() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + kind + fields { + name + type { + kind + ofType { + name + } + } + } + } + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Human", + "kind": "OBJECT", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }, { + "name": "homePlanet", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }] + }}), + vec![], + )), + ); + } +} + mod argument { use super::*; @@ -556,6 +652,239 @@ mod default_argument { } } +mod generic { + use super::*; + + struct Human { + id: A, + _home_planet: B, + } + + #[graphql_subscription] + impl Human { + async fn id(&self) -> Stream<'static, i32> { + Box::pin(stream::once(future::ready(self.id))) + } + } + + #[graphql_subscription(name = "HumanString")] + impl Human { + async fn id(&self) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(self.id.to_owned()))) + } + } + + #[tokio::test] + async fn resolves_human() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema( + Query, + Human { + id: 34i32, + _home_planet: (), + }, + ); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": 34}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_human_string() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema( + Query, + Human { + id: "human-32".to_owned(), + _home_planet: (), + }, + ); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema( + Query, + Human { + id: 0i32, + _home_planet: (), + }, + ); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + +mod generic_lifetime { + use super::*; + + struct Human<'p, A = ()> { + id: A, + home_planet: &'p str, + } + + #[graphql_subscription] + impl<'p> Human<'p, i32> { + async fn id(&self) -> Stream<'static, i32> { + Box::pin(stream::once(future::ready(self.id))) + } + + // TODO: Make it work with `Stream<'_, &str>`. + async fn planet(&self) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(self.home_planet.to_owned()))) + } + } + + #[graphql_subscription(name = "HumanString")] + impl<'id, 'p> Human<'p, &'id str> { + // TODO: Make it work with `Stream<'_, &str>`. + async fn id(&self) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(self.id.to_owned()))) + } + + // TODO: Make it work with `Stream<'_, &str>`. + async fn planet(&self) -> Stream<'static, String> { + Box::pin(stream::once(future::ready(self.home_planet.to_owned()))) + } + } + + #[tokio::test] + async fn resolves_human_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema( + Query, + Human { + id: 34i32, + home_planet: "earth", + }, + ); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": 34}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_human_planet_field() { + const DOC: &str = r#"subscription { + planet + }"#; + + let schema = schema( + Query, + Human { + id: 34i32, + home_planet: "earth", + }, + ); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"planet": "earth"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_human_string_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema( + Query, + Human { + id: "human-32", + home_planet: "mars", + }, + ); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_human_string_planet_field() { + const DOC: &str = r#"subscription { + planet + }"#; + + let schema = schema( + Query, + Human { + id: "human-32", + home_planet: "mars", + }, + ); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"planet": "mars"}), vec![])), + ); + } + + #[tokio::test] + async fn uses_type_name_without_type_params() { + const DOC: &str = r#"{ + __type(name: "Human") { + name + } + }"#; + + let schema = schema( + Query, + Human { + id: 34i32, + home_planet: "earth", + }, + ); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok((graphql_value!({"__type": {"name": "Human"}}), vec![])), + ); + } +} + mod description_from_doc_comment { use super::*; @@ -1004,6 +1333,208 @@ mod renamed_all_fields_and_args { } } +mod explicit_scalar { + use super::*; + + struct Human; + + #[graphql_subscription(scalar = DefaultScalarValue)] + impl Human { + async fn id(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + async fn home_planet() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("earth"))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth"}), vec![])), + ); + } +} + +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + struct Human; + + #[graphql_subscription(scalar = MyScalarValue)] + impl Human { + async fn id(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + async fn home_planet() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("earth"))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema_with_scalar::(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet + }"#; + + let schema = schema_with_scalar::(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth"}), vec![])), + ); + } +} + +mod explicit_generic_scalar { + use std::marker::PhantomData; + + use super::*; + + struct Human(PhantomData); + + #[graphql_subscription(scalar = S)] + impl Human { + async fn id(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + async fn home_planet(_executor: &Executor<'_, '_, (), S>) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("earth"))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human::(PhantomData)); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet + }"#; + + let schema = schema(Query, Human::(PhantomData)); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth"}), vec![])), + ); + } +} + +mod bounded_generic_scalar { + use super::*; + + struct Human; + + #[graphql_subscription(scalar = S: ScalarValue + Clone)] + impl Human { + async fn id(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("human-32"))) + } + + async fn home_planet( + _executor: &Executor<'_, '_, (), S>, + ) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("earth"))) + } + } + + #[tokio::test] + async fn resolves_id_field() { + const DOC: &str = r#"subscription { + id + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"id": "human-32"}), vec![])), + ); + } + + #[tokio::test] + async fn resolves_home_planet_field() { + const DOC: &str = r#"subscription { + homePlanet + }"#; + + let schema = schema(Query, Human); + + assert_eq!( + resolve_into_stream(DOC, None, &schema, &Variables::new(), &()) + .then(|s| extract_next(s)) + .await, + Ok((graphql_value!({"homePlanet": "earth"}), vec![])), + ); + } +} + mod explicit_custom_context { use super::*; @@ -1187,12 +1718,13 @@ mod executor { #[graphql_subscription(scalar = S: ScalarValue)] impl Human { - async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> Stream<'e, &'e str> + // TODO: Make work for `Stream<'e, &'e str>`. + async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> Stream<'static, String> where S: ScalarValue, { Box::pin(stream::once(future::ready( - executor.look_ahead().field_name(), + executor.look_ahead().field_name().to_owned(), ))) } @@ -1204,7 +1736,10 @@ mod executor { Box::pin(stream::once(future::ready(arg))) } - async fn info2<'e, S>(_executor: &'e Executor<'_, '_, (), S>) -> Stream<'e, &'e str> { + // TODO: Make work for `Stream<'e, &'e str>`. + async fn info2<'e, S>( + _executor: &'e Executor<'_, '_, (), S>, + ) -> Stream<'static, &'static str> { Box::pin(stream::once(future::ready("no info"))) } } diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index b38958dae..241878e50 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,6 +1,3 @@ //! Helper definitions for macros. pub mod helper; - -#[cfg(test)] -mod tests; diff --git a/juniper/src/macros/tests/impl_subscription.rs b/juniper/src/macros/tests/impl_subscription.rs deleted file mode 100644 index 918629658..000000000 --- a/juniper/src/macros/tests/impl_subscription.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::{collections::HashMap, pin::Pin}; - -use futures::{stream, StreamExt as _}; - -use crate::{ - graphql_object, graphql_subscription, graphql_value, resolve_into_stream, DefaultScalarValue, - EmptyMutation, Executor, RootNode, Value, -}; - -#[derive(Default)] -struct Context { - flag1: bool, -} - -impl crate::Context for Context {} - -struct WithLifetime<'a> { - value: &'a str, -} - -#[graphql_object(context = Context)] -impl<'a> WithLifetime<'a> { - fn value(&self) -> &str { - self.value - } -} - -struct WithContext; - -#[graphql_object(context = Context)] -impl WithContext { - fn ctx(ctx: &Context) -> bool { - ctx.flag1 - } -} - -#[derive(Default)] -struct Query; - -#[graphql_object(context = Context)] -impl Query { - fn empty() -> bool { - true - } -} - -#[derive(Default)] -struct Mutation; - -#[graphql_object(context = Context)] -impl Mutation { - fn empty() -> bool { - true - } -} - -type Stream = Pin + Send>>; - -#[derive(Default)] -struct Subscription { - b: bool, -} - -#[graphql_subscription( - name = "Subscription", - context = Context, - scalar = DefaultScalarValue, -)] -/// Subscription Description. -impl Subscription { - async fn with_executor(_executor: &Executor<'_, '_, Context>) -> Stream { - Box::pin(stream::once(async { true })) - } - - async fn with_executor_and_self(&self, _executor: &Executor<'_, '_, Context>) -> Stream { - Box::pin(stream::once(async { true })) - } - - async fn with_context_child(&self) -> Stream { - Box::pin(stream::once(async { WithContext })) - } - - async fn with_implicit_lifetime_child(&self) -> Stream> { - Box::pin(stream::once(async { WithLifetime { value: "blub" } })) - } -} diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs deleted file mode 100644 index 6f5c65a7e..000000000 --- a/juniper/src/macros/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod impl_subscription; From 510f6ac942bb2cc7ed16d77c338db581db073115 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Aug 2021 13:40:12 +0300 Subject: [PATCH 32/39] Codegen failure tests for objects --- .../object/argument_double_underscored.rs | 12 ++++++++++++ ...err => argument_double_underscored.stderr} | 6 +++--- .../fail/object/argument_non_input_type.rs | 17 +++++++++++++++++ .../object/argument_non_input_type.stderr | 16 ++++++++++++++++ .../object/argument_wrong_default_array.rs | 12 ++++++++++++ ...rr => argument_wrong_default_array.stderr} | 8 ++++---- .../object/attr_field_double_underscored.rs | 12 ++++++++++++ .../attr_field_double_underscored.stderr | 7 +++++++ .../attr_field_non_output_return_type.rs | 17 +++++++++++++++++ .../attr_field_non_output_return_type.stderr | 8 ++++++++ .../fail/object/attr_fields_duplicate.rs | 17 +++++++++++++++++ ...ue.stderr => attr_fields_duplicate.stderr} | 6 +++--- .../object/attr_name_double_underscored.rs | 12 ++++++++++++ ...rr => attr_name_double_underscored.stderr} | 6 +++--- .../fail/object/attr_no_fields.rs | 8 ++++++++ ...no_fields.stderr => attr_no_fields.stderr} | 6 +++--- .../fail/object/attr_wrong_item.rs | 6 ++++++ .../fail/object/attr_wrong_item.stderr | 7 +++++++ .../object/derive_field_double_underscored.rs | 8 ++++++++ ...=> derive_field_double_underscored.stderr} | 6 +++--- .../derive_field_non_output_return_type.rs | 13 +++++++++++++ ...derive_field_non_output_return_type.stderr | 8 ++++++++ .../fail/object/derive_fields_duplicate.rs | 10 ++++++++++ .../object/derive_fields_duplicate.stderr | 11 +++++++++++ .../fail/object/derive_fields_unique.rs | 8 -------- .../fail/object/derive_fields_unique.stderr | 11 ----------- ...rive_incompatible_input_object.rs.disabled | 12 ------------ .../object/derive_name_double_underscored.rs | 8 ++++++++ .../derive_name_double_underscored.stderr | 7 +++++++ .../fail/object/derive_no_fields.rs | 6 ++++-- .../fail/object/derive_no_fields.stderr | 6 +++--- .../fail/object/derive_no_underscore.rs | 7 ------- .../fail/object/derive_wrong_item.rs | 6 ++++++ .../fail/object/derive_wrong_item.stderr | 5 +++++ .../fail/object/impl_argument_no_object.rs | 15 --------------- .../object/impl_argument_no_object.stderr | 16 ---------------- .../impl_argument_wrong_default_array.rs | 10 ---------- .../fail/object/impl_fields_unique.rs | 14 -------------- ...impl_incompatible_input_object.rs.disabled | 19 ------------------- .../object/impl_no_argument_underscore.rs | 10 ---------- .../fail/object/impl_no_fields.rs | 6 ------ .../fail/object/impl_no_underscore.rs | 11 ----------- 42 files changed, 253 insertions(+), 163 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/object/argument_double_underscored.rs rename integration_tests/codegen_fail/fail/object/{impl_no_argument_underscore.stderr => argument_double_underscored.stderr} (60%) create mode 100644 integration_tests/codegen_fail/fail/object/argument_non_input_type.rs create mode 100644 integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr create mode 100644 integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs rename integration_tests/codegen_fail/fail/object/{impl_argument_wrong_default_array.stderr => argument_wrong_default_array.stderr} (60%) create mode 100644 integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr create mode 100644 integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs rename integration_tests/codegen_fail/fail/object/{impl_fields_unique.stderr => attr_fields_duplicate.stderr} (64%) create mode 100644 integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs rename integration_tests/codegen_fail/fail/object/{impl_no_underscore.stderr => attr_name_double_underscored.stderr} (72%) create mode 100644 integration_tests/codegen_fail/fail/object/attr_no_fields.rs rename integration_tests/codegen_fail/fail/object/{impl_no_fields.stderr => attr_no_fields.stderr} (63%) create mode 100644 integration_tests/codegen_fail/fail/object/attr_wrong_item.rs create mode 100644 integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr create mode 100644 integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs rename integration_tests/codegen_fail/fail/object/{derive_no_underscore.stderr => derive_field_double_underscored.stderr} (71%) create mode 100644 integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr create mode 100644 integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs create mode 100644 integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/derive_fields_unique.rs delete mode 100644 integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled create mode 100644 integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/derive_no_underscore.rs create mode 100644 integration_tests/codegen_fail/fail/object/derive_wrong_item.rs create mode 100644 integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs delete mode 100644 integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr delete mode 100644 integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs delete mode 100644 integration_tests/codegen_fail/fail/object/impl_fields_unique.rs delete mode 100644 integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled delete mode 100644 integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs delete mode 100644 integration_tests/codegen_fail/fail/object/impl_no_fields.rs delete mode 100644 integration_tests/codegen_fail/fail/object/impl_no_underscore.rs diff --git a/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs new file mode 100644 index 000000000..b85c97216 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/argument_double_underscored.rs @@ -0,0 +1,12 @@ +use juniper::graphql_object; + +struct Obj; + +#[graphql_object] +impl Obj { + fn id(&self, __num: i32) -> &str { + "funA" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr b/integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr similarity index 60% rename from integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr rename to integration_tests/codegen_fail/fail/object/argument_double_underscored.stderr index 9c783fcd5..433c024e0 100644 --- a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.stderr +++ b/integration_tests/codegen_fail/fail/object/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. - --> $DIR/impl_no_argument_underscore.rs:5:30 + --> $DIR/argument_double_underscored.rs:7:18 | -5 | fn test(&self, #[graphql(name = "__arg")] arg: String) -> String { - | ^^^^ +7 | fn id(&self, __num: i32) -> &str { + | ^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs new file mode 100644 index 000000000..73dce0de0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/argument_non_input_type.rs @@ -0,0 +1,17 @@ +use juniper::{graphql_object, GraphQLObject}; + +#[derive(GraphQLObject)] +struct ObjA { + test: String, +} + +struct ObjB; + +#[graphql_object] +impl ObjB { + fn id(&self, obj: ObjA) -> &str { + "funA" + } +} + +fn main() {} 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 new file mode 100644 index 000000000..1a054e5ef --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/argument_non_input_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied + --> $DIR/argument_non_input_type.rs:10:1 + | +10 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::IsInputType::mark` + = 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 + --> $DIR/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/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs new file mode 100644 index 000000000..fab39ef73 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.rs @@ -0,0 +1,12 @@ +use juniper::graphql_object; + +struct ObjA; + +#[graphql_object] +impl ObjA { + fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { + input[0] + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr similarity index 60% rename from integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr rename to integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr index dc3736f9f..83128b5c8 100644 --- a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/object/argument_wrong_default_array.stderr @@ -1,12 +1,12 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> $DIR/impl_argument_wrong_default_array.rs:3:1 + --> $DIR/argument_wrong_default_array.rs:5:1 | -3 | #[juniper::graphql_object] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` +5 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ 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>> = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) + = 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/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs new file mode 100644 index 000000000..ff854d0a7 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.rs @@ -0,0 +1,12 @@ +use juniper::graphql_object; + +struct ObjA; + +#[graphql_object] +impl Character for ObjA { + fn __id(&self) -> &str { + "funA" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr new file mode 100644 index 000000000..cd1849d85 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_field_double_underscored.stderr @@ -0,0 +1,7 @@ +error: #[graphql_object] attribute is applicable to non-trait `impl` blocks only + --> $DIR/attr_field_double_underscored.rs:5:1 + | +5 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ + | + = 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.rs b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs new file mode 100644 index 000000000..c5d748b51 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.rs @@ -0,0 +1,17 @@ +use juniper::{graphql_object, GraphQLInputObject}; + +#[derive(GraphQLInputObject)] +struct ObjB { + id: i32, +} + +struct ObjA; + +#[graphql_object] +impl ObjA { + fn id(&self) -> ObjB { + ObjB { id: 34 } + } +} + +fn main() {} 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 new file mode 100644 index 000000000..9bc4c6bda --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_field_non_output_return_type.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> $DIR/attr_field_non_output_return_type.rs:10:1 + | +10 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: required by `juniper::marker::IsOutputType::mark` + = 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_fields_duplicate.rs b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs new file mode 100644 index 000000000..4fe14664c --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.rs @@ -0,0 +1,17 @@ +use juniper::graphql_object; + +struct ObjA; + +#[graphql_object] +impl ObjA { + fn id(&self) -> &str { + "funA" + } + + #[graphql(name = "id")] + fn id2(&self) -> &str { + "funB" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr similarity index 64% rename from integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr rename to integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr index f3555ea6a..468e4cde5 100644 --- a/integration_tests/codegen_fail/fail/object/impl_fields_unique.stderr +++ b/integration_tests/codegen_fail/fail/object/attr_fields_duplicate.stderr @@ -1,7 +1,7 @@ error: GraphQL object must have a different name for each field - --> $DIR/impl_fields_unique.rs:4:6 + --> $DIR/attr_fields_duplicate.rs:6:6 | -4 | impl Object { - | ^^^^^^ +6 | impl ObjA { + | ^^^^ | = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs new file mode 100644 index 000000000..37ec8ba70 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.rs @@ -0,0 +1,12 @@ +use juniper::graphql_object; + +struct __Obj; + +#[graphql_object] +impl __Obj { + fn id(&self) -> &str { + "funA" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_no_underscore.stderr b/integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr similarity index 72% rename from integration_tests/codegen_fail/fail/object/impl_no_underscore.stderr rename to integration_tests/codegen_fail/fail/object/attr_name_double_underscored.stderr index b7e531e5c..e7bedddbf 100644 --- a/integration_tests/codegen_fail/fail/object/impl_no_underscore.stderr +++ b/integration_tests/codegen_fail/fail/object/attr_name_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. - --> $DIR/impl_no_underscore.rs:5:15 + --> $DIR/attr_name_double_underscored.rs:6:6 | -5 | #[graphql(name = "__test")] - | ^^^^ +6 | impl __Obj { + | ^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/object/attr_no_fields.rs b/integration_tests/codegen_fail/fail/object/attr_no_fields.rs new file mode 100644 index 000000000..b36c58697 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_no_fields.rs @@ -0,0 +1,8 @@ +use juniper::graphql_object; + +struct Obj; + +#[graphql_object] +impl Obj {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr b/integration_tests/codegen_fail/fail/object/attr_no_fields.stderr similarity index 63% rename from integration_tests/codegen_fail/fail/object/impl_no_fields.stderr rename to integration_tests/codegen_fail/fail/object/attr_no_fields.stderr index ea6ed2e0f..f783cc91a 100644 --- a/integration_tests/codegen_fail/fail/object/impl_no_fields.stderr +++ b/integration_tests/codegen_fail/fail/object/attr_no_fields.stderr @@ -1,7 +1,7 @@ error: GraphQL object must have at least one field - --> $DIR/impl_no_fields.rs:4:6 + --> $DIR/attr_no_fields.rs:6:6 | -4 | impl Object {} - | ^^^^^^ +6 | impl Obj {} + | ^^^ | = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs b/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs new file mode 100644 index 000000000..92ba57ec2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_wrong_item.rs @@ -0,0 +1,6 @@ +use juniper::graphql_object; + +#[graphql_object] +enum Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr b/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr new file mode 100644 index 000000000..cf9141485 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/attr_wrong_item.stderr @@ -0,0 +1,7 @@ +error: #[graphql_object] attribute is applicable to non-trait `impl` blocks only + --> $DIR/attr_wrong_item.rs:3:1 + | +3 | #[graphql_object] + | ^^^^^^^^^^^^^^^^^ + | + = 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/derive_field_double_underscored.rs b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs new file mode 100644 index 000000000..286383edf --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLObject; + +#[derive(GraphQLObject)] +struct Object { + __test: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_no_underscore.stderr b/integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr similarity index 71% rename from integration_tests/codegen_fail/fail/object/derive_no_underscore.stderr rename to integration_tests/codegen_fail/fail/object/derive_field_double_underscored.stderr index eb7eb67b5..a6f3cb8db 100644 --- a/integration_tests/codegen_fail/fail/object/derive_no_underscore.stderr +++ b/integration_tests/codegen_fail/fail/object/derive_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. - --> $DIR/derive_no_underscore.rs:3:15 + --> $DIR/derive_field_double_underscored.rs:5:5 | -3 | #[graphql(name = "__test")] - | ^^^^ +5 | __test: String, + | ^^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs new file mode 100644 index 000000000..7197cd595 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.rs @@ -0,0 +1,13 @@ +use juniper::{GraphQLInputObject, GraphQLObject}; + +#[derive(GraphQLInputObject)] +struct ObjB { + id: i32, +} + +#[derive(GraphQLObject)] +struct ObjA { + id: ObjB, +} + +fn main() {} 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 new file mode 100644 index 000000000..d112debd2 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_field_non_output_return_type.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> $DIR/derive_field_non_output_return_type.rs:8:10 + | +8 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: required by `juniper::marker::IsOutputType::mark` + = note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs new file mode 100644 index 000000000..cd73e1f3d --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.rs @@ -0,0 +1,10 @@ +use juniper::GraphQLObject; + +#[derive(GraphQLObject)] +struct ObjA { + id: String, + #[graphql(name = "id")] + id2: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr new file mode 100644 index 000000000..74004561b --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_fields_duplicate.stderr @@ -0,0 +1,11 @@ +error: GraphQL object must have a different name for each field + --> $DIR/derive_fields_duplicate.rs:4:1 + | +4 | / struct ObjA { +5 | | id: String, +6 | | #[graphql(name = "id")] +7 | | id2: String, +8 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_unique.rs b/integration_tests/codegen_fail/fail/object/derive_fields_unique.rs deleted file mode 100644 index ee5afa112..000000000 --- a/integration_tests/codegen_fail/fail/object/derive_fields_unique.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[derive(juniper::GraphQLObject)] -struct Object { - test: String, - #[graphql(name = "test")] - test2: String, -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr b/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr deleted file mode 100644 index 66474ee1f..000000000 --- a/integration_tests/codegen_fail/fail/object/derive_fields_unique.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: GraphQL object must have a different name for each field - --> $DIR/derive_fields_unique.rs:2:1 - | -2 | / struct Object { -3 | | test: String, -4 | | #[graphql(name = "test")] -5 | | test2: String, -6 | | } - | |_^ - | - = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled b/integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled deleted file mode 100644 index 0e0122190..000000000 --- a/integration_tests/codegen_fail/fail/object/derive_incompatible_input_object.rs.disabled +++ /dev/null @@ -1,12 +0,0 @@ -// FIXME: enable this if interfaces are supported -#[derive(juniper::GraphQLInputObject)] -struct ObjectA { - test: String, -} - -#[derive(juniper::GraphQLObject)] -struct Object { - field: ObjectA, -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs b/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs new file mode 100644 index 000000000..209b3f37b --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.rs @@ -0,0 +1,8 @@ +use juniper::GraphQLObject; + +#[derive(GraphQLObject)] +struct __Obj { + id: String, +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr b/integration_tests/codegen_fail/fail/object/derive_name_double_underscored.stderr new file mode 100644 index 000000000..11eafcef6 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/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. + --> $DIR/derive_name_double_underscored.rs:4:8 + | +4 | struct __Obj { + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.rs b/integration_tests/codegen_fail/fail/object/derive_no_fields.rs index cd72632ae..27795d55a 100644 --- a/integration_tests/codegen_fail/fail/object/derive_no_fields.rs +++ b/integration_tests/codegen_fail/fail/object/derive_no_fields.rs @@ -1,4 +1,6 @@ -#[derive(juniper::GraphQLObject)] -struct Object {} +use juniper::GraphQLObject; + +#[derive(GraphQLObject)] +struct Obj {} fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr b/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr index 523fe8acc..1975c3611 100644 --- a/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr +++ b/integration_tests/codegen_fail/fail/object/derive_no_fields.stderr @@ -1,7 +1,7 @@ error: GraphQL object must have at least one field - --> $DIR/derive_no_fields.rs:2:1 + --> $DIR/derive_no_fields.rs:4:1 | -2 | struct Object {} - | ^^^^^^^^^^^^^^^^ +4 | struct Obj {} + | ^^^^^^^^^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/object/derive_no_underscore.rs b/integration_tests/codegen_fail/fail/object/derive_no_underscore.rs deleted file mode 100644 index 9b270b546..000000000 --- a/integration_tests/codegen_fail/fail/object/derive_no_underscore.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(juniper::GraphQLObject)] -struct Object { - #[graphql(name = "__test")] - test: String, -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs b/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs new file mode 100644 index 000000000..8318256fc --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_wrong_item.rs @@ -0,0 +1,6 @@ +use juniper::GraphQLObject; + +#[derive(GraphQLObject)] +enum Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr b/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr new file mode 100644 index 000000000..50680bec9 --- /dev/null +++ b/integration_tests/codegen_fail/fail/object/derive_wrong_item.stderr @@ -0,0 +1,5 @@ +error: GraphQL object can only be derived for structs + --> $DIR/derive_wrong_item.rs:4:1 + | +4 | enum Character {} + | ^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs deleted file mode 100644 index a584a16f3..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[derive(juniper::GraphQLObject)] -struct Obj { - field: String, -} - -struct Object {} - -#[juniper::graphql_object] -impl Object { - fn test(&self, test: Obj) -> String { - String::new() - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr b/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr deleted file mode 100644 index 0a4e56ffd..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_argument_no_object.stderr +++ /dev/null @@ -1,16 +0,0 @@ -error[E0277]: the trait bound `Obj: IsInputType<__S>` is not satisfied - --> $DIR/impl_argument_no_object.rs:8:1 - | -8 | #[juniper::graphql_object] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `Obj` - | - = note: required by `juniper::marker::IsInputType::mark` - = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Obj: FromInputValue<__S>` is not satisfied - --> $DIR/impl_argument_no_object.rs:8:1 - | -8 | #[juniper::graphql_object] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `Obj` - | - = note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs deleted file mode 100644 index 6b6c10e1c..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_argument_wrong_default_array.rs +++ /dev/null @@ -1,10 +0,0 @@ -struct Object; - -#[juniper::graphql_object] -impl Object { - fn wrong(#[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { - input[0] - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_fields_unique.rs b/integration_tests/codegen_fail/fail/object/impl_fields_unique.rs deleted file mode 100644 index c89fc9aa7..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_fields_unique.rs +++ /dev/null @@ -1,14 +0,0 @@ -struct Object {} - -#[juniper::graphql_object] -impl Object { - fn test(&self) -> String { - String::new() - } - - fn test(&self) -> String { - String::new() - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled b/integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled deleted file mode 100644 index e7661e402..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_incompatible_input_object.rs.disabled +++ /dev/null @@ -1,19 +0,0 @@ -// FIXME: enable this if interfaces are supported -#[derive(juniper::GraphQLInputObject)] -#[graphql(scalar = juniper::DefaultScalarValue)] -struct Obj { - field: String, -} - -struct Object {} - -#[juniper::graphql_object] -impl Object { - fn test(&self) -> Obj { - Obj { - field: String::new(), - } - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs b/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs deleted file mode 100644 index 3eb2de5ed..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_no_argument_underscore.rs +++ /dev/null @@ -1,10 +0,0 @@ -struct Object {} - -#[juniper::graphql_object] -impl Object { - fn test(&self, #[graphql(name = "__arg")] arg: String) -> String { - arg - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_no_fields.rs b/integration_tests/codegen_fail/fail/object/impl_no_fields.rs deleted file mode 100644 index aa863c7cd..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_no_fields.rs +++ /dev/null @@ -1,6 +0,0 @@ -struct Object {} - -#[juniper::graphql_object] -impl Object {} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/object/impl_no_underscore.rs b/integration_tests/codegen_fail/fail/object/impl_no_underscore.rs deleted file mode 100644 index 5e9ddbfac..000000000 --- a/integration_tests/codegen_fail/fail/object/impl_no_underscore.rs +++ /dev/null @@ -1,11 +0,0 @@ -struct Object {} - -#[juniper::graphql_object] -impl Object { - #[graphql(name = "__test")] - fn test(&self) -> String { - String::new() - } -} - -fn main() {} From 136b1a92fb193ff6fbe99041fed5935d06c7e7ae Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Aug 2021 14:09:01 +0300 Subject: [PATCH 33/39] Codegen failure tests for subscriptions --- .../argument_double_underscored.rs | 16 ++++++++++++++ .../argument_double_underscored.stderr | 7 ++++++ .../subscription/argument_non_input_type.rs | 22 +++++++++++++++++++ .../argument_non_input_type.stderr | 16 ++++++++++++++ .../argument_wrong_default_array.rs | 20 +++++++++++++++++ .../argument_wrong_default_array.stderr | 12 ++++++++++ .../subscription/field_double_underscored.rs | 16 ++++++++++++++ .../field_double_underscored.stderr | 7 ++++++ .../field_non_output_return_type.rs | 22 +++++++++++++++++++ .../field_non_output_return_type.stderr | 8 +++++++ .../fail/subscription/field_not_async.rs | 16 ++++++++++++++ .../fail/subscription/field_not_async.stderr | 8 +++++++ .../fail/subscription/fields_duplicate.rs | 21 ++++++++++++++++++ .../fail/subscription/fields_duplicate.stderr | 7 ++++++ .../subscription/name_double_underscored.rs | 16 ++++++++++++++ .../name_double_underscored.stderr | 7 ++++++ .../fail/subscription/no_fields.rs | 8 +++++++ .../fail/subscription/no_fields.stderr | 7 ++++++ .../fail/subscription/wrong_item.rs | 6 +++++ .../fail/subscription/wrong_item.stderr | 7 ++++++ 20 files changed, 249 insertions(+) create mode 100644 integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/field_not_async.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/field_not_async.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/no_fields.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/no_fields.stderr create mode 100644 integration_tests/codegen_fail/fail/subscription/wrong_item.rs create mode 100644 integration_tests/codegen_fail/fail/subscription/wrong_item.stderr diff --git a/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs new file mode 100644 index 000000000..fe01fc69e --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.rs @@ -0,0 +1,16 @@ +use std::pin::Pin; + +use juniper::graphql_subscription; + +type Stream<'a, I> = Pin + Send + 'a>>; + +struct Obj; + +#[graphql_subscription] +impl Obj { + async fn id(&self, __num: i32) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("funA"))) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr new file mode 100644 index 000000000..97d9d6df1 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/argument_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> $DIR/argument_double_underscored.rs:11:24 + | +11 | async fn id(&self, __num: i32) -> Stream<'static, &'static str> { + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs new file mode 100644 index 000000000..90b24bb21 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.rs @@ -0,0 +1,22 @@ +use std::pin::Pin; + +use futures::{future, stream}; +use juniper::{graphql_subscription, GraphQLObject}; + +type Stream<'a, I> = Pin + Send + 'a>>; + +#[derive(GraphQLObject)] +struct ObjA { + test: String, +} + +struct ObjB; + +#[graphql_subscription] +impl ObjB { + async fn id(&self, obj: ObjA) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("funA"))) + } +} + +fn main() {} 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 new file mode 100644 index 000000000..87d4a9ee7 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/argument_non_input_type.stderr @@ -0,0 +1,16 @@ +error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied + --> $DIR/argument_non_input_type.rs:15:1 + | +15 | #[graphql_subscription] + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: required by `juniper::marker::IsInputType::mark` + = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied + --> $DIR/argument_non_input_type.rs:15:1 + | +15 | #[graphql_subscription] + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` + | + = 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/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs new file mode 100644 index 000000000..c0ec68639 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.rs @@ -0,0 +1,20 @@ +use std::pin::Pin; + +use futures::{future, stream}; +use juniper::graphql_subscription; + +type Stream<'a, I> = Pin + Send + 'a>>; + +struct ObjA; + +#[graphql_subscription] +impl ObjA { + async fn wrong( + &self, + #[graphql(default = [true, false, false])] input: [bool; 2], + ) -> Stream<'static, bool> { + Box::pin(stream::once(future::ready(input[0]))) + } +} + +fn main() {} 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 new file mode 100644 index 000000000..56b033cbb --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/argument_wrong_default_array.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied + --> $DIR/argument_wrong_default_array.rs:10:1 + | +10 | #[graphql_subscription] + | ^^^^^^^^^^^^^^^^^^^^^^^ 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>> + = 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/integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs new file mode 100644 index 000000000..686dccc27 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.rs @@ -0,0 +1,16 @@ +use std::pin::Pin; + +use juniper::graphql_subscription; + +type Stream<'a, I> = Pin + Send + 'a>>; + +struct ObjA; + +#[graphql_subscription] +impl Character for ObjA { + async fn __id() -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("funA"))) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr new file mode 100644 index 000000000..1f5e519af --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/field_double_underscored.stderr @@ -0,0 +1,7 @@ +error: #[graphql_subscription] attribute is applicable to non-trait `impl` blocks only + --> $DIR/field_double_underscored.rs:9:1 + | +9 | #[graphql_subscription] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs new file mode 100644 index 000000000..8cc918542 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.rs @@ -0,0 +1,22 @@ +use std::pin::Pin; + +use futures::{future, stream}; +use juniper::{graphql_subscription, GraphQLInputObject}; + +type Stream<'a, I> = Pin + Send + 'a>>; + +#[derive(GraphQLInputObject)] +struct ObjB { + id: i32, +} + +struct ObjA; + +#[graphql_subscription] +impl ObjA { + async fn id(&self) -> Stream<'static, ObjB> { + Box::pin(stream::once(future::ready(ObjB { id: 34 }))) + } +} + +fn main() {} 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 new file mode 100644 index 000000000..84d4cf63b --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/field_non_output_return_type.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied + --> $DIR/field_non_output_return_type.rs:15:1 + | +15 | #[graphql_subscription] + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: required by `juniper::marker::IsOutputType::mark` + = 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_not_async.rs b/integration_tests/codegen_fail/fail/subscription/field_not_async.rs new file mode 100644 index 000000000..f9cfb5b53 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/field_not_async.rs @@ -0,0 +1,16 @@ +use std::pin::Pin; + +use juniper::graphql_subscription; + +type Stream<'a, I> = Pin + Send + 'a>>; + +struct ObjA; + +#[graphql_subscription] +impl ObjA { + fn id(&self) -> Stream<'static, bool> { + Box::pin(stream::once(future::ready(true))) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr b/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr new file mode 100644 index 000000000..3e5209c8e --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/field_not_async.stderr @@ -0,0 +1,8 @@ +error: GraphQL object synchronous resolvers are not supported + --> $DIR/field_not_async.rs:11:5 + | +11 | fn id(&self) -> Stream<'static, bool> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Objects + = note: Specify that this function is async: `async fn foo()` diff --git a/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs new file mode 100644 index 000000000..22ccc9778 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.rs @@ -0,0 +1,21 @@ +use std::pin::Pin; + +use juniper::graphql_subscription; + +type Stream<'a, I> = Pin + Send + 'a>>; + +struct ObjA; + +#[graphql_subscription] +impl ObjA { + async fn id(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("funA"))) + } + + #[graphql(name = "id")] + async fn id2(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("funB"))) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr new file mode 100644 index 000000000..d9156450c --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/fields_duplicate.stderr @@ -0,0 +1,7 @@ +error: GraphQL object must have a different name for each field + --> $DIR/fields_duplicate.rs:10:6 + | +10 | impl ObjA { + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs new file mode 100644 index 000000000..d9911148b --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.rs @@ -0,0 +1,16 @@ +use std::pin::Pin; + +use juniper::graphql_subscription; + +type Stream<'a, I> = Pin + Send + 'a>>; + +struct __Obj; + +#[graphql_subscription] +impl __Obj { + fn id(&self) -> Stream<'static, &'static str> { + Box::pin(stream::once(future::ready("funA"))) + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr new file mode 100644 index 000000000..ac1081e35 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/name_double_underscored.stderr @@ -0,0 +1,7 @@ +error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. + --> $DIR/name_double_underscored.rs:10:6 + | +10 | impl __Obj { + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/subscription/no_fields.rs b/integration_tests/codegen_fail/fail/subscription/no_fields.rs new file mode 100644 index 000000000..0c199c13c --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/no_fields.rs @@ -0,0 +1,8 @@ +use juniper::graphql_subscription; + +struct Obj; + +#[graphql_subscription] +impl Obj {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/no_fields.stderr b/integration_tests/codegen_fail/fail/subscription/no_fields.stderr new file mode 100644 index 000000000..93b5487f5 --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/no_fields.stderr @@ -0,0 +1,7 @@ +error: GraphQL object must have at least one field + --> $DIR/no_fields.rs:6:6 + | +6 | impl Obj {} + | ^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Objects diff --git a/integration_tests/codegen_fail/fail/subscription/wrong_item.rs b/integration_tests/codegen_fail/fail/subscription/wrong_item.rs new file mode 100644 index 000000000..3ab174c6f --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/wrong_item.rs @@ -0,0 +1,6 @@ +use juniper::graphql_subscription; + +#[graphql_subscription] +enum Character {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr b/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr new file mode 100644 index 000000000..d149b56be --- /dev/null +++ b/integration_tests/codegen_fail/fail/subscription/wrong_item.stderr @@ -0,0 +1,7 @@ +error: #[graphql_subscription] attribute is applicable to non-trait `impl` blocks only + --> $DIR/wrong_item.rs:3:1 + | +3 | #[graphql_subscription] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info) From d870ad691238354deff7da76ea815e512004c5a1 Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 10 Aug 2021 18:13:13 +0300 Subject: [PATCH 34/39] Renew docs, vol.1 --- juniper_codegen/src/lib.rs | 604 +++++++++++++++++++------------------ 1 file changed, 311 insertions(+), 293 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 133ea98dd..f000fd9a7 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -196,266 +196,6 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { } } -// TODO -#[proc_macro_error] -#[proc_macro_derive(GraphQLObject, attributes(graphql))] -pub fn derive_object(body: TokenStream) -> TokenStream { - self::graphql_object::derive::expand(body.into()) - .unwrap_or_abort() - .into() -} - -// TODO -/** -The `object` proc macro is the primary way of defining GraphQL resolvers -that can not be implemented with the GraphQLObject derive. - -It enables you to write GraphQL field resolvers for a type by declaring a -regular Rust `impl` block. Under the hood, the procedural macro implements -the GraphQLType trait. - -`object` comes with many features that allow customization of -your fields, all of which are detailed below. - -### Getting Started - -This simple example will show you the most basic use of `object`. -More advanced use cases are introduced step by step. - -``` -// So we can declare it as a plain struct without any members. -struct Query; - -// We prefix the impl Block with the procedural macro. -#[juniper::graphql_object] -impl Query { - - // A **warning**: only GraphQL fields can be specified in this impl block. - // If you want to define normal methods on the struct, - // you have to do so in a separate, normal `impl` block. - - - // This defines a simple, static field which does not require any context. - // You can return any value that implements the `GraphQLType` trait. - // This trait is implemented for: - // - basic scalar types like bool, &str, String, i32, f64 - // - GraphQL compatible wrappers like Option<_>, Vec<_>. - // - types which use the `#derive[juniper::GraphQLObject]` - // - `object` structs. - // - // An important note regarding naming: - // By default, field names will be converted to camel case. - // For your GraphQL queries, the field will be available as `apiVersion`. - // - // You can also manually customize the field name if required. (See below) - fn api_version() -> &'static str { - "0.1" - } - - // This field takes two arguments. - // GraphQL arguments are just regular function parameters. - // **Note**: in Juniper, arguments are non-nullable by default. - // for optional arguments, you have to specify them with Option. - fn add(a: f64, b: f64, c: Option) -> f64 { - a + b + c.unwrap_or(0.0) - } -} -``` - -## Accessing self - -``` -struct Person { - first_name: String, - last_name: String, -} - -impl Person { - // The full name method is useful outside of GraphQL, - // so we define it as a normal method. - fn build_full_name(&self) -> String { - format!("{} {}", self.first_name, self.last_name) - } -} - -#[juniper::graphql_object] -impl Person { - fn first_name(&self) -> &str { - &self.first_name - } - - fn last_name(&self) -> &str { - &self.last_name - } - - fn full_name(&self) -> String { - self.build_full_name() - } -} -``` - -## Context (+ Executor) - -You can specify a context that will be available across -all your resolvers during query execution. - -The Context can be injected into your resolvers by just -specifying an argument with the same type as the context -(but as a reference). - -``` -# #[derive(juniper::GraphQLObject)] struct User { id: i32 } -# struct DbPool; -# impl DbPool { fn user(&self, id: i32) -> Option { unimplemented!() } } - -struct Context { - db: DbPool, -} - -// Mark our struct for juniper. -impl juniper::Context for Context {} - -struct Query; - -// Here we specify the context type for this object. -#[juniper::graphql_object(context = Context)] -impl Query { - // Context is injected by specifying a argument - // as a reference to the Context. - fn user(context: &Context, id: i32) -> Option { - context.db.user(id) - } - - // You can also gain access to the executor, which - // allows you to do look aheads. - fn with_executor<__S: juniper::ScalarValue>( - executor: &juniper::Executor<'_, '_, Context, __S>, - ) -> bool { - let info = executor.look_ahead(); - // ... - true - } -} -``` - -## Customization (Documentation, Renaming, ...) - -``` -struct InternalQuery; - -// Doc comments can be used to specify graphql documentation. -/// GRAPHQL DOCUMENTATION. -/// More info for GraphQL users.... -#[juniper::graphql_object( - // You can rename the type for GraphQL by specifying the name here. - name = "Query", - // You can also specify a description here. - // If present, doc comments will be ignored. - description = "...", -)] -impl InternalQuery { - // Documentation doc comments also work on fields. - /// GraphQL description... - fn field_with_description() -> bool { true } - - // Fields can also be customized with the #[graphql] attribute. - #[graphql( - // overwrite the public name - name = "actualFieldName", - // Can be used instead of doc comments. - description = "field description", - )] - fn internal_name() -> bool { true } - - // Fields can be deprecated too. - #[graphql( - deprecated = "deprecatin info...", - // Note: just "deprecated," without a description works too. - )] - fn deprecated_field_simple() -> bool { true } - - fn args( - // You can specify default values. - // A default can be any valid expression that yields the right type. - #[graphql(default = true, description = "Argument description....")] - arg1: bool, - // If expression is not specified then `Default::default()` is used. - #[graphql(default, description = "arg2 description...")] - arg2: bool, - ) -> bool { - arg1 && arg2 - } -} -``` - -## Lifetimes, Generics and custom Scalars - -Lifetimes work just like you'd expect. - - -``` -struct WithLifetime<'a> { - value: &'a str, -} - -#[juniper::graphql_object] -impl<'a> WithLifetime<'a> { - fn value(&self) -> &str { - self.value - } -} - -``` - -Juniper has support for custom scalars. -Mostly you will only need the default scalar type juniper::DefaultScalarValue. - -You can easily specify a custom scalar though. - - -``` - -# type MyCustomScalar = juniper::DefaultScalarValue; - -struct Query; - -#[juniper::graphql_object( - Scalar = MyCustomScalar, -)] -impl Query { - fn test(&self) -> i32 { - 0 - } -} -``` - -## Raw identifiers - -You can use [raw identifiers](https://doc.rust-lang.org/stable/edition-guide/rust-2018/module-system/raw-identifiers.html) -if you want a GrahpQL field that happens to be a Rust keyword: - -``` -struct User { - r#type: String, -} - -#[juniper::graphql_object] -impl User { - fn r#type(&self) -> &str { - &self.r#type - } -} -``` - -*/ -#[proc_macro_error] -#[proc_macro_attribute] -pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { - self::graphql_object::attr::expand(attr.into(), body.into()) - .unwrap_or_abort() - .into() -} - /// Expose GraphQL scalars /// /// The GraphQL language defines a number of built-in scalars: strings, numbers, and @@ -602,7 +342,7 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// /// # Custom name, description, deprecation and argument defaults /// -/// The name of [GraphQL interface][1], its field, or a field argument may be overriden with a +/// The name of [GraphQL interface][1], its field, or a field argument may be overridden with a /// `name` attribute's argument. By default, a type name is used or `camelCased` method/argument /// name. /// @@ -610,13 +350,12 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// either with a `description`/`desc` attribute's argument, or with a regular Rust doc comment. /// /// A field of [GraphQL interface][1] may be deprecated by specifying a `deprecated` attribute's -/// argument, or with regulat Rust `#[deprecated]` attribute. +/// argument, or with regular Rust `#[deprecated]` attribute. /// /// 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). /// /// ``` -/// # #![allow(deprecated)] /// # use juniper::graphql_interface; /// # /// #[graphql_interface(name = "Character", desc = "Possible episode characters.")] @@ -647,10 +386,10 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments. /// /// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly -/// with `context`/`Context` attribute's argument. +/// with `context` attribute's argument. /// /// If trait method represents a [GraphQL interface][1] field and its argument is named as `context` -/// or `ctx` then this argument is assumed as [`Context`] and will be omited in GraphQL schema. +/// 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. /// /// ``` @@ -706,7 +445,7 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// /// If an [`Executor`] is required in a trait method to resolve a [GraphQL interface][1] field, /// specify it as an argument named as `executor` or explicitly marked with an `executor` -/// attribute's argument. Such method argument will be omited in GraphQL schema. +/// attribute's argument. Such method argument will be omitted in GraphQL schema. /// /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// @@ -714,7 +453,7 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; /// # /// // NOTICE: Specifying `ScalarValue` as existing type parameter. -/// #[graphql_interface(for = Human, Scalar = S)] +/// #[graphql_interface(for = Human, scalar = S)] /// trait Character { /// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where @@ -734,7 +473,7 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// id: String, /// name: String, /// } -/// #[graphql_interface(Scalar = S)] +/// #[graphql_interface(scalar = S)] /// impl Character for Human { /// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where @@ -754,28 +493,29 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// /// # Custom `ScalarValue` /// -/// By default, `#[graphql_interface]` macro generates code, which is generic over a [`ScalarValue`] -/// type. This may introduce a problem when at least one of [GraphQL interface][1] implementers is -/// restricted to a concrete [`ScalarValue`] type in its implementation. To resolve such problem, a -/// concrete [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue` +/// By default, `#[graphql_interface]` macro generates code, which is generic +/// over a [`ScalarValue`] type. This may introduce a problem when at least one +/// of [GraphQL interface][1] implementers is restricted to a concrete +/// [`ScalarValue`] type in its implementation. To resolve such problem, a +/// concrete [`ScalarValue`] type should be specified with a `scalar` /// attribute's argument. /// /// ``` /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// // NOTICE: Removing `Scalar` argument will fail compilation. -/// #[graphql_interface(for = [Human, Droid], Scalar = DefaultScalarValue)] +/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)] /// trait Character { /// fn id(&self) -> &str; /// } /// /// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)] +/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface(Scalar = DefaultScalarValue)] +/// #[graphql_interface(scalar = DefaultScalarValue)] /// impl Character for Human { /// fn id(&self) -> &str{ /// &self.id @@ -783,12 +523,12 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// } /// /// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)] +/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] /// struct Droid { /// id: String, /// primary_function: String, /// } -/// #[graphql_interface(Scalar = DefaultScalarValue)] +/// #[graphql_interface(scalar = DefaultScalarValue)] /// impl Character for Droid { /// fn id(&self) -> &str { /// &self.id @@ -896,6 +636,284 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +// TODO +#[proc_macro_error] +#[proc_macro_derive(GraphQLObject, attributes(graphql))] +pub fn derive_object(body: TokenStream) -> TokenStream { + self::graphql_object::derive::expand(body.into()) + .unwrap_or_abort() + .into() +} + +/// `#[graphql_object]` macro for generating a [GraphQL object][1] +/// implementation for structs with computable field resolvers (declared via +/// a regular Rust `impl` block). +/// +/// It enables you to write GraphQL field resolvers for a type by declaring a +/// regular Rust `impl` block. Under the hood, the macro implements +/// the GraphQLType trait. +/// +/// Specifying multiple `#[graphql_object]` attributes on the same definition +/// is totally okay. +/// They all will be treated as a single attribute. +/// +/// ``` +/// use juniper::graphql_object; +/// +/// // We can declare the type as a plain struct without any members. +/// struct Query; +/// +/// #[graphql_object] +/// impl Query { +/// // WARNING: Only GraphQL fields can be specified in this `impl` block. +/// // If normal methods are required on the struct, they can be +/// // defined either in a separate "normal" `impl` block, or +/// // marked with `#[graphql(ignore)]` attribute. +/// +/// // This defines a simple, static field which does not require any +/// // context. +/// // Such field can return any value that implements `GraphQLType` and +/// // `GraphQLValue` traits. +/// // +/// // NOTICE: By default, field names will be converted to `camelCase`. +/// // In the generated GraphQL schema this field will be available +/// // as `apiVersion`. +/// fn api_version() -> &'static str { +/// "0.1" +/// } +/// +/// // This field takes two arguments. +/// // GraphQL arguments are just regular function parameters. +/// // +/// // NOTICE: In `juniper`, arguments are non-nullable by default. For +/// // optional arguments, you have to specify them as `Option<_>`. +/// async fn add(a: f64, b: f64, c: Option) -> f64 { +/// a + b + c.unwrap_or(0.0) +/// } +/// } +/// ``` +/// +/// # Accessing self +/// +/// Fields may also have a `self` receiver. +/// +/// ``` +/// # use juniper::graphql_object; +/// # +/// struct Person { +/// first_name: String, +/// last_name: String, +/// } +/// +/// #[graphql_object] +/// impl Person { +/// fn first_name(&self) -> &str { +/// &self.first_name +/// } +/// +/// fn last_name(&self) -> &str { +/// &self.last_name +/// } +/// +/// fn full_name(&self) -> String { +/// self.build_full_name() +/// } +/// +/// // This method is useful only to define GraphQL fields, but is not +/// // a field itself, so we ignore it in schema. +/// #[graphql(ignore)] +/// fn build_full_name(&self) -> String { +/// format!("{} {}", self.first_name, self.last_name) +/// } +/// } +/// ``` +/// +/// # Custom name, description, deprecation and argument defaults +/// +/// The name of [GraphQL object][1], its field, or a field argument may be +/// overridden with a `name` attribute's argument. By default, a type name is +/// used or `camelCased` method/argument name. +/// +/// The description of [GraphQL object][1], its field, or a field argument may +/// be specified either with a `description`/`desc` attribute's argument, or +/// with a regular Rust doc comment. +/// +/// A field of [GraphQL object][1] may be deprecated by specifying a +/// `deprecated` attribute's argument, or with regular Rust `#[deprecated]` +/// attribute. +/// +/// 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). +/// +/// ``` +/// # use juniper::graphql_object; +/// # +/// struct HumanWithAttrs; +/// +/// #[graphql_object( +/// // Rename the type for GraphQL by specifying the name here. +/// name = "Human", +/// // You may also specify a description here. +/// // If present, doc comments will be ignored. +/// desc = "Possible episode human.", +/// )] +/// impl HumanWithAttrs { +/// #[graphql(name = "id", desc = "ID of the human.")] +/// #[graphql(deprecated = "Don't use it")] +/// fn some_id( +/// &self, +/// #[graphql(name = "number", desc = "Arbitrary number.")] +/// // You may specify default values. +/// // A default can be any valid expression that yields the right type. +/// #[graphql(default = 5)] +/// num: i32, +/// ) -> &str { +/// "Don't use me!" +/// } +/// } +/// +/// struct HumanWithDocs; +/// +/// // Rust docs are used as GraphQL description. +/// /// Possible episode human. +/// #[graphql_object] +/// impl HumanWithDocs { +/// // Doc comments also work on fields. +/// /// ID of the human. +/// #[deprecated] +/// fn id( +/// &self, +/// // If expression is not specified then `Default::default()` is used. +/// #[graphql(default)] num: i32, +/// ) -> &str { +/// "Deprecated" +/// } +/// } +/// ``` +/// +/// # Custom context +/// +/// By default, the generated implementation tries to infer [`Context`] type +/// from signatures of `impl` block methods, and uses [unit type `()`][4] if +/// signatures contains no [`Context`] arguments. +/// +/// If [`Context`] type cannot be inferred or is inferred incorrectly, then +/// specify it explicitly with `context` attribute's argument. +/// +/// If method argument is named as `context` 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. +/// +/// ``` +/// # use std::collections::HashMap; +/// # use juniper::graphql_object; +/// # +/// struct Database { +/// humans: HashMap, +/// } +/// impl juniper::Context for Database {} +/// +/// struct Human { +/// id: String, +/// home_planet: String, +/// } +/// +/// #[graphql_object(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, context: &'db Database) -> Option<&'db str> { +/// context.humans.get(&self.id).map(|h| h.home_planet.as_str()) +/// } +/// } +/// ``` +/// +/// # Using `Executor` +/// +/// If an [`Executor`] is required in a method to resolve a [GraphQL object][1] +/// field, specify it as an argument named as `executor` or explicitly marked +/// with an `executor` attribute's argument. Such method argument will be +/// omitted in GraphQL schema. +/// +/// However, this requires to explicitly parametrize over [`ScalarValue`], as +/// [`Executor`] does so. +/// +/// ``` +/// # use juniper::{graphql_object, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +/// # +/// struct Human { +/// name: String, +/// } +/// +/// // NOTICE: Specifying `ScalarValue` as custom named type parameter. +/// // Its name should be similar to the one used in methods. +/// #[graphql_object(scalar = S: ScalarValue)] +/// impl Human { +/// async fn id<'a, S: ScalarValue>( +/// &self, +/// executor: &'a Executor<'_, '_, (), S>, +/// ) -> &'a str { +/// executor.look_ahead().field_name() +/// } +/// +/// fn name<'b, S: ScalarValue>( +/// &'b self, +/// #[graphql(executor)] _another: &Executor<'_, '_, (), S>, +/// ) -> &'b str { +/// &self.name +/// } +/// } +/// ``` +/// +/// # Custom `ScalarValue` +/// +/// By default, `#[graphql_object]` macro generates code, which is generic over +/// a [`ScalarValue`] type. This may introduce a problem when at least one of +/// its fields is restricted to a concrete [`ScalarValue`] type in its +/// implementation. To resolve such problem, a concrete [`ScalarValue`] type +/// should be specified with a `scalar` attribute's argument. +/// +/// ``` +/// # use juniper::{graphql_object, DefaultScalarValue, GraphQLObject}; +/// # +/// struct Human(String); +/// +/// // NOTICE: Removing `scalar` argument will fail compilation. +/// #[graphql_object(scalar = DefaultScalarValue)] +/// impl Human { +/// fn id(&self) -> &str { +/// &self.0 +/// } +/// +/// fn helper(&self) -> Droid { +/// Droid { +/// id: self.0.clone(), +/// } +/// } +/// } +/// +/// #[derive(GraphQLObject)] +/// #[graphql(scalar = DefaultScalarValue)] +/// struct Droid { +/// id: String, +/// } +/// ``` +/// +/// [`Context`]: juniper::Context +/// [`Executor`]: juniper::Executor +/// [`ScalarValue`]: juniper::ScalarValue +/// [1]: https://spec.graphql.org/June2018/#sec-Objects +#[proc_macro_error] +#[proc_macro_attribute] +pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_object::attr::expand(attr.into(), body.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and /// structs. /// @@ -978,7 +996,7 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// /// By default, the generated implementation uses [unit type `()`][4] as [`Context`]. To use a /// custom [`Context`] type for [GraphQL union][1] variants types or external resolver functions, -/// specify it with `context`/`Context` attribute's argument. +/// specify it with `context` attribute's argument. /// /// ``` /// # use juniper::{GraphQLObject, GraphQLUnion}; @@ -1010,17 +1028,17 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// /// # Custom `ScalarValue` /// -/// By default, this macro generates code, which is generic over a [`ScalarValue`] type. -/// This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a -/// concrete [`ScalarValue`] type in its implementation. To resolve such problem, a concrete -/// [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's -/// argument. +/// By default, this macro generates code, which is generic over a +/// [`ScalarValue`] type. This may introduce a problem when at least one of +/// [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`] type +/// in its implementation. To resolve such problem, a concrete [`ScalarValue`] +/// type should be specified with a `scalar` attribute's argument. /// /// ``` /// # use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion}; /// # /// #[derive(GraphQLObject)] -/// #[graphql(Scalar = DefaultScalarValue)] +/// #[graphql(scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// home_planet: String, @@ -1034,7 +1052,7 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// /// // NOTICE: Removing `Scalar` argument will fail compilation. /// #[derive(GraphQLUnion)] -/// #[graphql(Scalar = DefaultScalarValue)] +/// #[graphql(scalar = DefaultScalarValue)] /// enum Character { /// Human(Human), /// Droid(Droid), @@ -1302,7 +1320,7 @@ pub fn derive_union(body: TokenStream) -> TokenStream { /// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments. /// /// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly -/// with `context`/`Context` attribute's argument. +/// with `context` attribute's argument. /// /// ``` /// # use std::collections::HashMap; @@ -1349,17 +1367,17 @@ pub fn derive_union(body: TokenStream) -> TokenStream { /// /// # Custom `ScalarValue` /// -/// By default, `#[graphql_union]` macro generates code, which is generic over a [`ScalarValue`] -/// type. This may introduce a problem when at least one of [GraphQL union][1] variants is -/// restricted to a concrete [`ScalarValue`] type in its implementation. To resolve such problem, a -/// concrete [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue` -/// attribute's argument. +/// By default, `#[graphql_union]` macro generates code, which is generic over +/// a [`ScalarValue`] type. This may introduce a problem when at least one of +/// [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`] type +/// in its implementation. To resolve such problem, a concrete [`ScalarValue`] +/// type should be specified with a `scalar` attribute's argument. /// /// ``` /// # use juniper::{graphql_union, DefaultScalarValue, GraphQLObject}; /// # /// #[derive(GraphQLObject)] -/// #[graphql(Scalar = DefaultScalarValue)] +/// #[graphql(scalar = DefaultScalarValue)] /// struct Human { /// id: String, /// home_planet: String, @@ -1372,7 +1390,7 @@ pub fn derive_union(body: TokenStream) -> TokenStream { /// } /// /// // NOTICE: Removing `Scalar` argument will fail compilation. -/// #[graphql_union(Scalar = DefaultScalarValue)] +/// #[graphql_union(scalar = DefaultScalarValue)] /// trait Character { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } From 1fe0de08cc79b14af4a1bdd25d415127f88daa97 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Aug 2021 13:26:30 +0300 Subject: [PATCH 35/39] Renew docs, vol.2 --- juniper_codegen/src/lib.rs | 193 ++++++++++++++++++++++++++++++------- 1 file changed, 159 insertions(+), 34 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index f000fd9a7..dda3888d7 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -257,16 +257,6 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { } } -// TODO -/// A proc macro for defining a GraphQL subscription. -#[proc_macro_error] -#[proc_macro_attribute] -pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream { - self::graphql_subscription::attr::expand(attr.into(), body.into()) - .unwrap_or_abort() - .into() -} - /// `#[graphql_interface]` macro for generating a [GraphQL interface][1] /// implementation for traits and its implementers. /// @@ -380,6 +370,23 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// } /// ``` /// +/// # Ignoring trait methods +/// +/// 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. +/// +/// ``` +/// # use juniper::graphql_interface; +/// # +/// #[graphql_interface] +/// trait Character { +/// fn id(&self) -> &str; +/// +/// #[graphql(ignore)] +/// fn kaboom(&mut self); +/// } +/// ``` +/// /// # Custom context /// /// By default, the generated implementation tries to infer [`Context`] type from signatures of @@ -536,23 +543,6 @@ pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream /// } /// ``` /// -/// # Ignoring trait methods -/// -/// To omit some trait method to be assumed as a [GraphQL interface][1] field and ignore it, use an -/// `ignore`/`skip` attribute's argument directly on that method. -/// -/// ``` -/// # use juniper::graphql_interface; -/// # -/// #[graphql_interface] -/// trait Character { -/// fn id(&self) -> &str; -/// -/// #[graphql(ignore)] // or `#[graphql(skip)]`, your choice -/// fn kaboom(&mut self); -/// } -/// ``` -/// /// # Downcasting /// /// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via @@ -636,7 +626,111 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } -// TODO +/// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] +/// implementation for structs. +/// +/// 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::GraphQLObject; +/// +/// #[derive(GraphQLObject)] +/// struct Query { +/// // NOTICE: By default, field names will be converted to `camelCase`. +/// // In the generated GraphQL schema this field will be available +/// // as `apiVersion`. +/// api_version: &'static str, +/// } +/// ``` +/// +/// # Custom name, description and deprecation +/// +/// The name of [GraphQL object][1] or its field may be overridden with a `name` +/// attribute's argument. By default, a type name is used or `camelCased` field +/// name. +/// +/// The description of [GraphQL object][1] or its field may be specified either +/// with a `description`/`desc` attribute's argument, or with a regular Rust doc +/// comment. +/// +/// A field of [GraphQL object][1] may be deprecated by specifying a +/// `deprecated` attribute's argument, or with regular Rust `#[deprecated]` +/// attribute. +/// +/// ``` +/// # use juniper::GraphQLObject; +/// # +/// #[derive(GraphQLObject)] +/// #[graphql( +/// // Rename the type for GraphQL by specifying the name here. +/// name = "Human", +/// // You may also specify a description here. +/// // If present, doc comments will be ignored. +/// desc = "Possible episode human.", +/// )] +/// struct HumanWithAttrs { +/// #[graphql(name = "id", desc = "ID of the human.")] +/// #[graphql(deprecated = "Don't use it")] +/// some_id: String, +/// } +/// +/// // Rust docs are used as GraphQL description. +/// /// Possible episode human. +/// #[derive(GraphQLObject)] +/// struct HumanWithDocs { +/// // Doc comments also work on fields. +/// /// ID of the human. +/// #[deprecated] +/// id: String, +/// } +/// ``` +/// +/// # Ignoring struct fields +/// +/// To omit exposing a struct field in the GraphQL schema, use an `ignore` +/// attribute's argument directly on that field. +/// +/// ``` +/// # use juniper::GraphQLObject; +/// # +/// #[derive(GraphQLObject)] +/// struct Human { +/// id: String, +/// #[graphql(ignore)] +/// home_planet: String, +/// } +/// ``` +/// +/// # Custom `ScalarValue` +/// +/// By default, `#[derive(GraphQLObject)]` macro generates code, which is +/// generic over a [`ScalarValue`] type. This may introduce a problem when at +/// least one of its fields is restricted to a concrete [`ScalarValue`] type in +/// its implementation. To resolve such problem, a concrete [`ScalarValue`] type +/// should be specified with a `scalar` attribute's argument. +/// +/// ``` +/// # use juniper::{DefaultScalarValue, GraphQLObject}; +/// # +/// #[derive(GraphQLObject)] +/// // NOTICE: Removing `scalar` argument will fail compilation. +/// #[graphql(scalar = DefaultScalarValue)] +/// struct Human { +/// id: String, +/// helper: Droid, +/// } +/// +/// #[derive(GraphQLObject)] +/// #[graphql(scalar = DefaultScalarValue)] +/// struct Droid { +/// id: String, +/// } +/// ``` +/// +/// [`ScalarValue`]: juniper::ScalarValue +/// [1]: https://spec.graphql.org/June2018/#sec-Objects #[proc_macro_error] #[proc_macro_derive(GraphQLObject, attributes(graphql))] pub fn derive_object(body: TokenStream) -> TokenStream { @@ -792,6 +886,27 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// } /// ``` /// +/// # Ignoring trait methods +/// +/// To omit some method to be assumed as a [GraphQL object][1] field and ignore +/// it, use an `ignore` attribute's argument directly on that method. +/// +/// ``` +/// # use juniper::graphql_object; +/// # +/// struct Human(String); +/// +/// #[graphql_object] +/// impl Human { +/// fn id(&self) -> &str { +/// &self.0 +/// } +/// +/// #[graphql(ignore)] +/// fn kaboom(&mut self) {} +/// } +/// ``` +/// /// # Custom context /// /// By default, the generated implementation tries to infer [`Context`] type @@ -914,6 +1029,16 @@ pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +// TODO +/// A proc macro for defining a GraphQL subscription. +#[proc_macro_error] +#[proc_macro_attribute] +pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_subscription::attr::expand(attr.into(), body.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and /// structs. /// @@ -1061,8 +1186,8 @@ pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { /// /// # Ignoring enum variants /// -/// To omit exposing an enum variant in the GraphQL schema, use an `ignore`/`skip` attribute's -/// argument directly on that variant. +/// To omit exposing an enum variant in the GraphQL schema, use an `ignore` +/// attribute's argument directly on that variant. /// /// > __WARNING__: /// > It's the _library user's responsibility_ to ensure that ignored enum variant is _never_ @@ -1090,7 +1215,7 @@ pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { /// Human(Human), /// Droid(Droid), /// #[from(ignore)] -/// #[graphql(ignore)] // or `#[graphql(skip)]`, your choice +/// #[graphql(ignore)] /// _State(PhantomData), /// } /// ``` @@ -1402,8 +1527,8 @@ pub fn derive_union(body: TokenStream) -> TokenStream { /// /// # Ignoring trait methods /// -/// To omit some trait method to be assumed as a [GraphQL union][1] variant and ignore it, use an -/// `ignore`/`skip` attribute's argument directly on that method. +/// To omit some trait method to be assumed as a [GraphQL union][1] variant and +/// ignore it, use an `ignore` attribute's argument directly on that method. /// /// ``` /// # use juniper::{graphql_union, GraphQLObject}; @@ -1424,7 +1549,7 @@ pub fn derive_union(body: TokenStream) -> TokenStream { /// trait Character { /// fn as_human(&self) -> Option<&Human> { None } /// fn as_droid(&self) -> Option<&Droid> { None } -/// #[graphql(ignore)] // or `#[graphql(skip)]`, your choice +/// #[graphql(ignore)] /// fn id(&self) -> &str; /// } /// # From 5287c489f8c4d9aedbc77b229aa59ba1ec231567 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Aug 2021 13:56:00 +0300 Subject: [PATCH 36/39] Renew docs, vol.3 --- juniper_codegen/src/lib.rs | 100 ++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index dda3888d7..3f912cc97 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -370,6 +370,45 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// } /// ``` /// +/// # Renaming policy +/// +/// By default, all [GraphQL interface][1] fields and their arguments are renamed +/// via `camelCase` policy (so `fn my_id(&self) -> String` becomes `myId` field +/// in GraphQL schema, and so on). This complies with default GraphQL naming +/// conventions [demonstrated in spec][0]. +/// +/// 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::{graphql_interface, GraphQLObject}; +/// # +/// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming +/// trait Character { +/// // NOTICE: In the generated GraphQL schema this field and its argument +/// // will be `detailed_info` and `info_kind`. +/// 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 { +/// (info_kind == "planet") +/// .then(|| &self.home_planet) +/// .unwrap_or(&self.id) +/// .clone() +/// } +/// } +/// ``` +/// /// # Ignoring trait methods /// /// To omit some trait method to be assumed as a [GraphQL interface][1] field @@ -614,6 +653,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// [`Context`]: juniper::Context /// [`Executor`]: juniper::Executor /// [`ScalarValue`]: juniper::ScalarValue +/// [0]: https://spec.graphql.org/June2018 /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety /// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html @@ -687,6 +727,30 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { /// } /// ``` /// +/// # Renaming policy +/// +/// By default, all [GraphQL object][1] fields are renamed via `camelCase` +/// policy (so `api_version: String` becomes `apiVersion` field in GraphQL +/// schema, and so on). This complies with default GraphQL naming conventions +/// [demonstrated in spec][0]. +/// +/// 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::GraphQLObject; +/// # +/// #[derive(GraphQLObject)] +/// #[graphql(rename_all = "none")] // disables renaming +/// struct Query { +/// // NOTICE: In the generated GraphQL schema this field will be available +/// // as `api_version`. +/// api_version: String, +/// } +/// ``` +/// /// # Ignoring struct fields /// /// To omit exposing a struct field in the GraphQL schema, use an `ignore` @@ -886,7 +950,40 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// } /// ``` /// -/// # Ignoring trait methods +/// # Renaming policy +/// +/// By default, all [GraphQL object][1] fields and their arguments are renamed +/// via `camelCase` policy (so `fn api_version() -> String` becomes `apiVersion` +/// field in GraphQL schema, and so on). This complies with default GraphQL +/// naming conventions [demonstrated in spec][0]. +/// +/// 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::graphql_object; +/// # +/// struct Query; +/// +/// #[graphql_object(rename_all = "none")] // disables renaming +/// impl Query { +/// // NOTICE: In the generated GraphQL schema this field will be available +/// // as `api_version`. +/// fn api_version() -> &'static str { +/// "0.1" +/// } +/// +/// // NOTICE: In the generated GraphQL schema these field arguments will be +/// // available as `arg_a` and `arg_b`. +/// async fn add(arg_a: f64, arg_b: f64, c: Option) -> f64 { +/// arg_a + arg_b + c.unwrap_or(0.0) +/// } +/// } +/// ``` +/// +/// # Ignoring methods /// /// To omit some method to be assumed as a [GraphQL object][1] field and ignore /// it, use an `ignore` attribute's argument directly on that method. @@ -1020,6 +1117,7 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// [`Context`]: juniper::Context /// [`Executor`]: juniper::Executor /// [`ScalarValue`]: juniper::ScalarValue +/// [0]: https://spec.graphql.org/June2018 /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[proc_macro_error] #[proc_macro_attribute] From 0ea9d2731d7c694d1ad717e8424ace92922919d5 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Aug 2021 14:10:18 +0300 Subject: [PATCH 37/39] Renew docs, vol.4 --- juniper_codegen/src/lib.rs | 57 +++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 3f912cc97..e0b0729c9 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -261,8 +261,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// implementation for traits and its implementers. /// /// Specifying multiple `#[graphql_interface]` attributes on the same definition -/// is totally okay. -/// They all will be treated as a single attribute. +/// is totally okay. They all will be treated as a single attribute. /// /// The main difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to @@ -809,11 +808,10 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// /// It enables you to write GraphQL field resolvers for a type by declaring a /// regular Rust `impl` block. Under the hood, the macro implements -/// the GraphQLType trait. +/// [`GraphQLType`]/[`GraphQLValue`] traits. /// /// Specifying multiple `#[graphql_object]` attributes on the same definition -/// is totally okay. -/// They all will be treated as a single attribute. +/// is totally okay. They all will be treated as a single attribute. /// /// ``` /// use juniper::graphql_object; @@ -1116,6 +1114,8 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// /// [`Context`]: juniper::Context /// [`Executor`]: juniper::Executor +/// [`GraphQLType`]: juniper::GraphQLType +/// [`GraphQLValue`]: juniper::GraphQLValue /// [`ScalarValue`]: juniper::ScalarValue /// [0]: https://spec.graphql.org/June2018 /// [1]: https://spec.graphql.org/June2018/#sec-Objects @@ -1127,8 +1127,51 @@ pub fn graphql_object(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } -// TODO -/// A proc macro for defining a GraphQL subscription. +/// `#[graphql_subscription]` macro for generating a [GraphQL subscription][1] +/// implementation for structs with computable field resolvers (declared via +/// a regular Rust `impl` block). +/// +/// It enables you to write GraphQL field resolvers for a type by declaring a +/// regular Rust `impl` block. Under the hood, the macro implements +/// [`GraphQLType`]/[`GraphQLSubscriptionValue`] traits. +/// +/// Specifying multiple `#[graphql_subscription]` attributes on the same +/// definition is totally okay. They all will be treated as a single attribute. +/// +/// This macro is similar to [`#[graphql_object]` macro](macro@graphql_object) +/// and has all its properties, but requires methods to be `async` and return +/// [`Stream`] of values instead of a value itself. +/// +/// ``` +/// # use futures::stream::{self, BoxStream}; +/// use juniper::graphql_subscription; +/// +/// // We can declare the type as a plain struct without any members. +/// struct Subscription; +/// +/// #[graphql_subscription] +/// impl Subscription { +/// // WARNING: Only GraphQL fields can be specified in this `impl` block. +/// // If normal methods are required on the struct, they can be +/// // defined either in a separate "normal" `impl` block, or +/// // marked with `#[graphql(ignore)]` attribute. +/// +/// // This defines a simple, static field which does not require any +/// // context. +/// // Such field can return a `Stream` of any value implementing +/// // `GraphQLType` and `GraphQLValue` traits. +/// // +/// // NOTICE: Method must be `async`. +/// async fn api_version() -> BoxStream<'static, &'static str> { +/// Box::pin(stream::once(async { "0.1" })) +/// } +/// } +/// ``` +/// +/// [`GraphQLType`]: juniper::GraphQLType +/// [`GraphQLSubscriptionValue`]: juniper::GraphQLSubscriptionValue +/// [`Stream`]: futures::Stream +/// [1]: https://spec.graphql.org/June2018/#sec-Subscription #[proc_macro_error] #[proc_macro_attribute] pub fn graphql_subscription(attr: TokenStream, body: TokenStream) -> TokenStream { From d7bff0a1d8dbefde89f46a926404ddf56df7e5ed Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Aug 2021 16:20:45 +0300 Subject: [PATCH 38/39] Renew book docs, vol.5 --- docs/book/content/quickstart.md | 4 +- docs/book/content/types/interfaces.md | 15 ++++ .../content/types/objects/complex_fields.md | 68 +++++++++++++------ .../content/types/objects/defining_objects.md | 25 +++++-- .../content/types/objects/error_handling.md | 10 ++- 5 files changed, 89 insertions(+), 33 deletions(-) diff --git a/docs/book/content/quickstart.md b/docs/book/content/quickstart.md index b33f33444..1fbcd882a 100644 --- a/docs/book/content/quickstart.md +++ b/docs/book/content/quickstart.md @@ -6,11 +6,9 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch ## Installation -!FILENAME Cargo.toml - ```toml [dependencies] -juniper = { git = "https://github.com/graphql-rust/juniper" } +juniper = "0.15" ``` ## Schema example diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index b5e81e5ca..414d2f299 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -232,6 +232,21 @@ trait Character { # fn main() {} ``` +Renaming policies for all [GraphQL interface][1] fields and arguments are supported as well: +```rust +# #![allow(deprecated)] +# extern crate juniper; +use juniper::graphql_interface; + +#[graphql_interface(rename_all = "none")] // disables any renaming +trait Character { + // Now exposed as `my_id` and `my_num` in the schema + fn my_id(&self, my_num: i32) -> &str; +} +# +# fn main() {} +``` + ### Custom context diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md index 60dce4ae3..86d310eca 100644 --- a/docs/book/content/types/objects/complex_fields.md +++ b/docs/book/content/types/objects/complex_fields.md @@ -3,9 +3,10 @@ If you've got a struct that can't be mapped directly to GraphQL, that contains computed fields or circular structures, you have to use a more powerful tool: the `#[graphql_object]` procedural macro. This macro lets you define GraphQL object -fields in a Rust `impl` block for a type. Note that only GraphQL fields -can be specified in this `impl` block. If you want to define normal methods on the struct, -you have to do so in a separate, normal `impl` block. Continuing with the +fields in a Rust `impl` block for a type. Note, that GraphQL fields are defined in +this `impl` block by default. If you want to define normal methods on the struct, +you have to do so either in a separate "normal" `impl` block, or mark them with +`#[graphql(ignore)]` attribute to be omitted by the macro. Continuing with the example from the last chapter, this is how you would define `Person` using the macro: @@ -28,12 +29,15 @@ impl Person { fn age(&self) -> i32 { self.age } + + #[graphql(ignore)] + pub fn hidden_from_graphql(&self) { + // [...] + } } -// Note that this syntax generates an implementation of the GraphQLType trait, -// the base impl of your struct can still be written like usual: impl Person { - pub fn hidden_from_graphql(&self) { + pub fn hidden_from_graphql2(&self) { // [...] } } @@ -44,7 +48,6 @@ impl Person { While this is a bit more verbose, it lets you write any kind of function in the field resolver. With this syntax, fields can also take arguments: - ```rust # extern crate juniper; # use juniper::{graphql_object, GraphQLObject}; @@ -61,7 +64,7 @@ struct House { #[graphql_object] impl House { - // Creates the field inhabitantWithName(name), returning a nullable person + // Creates the field `inhabitantWithName(name)`, returning a nullable `Person`. fn inhabitant_with_name(&self, name: String) -> Option<&Person> { self.inhabitants.iter().find(|p| p.name == name) } @@ -127,15 +130,29 @@ impl Person { # fn main() { } ``` +Or provide a different renaming policy on a `impl` block for all its fields: +```rust +# extern crate juniper; +# use juniper::graphql_object; +struct Person; + +#[graphql_object(rename_all = "none")] // disables any renaming +impl Person { + // Now exposed as `renamed_field` in the schema + fn renamed_field() -> bool { + true + } +} +# +# fn main() {} +``` + ## Customizing arguments Method field arguments can also be customized. They can have custom descriptions and default values. -**Note**: The syntax for this is currently a little awkward. -This will become better once the [Rust RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented. - ```rust # extern crate juniper; # use juniper::graphql_object; @@ -147,6 +164,8 @@ impl Person { fn field1( &self, #[graphql( + // Arguments can also be renamed if required. + name = "arg", // Set a default value which will be injected if not present. // The default can be any valid Rust expression, including a function call, etc. default = true, @@ -154,7 +173,8 @@ impl Person { description = "The first argument..." )] arg1: bool, - #[graphql(default = 0)] + // If default expression is not specified then `Default::default()` value is used. + #[graphql(default)] arg2: i32, ) -> String { format!("{} {}", arg1, arg2) @@ -164,13 +184,23 @@ impl Person { # fn main() { } ``` -## More features +Provide a different renaming policy on a `impl` block also implies for arguments: +```rust +# extern crate juniper; +# use juniper::graphql_object; +struct Person; -GraphQL fields expose more features than Rust's standard method syntax gives us: +#[graphql_object(rename_all = "none")] // disables any renaming +impl Person { + // Now exposed as `my_arg` in the schema + fn field(my_arg: bool) -> bool { + my_arg + } +} +# +# fn main() {} +``` -* Per-field description and deprecation messages -* Per-argument default values -* Per-argument descriptions +## More features -These, and more features, are described more thoroughly in [the reference -documentation](https://docs.rs/juniper/latest/juniper/macro.object.html). +These, and more features, are described more thoroughly in [the reference documentation](https://docs.rs/juniper/latest/juniper/attr.graphql_object.html). diff --git a/docs/book/content/types/objects/defining_objects.md b/docs/book/content/types/objects/defining_objects.md index 1a4dcb7da..5f32c6862 100644 --- a/docs/book/content/types/objects/defining_objects.md +++ b/docs/book/content/types/objects/defining_objects.md @@ -152,7 +152,22 @@ struct Person { name: String, age: i32, #[graphql(name = "websiteURL")] - website_url: Option, // Now exposed as websiteURL in the schema + website_url: Option, // now exposed as `websiteURL` in the schema +} +# +# fn main() {} +``` + +Or provide a different renaming policy on a struct for all its fields: +```rust +# extern crate juniper; +# use juniper::GraphQLObject; +#[derive(GraphQLObject)] +#[graphql(rename_all = "none")] // disables any renaming +struct Person { + name: String, + age: i32, + website_url: Option, // now exposed as `website_url` in the schema } # # fn main() {} @@ -181,9 +196,9 @@ The `name`, `description`, and `deprecation` arguments can of course be combined. Some restrictions from the GraphQL spec still applies though; you can only deprecate object fields and enum values. -## Skipping fields +## Ignoring fields -By default all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(skip)]`: +By default, all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(ignore)]`: ```rust # extern crate juniper; @@ -192,9 +207,9 @@ By default all fields in a `GraphQLObject` are included in the generated GraphQL struct Person { name: String, age: i32, - #[graphql(skip)] + #[graphql(ignore)] # #[allow(dead_code)] - password_hash: String, // This cannot be queried or modified from GraphQL + password_hash: String, // cannot be queried or modified from GraphQL } # # fn main() {} diff --git a/docs/book/content/types/objects/error_handling.md b/docs/book/content/types/objects/error_handling.md index 3a96a70f7..4adc269fa 100644 --- a/docs/book/content/types/objects/error_handling.md +++ b/docs/book/content/types/objects/error_handling.md @@ -17,8 +17,7 @@ it will bubble up to the surrounding framework and hopefully be dealt with there. For recoverable errors, Juniper works well with the built-in `Result` type, you -can use the `?` operator or the `try!` macro and things will generally just work -as you expect them to: +can use the `?` operator and things will generally just work as you expect them to: ```rust # extern crate juniper; @@ -63,7 +62,6 @@ there - those errors are automatically converted into `FieldError`. Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability). - When a field returns an error, the field's result is replaced by `null`, an additional `errors` object is created at the top level of the response, and the execution is resumed. For example, with the previous example and the following @@ -83,12 +81,12 @@ returned: !FILENAME Response for nullable field with error -```js +```json { "data": { "example": { contents: "", - foo: null, + foo: null } }, "errors": [ @@ -117,7 +115,7 @@ following would be returned: !FILENAME Response for non-null field with error and no nullable parent -```js +```json { "errors": [ "message": "Permission denied (os error 13)", From 9d93eb75dd0d3e29eb0f811cab03b7db6df1ad90 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Aug 2021 16:55:49 +0300 Subject: [PATCH 39/39] Upd CHANGELOG --- juniper/CHANGELOG.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index dcbc294d2..eb0829b90 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,8 +1,19 @@ # master -- 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)) -- Expose `GraphQLRequest` fields ([#750](https://github.com/graphql-rust/juniper/issues/750)) -- 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)) +## Breaking Changes + +- `#[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) + +## 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) + +## 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)) # [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7) @@ -41,8 +52,8 @@ ## 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. + - `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))