1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
use darling::util::SpannedValue; use proc_macro2::{Span, TokenStream}; use crate::{load_schema, Ident, TypePath}; pub mod input; pub use input::InlineFragmentsDeriveInput; use input::{InlineFragmentsDeriveField, InlineFragmentsDeriveVariant}; pub fn inline_fragments_derive(ast: &syn::DeriveInput) -> Result<TokenStream, syn::Error> { use darling::FromDeriveInput; match InlineFragmentsDeriveInput::from_derive_input(ast) { Ok(input) => inline_fragments_derive_impl(input), Err(e) => Ok(e.write_errors()), } } pub(crate) fn inline_fragments_derive_impl( input: InlineFragmentsDeriveInput, ) -> Result<TokenStream, syn::Error> { use quote::{quote, quote_spanned}; let schema = load_schema(&*input.schema_path).map_err(|e| e.to_syn_error(input.schema_path.span()))?; if !find_union_type(&input.graphql_type, &schema) { return Err(syn::Error::new( input.graphql_type.span(), format!("Could not find a Union type named {}", &*input.graphql_type), )); } let argument_struct = if let Some(arg_struct) = input.argument_struct { let span = arg_struct.span(); let arg_struct_val: Ident = arg_struct.into(); let argument_struct = quote_spanned! { span => #arg_struct_val }; syn::parse2(argument_struct)? } else { syn::parse2(quote! { () })? }; if let darling::ast::Data::Enum(variants) = &input.data { let inline_fragments_impl = InlineFragmentsImpl { target_struct: input.ident.clone(), type_lock: TypePath::concat(&[ Ident::new_spanned(&*input.query_module, input.query_module.span()).into(), Ident::for_type(&*input.graphql_type).into(), ]), argument_struct, possible_types: possible_types_from_variants(variants)?, graphql_type_name: (*input.graphql_type).clone(), }; Ok(quote! { #inline_fragments_impl }) } else { Err(syn::Error::new( Span::call_site(), format!("InlineFragments can only be derived from an enum"), )) } } fn possible_types_from_variants( variants: &[SpannedValue<InlineFragmentsDeriveVariant>], ) -> Result<Vec<(syn::Ident, syn::Type)>, syn::Error> { let mut result = vec![]; for variant in variants { if variant.fields.style != darling::ast::Style::Tuple || variant.fields.fields.len() != 1 { return Err(syn::Error::new( variant.span(), "InlineFragments derive requires enum variants to have one unnamed field", )); } let field = variant.fields.fields.first().unwrap(); result.push((variant.ident.clone(), field.ty.clone())); } Ok(result) } struct InlineFragmentsImpl { target_struct: syn::Ident, type_lock: TypePath, argument_struct: syn::Type, possible_types: Vec<(syn::Ident, syn::Type)>, graphql_type_name: String, } impl quote::ToTokens for InlineFragmentsImpl { fn to_tokens(&self, tokens: &mut TokenStream) { use quote::{quote, TokenStreamExt}; let target_struct = &self.target_struct; let type_lock = &self.type_lock; let arguments = &self.argument_struct; let internal_types: Vec<_> = self.possible_types.iter().map(|(_, ty)| ty).collect(); let variants: Vec<_> = self.possible_types.iter().map(|(v, _)| v).collect(); let graphql_type = proc_macro2::Literal::string(&self.graphql_type_name); tokens.append_all(quote! { impl ::cynic::InlineFragments for #target_struct { type TypeLock = #type_lock; type Arguments = #arguments; fn fragments(arguments: Self::Arguments) -> Vec<(String, ::cynic::SelectionSet<'static, Self, Self::TypeLock>)> { use ::cynic::QueryFragment; let mut rv = vec![]; #( rv.push(( #internal_types::graphql_type(), #internal_types::fragment(arguments) .map(#target_struct::#variants) .transform_typelock() )); )* rv } fn graphql_type() -> String { #graphql_type.to_string() } } }); } } fn find_union_type(name: &str, schema: &graphql_parser::schema::Document) -> bool { for definition in &schema.definitions { use graphql_parser::schema::{Definition, TypeDefinition}; match definition { Definition::TypeDefinition(TypeDefinition::Union(union)) => { if union.name == name { return true; } } _ => {} } } return false; }