From 1312c6a026c962808a4926eac8e53685d4eaf470 Mon Sep 17 00:00:00 2001 From: theduke Date: Sat, 24 Jun 2017 20:20:00 +0200 Subject: [PATCH] Implement derive for input objects --- juniper_codegen/src/input_objects.rs | 212 +++++++++++++++++++++++++++ juniper_codegen/src/lib.rs | 9 ++ 2 files changed, 221 insertions(+) create mode 100644 juniper_codegen/src/input_objects.rs diff --git a/juniper_codegen/src/input_objects.rs b/juniper_codegen/src/input_objects.rs new file mode 100644 index 00000000..78440b14 --- /dev/null +++ b/juniper_codegen/src/input_objects.rs @@ -0,0 +1,212 @@ +use syn; +use syn::*; +use quote::Tokens; + +use ::util::*; + + +#[derive(Default, Debug)] +struct ObjAttrs { + name: Option, + description: Option, +} + +impl ObjAttrs { + fn from_input(input: &DeriveInput) -> ObjAttrs { + let mut res = ObjAttrs::default(); + + // Check attributes for name and description. + if let Some(items) = get_graphl_attr(&input.attrs) { + for item in items { + if let Some(val) = keyed_item_value(item, "name", true) { + res.name = Some(val); + continue; + } + if let Some(val) = keyed_item_value(item, "description", true) { + res.description = Some(val); + continue; + } + panic!(format!( + "Unknown attribute for #[derive(GraphQLInputObject)]: {:?}", + item)); + } + } + res + } +} + +#[derive(Default)] +struct ObjFieldAttrs { + name: Option, + description: Option, + default: Option, +} + +impl ObjFieldAttrs { + fn from_input(variant: &Field) -> ObjFieldAttrs { + let mut res = ObjFieldAttrs::default(); + + // Check attributes for name and description. + if let Some(items) = get_graphl_attr(&variant.attrs) { + for item in items { + if let Some(val) = keyed_item_value(item, "name", true) { + res.name = Some(val); + continue; + } + if let Some(val) = keyed_item_value(item, "description", true) { + res.description = Some(val); + continue; + } + if let Some(val) = keyed_item_value(item, "default", true) { + res.default = Some(val); + continue; + } + panic!(format!( + "Unknown attribute for #[derive(GraphQLInputObject)]: {:?}", + item)); + } + } + res + } +} + +pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens { + let fields = match ast.body { + Body::Struct(ref data) => { + match data { + &VariantData::Struct(ref fields) => fields, + _ => { + panic!("#[derive(GraphQLInputObject)] may only be used on regular structs with fields"); + }, + } + }, + Body::Enum(_) => { + panic!("#[derive(GraphlQLInputObject)] may only be applied to structs, not to enums"); + }, + }; + + // Parse attributes. + let ident = &ast.ident; + let attrs = ObjAttrs::from_input(ast); + let name = attrs.name.unwrap_or(ast.ident.to_string()); + + let mut meta_fields = Vec::::new(); + let mut from_inputs = Vec::::new(); + let mut to_inputs = Vec::::new(); + + for field in fields { + let field_ty = &field.ty; + let field_attrs = ObjFieldAttrs::from_input(field); + let field_ident = field.ident.as_ref().unwrap(); + + // Build value. + let name = match field_attrs.name { + Some(ref name) => { + // Custom name specified. + name.to_string() + }, + None => { + // Note: auto camel casing when no custom name specified. + ::util::to_camel_case(field_ident.as_ref()) + }, + }; + let descr = match field_attrs.description { + Some(s) => quote!{ Some(#s.to_string()) }, + None => quote!{ None }, + }; + + let default = match field_attrs.default { + Some(ref def) => { + match syn::parse_token_trees(def) { + Ok(t) => Some(quote!{ #(#t)* }), + Err(_) => { + panic!("#graphql(default = ?) must be a valid Rust expression inside a string"); + }, + } + }, + None => None, + }; + + let meta_field = match default { + Some(ref def) => { + quote!{ + registry.arg_with_default::<#field_ty>( #name, &#def), + } + }, + None => { + quote!{ + registry.arg::<#field_ty>(#name), + } + } + }; + meta_fields.push(meta_field); + + // Buil from_input clause. + + let from_input_default = match default { + Some(ref def) => { + quote!{ + Some(&&::juniper::InputValue::Null) | None if true => #def, + } + }, + None => quote!{}, + }; + + let from_input = quote!{ + #field_ident: { + // TODO: investigate the unwraps here, they seem dangerous! + match obj.get(#name) { + #from_input_default + Some(v) => ::juniper::FromInputValue::from(v).unwrap(), + _ => ::juniper::FromInputValue::from(&::juniper::InputValue::null()).unwrap() + } + }, + }; + from_inputs.push(from_input); + + // Build to_input clause. + let to_input = quote!{ + (#name, self.#field_ident.to()), + }; + to_inputs.push(to_input); + } + + quote! { + impl ::juniper::GraphQLType for #ident { + type Context = (); + + fn name() -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>(registry: &mut ::juniper::Registry<'r>) -> ::juniper::meta::MetaType<'r> { + let fields = &[ + #(#meta_fields)* + ]; + registry.build_input_object_type::<#ident>(fields).into_meta() + } + } + + impl ::juniper::FromInputValue for #ident { + fn from(value: &::juniper::InputValue) -> Option<#ident> { + if let Some(obj) = value.to_object_value() { + let item = #ident { + #(#from_inputs)* + }; + Some(item) + } + else { + None + } + } + } + + impl ::juniper::ToInputValue for #ident { + fn to(&self) -> ::juniper::InputValue { + ::juniper::InputValue::object(vec![ + #(#to_inputs)* + ].into_iter().collect()) + } + } + } +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index e555579d..16375fb9 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -7,6 +7,7 @@ extern crate quote; mod util; mod enums; +mod input_objects; use proc_macro::TokenStream; @@ -17,3 +18,11 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { let gen = enums::impl_enum(&ast); gen.parse().unwrap() } + +#[proc_macro_derive(GraphQLInputObject, attributes(graphql))] +pub fn derive_input_object(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + let gen = input_objects::impl_input_object(&ast); + gen.parse().unwrap() +}