use proc_macro::TokenStream; use proc_macro_error::MacroError; use quote::quote; use syn::spanned::Spanned; use crate::util; struct ResolverVariant { pub ty: syn::Type, pub resolver: syn::Expr, } struct ResolveBody { pub variants: Vec, } impl syn::parse::Parse for ResolveBody { fn parse(input: syn::parse::ParseStream) -> Result { input.parse::()?; input.parse::()?; let match_body; syn::braced!( match_body in input ); let mut variants = Vec::new(); while !match_body.is_empty() { let ty = match_body.parse::()?; match_body.parse::()?; let resolver = match_body.parse::()?; variants.push(ResolverVariant { ty, resolver }); // Optinal trailing comma. match_body.parse::().ok(); } if !input.is_empty() { return Err(input.error("Unexpected input")); } Ok(Self { variants }) } } pub fn impl_union( is_internal: bool, attrs: TokenStream, body: TokenStream, ) -> Result { let _impl = util::parse_impl::ImplBlock::parse(attrs, body); // Validate trait target name, if present. if let Some((name, path)) = &_impl.target_trait { if !(name == "GraphQLUnion" || name == "juniper.GraphQLUnion") { return Err(MacroError::new( path.span(), "Invalid impl target trait: expected 'GraphQLUnion'".to_string(), )); } } let type_ident = &_impl.type_ident; let name = _impl .attrs .name .clone() .unwrap_or_else(|| type_ident.to_string()); let crate_name = util::juniper_path(is_internal); let scalar = _impl .attrs .scalar .as_ref() .map(|s| quote!( #s )) .unwrap_or_else(|| { quote! { #crate_name::DefaultScalarValue } }); if !_impl.has_resolve_method() { return Err(MacroError::new( _impl.target_type.span(), "Invalid impl body: expected one method with signature: fn resolve(&self) { ... }" .to_string(), )); } let method = _impl .methods .iter() .find(|&m| _impl.parse_resolve_method(&m).is_ok()); if _impl.methods.is_empty() || method.is_none() { return Err(MacroError::new( _impl.target_type.span(), "Invalid impl body: expected one method with signature: fn resolve(&self) { ... }" .to_string(), )); } let method = method.expect("checked above"); let resolve_args = _impl .parse_resolve_method(method) .expect("Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"); let stmts = &method.block.stmts; let body_raw = quote!( #( #stmts )* ); let body = syn::parse::(body_raw.into())?; let meta_types = body.variants.iter().map(|var| { let var_ty = &var.ty; quote! { registry.get_type::<&#var_ty>(&(())), } }); let concrete_type_resolver = body.variants.iter().map(|var| { let var_ty = &var.ty; let resolve = &var.resolver; quote! { if ({#resolve} as std::option::Option<&#var_ty>).is_some() { return <#var_ty as #crate_name::GraphQLType<#scalar>>::name(&()).unwrap().to_string(); } } }); let resolve_into_type = body.variants.iter().map(|var| { let var_ty = &var.ty; let resolve = &var.resolver; quote! { if type_name == (<#var_ty as #crate_name::GraphQLType<#scalar>>::name(&())).unwrap() { return executor.resolve(&(), &{ #resolve }); } } }); let generics = _impl.generics; let (impl_generics, _, where_clause) = generics.split_for_impl(); let description = match _impl.description.as_ref() { Some(value) => quote!( .description( #value ) ), None => quote!(), }; let context = _impl .attrs .context .map(|c| quote! { #c }) .unwrap_or_else(|| quote! { () }); let ty = _impl.target_type; let output = quote! { impl #impl_generics #crate_name::GraphQLType<#scalar> for #ty #where_clause { type Context = #context; type TypeInfo = (); fn name(_ : &Self::TypeInfo) -> Option<&str> { Some(#name) } fn meta<'r>( info: &Self::TypeInfo, registry: &mut #crate_name::Registry<'r, #scalar> ) -> #crate_name::meta::MetaType<'r, #scalar> where #scalar: 'r, { let types = &[ #( #meta_types )* ]; registry.build_union_type::<#ty>( info, types ) #description .into_meta() } #[allow(unused_variables)] fn concrete_type_name(&self, context: &Self::Context, _info: &Self::TypeInfo) -> String { #( #concrete_type_resolver )* panic!("Concrete type not handled by instance resolvers on {}", #name); } fn resolve_into_type( &self, _info: &Self::TypeInfo, type_name: &str, _: Option<&[#crate_name::Selection<#scalar>]>, executor: &#crate_name::Executor, ) -> #crate_name::ExecutionResult<#scalar> { let context = &executor.context(); #( #resolve_args )* #( #resolve_into_type )* panic!("Concrete type not handled by instance resolvers on {}", #name); } } }; Ok(output.into()) }