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 {
|
macro_rules! graphql_enum {
|
||||||
( @as_expr, $e:expr) => { $e };
|
( @as_expr, $e:expr) => { $e };
|
||||||
( @as_pattern, $p:pat) => { $p };
|
( @as_pattern, $p:pat) => { $p };
|
||||||
|
( @as_path, $p:path) => { $p };
|
||||||
|
|
||||||
// EnumName as "__ExportedNmae" { Enum::Value => "STRING_VALUE", }
|
// Calls $val.$func($arg) if $arg is not None
|
||||||
// with no trailing comma
|
( @maybe_apply, None, $func:ident, $val:expr ) => { $val };
|
||||||
( $name:path as $outname:tt { $($eval:path => $ename:tt),* }) => {
|
( @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 {
|
impl<CtxT> $crate::GraphQLType<CtxT> for $name {
|
||||||
fn name() -> Option<&'static str> {
|
fn name() -> Option<&'static str> {
|
||||||
Some(graphql_enum!(@as_expr, $outname))
|
Some(graphql_enum!(@as_expr, $outname))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
|
fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
|
||||||
|
graphql_enum!(
|
||||||
|
@maybe_apply, $descr, description,
|
||||||
registry.build_enum_type::<$name>()(&[
|
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()
|
.into_meta()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,18 +108,79 @@ macro_rules! graphql_enum {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same as above, *with* trailing comma
|
// No more items to parse
|
||||||
( $name:path as $outname:tt { $($eval:path => $ename:tt, )* }) => {
|
( @parse, $meta:tt, $acc:tt, ) => {
|
||||||
graphql_enum!($name as $outname { $( $eval => $ename ),* });
|
graphql_enum!( @generate, $meta, $acc );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default named enum, without trailing comma
|
// Remove extraneous commas
|
||||||
( $name:path { $($eval:path => $ename:tt),* }) => {
|
( @parse, $meta:tt, $acc:tt, , $($rest:tt)* ) => {
|
||||||
graphql_enum!($name as (stringify!($name)) { $( $eval => $ename ),* });
|
graphql_enum!( @parse, $meta, $acc, $($rest)* );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default named enum, with trailing comma
|
// description: <description>
|
||||||
( $name:path { $($eval:path => $ename:tt, )* }) => {
|
(
|
||||||
graphql_enum!($name as (stringify!($name)) { $( $eval => $ename ),* });
|
@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 scalar;
|
||||||
#[macro_use] mod args;
|
#[macro_use] mod args;
|
||||||
#[macro_use] mod field;
|
#[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