Skip to content

Commit bd53872

Browse files
ForsakenHarmonytheduke
authored andcommitted
Add impl derive for graphql objects
1 parent 6562440 commit bd53872

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed
+248
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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+
}

juniper_codegen/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern crate regex;
1919
mod derive_enum;
2020
mod derive_input_object;
2121
mod derive_object;
22+
mod derive_object_impl;
2223
mod derive_scalar_value;
2324
mod util;
2425

@@ -61,6 +62,13 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
6162
gen.into()
6263
}
6364

65+
#[proc_macro_attribute]
66+
pub fn gql_object(_metadata: TokenStream, input: TokenStream) -> TokenStream {
67+
let ast = syn::parse::<syn::Item>(input).unwrap();
68+
let gen = derive_object_impl::impl_gql_object(ast);
69+
gen.into()
70+
}
71+
6472
#[proc_macro_derive(GraphQLScalarValue)]
6573
pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
6674
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();

0 commit comments

Comments
 (0)