Extend scalar macro, add more tests
This commit is contained in:
parent
ca6c637aa4
commit
42e4070e4b
3 changed files with 250 additions and 55 deletions
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($rest)*);
|
||||
};
|
||||
|
||||
(
|
||||
@build_scalar_conv_impl,
|
||||
$name:ty; [$($lifetime:tt),*];
|
||||
from_input_value($arg:ident: &InputValue) -> $result:ty $body:block
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
impl<$($lifetime),*> $crate::FromInputValue for $name {
|
||||
fn from($arg: &$crate::InputValue) -> $result {
|
||||
$body
|
||||
}
|
||||
}
|
||||
|
||||
graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($rest)*);
|
||||
};
|
||||
|
||||
(
|
||||
@build_scalar_conv_impl,
|
||||
$name:ty; $($lifetime:tt),*;
|
||||
) => {
|
||||
};
|
||||
|
||||
(($($lifetime:tt),*) $name:ty as $outname:expr => { $( $items:tt )* }) => {
|
||||
impl<$($lifetime,)* CtxT> $crate::GraphQLType<CtxT> for $name {
|
||||
impl<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!(
|
||||
@maybe_apply, $descr, description,
|
||||
registry.build_scalar_type::<Self>())
|
||||
.into_meta()
|
||||
}
|
||||
|
||||
graphql_scalar!(@build_scalar_resolver, $($items)*);
|
||||
fn resolve(
|
||||
&$resolve_selfvar,
|
||||
_: Option<Vec<$crate::Selection>>,
|
||||
_: &mut $crate::Executor<CtxT>) -> $crate::Value {
|
||||
$resolve_body
|
||||
}
|
||||
}
|
||||
|
||||
graphql_scalar!(@build_scalar_conv_impl, $name; [$($lifetime),*]; $($items)*);
|
||||
impl $crate::ToInputValue for $name {
|
||||
fn to(&$resolve_selfvar) -> $crate::InputValue {
|
||||
$crate::ToInputValue::to(&$resolve_body)
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::FromInputValue for $name {
|
||||
fn from($fiv_arg: &$crate::InputValue) -> $fiv_result {
|
||||
$fiv_body
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(<$($lifetime:tt),*> $name:ty as $outname:tt { $( $items:tt )* }) => {
|
||||
graphql_scalar!(($($lifetime),*) $name as $outname => { $( $items )* });
|
||||
// No more items to parse
|
||||
(
|
||||
@parse,
|
||||
$meta:tt,
|
||||
$acc:tt,
|
||||
) => {
|
||||
graphql_scalar!( @generate, $meta, $acc );
|
||||
};
|
||||
|
||||
// resolve(&self) -> Value { ... }
|
||||
(
|
||||
@parse,
|
||||
$meta:tt,
|
||||
( $_ignored:tt, $fiv:tt ),
|
||||
resolve(&$selfvar:ident) -> Value $body:block $($rest:tt)*
|
||||
) => {
|
||||
graphql_scalar!( @parse, $meta, ( ($selfvar, $body), $fiv ), $($rest)* );
|
||||
};
|
||||
|
||||
// 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)* );
|
||||
};
|
||||
|
||||
// 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 +1,2 @@
|
|||
mod enums;
|
||||
mod scalar;
|
||||
|
|
156
src/macros/tests/scalar.rs
Normal file
156
src/macros/tests/scalar.rs
Normal file
|
@ -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")));
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue