From d33db983deb7f6f05b3e724bf45508956b33e2ff Mon Sep 17 00:00:00 2001 From: theduke <chris@theduke.at> Date: Sat, 24 Jun 2017 13:23:20 +0200 Subject: [PATCH] Add juniper_codegen crate. This crate will contain multiple custom_derive implementations. For now, only enum is supported. --- juniper_codegen/Cargo.toml | 19 ++++ juniper_codegen/src/enums.rs | 179 +++++++++++++++++++++++++++++++++++ juniper_codegen/src/lib.rs | 19 ++++ juniper_codegen/src/util.rs | 48 ++++++++++ 4 files changed, 265 insertions(+) create mode 100644 juniper_codegen/Cargo.toml create mode 100644 juniper_codegen/src/enums.rs create mode 100644 juniper_codegen/src/lib.rs create mode 100644 juniper_codegen/src/util.rs diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml new file mode 100644 index 00000000..6c979a91 --- /dev/null +++ b/juniper_codegen/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "juniper_codegen" +version = "0.8.1" +authors = ["Magnus Hallin <mhallin@fastmail.com>"] +description = "Internal custom derive trait for Juniper GraphQL" +license = "BSD-2-Clause" +documentation = "https://docs.rs/juniper" +repository = "https://github.com/mhallin/juniper" + +[lib] +proc-macro = true + +[dependencies] +syn = "0.11.11" +quote = "0.3.15" + +[badges] +travis-ci = { repository = "mhallin/juniper" } +appveyor = { repository = "mhallin/juniper" } diff --git a/juniper_codegen/src/enums.rs b/juniper_codegen/src/enums.rs new file mode 100644 index 00000000..edbde797 --- /dev/null +++ b/juniper_codegen/src/enums.rs @@ -0,0 +1,179 @@ +use syn; +use syn::*; +use quote::Tokens; + +use ::util::*; + + +#[derive(Default, Debug)] +struct EnumAttrs { + name: Option<String>, + description: Option<String>, +} + +impl EnumAttrs { + fn from_input(input: &DeriveInput) -> EnumAttrs { + let mut res = EnumAttrs::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(GraphQLEnum)]: {:?}", + item)); + } + } + res + } +} + +#[derive(Default)] +struct EnumVariantAttrs { + name: Option<String>, + description: Option<String>, + deprecation: Option<String>, +} + +impl EnumVariantAttrs { + fn from_input(variant: &Variant) -> EnumVariantAttrs { + let mut res = EnumVariantAttrs::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, "deprecated", true) { + res.deprecation = Some(val); + continue; + } + panic!(format!( + "Unknown attribute for #[derive(GraphQLEnum)]: {:?}", + item)); + } + } + res + } +} + + +pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens { + let variants = match ast.body { + Body::Enum(ref var) => var, + Body::Struct(_) => { + panic!("#[derive(GraphlQLEnum)] may only be applied to enums, not to structs"); + }, + }; + + // Parse attributes. + let ident = &ast.ident; + let attrs = EnumAttrs::from_input(ast); + let name = attrs.name.unwrap_or(ast.ident.to_string()); + + let mut values = Vec::<Tokens>::new(); + let mut resolves = Vec::<Tokens>::new(); + let mut from_inputs = Vec::<Tokens>::new(); + let mut to_inputs = Vec::<Tokens>::new(); + + for variant in variants { + if variant.data != VariantData::Unit { + panic!(format!( + "Invalid enum variant {}.\nGraphQL enums may only contain unit variants.", + variant.ident)); + } + let vattrs = EnumVariantAttrs::from_input(variant); + let vident = &variant.ident; + + // Build value. + let name = vattrs.name.unwrap_or(variant.ident.to_string()); + let descr = match vattrs.description { + Some(s) => quote!{ Some(#s.to_string()) }, + None => quote!{ None }, + }; + let depr = match vattrs.deprecation { + Some(s) => quote!{ Some(#s.to_string()) }, + None => quote!{ None }, + }; + let value = quote!{ + ::juniper::meta::EnumValue{ + name: #name.to_string(), + description: #descr, + deprecation_reason: #depr, + }, + }; + values.push(value); + + // Build resolve match clause. + let resolve = quote!{ + &#ident::#vident => ::juniper::Value::String(#name.to_string()), + }; + resolves.push(resolve); + + // Buil from_input clause. + let from_input = quote!{ + Some(#name) => Some(#ident::#vident), + }; + from_inputs.push(from_input); + + // Buil to_input clause. + let to_input = quote!{ + &#ident::#vident => + ::juniper::InputValue::string(#name.to_string()), + }; + 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> { + registry.build_enum_type::<#ident>(&[ + #(#values)* + ]) + .into_meta() + } + + fn resolve(&self, _: Option<&[::juniper::Selection]>, _: &::juniper::Executor<Self::Context>) -> ::juniper::Value { + match self { + #(#resolves)* + } + } + } + + impl ::juniper::FromInputValue for #ident { + fn from(v: &::juniper::InputValue) -> Option<#ident> { + match v.as_enum_value().or_else(|| v.as_string_value()) { + #(#from_inputs)* + _ => None, + } + } + } + + impl ::juniper::ToInputValue for #ident { + fn to(&self) -> ::juniper::InputValue { + match self { + #(#to_inputs)* + } + } + } + } +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs new file mode 100644 index 00000000..e555579d --- /dev/null +++ b/juniper_codegen/src/lib.rs @@ -0,0 +1,19 @@ +#![recursion_limit = "1024"] + +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +mod util; +mod enums; + +use proc_macro::TokenStream; + +#[proc_macro_derive(GraphQLEnum, attributes(graphql))] +pub fn derive_enum(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + let gen = enums::impl_enum(&ast); + gen.parse().unwrap() +} diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs new file mode 100644 index 00000000..e38169da --- /dev/null +++ b/juniper_codegen/src/util.rs @@ -0,0 +1,48 @@ +use syn::*; + +pub fn get_graphl_attr(attrs: &Vec<Attribute>) -> Option<&Vec<NestedMetaItem>> { + for attr in attrs { + match attr.value { + MetaItem::List(ref attr_name, ref items) => { + if attr_name == "graphql" { + return Some(items); + } + }, + _ => {}, + } + } + None +} + +pub fn keyed_item_value(item: &NestedMetaItem, name: &str, must_be_string: bool) + -> Option<String> +{ + let item = match item { + &NestedMetaItem::MetaItem(ref item) => item, + _ => { return None; } + }; + let lit = match item { + &MetaItem::NameValue(ref ident, ref lit) => { + if ident == name { + lit + } else { + return None; + } + }, + _ => { return None; }, + }; + match lit { + &Lit::Str(ref val, _) => { + Some(val.clone()) + }, + _ => { + if must_be_string { + panic!(format!( + "Invalid format for attribute \"{:?}\": expected a string", + item)); + } else { + None + } + }, + } +}