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
+            }
+        },
+    }
+}