From f858f416b8e188e7366ba8cee48499a62a17d8f4 Mon Sep 17 00:00:00 2001 From: theduke Date: Sat, 2 Dec 2017 12:42:07 +0100 Subject: [PATCH] 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 --- juniper/src/ast.rs | 6 +- juniper/src/executor_tests/enums.rs | 9 +- juniper/src/executor_tests/introspection.rs | 7 +- juniper/src/macros/enums.rs | 193 -------------------- juniper/src/macros/mod.rs | 2 - juniper/src/macros/tests/enums.rs | 94 +++++----- juniper/src/macros/tests/mod.rs | 2 +- juniper/src/schema/model.rs | 6 +- juniper/src/schema/schema.rs | 19 -- juniper/src/tests/model.rs | 4 +- juniper/src/tests/schema.rs | 6 - juniper/src/types/base.rs | 6 +- juniper_codegen/src/derive_enum.rs | 78 ++++++-- 13 files changed, 127 insertions(+), 305 deletions(-) delete mode 100644 juniper/src/macros/enums.rs diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index 38b9ece9..1ae53929 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -150,9 +150,9 @@ pub type Document<'a> = Vec>; /// 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; diff --git a/juniper/src/executor_tests/enums.rs b/juniper/src/executor_tests/enums.rs index 5081bf48..5258edfa 100644 --- a/juniper/src/executor_tests/enums.rs +++ b/juniper/src/executor_tests/enums.rs @@ -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) diff --git a/juniper/src/executor_tests/introspection.rs b/juniper/src/executor_tests/introspection.rs index 0832654b..1480ca17 100644 --- a/juniper/src/executor_tests/introspection.rs +++ b/juniper/src/executor_tests/introspection.rs @@ -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" diff --git a/juniper/src/macros/enums.rs b/juniper/src/macros/enums.rs deleted file mode 100644 index daa1cbfa..00000000 --- a/juniper/src/macros/enums.rs +++ /dev/null @@ -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) -> $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: - ( - @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 - ( - @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 deprecated - ( - @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 - ( - @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)* ); - }; -} diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index d62970ea..c41f6be2 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,6 +1,4 @@ #[macro_use] -mod enums; -#[macro_use] mod object; #[macro_use] mod interface; diff --git a/juniper/src/macros/tests/enums.rs b/juniper/src/macros/tests/enums.rs index f0fcec22..9ffc5c9f 100644 --- a/juniper/src/macros/tests/enums.rs +++ b/juniper/src/macros/tests/enums.rs @@ -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 } diff --git a/juniper/src/macros/tests/mod.rs b/juniper/src/macros/tests/mod.rs index a4d4273d..5b458865 100644 --- a/juniper/src/macros/tests/mod.rs +++ b/juniper/src/macros/tests/mod.rs @@ -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 diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index eb2b17ee..249b7382 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -49,13 +49,17 @@ pub struct DirectiveType<'a> { pub arguments: Vec>, } -#[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, } diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 2c29dcca..0d0491aa 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -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", -}); diff --git a/juniper/src/tests/model.rs b/juniper/src/tests/model.rs index eff453de..b0be619c 100644 --- a/juniper/src/tests/model.rs +++ b/juniper/src/tests/model.rs @@ -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, diff --git a/juniper/src/tests/schema.rs b/juniper/src/tests/schema.rs index 607c918a..0f32d266 100644 --- a/juniper/src/tests/schema.rs +++ b/juniper/src/tests/schema.rs @@ -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" diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index b1d29a70..f1deca83 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -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, } diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index 2b92844e..40951d9b 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -9,11 +9,17 @@ use util::*; struct EnumAttrs { name: Option, description: Option, + 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) -> ::juniper::Value { + fn resolve(&self, _: &(), _: Option<&[_juniper::Selection]>, _: &_juniper::Executor) -> _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 }