|
| 1 | +use proc_macro::TokenStream; |
| 2 | +use quote::Tokens; |
| 3 | +use std::collections::HashMap; |
| 4 | +use syn::{ |
| 5 | + parse, Attribute, FnArg, Ident, ImplItem, ImplItemMethod, Item, ItemImpl, Lit, Meta, |
| 6 | + NestedMeta, Pat, ReturnType, Type, TypeReference, |
| 7 | +}; |
| 8 | + |
| 9 | +fn get_attr_map(attr: &Attribute) -> Option<(String, HashMap<String, String>)> { |
| 10 | + let meta = attr.interpret_meta(); |
| 11 | + |
| 12 | + let meta_list = match meta { |
| 13 | + Some(Meta::List(ref meta_list)) => meta_list, |
| 14 | + _ => return None, |
| 15 | + }; |
| 16 | + |
| 17 | + let ident = meta_list.ident.to_string(); |
| 18 | + |
| 19 | + let mut attr_map = HashMap::new(); |
| 20 | + |
| 21 | + for meta in meta_list.nested.iter() { |
| 22 | + let value = match meta { |
| 23 | + NestedMeta::Meta(Meta::NameValue(ref value)) => value, |
| 24 | + _ => continue, |
| 25 | + }; |
| 26 | + |
| 27 | + let name = value.ident.to_string(); |
| 28 | + |
| 29 | + let ident = match value.lit { |
| 30 | + Lit::Str(ref string) => string.value(), |
| 31 | + _ => continue, |
| 32 | + }; |
| 33 | + |
| 34 | + attr_map.insert(name, ident); |
| 35 | + } |
| 36 | + |
| 37 | + Some((ident, attr_map)) |
| 38 | +} |
| 39 | + |
| 40 | +pub fn impl_gql_object(ast: Item) -> Tokens { |
| 41 | + let ItemImpl { |
| 42 | + attrs, |
| 43 | + defaultness, |
| 44 | + unsafety, |
| 45 | + impl_token, |
| 46 | + generics, |
| 47 | + trait_, |
| 48 | + self_ty, |
| 49 | + mut items, |
| 50 | + brace_token, |
| 51 | + } = if let Item::Impl(imp) = ast { |
| 52 | + imp |
| 53 | + } else { |
| 54 | + panic!("#[gql_object] Can only be applied to impl blocks"); |
| 55 | + }; |
| 56 | + |
| 57 | + let (context, description) = if let Some((_, map)) = attrs |
| 58 | + .iter() |
| 59 | + .filter_map(get_attr_map) |
| 60 | + .find(|(name, _)| name == "graphql") |
| 61 | + { |
| 62 | + ( |
| 63 | + map.get("context").map(|st| Ident::from(st.clone())), |
| 64 | + map.get("description").map(|i| i.to_string()), |
| 65 | + ) |
| 66 | + } else { |
| 67 | + (None, None) |
| 68 | + }; |
| 69 | + |
| 70 | + let parsed: TypeReference = parse(quote!(&Executor<#context>).into()).unwrap(); |
| 71 | + |
| 72 | + let mut fns = Vec::new(); |
| 73 | + |
| 74 | + for item in &mut items { |
| 75 | + match item { |
| 76 | + ImplItem::Const(..) => panic!("Unexpected const item"), |
| 77 | + ImplItem::Macro(..) => panic!("Unexpected macro item"), |
| 78 | + ImplItem::Verbatim(..) => panic!("Unexpected verbatim item"), |
| 79 | + ImplItem::Type(..) => panic!("Unexpected type item"), |
| 80 | + ImplItem::Method(ImplItemMethod { |
| 81 | + sig, ref mut attrs, .. |
| 82 | + }) => { |
| 83 | + let (description, deprecated) = if let Some((_, map)) = attrs |
| 84 | + .iter() |
| 85 | + .filter_map(get_attr_map) |
| 86 | + .find(|(name, _)| name == "graphql") |
| 87 | + { |
| 88 | + ( |
| 89 | + map.get("description").map(|i| i.to_string()), |
| 90 | + map.get("deprecated").map(|i| i.to_string()), |
| 91 | + ) |
| 92 | + } else { |
| 93 | + (None, None) |
| 94 | + }; |
| 95 | + |
| 96 | + attrs.clear(); |
| 97 | + |
| 98 | + match sig.decl.inputs[0] { |
| 99 | + FnArg::Captured(ref arg) => match arg.ty { |
| 100 | + Type::Reference(ref ty) if ty == &parsed => {} |
| 101 | + _ => continue, |
| 102 | + }, |
| 103 | + _ => continue, |
| 104 | + } |
| 105 | + |
| 106 | + let ret: Type = match sig.decl.output { |
| 107 | + ReturnType::Type(_, ref ret) => (**ret).clone(), |
| 108 | + _ => continue, |
| 109 | + }; |
| 110 | + |
| 111 | + let mut fn_args = Vec::new(); |
| 112 | + |
| 113 | + for arg in sig.decl.inputs.iter().skip(1) { |
| 114 | + if let FnArg::Captured(arg) = arg { |
| 115 | + fn_args.push((arg.pat.clone(), arg.ty.clone())); |
| 116 | + } else { |
| 117 | + panic!("invalid arg {:?}", stringify!(arg)); |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + fns.push((sig.ident, fn_args, ret, description, deprecated)); |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + let name = (*self_ty).clone(); |
| 127 | + |
| 128 | + let item = Item::Impl(ItemImpl { |
| 129 | + attrs: Vec::new(), |
| 130 | + defaultness, |
| 131 | + unsafety, |
| 132 | + impl_token, |
| 133 | + generics, |
| 134 | + trait_, |
| 135 | + self_ty, |
| 136 | + items, |
| 137 | + brace_token, |
| 138 | + }); |
| 139 | + |
| 140 | + let exec_fns = fns.iter().map(|(name, args, _, _, _)| { |
| 141 | + let get_args = args.iter().map(|(arg_name, arg_type)| { |
| 142 | + quote! { |
| 143 | + let #arg_name: #arg_type = args.get(&to_camel_case(stringify!(#arg_name))).expect("Argument missing - validation must have failed"); |
| 144 | + } |
| 145 | + }); |
| 146 | + |
| 147 | + let arg_names = args.iter().map(|(name, _)| name); |
| 148 | + |
| 149 | + quote! { |
| 150 | + if field == &to_camel_case(stringify!(#name)) { |
| 151 | + #(#get_args)* |
| 152 | + |
| 153 | + let result = Self::#name(&executor, #( #arg_names ),*); |
| 154 | + return (IntoResolvable::into(result, executor.context())).and_then(|res| |
| 155 | + match res { |
| 156 | + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), |
| 157 | + None => Ok(Value::null()), |
| 158 | + }); |
| 159 | + } |
| 160 | + } |
| 161 | + }); |
| 162 | + |
| 163 | + let register_fns = fns |
| 164 | + .iter() |
| 165 | + .map(|(name, args, ret, description, deprecation)| { |
| 166 | + let args = args.iter().map(|(arg_name, arg_type)| { |
| 167 | + quote! { |
| 168 | + .argument(registry.arg::<#arg_type>(&to_camel_case(stringify!(#arg_name)), info)) |
| 169 | + } |
| 170 | + }); |
| 171 | + |
| 172 | + let description = match description { |
| 173 | + Some(description) => quote!(.description(#description)), |
| 174 | + None => quote!(), |
| 175 | + }; |
| 176 | + |
| 177 | + let deprecation = match deprecation { |
| 178 | + Some(deprecation) => quote!(.deprecation(#deprecation)), |
| 179 | + None => quote!(), |
| 180 | + }; |
| 181 | + |
| 182 | + quote! { |
| 183 | + fields.push( |
| 184 | + registry |
| 185 | + .field_convert::<#ret, _, Self::Context>(&to_camel_case(stringify!(#name)), info) |
| 186 | + #(#args)* |
| 187 | + #description |
| 188 | + #deprecation |
| 189 | + ); |
| 190 | + } |
| 191 | + }); |
| 192 | + |
| 193 | + let description = match description { |
| 194 | + Some(description) => quote!(mt = mt.description(#description);), |
| 195 | + None => quote!(), |
| 196 | + }; |
| 197 | + |
| 198 | + let gql_impl = quote! { |
| 199 | + impl GraphQLType for #name { |
| 200 | + type Context = #context; |
| 201 | + type TypeInfo = (); |
| 202 | + |
| 203 | + fn name(info: &<Self as GraphQLType>::TypeInfo) -> Option<&str> { |
| 204 | + Some(stringify!(#name)) |
| 205 | + } |
| 206 | + |
| 207 | + #[allow(unused_assignments)] |
| 208 | + #[allow(unused_mut)] |
| 209 | + fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r>) -> MetaType<'r> { |
| 210 | + let mut fields = Vec::new(); |
| 211 | + let mut interfaces: Option<Vec<Type>> = None; |
| 212 | + #(#register_fns)* |
| 213 | + let mut mt = registry.build_object_type::<#name>(info, &fields); |
| 214 | + |
| 215 | + #description |
| 216 | + |
| 217 | + if let Some(interfaces) = interfaces { |
| 218 | + mt = mt.interfaces(&interfaces); |
| 219 | + } |
| 220 | + |
| 221 | + mt.into_meta() |
| 222 | + } |
| 223 | + |
| 224 | + #[allow(unused_variables)] |
| 225 | + #[allow(unused_mut)] |
| 226 | + fn resolve_field( |
| 227 | + &self, |
| 228 | + info: &(), |
| 229 | + field: &str, |
| 230 | + args: &Arguments, |
| 231 | + executor: &Executor<Self::Context>, |
| 232 | + ) -> ExecutionResult { |
| 233 | + #(#exec_fns)* |
| 234 | + |
| 235 | + panic!("Field {} not found on type {}", field, "Mutation"); |
| 236 | + } |
| 237 | + |
| 238 | + fn concrete_type_name(&self, _: &Self::Context) -> String { |
| 239 | + strin gify!(#name).to_string() |
| 240 | + } |
| 241 | + } |
| 242 | + }; |
| 243 | + |
| 244 | + quote! { |
| 245 | + #item |
| 246 | + #gql_impl |
| 247 | + } |
| 248 | +} |
0 commit comments