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:
parent
ac51f09873
commit
ac01b8e406
4 changed files with 454 additions and 15 deletions
|
@ -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)* );
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
349
src/macros/tests/enums.rs
Normal 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
1
src/macros/tests/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod enums;
|
Loading…
Reference in a new issue