Add juniper_codegen crate.
This crate will contain multiple custom_derive implementations. For now, only enum is supported.
This commit is contained in:
parent
4ab2f20b8f
commit
d33db983de
4 changed files with 265 additions and 0 deletions
19
juniper_codegen/Cargo.toml
Normal file
19
juniper_codegen/Cargo.toml
Normal file
|
@ -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" }
|
179
juniper_codegen/src/enums.rs
Normal file
179
juniper_codegen/src/enums.rs
Normal file
|
@ -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)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
juniper_codegen/src/lib.rs
Normal file
19
juniper_codegen/src/lib.rs
Normal file
|
@ -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()
|
||||
}
|
48
juniper_codegen/src/util.rs
Normal file
48
juniper_codegen/src/util.rs
Normal file
|
@ -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
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue