Extend enum macro, improve test coverage

This adds description and deprecation support to the graphql_enum!
macro, as well as extensive tests for all syntactical cases.
This commit is contained in:
Magnus Hallin 2016-10-09 14:30:29 +02:00
parent ac51f09873
commit ac01b8e406
4 changed files with 454 additions and 15 deletions

View file

@ -35,19 +35,45 @@ you can write `graphql_enum!(Color as "MyColor" { ...`.
macro_rules! graphql_enum {
( @as_expr, $e:expr) => { $e };
( @as_pattern, $p:pat) => { $p };
( @as_path, $p:path) => { $p };
// EnumName as "__ExportedNmae" { Enum::Value => "STRING_VALUE", }
// with no trailing comma
( $name:path as $outname:tt { $($eval:path => $ename:tt),* }) => {
// Calls $val.$func($arg) if $arg is not None
( @maybe_apply, None, $func:ident, $val:expr ) => { $val };
( @maybe_apply, $arg:tt, $func:ident, $val:expr ) => { $val.$func($arg) };
// Each of the @parse match arms accumulates data up to a call to @generate.
//
// ( $name, $outname, $descr ): the name of the Rust enum, the name of the
// GraphQL enum (as a string), and the description of the enum (as a string or None)
//
// [ ( $eval, $ename, $edescr, $edepr ) , ] the value of the Rust enum,
// the value of the GraphQL enum (as a string), the description of the enum
// value (as a string or None), and the deprecation reason of the enum value
// (as a string or None).
(
@generate,
( $name:path, $outname:tt, $descr:tt ),
[ $( ( $eval:tt, $ename:tt, $edescr:tt, $edepr:tt ) , )* ]
) => {
impl<CtxT> $crate::GraphQLType<CtxT> for $name {
fn name() -> Option<&'static str> {
Some(graphql_enum!(@as_expr, $outname))
}
fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
graphql_enum!(
@maybe_apply, $descr, description,
registry.build_enum_type::<$name>()(&[
$( $crate::meta::EnumValue::new(graphql_enum!(@as_expr, $ename)) ),*
])
$(
graphql_enum!(
@maybe_apply,
$edepr, deprecated,
graphql_enum!(
@maybe_apply,
$edescr, description,
$crate::meta::EnumValue::new(graphql_enum!(@as_expr, $ename))))
),*
]))
.into_meta()
}
@ -82,18 +108,79 @@ macro_rules! graphql_enum {
}
};
// Same as above, *with* trailing comma
( $name:path as $outname:tt { $($eval:path => $ename:tt, )* }) => {
graphql_enum!($name as $outname { $( $eval => $ename ),* });
// No more items to parse
( @parse, $meta:tt, $acc:tt, ) => {
graphql_enum!( @generate, $meta, $acc );
};
// Default named enum, without trailing comma
( $name:path { $($eval:path => $ename:tt),* }) => {
graphql_enum!($name as (stringify!($name)) { $( $eval => $ename ),* });
// Remove extraneous commas
( @parse, $meta:tt, $acc:tt, , $($rest:tt)* ) => {
graphql_enum!( @parse, $meta, $acc, $($rest)* );
};
// Default named enum, with trailing comma
( $name:path { $($eval:path => $ename:tt, )* }) => {
graphql_enum!($name as (stringify!($name)) { $( $eval => $ename ),* });
// description: <description>
(
@parse,
( $name:tt, $outname:tt, $_ignore:tt ),
$acc:tt,
description: $descr:tt $($items:tt)*
) => {
graphql_enum!( @parse, ( $name, $outname, $descr ), $acc, $($items)* );
};
// RustEnumValue => "GraphQL enum value" deprecated <reason>
(
@parse,
$meta:tt,
[ $($acc:tt ,)* ],
$eval:path => $ename:tt deprecated $depr:tt $($rest:tt)*
) => {
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval, $ename, None, $depr ), ], $($rest)* );
};
// RustEnumValue => "GraphQL enum value" as <description> deprecated <reason>
(
@parse,
$meta:tt,
[ $($acc:tt ,)* ],
$eval:path => $ename:tt as $descr:tt deprecated $depr:tt $($rest:tt)*
) => {
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval, $ename, $descr, $depr ), ], $($rest)* );
};
// RustEnumValue => "GraphQL enum value" as <description>
(
@parse,
$meta:tt,
[ $($acc:tt ,)* ],
$eval:path => $ename:tt as $descr:tt $($rest:tt)*
) => {
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval, $ename, $descr, None ), ], $($rest)* );
};
// RustEnumValue => "GraphQL enum value"
(
@parse,
$meta:tt,
[ $($acc:tt ,)* ],
$eval:path => $ename:tt $($rest:tt)*
) => {
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval , $ename , None , None ), ], $($rest)* );
};
// Entry point:
// RustEnumName as "GraphQLEnumName" { ... }
(
$name:path as $outname:tt { $($items:tt)* }
) => {
graphql_enum!( @parse, ( $name, $outname, None ), [ ], $($items)* );
};
// Entry point
// RustEnumName { ... }
(
$name:path { $($items:tt)* }
) => {
graphql_enum!( @parse, ( $name, (stringify!($name)), None ), [ ], $($items)* );
};
}

