Remove graphql_enum! macro in favor of custom derive

* Extend derive for enums to allow deriving inside the juniper crate
  itself. Note: this is a rather ugly hack right now, FIXME is in the
  code
* Remove the graphql_enum! macro and replace all internal use with
  derive
* Refactor introspection tests to use derive
This commit is contained in:
theduke 2017-12-02 12:42:07 +01:00
parent b1a62d68e9
commit f858f416b8
13 changed files with 127 additions and 305 deletions

View file

@ -150,9 +150,9 @@ pub type Document<'a> = Vec<Definition<'a>>;
/// Parse an unstructured input value into a Rust data type.
///
/// The conversion _can_ fail, and must in that case return None. Implemented
/// automatically by the convenience macros `graphql_enum!` and
/// `graphql_scalar!`. Must be implemented manually when manually exposing new
/// enums or scalars.
/// automatically by the convenience macro `graphql_scalar!` or by deriving GraphQLEnum.
///
/// Must be implemented manually when manually exposing new enums or scalars.
pub trait FromInputValue: Sized {
/// Performs the conversion.
fn from_input_value(v: &InputValue) -> Option<Self>;

View file

@ -9,7 +9,8 @@ use validation::RuleError;
use parser::SourcePosition;
use types::scalars::EmptyMutation;
#[derive(Debug)]
#[derive(GraphQLEnum, Debug)]
#[graphql(_internal)]
enum Color {
Red,
Green,
@ -17,12 +18,6 @@ enum Color {
}
struct TestType;
graphql_enum!(Color {
Color::Red => "RED",
Color::Green => "GREEN",
Color::Blue => "BLUE",
});
graphql_object!(TestType: () |&self| {
field to_string(color: Color) -> String {
format!("Color::{:?}", color)

View file

@ -3,6 +3,8 @@ use value::Value;
use schema::model::RootNode;
use types::scalars::EmptyMutation;
#[derive(GraphQLEnum)]
#[graphql(name = "SampleEnum", _internal)]
enum Sample {
One,
Two,
@ -24,11 +26,6 @@ graphql_scalar!(Scalar as "SampleScalar" {
}
});
graphql_enum!(Sample as "SampleEnum" {
Sample::One => "ONE",
Sample::Two => "TWO",
});
graphql_interface!(Interface: () as "SampleInterface" |&self| {
description: "A sample interface"

View file

@ -1,193 +0,0 @@
/**
Expose simple enums
GraphQL enums are similar to enums classes C++ - more like grouped constants
with type safety than what Rust enums offer. This macro can be used to export
non-data carrying Rust enums to GraphQL:
```rust
# #[macro_use] extern crate juniper;
enum Color {
Red,
Orange,
Green,
Blue,
Black,
}
graphql_enum!(Color {
Color::Red => "RED" as "The color red",
Color::Orange => "ORANGE",
Color::Green => "GREEN",
Color::Blue => "BLUE",
Color::Black => "BLACK" deprecated "Superseded by ORANGE",
});
# fn main() { }
```
The macro expands to a `match` statement which will result in a compilation
error if not all enum variants are covered. It also creates an implementation
for `FromInputValue` and `ToInputValue`, making it usable in arguments and
default values.
If you want to expose the enum under a different name than the Rust type,
you can write `graphql_enum!(Color as "MyColor" { ...`.
*/
#[macro_export]
macro_rules! graphql_enum {
( @as_expr, $e:expr) => { $e };
( @as_pattern, $p:pat) => { $p };
( @as_path, $p:path) => { $p };
// 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 $crate::GraphQLType for $name {
type Context = ();
type TypeInfo = ();
fn name(_: &()) -> Option<&str> {
Some(graphql_enum!(@as_expr, $outname))
}
fn meta<'r>(info: &(), registry: &mut $crate::Registry<'r>) -> $crate::meta::MetaType<'r> {
graphql_enum!(
@maybe_apply, $descr, description,
registry.build_enum_type::<$name>(info, &[
$(
graphql_enum!(
@maybe_apply,
$edepr, deprecated,
graphql_enum!(
@maybe_apply,
$edescr, description,
$crate::meta::EnumValue::new(graphql_enum!(@as_expr, $ename))))
),*
]))
.into_meta()
}
fn resolve(&self, _: &(), _: Option<&[$crate::Selection]>, _: &$crate::Executor<Self::Context>) -> $crate::Value {
match *self {
$(
graphql_enum!(@as_pattern, $eval) =>
$crate::Value::string(graphql_enum!(@as_expr, $ename)) ),*
}
}
}
impl $crate::FromInputValue for $name {
fn from_input_value(v: &$crate::InputValue) -> Option<$name> {
match v.as_enum_value().or_else(|| v.as_string_value()) {
$(
Some(graphql_enum!(@as_pattern, $ename))
=> Some(graphql_enum!(@as_expr, $eval)), )*
_ => None,
}
}
}
impl $crate::ToInputValue for $name {
fn to_input_value(&self) -> $crate::InputValue {
match *self {
$(
graphql_enum!(@as_pattern, $eval) =>
$crate::InputValue::string(graphql_enum!(@as_expr, $ename)) ),*
}
}
}
};
// No more items to parse
( @parse, $meta:tt, $acc:tt, ) => {
graphql_enum!( @generate, $meta, $acc );
};
// Remove extraneous commas
( @parse, $meta:tt, $acc:tt, , $($rest:tt)* ) => {
graphql_enum!( @parse, $meta, $acc, $($rest)* );
};
// 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

@ -1,6 +1,4 @@
#[macro_use]
mod enums;
#[macro_use]
mod object;
#[macro_use]
mod interface;

View file

@ -5,34 +5,6 @@ use value::Value;
use schema::model::RootNode;
use types::scalars::EmptyMutation;
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:
@ -45,37 +17,53 @@ Syntax to validate:
*/
graphql_enum!(DefaultName {
DefaultName::Foo => "FOO",
DefaultName::Bar => "BAR",
});
#[derive(GraphQLEnum)]
#[graphql(_internal)]
enum DefaultName {
Foo,
Bar,
}
graphql_enum!(Named as "ANamedEnum" {
Named::Foo => "FOO",
Named::Bar => "BAR",
});
#[derive(GraphQLEnum)]
#[graphql(name = "ANamedEnum", _internal)]
enum Named {
Foo,
Bar,
}
graphql_enum!(NoTrailingComma {
NoTrailingComma::Foo => "FOO",
NoTrailingComma::Bar => "BAR"
});
#[derive(GraphQLEnum)]
#[graphql(_internal)]
enum NoTrailingComma {
Foo,
Bar,
}
graphql_enum!(EnumDescription {
description: "A description of the enum itself"
#[derive(GraphQLEnum)]
#[graphql(description = "A description of the enum itself", _internal)]
enum EnumDescription {
Foo,
Bar,
}
EnumDescription::Foo => "FOO",
EnumDescription::Bar => "BAR",
});
#[derive(GraphQLEnum)]
#[graphql(_internal)]
enum EnumValueDescription {
#[graphql(description = "The FOO value")]
Foo,
#[graphql(description = "The BAR value")]
Bar,
}
graphql_enum!(EnumValueDescription {
EnumValueDescription::Foo => "FOO" as "The FOO value",
EnumValueDescription::Bar => "BAR" as "The BAR value",
});
#[derive(GraphQLEnum)]
#[graphql(_internal)]
enum EnumDeprecation {
#[graphql(deprecated = "Please don't use FOO any more")]
Foo,
#[graphql(description = "The BAR value", deprecated = "Please don't use BAR any more")]
Bar,
}
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",
});
struct Root;
graphql_object!(Root: () |&self| {
field default_name() -> DefaultName { DefaultName::Foo }

View file

@ -1,4 +1,3 @@
mod enums;
mod scalar;
#[allow(dead_code)]
mod input_object;
@ -7,6 +6,7 @@ mod field;
mod object;
mod interface;
mod union;
mod enums;
// This asserts that the input objects defined public actually became public

View file

@ -49,13 +49,17 @@ pub struct DirectiveType<'a> {
pub arguments: Vec<Argument<'a>>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(GraphQLEnum, Clone, PartialEq, Eq, Debug)]
#[graphql(name = "__DirectiveLocation", _internal)]
pub enum DirectiveLocation {
Query,
Mutation,
Field,
#[graphql(name = "FRAGMENT_DEFINITION")]
FragmentDefinition,
#[graphql(name = "FRAGMENT_SPREAD")]
FragmentSpread,
#[graphql(name = "INLINE_SPREAD")]
InlineFragment,
}

View file

@ -232,17 +232,6 @@ graphql_object!(EnumValue: () as "__EnumValue" |&self| {
}
});
graphql_enum!(TypeKind as "__TypeKind" {
TypeKind::Scalar => "SCALAR",
TypeKind::Object => "OBJECT",
TypeKind::Interface => "INTERFACE",
TypeKind::Union => "UNION",
TypeKind::Enum => "ENUM",
TypeKind::InputObject => "INPUT_OBJECT",
TypeKind::List => "LIST",
TypeKind::NonNull => "NON_NULL",
});
graphql_object!(<'a> DirectiveType<'a>: SchemaType<'a> as "__Directive" |&self| {
field name() -> &String {
@ -282,11 +271,3 @@ graphql_object!(<'a> DirectiveType<'a>: SchemaType<'a> as "__Directive" |&self|
}
});
graphql_enum!(DirectiveLocation as "__DirectiveLocation" {
DirectiveLocation::Query => "QUERY",
DirectiveLocation::Mutation => "MUTATION",
DirectiveLocation::Field => "FIELD",
DirectiveLocation::FragmentDefinition => "FRAGMENT_DEFINITION",
DirectiveLocation::FragmentSpread => "FRAGMENT_SPREAD",
DirectiveLocation::InlineFragment => "INLINE_FRAGMENT",
});

View file

@ -2,8 +2,10 @@
use std::collections::HashMap;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[derive(GraphQLEnum, Copy, Clone, Eq, PartialEq, Debug)]
#[graphql(_internal)]
pub enum Episode {
#[graphql(name = "NEW_HOPE")]
NewHope,
Empire,
Jedi,

View file

@ -3,12 +3,6 @@ use executor::Context;
impl Context for Database {}
graphql_enum!(Episode {
Episode::NewHope => "NEW_HOPE",
Episode::Empire => "EMPIRE",
Episode::Jedi => "JEDI",
});
graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
description: "A character in the Star Wars Trilogy"

View file

@ -13,7 +13,9 @@ use parser::Spanning;
///
/// The GraphQL specification defines a number of type kinds - the meta type
/// of a type.
#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(GraphQLEnum, Clone, Eq, PartialEq, Debug)]
// Note: _internal flag needed to make derive work in juniper crate itself.
#[graphql(name = "__TypeKind", _internal)]
pub enum TypeKind {
/// ## Scalar types
///
@ -48,6 +50,7 @@ pub enum TypeKind {
/// ## Input objects
///
/// Represents complex values provided in queries _into_ the system.
#[graphql(name = "INPUT_OBJECT")]
InputObject,
/// ## List types
@ -61,6 +64,7 @@ pub enum TypeKind {
///
/// In GraphQL, nullable types are the default. By putting a `!` after a
/// type, it becomes non-nullable.
#[graphql(name = "NON_NULL")]
NonNull,
}

View file

@ -9,11 +9,17 @@ use util::*;
struct EnumAttrs {
name: Option<String>,
description: Option<String>,
internal: bool,
}
impl EnumAttrs {
fn from_input(input: &DeriveInput) -> EnumAttrs {
let mut res = EnumAttrs::default();
let mut res = EnumAttrs{
name: None,
description: None,
/// Flag to specify whether the calling crate is the "juniper" crate itself.
internal: false,
};
// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&input.attrs) {
@ -26,6 +32,15 @@ impl EnumAttrs {
res.description = Some(val);
continue;
}
match item {
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
if ident == "_internal" {
res.internal = true;
continue;
}
},
_ => {},
}
panic!(format!(
"Unknown attribute for #[derive(GraphQLEnum)]: {:?}",
item
@ -119,7 +134,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
None => quote!{ None },
};
let value = quote!{
::juniper::meta::EnumValue{
_juniper::meta::EnumValue{
name: #name.to_string(),
description: #descr,
deprecation_reason: #depr,
@ -129,7 +144,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
// Build resolve match clause.
let resolve = quote!{
&#ident::#var_ident => ::juniper::Value::String(#name.to_string()),
&#ident::#var_ident => _juniper::Value::String(#name.to_string()),
};
resolves.push(resolve);
@ -142,13 +157,13 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
// Buil to_input clause.
let to_input = quote!{
&#ident::#var_ident =>
::juniper::InputValue::string(#name.to_string()),
_juniper::InputValue::string(#name.to_string()),
};
to_inputs.push(to_input);
}
quote! {
impl ::juniper::GraphQLType for #ident {
let body = quote! {
impl _juniper::GraphQLType for #ident {
type Context = ();
type TypeInfo = ();
@ -156,7 +171,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
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 meta = registry.build_enum_type::<#ident>(&(), &[
#(#values)*
]);
@ -164,15 +179,15 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
meta.into_meta()
}
fn resolve(&self, _: &(), _: Option<&[::juniper::Selection]>, _: &::juniper::Executor<Self::Context>) -> ::juniper::Value {
fn resolve(&self, _: &(), _: Option<&[_juniper::Selection]>, _: &_juniper::Executor<Self::Context>) -> _juniper::Value {
match self {
#(#resolves)*
}
}
}
impl ::juniper::FromInputValue for #ident {
fn from_input_value(v: &::juniper::InputValue) -> Option<#ident> {
impl _juniper::FromInputValue for #ident {
fn from_input_value(v: &_juniper::InputValue) -> Option<#ident> {
match v.as_enum_value().or_else(|| v.as_string_value()) {
#(#from_inputs)*
_ => None,
@ -180,12 +195,49 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
}
}
impl ::juniper::ToInputValue for #ident {
fn to_input_value(&self) -> ::juniper::InputValue {
impl _juniper::ToInputValue for #ident {
fn to_input_value(&self) -> _juniper::InputValue {
match self {
#(#to_inputs)*
}
}
}
}
};
let dummy_const = Ident::new(format!("_IMPL_GRAPHQLENUM_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,
Value,
ToInputValue,
FromInputValue,
Executor,
Selection,
Registry,
GraphQLType,
meta
};
}
}
} 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
}