Skip to content

Commit

Permalink
Extend scalar macro, add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mhallin committed Oct 9, 2016
1 parent ca6c637 commit 42e4070
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 49 deletions.
136 changes: 87 additions & 49 deletions src/macros/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ datatype appropriate for that platform.
# use juniper::{Value, FieldResult};
struct UserID(String);
graphql_scalar!(UserID as "UserID" {
graphql_scalar!(UserID {
description: "An opaque identifier, represented as a string"
resolve(&self) -> Value {
Value::string(&self.0)
}
Expand All @@ -33,77 +35,113 @@ In addition to implementing `GraphQLType` for the type in question,
`FromInputValue` and `ToInputValue` is also implemented. This makes the type
usable as arguments and default values.
`graphql_scalar!` supports generic and lifetime parameters similar to
`graphql_object!`.
*/
#[macro_export]
macro_rules! graphql_scalar {
// 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 type and the name of the
// GraphQL scalar (as a string), and the description of the scalar (as a
// string or none).
//
// ( $resolve_selfvar, $resolve_body ): the "self" argument and body for the
// resolve() method on GraphQLType and the to() method on ToInputValue.
//
// ( $fiv_arg, $fiv_result, $fiv_body ): the method argument, result type,
// and body for the from() method on FromInputValue.
(
@build_scalar_resolver,
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
@generate,
( $name:ty, $outname:tt, $descr:tt ),
(
( $resolve_selfvar:ident, $resolve_body:block ),
( $fiv_arg:ident, $fiv_result:ty, $fiv_body:block )
)
) => {
fn resolve(&$selfvar, _: Option<Vec<$crate::Selection>>, _: &mut $crate::Executor<CtxT>) -> $crate::Value {
$body
impl<CtxT> $crate::GraphQLType<CtxT> for $name {
fn name() -> Option<&'static str> {
Some($outname)
}

fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
graphql_scalar!(
@maybe_apply, $descr, description,
registry.build_scalar_type::<Self>())
.into_meta()
}

fn resolve(
&$resolve_selfvar,
_: Option<Vec<$crate::Selection>>,
_: &mut $crate::Executor<CtxT>) -> $crate::Value {
$resolve_body
}
}
};

(
@build_scalar_conv_impl,
$name:ty; [$($lifetime:tt),*];
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
) => {
impl<$($lifetime),*> $crate::ToInputValue for $name {
fn to(&$selfvar) -> $crate::InputValue {
$crate::ToInputValue::to(&$body)
impl $crate::ToInputValue for $name {
fn to(&$resolve_selfvar) -> $crate::InputValue {
$crate::ToInputValue::to(&$resolve_body)
}
}

graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($rest)*);
impl $crate::FromInputValue for $name {
fn from($fiv_arg: &$crate::InputValue) -> $fiv_result {
$fiv_body
}
}
};

// No more items to parse
(
@build_scalar_conv_impl,
$name:ty; [$($lifetime:tt),*];
from_input_value($arg:ident: &InputValue) -> $result:ty $body:block
$($rest:tt)*
@parse,
$meta:tt,
$acc:tt,
) => {
impl<$($lifetime),*> $crate::FromInputValue for $name {
fn from($arg: &$crate::InputValue) -> $result {
$body
}
}

graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($rest)*);
graphql_scalar!( @generate, $meta, $acc );
};

// resolve(&self) -> Value { ... }
(
@build_scalar_conv_impl,
$name:ty; $($lifetime:tt),*;
@parse,
$meta:tt,
( $_ignored:tt, $fiv:tt ),
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
) => {
graphql_scalar!( @parse, $meta, ( ($selfvar, $body), $fiv ), $($rest)* );
};

(($($lifetime:tt),*) $name:ty as $outname:expr => { $( $items:tt )* }) => {
impl<$($lifetime,)* CtxT> $crate::GraphQLType<CtxT> for $name {
fn name() -> Option<&'static str> {
Some($outname)
}

fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
registry.build_scalar_type::<Self>().into_meta()
}

graphql_scalar!(@build_scalar_resolver, $($items)*);
}

graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($items)*);
// from_input_value(arg: &InputValue) -> ... { ... }
(
@parse,
$meta:tt,
( $resolve:tt, $_ignored:tt ),
from_input_value($arg:ident: &InputValue) -> $result:ty $body:block $($rest:tt)*
) => {
graphql_scalar!( @parse, $meta, ( $resolve, ( $arg, $result, $body ) ), $($rest)* );
};

(<$($lifetime:tt),*> $name:ty as $outname:tt { $( $items:tt )* }) => {
graphql_scalar!(($($lifetime),*) $name as $outname => { $( $items )* });
// description: <description>
(
@parse,
( $name:ty, $outname:tt, $_ignored:tt ),
$acc:tt,
description: $descr:tt $($rest:tt)*
) => {
graphql_scalar!( @parse, ( $name, $outname, $descr ), $acc, $($rest)* );
};

// Entry point:
// RustName as "GraphQLName" { ... }
( $name:ty as $outname:tt { $( $items:tt )* }) => {
graphql_scalar!(() $name as $outname => { $( $items )* });
}
graphql_scalar!( @parse, ( $name, $outname, None ), ( None, None ), $($items)* );
};

// Entry point
// RustName { ... }
( $name:ty { $( $items:tt )* }) => {
graphql_scalar!( @parse, ( $name, (stringify!($name)), None ), ( None, None ), $($items)* );
};
}
1 change: 1 addition & 0 deletions src/macros/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod enums;
mod scalar;
156 changes: 156 additions & 0 deletions src/macros/tests/scalar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use std::collections::HashMap;

use executor::FieldResult;
use value::Value;
use schema::model::RootNode;

struct DefaultName(i64);
struct OtherOrder(i64);
struct Named(i64);
struct ScalarDescription(i64);

struct Root;

/*
Syntax to validate:
* Default name vs. custom name
* Description vs. no description on the scalar
*/

graphql_scalar!(DefaultName {
resolve(&self) -> Value {
Value::int(self.0)
}

from_input_value(v: &InputValue) -> Option<DefaultName> {
v.as_int_value().map(|i| DefaultName(i))
}
});

graphql_scalar!(OtherOrder {
from_input_value(v: &InputValue) -> Option<OtherOrder> {
v.as_int_value().map(|i| OtherOrder(i))
}

resolve(&self) -> Value {
Value::int(self.0)
}
});

graphql_scalar!(Named as "ANamedScalar" {
resolve(&self) -> Value {
Value::int(self.0)
}

from_input_value(v: &InputValue) -> Option<Named> {
v.as_int_value().map(|i| Named(i))
}
});

graphql_scalar!(ScalarDescription {
description: "A sample scalar, represented as an integer"

resolve(&self) -> Value {
Value::int(self.0)
}

from_input_value(v: &InputValue) -> Option<ScalarDescription> {
v.as_int_value().map(|i| ScalarDescription(i))
}
});

graphql_object!(Root: () as "Root" |&self| {
field default_name() -> FieldResult<DefaultName> { Ok(DefaultName(0)) }
field other_order() -> FieldResult<OtherOrder> { Ok(OtherOrder(0)) }
field named() -> FieldResult<Named> { Ok(Named(0)) }
field scalar_description() -> FieldResult<ScalarDescription> { Ok(ScalarDescription(0)) }
});

fn run_type_info_query<F>(doc: &str, f: F) where F: Fn(&HashMap<String, 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");

f(type_info);
}

#[test]
fn default_name_introspection() {
let doc = r#"
{
__type(name: "DefaultName") {
name
description
}
}
"#;

run_type_info_query(doc, |type_info| {
assert_eq!(type_info.get("name"), Some(&Value::string("DefaultName")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
});
}

#[test]
fn other_order_introspection() {
let doc = r#"
{
__type(name: "OtherOrder") {
name
description
}
}
"#;

run_type_info_query(doc, |type_info| {
assert_eq!(type_info.get("name"), Some(&Value::string("OtherOrder")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
});
}

#[test]
fn named_introspection() {
let doc = r#"
{
__type(name: "ANamedScalar") {
name
description
}
}
"#;

run_type_info_query(doc, |type_info| {
assert_eq!(type_info.get("name"), Some(&Value::string("ANamedScalar")));
assert_eq!(type_info.get("description"), Some(&Value::null()));
});
}

#[test]
fn scalar_description_introspection() {
let doc = r#"
{
__type(name: "ScalarDescription") {
name
description
}
}
"#;

run_type_info_query(doc, |type_info| {
assert_eq!(type_info.get("name"), Some(&Value::string("ScalarDescription")));
assert_eq!(type_info.get("description"), Some(&Value::string("A sample scalar, represented as an integer")));
});
}

0 comments on commit 42e4070

Please sign in to comment.