diff --git a/derive/src/object.rs b/derive/src/object.rs index 5a7b9a91..2676ef4e 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -1,7 +1,8 @@ use proc_macro::TokenStream; +use proc_macro2::Span; use quote::quote; use syn::ext::IdentExt; -use syn::{Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeReference}; +use syn::{Block, Error, FnArg, Ident, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeReference}; use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget}; use crate::output_type::OutputType; @@ -18,12 +19,14 @@ pub fn generate( let crate_name = get_crate_name(object_args.internal); let (self_ty, self_name) = get_type_path_and_name(item_impl.self_ty.as_ref())?; let generics = &item_impl.generics; + let generics_params = &generics.params; let where_clause = &item_impl.generics.where_clause; let extends = object_args.extends; let gql_typename = object_args .name .clone() .unwrap_or_else(|| RenameTarget::Type.rename(self_name.clone())); + let shadow_type = Ident::new(&format!("__Shadow{}", gql_typename), Span::call_site()); let desc = if object_args.use_type_description { quote! { ::std::option::Option::Some(::description()) } @@ -466,8 +469,10 @@ pub fn generate( let block = &method.block; let new_block = quote!({ { - let value:#inner_ty = async move #block.await; - ::std::result::Result::Ok(value) + ::std::result::Result::Ok(async move { + let value:#inner_ty = #block; + value + }.await) } }); method.block = syn::parse2::(new_block).expect("invalid block"); @@ -534,11 +539,15 @@ pub fn generate( } let visible = visible_fn(&object_args.visible); + let expanded = quote! { #item_impl + #[allow(non_snake_case)] + type #shadow_type<#generics_params> = #self_ty; + #[allow(clippy::all, clippy::pedantic)] - impl #generics #crate_name::Type for #self_ty #where_clause { + impl #generics #crate_name::Type for #shadow_type<#generics_params> #where_clause { fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { ::std::borrow::Cow::Borrowed(#gql_typename) } @@ -566,7 +575,7 @@ pub fn generate( #[allow(clippy::all, clippy::pedantic, clippy::suspicious_else_formatting)] #[allow(unused_braces, unused_variables, unused_parens, unused_mut)] #[#crate_name::async_trait::async_trait] - impl#generics #crate_name::resolver_utils::ContainerType for #self_ty #where_clause { + impl#generics #crate_name::resolver_utils::ContainerType for #shadow_type<#generics_params> #where_clause { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { #(#resolvers)* ::std::result::Result::Ok(::std::option::Option::None) @@ -592,13 +601,16 @@ pub fn generate( #[allow(clippy::all, clippy::pedantic)] #[#crate_name::async_trait::async_trait] - impl #generics #crate_name::OutputType for #self_ty #where_clause { + impl #generics #crate_name::OutputType for #shadow_type<#generics_params> #where_clause { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { #crate_name::resolver_utils::resolve_container(ctx, self).await } } - impl #generics #crate_name::ObjectType for #self_ty #where_clause {} + impl #generics #crate_name::ObjectType for #shadow_type<#generics_params> #where_clause {} }; + if gql_typename == "QueryRoot11" { + println!("{}", expanded); + } Ok(expanded.into()) } diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 7de05ad7..ea263630 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -7,7 +7,7 @@ use quote::quote; use syn::visit::Visit; use syn::{ Attribute, Error, Expr, ExprPath, Ident, Lit, LitStr, Meta, NestedMeta, Type, TypeGroup, - TypePath, + TypeParamBound, }; use thiserror::Error; @@ -380,10 +380,10 @@ pub fn remove_graphql_attrs(attrs: &mut Vec) { } } -pub fn get_type_path_and_name(ty: &Type) -> GeneratorResult<(&TypePath, String)> { +pub fn get_type_path_and_name(ty: &Type) -> GeneratorResult<(&Type, String)> { match ty { Type::Path(path) => Ok(( - path, + ty, path.path .segments .last() @@ -391,6 +391,19 @@ pub fn get_type_path_and_name(ty: &Type) -> GeneratorResult<(&TypePath, String)> .unwrap(), )), Type::Group(TypeGroup { elem, .. }) => get_type_path_and_name(&elem), + Type::TraitObject(trait_object) => Ok(( + ty, + trait_object + .bounds + .iter() + .find_map(|bound| match bound { + TypeParamBound::Trait(t) => { + Some(t.path.segments.last().map(|s| s.ident.to_string()).unwrap()) + } + _ => None, + }) + .unwrap(), + )), _ => Err(Error::new_spanned(ty, "Invalid type").into()), } } diff --git a/src/base.rs b/src/base.rs index a19fd3ec..b13571e7 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use crate::parser::types::Field; use crate::registry::Registry; @@ -77,6 +78,50 @@ impl OutputType for &T { } } +impl Type for Box { + fn type_name() -> Cow<'static, str> { + T::type_name() + } + + fn create_type_info(registry: &mut Registry) -> String { + T::create_type_info(registry) + } +} + +#[async_trait::async_trait] +impl OutputType for Box { + #[allow(clippy::trivially_copy_pass_by_ref)] + async fn resolve( + &self, + ctx: &ContextSelectionSet<'_>, + field: &Positioned, + ) -> ServerResult { + T::resolve(&**self, ctx, field).await + } +} + +impl Type for Arc { + fn type_name() -> Cow<'static, str> { + T::type_name() + } + + fn create_type_info(registry: &mut Registry) -> String { + T::create_type_info(registry) + } +} + +#[async_trait::async_trait] +impl OutputType for Arc { + #[allow(clippy::trivially_copy_pass_by_ref)] + async fn resolve( + &self, + ctx: &ContextSelectionSet<'_>, + field: &Positioned, + ) -> ServerResult { + T::resolve(&**self, ctx, field).await + } +} + impl Type for Result { fn type_name() -> Cow<'static, str> { T::type_name() diff --git a/src/lib.rs b/src/lib.rs index 59e6ee63..5e2df2f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,6 +316,8 @@ pub type FieldResult = Result; /// /// # Examples /// +/// Implements GraphQL Object for struct. +/// /// ```rust /// use async_graphql::*; /// @@ -363,6 +365,57 @@ pub type FieldResult = Result; /// })); /// }); /// ``` +/// +/// # Examples +/// +/// Implements GraphQL Object for trait object. +/// +/// ```rust +/// use async_graphql::*; +/// +/// trait MyTrait: Send + Sync { +/// fn name(&self) -> &str; +/// } +/// +/// #[Object] +/// impl dyn MyTrait { +/// #[graphql(name = "name")] +/// async fn gql_name(&self) -> &str { +/// self.name() +/// } +/// } +/// +/// struct MyObj(String); +/// +/// impl MyTrait for MyObj { +/// fn name(&self) -> &str { +/// &self.0 +/// } +/// } +/// +/// struct QueryRoot; +/// +/// #[Object] +/// impl QueryRoot { +/// async fn objs(&self) -> Vec> { +/// vec![ +/// Box::new(MyObj("a".to_string())), +/// Box::new(MyObj("b".to_string())), +/// ] +/// } +/// } +/// +/// async_std::task::block_on(async move { +/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); +/// let res = schema.execute("{ objs { name } }").await.into_result().unwrap().data; +/// assert_eq!(res, value!({ +/// "objs": [ +/// { "name": "a" }, +/// { "name": "b" }, +/// ] +/// })); +/// }); +/// ``` pub use async_graphql_derive::Object; /// Define a GraphQL object with fields diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 85ac2a3f..52acebd8 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -297,7 +297,7 @@ pub struct Registry { } impl Registry { - pub fn create_type MetaType>( + pub fn create_type MetaType>( &mut self, mut f: F, ) -> String { diff --git a/src/resolver_utils/container.rs b/src/resolver_utils/container.rs index c32e54fb..91f11372 100644 --- a/src/resolver_utils/container.rs +++ b/src/resolver_utils/container.rs @@ -36,7 +36,7 @@ pub trait ContainerType: OutputType { fields: &mut Fields<'a>, ) -> ServerResult<()> where - Self: Sized + Send + Sync, + Self: Send + Sync, { fields.add_set(ctx, self) } @@ -61,7 +61,7 @@ impl ContainerType for &T { } /// Resolve an container by executing each of the fields concurrently. -pub async fn resolve_container<'a, T: ContainerType + Send + Sync>( +pub async fn resolve_container<'a, T: ContainerType + Send + Sync + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, ) -> ServerResult { @@ -69,7 +69,7 @@ pub async fn resolve_container<'a, T: ContainerType + Send + Sync>( } /// Resolve an container by executing each of the fields serially. -pub async fn resolve_container_serial<'a, T: ContainerType + Send + Sync>( +pub async fn resolve_container_serial<'a, T: ContainerType + Send + Sync + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, ) -> ServerResult { @@ -102,7 +102,7 @@ fn insert_value(target: &mut BTreeMap, name: Name, value: Value) { } } -async fn resolve_container_inner<'a, T: ContainerType + Send + Sync>( +async fn resolve_container_inner<'a, T: ContainerType + Send + Sync + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, parallel: bool, @@ -134,7 +134,7 @@ pub struct Fields<'a>(Vec>); impl<'a> Fields<'a> { /// Add another set of fields to this set of fields using the given container. - pub fn add_set( + pub fn add_set( &mut self, ctx: &ContextSelectionSet<'a>, root: &'a T,