(codegen) improve derive for InputObject
* Implement hack to allow usage in juniper crate * implement (default) attribute for Default::default() * Improve tests
This commit is contained in:
parent
45859bf405
commit
643875838d
3 changed files with 115 additions and 27 deletions
|
@ -9,6 +9,7 @@ use util::*;
|
||||||
struct ObjAttrs {
|
struct ObjAttrs {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
internal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjAttrs {
|
impl ObjAttrs {
|
||||||
|
@ -26,6 +27,15 @@ impl ObjAttrs {
|
||||||
res.description = Some(val);
|
res.description = Some(val);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
match item {
|
||||||
|
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||||
|
if ident == "_internal" {
|
||||||
|
res.internal = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
panic!(format!(
|
panic!(format!(
|
||||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
||||||
item
|
item
|
||||||
|
@ -40,7 +50,8 @@ impl ObjAttrs {
|
||||||
struct ObjFieldAttrs {
|
struct ObjFieldAttrs {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
default: Option<String>,
|
default: bool,
|
||||||
|
default_expr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjFieldAttrs {
|
impl ObjFieldAttrs {
|
||||||
|
@ -59,9 +70,18 @@ impl ObjFieldAttrs {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(val) = keyed_item_value(item, "default", true) {
|
if let Some(val) = keyed_item_value(item, "default", true) {
|
||||||
res.default = Some(val);
|
res.default_expr = Some(val);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
match item {
|
||||||
|
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||||
|
if ident == "default" {
|
||||||
|
res.default = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
panic!(format!(
|
panic!(format!(
|
||||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
||||||
item
|
item
|
||||||
|
@ -122,14 +142,20 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
None => quote!{ let field = field; },
|
None => quote!{ let field = field; },
|
||||||
};
|
};
|
||||||
|
|
||||||
let default = match field_attrs.default {
|
let default = {
|
||||||
Some(ref def) => match syn::parse_token_trees(def) {
|
if field_attrs.default {
|
||||||
Ok(t) => Some(quote!{ #(#t)* }),
|
Some(quote! { Default::default() } )
|
||||||
Err(_) => {
|
} else {
|
||||||
panic!("#graphql(default = ?) must be a valid Rust expression inside a string");
|
match field_attrs.default_expr {
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let create_meta_field = match default {
|
let create_meta_field = match default {
|
||||||
|
@ -158,7 +184,7 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
let from_input_default = match default {
|
let from_input_default = match default {
|
||||||
Some(ref def) => {
|
Some(ref def) => {
|
||||||
quote!{
|
quote!{
|
||||||
Some(&&::juniper::InputValue::Null) | None if true => #def,
|
Some(&&_juniper::InputValue::Null) | None if true => #def,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => quote!{},
|
None => quote!{},
|
||||||
|
@ -169,8 +195,11 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
// TODO: investigate the unwraps here, they seem dangerous!
|
// TODO: investigate the unwraps here, they seem dangerous!
|
||||||
match obj.get(#name) {
|
match obj.get(#name) {
|
||||||
#from_input_default
|
#from_input_default
|
||||||
Some(v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
Some(v) => _juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||||
_ => ::juniper::FromInputValue::from_input_value(&::juniper::InputValue::null()).unwrap()
|
_ => {
|
||||||
|
_juniper::FromInputValue::from_input_value(&_juniper::InputValue::null())
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -183,8 +212,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
to_inputs.push(to_input);
|
to_inputs.push(to_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
quote! {
|
let body = quote! {
|
||||||
impl ::juniper::GraphQLType for #ident {
|
impl _juniper::GraphQLType for #ident {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
type TypeInfo = ();
|
type TypeInfo = ();
|
||||||
|
|
||||||
|
@ -192,7 +221,7 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
Some(#name)
|
Some(#name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn meta<'r>(_: &(), registry: &mut ::juniper::Registry<'r>) -> ::juniper::meta::MetaType<'r> {
|
fn meta<'r>(_: &(), registry: &mut _juniper::Registry<'r>) -> _juniper::meta::MetaType<'r> {
|
||||||
let fields = &[
|
let fields = &[
|
||||||
#(#meta_fields)*
|
#(#meta_fields)*
|
||||||
];
|
];
|
||||||
|
@ -202,8 +231,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::juniper::FromInputValue for #ident {
|
impl _juniper::FromInputValue for #ident {
|
||||||
fn from_input_value(value: &::juniper::InputValue) -> Option<#ident> {
|
fn from_input_value(value: &_juniper::InputValue) -> Option<#ident> {
|
||||||
if let Some(obj) = value.to_object_value() {
|
if let Some(obj) = value.to_object_value() {
|
||||||
let item = #ident {
|
let item = #ident {
|
||||||
#(#from_inputs)*
|
#(#from_inputs)*
|
||||||
|
@ -216,12 +245,46 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::juniper::ToInputValue for #ident {
|
impl _juniper::ToInputValue for #ident {
|
||||||
fn to_input_value(&self) -> ::juniper::InputValue {
|
fn to_input_value(&self) -> _juniper::InputValue {
|
||||||
::juniper::InputValue::object(vec![
|
_juniper::InputValue::object(vec![
|
||||||
#(#to_inputs)*
|
#(#to_inputs)*
|
||||||
].into_iter().collect())
|
].into_iter().collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let dummy_const = Ident::new(format!("_IMPL_GRAPHQLINPUTOBJECT_FOR_{}", ident));
|
||||||
|
|
||||||
|
// This ugly hack makes it possible to use the derive inside juniper itself.
|
||||||
|
// FIXME: Figure out a better way to do this!
|
||||||
|
let crate_reference = if attrs.internal {
|
||||||
|
quote! {
|
||||||
|
#[doc(hidden)]
|
||||||
|
mod _juniper {
|
||||||
|
pub use ::{
|
||||||
|
InputValue,
|
||||||
|
FromInputValue,
|
||||||
|
GraphQLType,
|
||||||
|
Registry,
|
||||||
|
meta,
|
||||||
|
ToInputValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
extern crate juniper as _juniper;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let generated = quote! {
|
||||||
|
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
const #dummy_const : () = {
|
||||||
|
#crate_reference
|
||||||
|
#body
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
generated
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use juniper::{self, FromInputValue, GraphQLType, ToInputValue};
|
use juniper::{self, FromInputValue, GraphQLType, InputValue};
|
||||||
|
|
||||||
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
||||||
#[graphql(name = "MyInput", description = "input descr")]
|
#[graphql(name = "MyInput", description = "input descr")]
|
||||||
|
@ -10,6 +10,9 @@ struct Input {
|
||||||
regular_field: String,
|
regular_field: String,
|
||||||
#[graphql(name = "haha", default = "33", description = "haha descr")]
|
#[graphql(name = "haha", default = "33", description = "haha descr")]
|
||||||
c: i32,
|
c: i32,
|
||||||
|
|
||||||
|
#[graphql(default)]
|
||||||
|
other: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -22,10 +25,31 @@ fn test_derived_input_object() {
|
||||||
assert_eq!(meta.name(), Some("MyInput"));
|
assert_eq!(meta.name(), Some("MyInput"));
|
||||||
assert_eq!(meta.description(), Some(&"input descr".to_string()));
|
assert_eq!(meta.description(), Some(&"input descr".to_string()));
|
||||||
|
|
||||||
let obj = Input {
|
// Test default value injection.
|
||||||
regular_field: "a".to_string(),
|
|
||||||
|
let input_no_defaults: InputValue = ::serde_json::from_value(json!({
|
||||||
|
"regularField": "a",
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
|
let output_no_defaults: Input = FromInputValue::from_input_value(&input_no_defaults).unwrap();
|
||||||
|
assert_eq!(output_no_defaults, Input{
|
||||||
|
regular_field: "a".into(),
|
||||||
c: 33,
|
c: 33,
|
||||||
};
|
other: None,
|
||||||
let restored: Input = FromInputValue::from_input_value(&obj.to_input_value()).unwrap();
|
});
|
||||||
assert_eq!(obj, restored);
|
|
||||||
|
// Test with all values supplied.
|
||||||
|
|
||||||
|
let input: InputValue = ::serde_json::from_value(json!({
|
||||||
|
"regularField": "a",
|
||||||
|
"haha": 55,
|
||||||
|
"other": true,
|
||||||
|
})).unwrap();
|
||||||
|
|
||||||
|
let output: Input = FromInputValue::from_input_value(&input).unwrap();
|
||||||
|
assert_eq!(output, Input{
|
||||||
|
regular_field: "a".into(),
|
||||||
|
c: 55,
|
||||||
|
other: Some(true),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate juniper;
|
extern crate juniper;
|
||||||
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in a new issue