Skip to content

Commit 42e4070

Browse files
committed
Extend scalar macro, add more tests
1 parent ca6c637 commit 42e4070

File tree

3 files changed

+244
-49
lines changed

3 files changed

+244
-49
lines changed

src/macros/scalar.rs

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ datatype appropriate for that platform.
1616
# use juniper::{Value, FieldResult};
1717
struct UserID(String);
1818
19-
graphql_scalar!(UserID as "UserID" {
19+
graphql_scalar!(UserID {
20+
description: "An opaque identifier, represented as a string"
21+
2022
resolve(&self) -> Value {
2123
Value::string(&self.0)
2224
}
@@ -33,77 +35,113 @@ In addition to implementing `GraphQLType` for the type in question,
3335
`FromInputValue` and `ToInputValue` is also implemented. This makes the type
3436
usable as arguments and default values.
3537
36-
`graphql_scalar!` supports generic and lifetime parameters similar to
37-
`graphql_object!`.
38-
3938
*/
4039
#[macro_export]
4140
macro_rules! graphql_scalar {
41+
// Calls $val.$func($arg) if $arg is not None
42+
( @maybe_apply, None, $func:ident, $val:expr ) => { $val };
43+
( @maybe_apply, $arg:tt, $func:ident, $val:expr ) => { $val.$func($arg) };
44+
45+
// Each of the @parse match arms accumulates data up to a call to @generate
46+
//
47+
// ( $name, $outname, $descr ): the name of the Rust type and the name of the
48+
// GraphQL scalar (as a string), and the description of the scalar (as a
49+
// string or none).
50+
//
51+
// ( $resolve_selfvar, $resolve_body ): the "self" argument and body for the
52+
// resolve() method on GraphQLType and the to() method on ToInputValue.
53+
//
54+
// ( $fiv_arg, $fiv_result, $fiv_body ): the method argument, result type,
55+
// and body for the from() method on FromInputValue.
4256
(
43-
@build_scalar_resolver,
44-
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
57+
@generate,
58+
( $name:ty, $outname:tt, $descr:tt ),
59+
(
60+
( $resolve_selfvar:ident, $resolve_body:block ),
61+
( $fiv_arg:ident, $fiv_result:ty, $fiv_body:block )
62+
)
4563
) => {
46-
fn resolve(&$selfvar, _: Option<Vec<$crate::Selection>>, _: &mut $crate::Executor<CtxT>) -> $crate::Value {
47-
$body
64+
impl<CtxT> $crate::GraphQLType<CtxT> for $name {
65+
fn name() -> Option<&'static str> {
66+
Some($outname)
67+
}
68+
69+
fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
70+
graphql_scalar!(
71+
@maybe_apply, $descr, description,
72+
registry.build_scalar_type::<Self>())
73+
.into_meta()
74+
}
75+
76+
fn resolve(
77+
&$resolve_selfvar,
78+
_: Option<Vec<$crate::Selection>>,
79+
_: &mut $crate::Executor<CtxT>) -> $crate::Value {
80+
$resolve_body
81+
}
4882
}
49-
};
5083

51-
(
52-
@build_scalar_conv_impl,
53-
$name:ty; [$($lifetime:tt),*];
54-
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
55-
) => {
56-
impl<$($lifetime),*> $crate::ToInputValue for $name {
57-
fn to(&$selfvar) -> $crate::InputValue {
58-
$crate::ToInputValue::to(&$body)
84+
impl $crate::ToInputValue for $name {
85+
fn to(&$resolve_selfvar) -> $crate::InputValue {
86+
$crate::ToInputValue::to(&$resolve_body)
5987
}
6088
}
6189

62-
graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($rest)*);
90+
impl $crate::FromInputValue for $name {
91+
fn from($fiv_arg: &$crate::InputValue) -> $fiv_result {
92+
$fiv_body
93+
}
94+
}
6395
};
6496

97+
// No more items to parse
6598
(
66-
@build_scalar_conv_impl,
67-
$name:ty; [$($lifetime:tt),*];
68-
from_input_value($arg:ident: &InputValue) -> $result:ty $body:block
69-
$($rest:tt)*
99+
@parse,
100+
$meta:tt,
101+
$acc:tt,
70102
) => {
71-
impl<$($lifetime),*> $crate::FromInputValue for $name {
72-
fn from($arg: &$crate::InputValue) -> $result {
73-
$body
74-
}
75-
}
76-
77-
graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($rest)*);
103+
graphql_scalar!( @generate, $meta, $acc );
78104
};
79105

106+
// resolve(&self) -> Value { ... }
80107
(
81-
@build_scalar_conv_impl,
82-
$name:ty; $($lifetime:tt),*;
108+
@parse,
109+
$meta:tt,
110+
( $_ignored:tt, $fiv:tt ),
111+
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
83112
) => {
113+
graphql_scalar!( @parse, $meta, ( ($selfvar, $body), $fiv ), $($rest)* );
84114
};
85115

86-
(($($lifetime:tt),*) $name:ty as $outname:expr => { $( $items:tt )* }) => {
87-
impl<$($lifetime,)* CtxT> $crate::GraphQLType<CtxT> for $name {
88-
fn name() -> Option<&'static str> {
89-
Some($outname)
90-
}
91-
92-
fn meta(registry: &mut $crate::Registry<CtxT>) -> $crate::meta::MetaType {
93-
registry.build_scalar_type::<Self>().into_meta()
94-
}
95-
96-
graphql_scalar!(@build_scalar_resolver, $($items)*);
97-
}
98-
99-
graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($items)*);
116+
// from_input_value(arg: &InputValue) -> ... { ... }
117+
(
118+
@parse,
119+
$meta:tt,
120+
( $resolve:tt, $_ignored:tt ),
121+
from_input_value($arg:ident: &InputValue) -> $result:ty $body:block $($rest:tt)*
122+
) => {
123+
graphql_scalar!( @parse, $meta, ( $resolve, ( $arg, $result, $body ) ), $($rest)* );
100124
};
101125

102-
(<$($lifetime:tt),*> $name:ty as $outname:tt { $( $items:tt )* }) => {
103-
graphql_scalar!(($($lifetime),*) $name as $outname => { $( $items )* });
126+
// description: <description>
127+
(
128+
@parse,
129+
( $name:ty, $outname:tt, $_ignored:tt ),
130+
$acc:tt,
131+
description: $descr:tt $($rest:tt)*
132+
) => {
133+
graphql_scalar!( @parse, ( $name, $outname, $descr ), $acc, $($rest)* );
104134
};
105135

136+
// Entry point:
137+
// RustName as "GraphQLName" { ... }
106138
( $name:ty as $outname:tt { $( $items:tt )* }) => {
107-
graphql_scalar!(() $name as $outname => { $( $items )* });
108-
}
139+
graphql_scalar!( @parse, ( $name, $outname, None ), ( None, None ), $($items)* );
140+
};
141+
142+
// Entry point
143+
// RustName { ... }
144+
( $name:ty { $( $items:tt )* }) => {
145+
graphql_scalar!( @parse, ( $name, (stringify!($name)), None ), ( None, None ), $($items)* );
146+
};
109147
}

src/macros/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
mod enums;
2+
mod scalar;

src/macros/tests/scalar.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use std::collections::HashMap;
2+
3+
use executor::FieldResult;
4+
use value::Value;
5+
use schema::model::RootNode;
6+
7+
struct DefaultName(i64);
8+
struct OtherOrder(i64);
9+
struct Named(i64);
10+
struct ScalarDescription(i64);
11+
12+
struct Root;
13+
14+
/*
15+
16+
Syntax to validate:
17+
18+
* Default name vs. custom name
19+
* Description vs. no description on the scalar
20+
21+
*/
22+
23+
graphql_scalar!(DefaultName {
24+
resolve(&self) -> Value {
25+
Value::int(self.0)
26+
}
27+
28+
from_input_value(v: &InputValue) -> Option<DefaultName> {
29+
v.as_int_value().map(|i| DefaultName(i))
30+
}
31+
});
32+
33+
graphql_scalar!(OtherOrder {
34+
from_input_value(v: &InputValue) -> Option<OtherOrder> {
35+
v.as_int_value().map(|i| OtherOrder(i))
36+
}
37+
38+
resolve(&self) -> Value {
39+
Value::int(self.0)
40+
}
41+
});
42+
43+
graphql_scalar!(Named as "ANamedScalar" {
44+
resolve(&self) -> Value {
45+
Value::int(self.0)
46+
}
47+
48+
from_input_value(v: &InputValue) -> Option<Named> {
49+
v.as_int_value().map(|i| Named(i))
50+
}
51+
});
52+
53+
graphql_scalar!(ScalarDescription {
54+
description: "A sample scalar, represented as an integer"
55+
56+
resolve(&self) -> Value {
57+
Value::int(self.0)
58+
}
59+
60+
from_input_value(v: &InputValue) -> Option<ScalarDescription> {
61+
v.as_int_value().map(|i| ScalarDescription(i))
62+
}
63+
});
64+
65+
graphql_object!(Root: () as "Root" |&self| {
66+
field default_name() -> FieldResult<DefaultName> { Ok(DefaultName(0)) }
67+
field other_order() -> FieldResult<OtherOrder> { Ok(OtherOrder(0)) }
68+
field named() -> FieldResult<Named> { Ok(Named(0)) }
69+
field scalar_description() -> FieldResult<ScalarDescription> { Ok(ScalarDescription(0)) }
70+
});
71+
72+
fn run_type_info_query<F>(doc: &str, f: F) where F: Fn(&HashMap<String, Value>) -> () {
73+
let schema = RootNode::new(Root {}, ());
74+
75+
let (result, errs) = ::execute(doc, None, &schema, &HashMap::new(), &())
76+
.expect("Execution failed");
77+
78+
assert_eq!(errs, []);
79+
80+
println!("Result: {:?}", result);
81+
82+
let type_info = result
83+
.as_object_value().expect("Result is not an object")
84+
.get("__type").expect("__type field missing")
85+
.as_object_value().expect("__type field not an object value");
86+
87+
f(type_info);
88+
}
89+
90+
#[test]
91+
fn default_name_introspection() {
92+
let doc = r#"
93+
{
94+
__type(name: "DefaultName") {
95+
name
96+
description
97+
}
98+
}
99+
"#;
100+
101+
run_type_info_query(doc, |type_info| {
102+
assert_eq!(type_info.get("name"), Some(&Value::string("DefaultName")));
103+
assert_eq!(type_info.get("description"), Some(&Value::null()));
104+
});
105+
}
106+
107+
#[test]
108+
fn other_order_introspection() {
109+
let doc = r#"
110+
{
111+
__type(name: "OtherOrder") {
112+
name
113+
description
114+
}
115+
}
116+
"#;
117+
118+
run_type_info_query(doc, |type_info| {
119+
assert_eq!(type_info.get("name"), Some(&Value::string("OtherOrder")));
120+
assert_eq!(type_info.get("description"), Some(&Value::null()));
121+
});
122+
}
123+
124+
#[test]
125+
fn named_introspection() {
126+
let doc = r#"
127+
{
128+
__type(name: "ANamedScalar") {
129+
name
130+
description
131+
}
132+
}
133+
"#;
134+
135+
run_type_info_query(doc, |type_info| {
136+
assert_eq!(type_info.get("name"), Some(&Value::string("ANamedScalar")));
137+
assert_eq!(type_info.get("description"), Some(&Value::null()));
138+
});
139+
}
140+
141+
#[test]
142+
fn scalar_description_introspection() {
143+
let doc = r#"
144+
{
145+
__type(name: "ScalarDescription") {
146+
name
147+
description
148+
}
149+
}
150+
"#;
151+
152+
run_type_info_query(doc, |type_info| {
153+
assert_eq!(type_info.get("name"), Some(&Value::string("ScalarDescription")));
154+
assert_eq!(type_info.get("description"), Some(&Value::string("A sample scalar, represented as an integer")));
155+
});
156+
}

0 commit comments

Comments
 (0)