View file

@ -4,3 +4,5 @@
#[macro_use] mod scalar;
#[macro_use] mod args;
#[macro_use] mod field;
#[cfg(test)] mod tests;

349
src/macros/tests/enums.rs Normal file
View file

@ -0,0 +1,349 @@
use std::collections::HashMap;
use executor::FieldResult;
use value::Value;
use schema::model::RootNode;
enum DefaultName { Foo, Bar }
enum Named { Foo, Bar }
enum NoTrailingComma { Foo, Bar }
enum EnumDescription { Foo, Bar }
enum EnumValueDescription { Foo, Bar }
enum EnumDeprecation { Foo, Bar }
struct Root;
/*
Syntax to validate:
* Default name vs. custom name
* Trailing comma vs. no trailing comma
* Description vs. no description on the enum
* Description vs. no description on the enum values themselves
* Deprecation on enum fields
*/
graphql_enum!(DefaultName {
DefaultName::Foo => "FOO",
DefaultName::Bar => "BAR",
});
graphql_enum!(Named as "ANamedEnum" {
Named::Foo => "FOO",
Named::Bar => "BAR",
});
graphql_enum!(NoTrailingComma {
NoTrailingComma::Foo => "FOO",
NoTrailingComma::Bar => "BAR"
});
graphql_enum!(EnumDescription {
description: "A description of the enum itself"
EnumDescription::Foo => "FOO",
EnumDescription::Bar => "BAR",
});
graphql_enum!(EnumValueDescription {
EnumValueDescription::Foo => "FOO" as "The FOO value",
EnumValueDescription::Bar => "BAR" as "The BAR value",
});
graphql_enum!(EnumDeprecation {
EnumDeprecation::Foo => "FOO" deprecated "Please don't use FOO any more",
EnumDeprecation::Bar => "BAR" as "The BAR value" deprecated "Please don't use BAR any more",
});
graphql_object!(Root: () as "Root" |&self| {
field default_name() -> FieldResult<DefaultName> { Ok(DefaultName::Foo) }
field named() -> FieldResult<Named> { Ok(Named::Foo) }
field no_trailing_comma() -> FieldResult<NoTrailingComma> { Ok(NoTrailingComma::Foo) }
field enum_description() -> FieldResult<EnumDescription> { Ok(EnumDescription::Foo) }
field enum_value_description() -> FieldResult<EnumValueDescription> { Ok(EnumValueDescription::Foo) }
field enum_deprecation() -> FieldResult<EnumDeprecation> { Ok(EnumDeprecation::Foo) }
});
fn run_type_info_query<F>(doc: &str, f: F) where F: Fn((&HashMap<String, Value>, &Vec<Value>)) -> () {
let schema = RootNode::new(Root {}, ());
let (result, errs) = ::execute(doc, None, &schema, &HashMap::new(), &())
.expect("Execution failed");
assert_eq!(errs, []);
println!("Result: {:?}", result);
let type_info = result
.as_object_value().expect("Result is not an object")
.get("__type").expect("__type field missing")
.as_object_value().expect("__type field not an object value");
let values = type_info
.get("enumValues").expect("enumValues field missing")
.as_list_value().expect("enumValues not a list");
f((type_info, values));
}
#[test]
fn default_name_introspection() {
let doc = r#"
{
__type(name: "DefaultName") {
name
description
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("DefaultName")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
assert_eq!(values.len(), 2);
assert!(values.contains(&Value::object(vec![
("name", Value::string("FOO")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
assert!(values.contains(&Value::object(vec![
("name", Value::string("BAR")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
});
}
#[test]
fn named_introspection() {
let doc = r#"
{
__type(name: "ANamedEnum") {
name
description
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("ANamedEnum")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
assert_eq!(values.len(), 2);
assert!(values.contains(&Value::object(vec![
("name", Value::string("FOO")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
assert!(values.contains(&Value::object(vec![
("name", Value::string("BAR")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
});
}
#[test]
fn no_trailing_comma_introspection() {
let doc = r#"
{
__type(name: "NoTrailingComma") {
name
description
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("NoTrailingComma")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
assert_eq!(values.len(), 2);
assert!(values.contains(&Value::object(vec![
("name", Value::string("FOO")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
assert!(values.contains(&Value::object(vec![
("name", Value::string("BAR")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
});
}
#[test]
fn enum_description_introspection() {
let doc = r#"
{
__type(name: "EnumDescription") {
name
description
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("EnumDescription")));
assert_eq!(type_info.get("description"), Some(&Value::string("A description of the enum itself")));
assert_eq!(values.len(), 2);
assert!(values.contains(&Value::object(vec![
("name", Value::string("FOO")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
assert!(values.contains(&Value::object(vec![
("name", Value::string("BAR")),
("description", Value::null()),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
});
}
#[test]
fn enum_value_description_introspection() {
let doc = r#"
{
__type(name: "EnumValueDescription") {
name
description
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("EnumValueDescription")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
assert_eq!(values.len(), 2);
assert!(values.contains(&Value::object(vec![
("name", Value::string("FOO")),
("description", Value::string("The FOO value")),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
assert!(values.contains(&Value::object(vec![
("name", Value::string("BAR")),
("description", Value::string("The BAR value")),
("isDeprecated", Value::boolean(false)),
("deprecationReason", Value::null()),
].into_iter().collect())));
});
}
#[test]
fn enum_deprecation_introspection() {
let doc = r#"
{
__type(name: "EnumDeprecation") {
name
description
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("EnumDeprecation")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
assert_eq!(values.len(), 2);
assert!(values.contains(&Value::object(vec![
("name", Value::string("FOO")),
("description", Value::null()),
("isDeprecated", Value::boolean(true)),
("deprecationReason", Value::string("Please don't use FOO any more")),
].into_iter().collect())));
assert!(values.contains(&Value::object(vec![
("name", Value::string("BAR")),
("description", Value::string("The BAR value")),
("isDeprecated", Value::boolean(true)),
("deprecationReason", Value::string("Please don't use BAR any more")),
].into_iter().collect())));
});
}
#[test]
fn enum_deprecation_no_values_introspection() {
let doc = r#"
{
__type(name: "EnumDeprecation") {
name
description
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}
"#;
run_type_info_query(doc, |(type_info, values)| {
assert_eq!(type_info.get("name"), Some(&Value::string("EnumDeprecation")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
assert_eq!(values.len(), 0);
});
}

1
src/macros/tests/mod.rs Normal file
View file

@ -0,0 +1 @@
mod enums;