Skip to content

Make graphql_scalar a proc macro #610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions docs/book/content/types/scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ There are two ways to define custom scalars.
* For simple scalars that just wrap a primitive type, you can use the newtype pattern with
a custom derive.
* For more advanced use cases with custom validation, you can use
the `graphql_scalar!` macro.
the `graphql_scalar` proc macro.


## Built-in scalars
Expand Down Expand Up @@ -79,7 +79,7 @@ pub struct UserId(i32);
## Custom scalars

For more complex situations where you also need custom parsing or validation,
you can use the `graphql_scalar!` macro.
you can use the `graphql_scalar` proc macro.

Typically, you represent your custom scalars as strings.

Expand Down Expand Up @@ -112,26 +112,28 @@ The example below is used just for illustration.
use juniper::{Value, ParseScalarResult, ParseScalarValue};
use date::Date;

juniper::graphql_scalar!(Date where Scalar = <S> {
description: "Date"

#[juniper::graphql_scalar(description = "Date")]
impl<S> GraphQLScalar for Date
where
S: ScalarValue
{
// Define how to convert your custom scalar into a primitive type.
resolve(&self) -> Value {
fn resolve(&self) -> Value {
Value::scalar(self.to_string())
}

// Define how to parse a primitive type into your custom scalar.
from_input_value(v: &InputValue) -> Option<Date> {
fn from_input_value(v: &InputValue) -> Option<Date> {
v.as_scalar_value()
.and_then(|v| v.as_str())
.and_then(|s| s.parse().ok())
}

// Define how to parse a string value.
from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
<String as ParseScalarValue<S>>::from_str(value)
}
});
}

# fn main() {}
```
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::{
executor::Variables,
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, Value},
use juniper::{
DefaultScalarValue, EmptyMutation, EmptySubscription, Object, ParseScalarResult,
ParseScalarValue, RootNode, Value, Variables,
};

struct DefaultName(i32);
Expand All @@ -21,66 +19,72 @@ Syntax to validate:

*/

graphql_scalar!(DefaultName where Scalar = <S> {
resolve(&self) -> Value {
#[juniper::graphql_scalar]
impl<S> GraphQLScalar for DefaultName
where
S: juniper::ScalarValue,
{
fn resolve(&self) -> Value {
Value::scalar(self.0)
}

from_input_value(v: &InputValue) -> Option<DefaultName> {
v.as_scalar_value().and_then(|s| s.as_int()).map(|i| DefaultName(i))
fn from_input_value(v: &juniper::InputValue) -> Option<DefaultName> {
v.as_scalar_value()
.and_then(|s| s.as_int())
.map(|i| DefaultName(i))
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, S> {
<i32 as ParseScalarValue<S>>::from_str(value)
}
});
}

graphql_scalar!(OtherOrder {
resolve(&self) -> Value {
#[juniper::graphql_scalar]
impl GraphQLScalar for OtherOrder {
fn resolve(&self) -> Value {
Value::scalar(self.0)
}

from_input_value(v: &InputValue) -> Option<OtherOrder> {
fn from_input_value(v: &juniper::InputValue) -> Option<OtherOrder> {
v.as_scalar_value::<i32>().map(|i| OtherOrder(*i))
}


from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
<i32 as ParseScalarValue>::from_str(value)
}
});
}

graphql_scalar!(Named as "ANamedScalar" where Scalar = DefaultScalarValue {
resolve(&self) -> Value {
#[juniper::graphql_scalar(name = "ANamedScalar")]
impl GraphQLScalar for Named {
fn resolve(&self) -> Value {
Value::scalar(self.0)
}

from_input_value(v: &InputValue) -> Option<Named> {
fn from_input_value(v: &juniper::InputValue) -> Option<Named> {
v.as_scalar_value::<i32>().map(|i| Named(*i))
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
<i32 as ParseScalarValue>::from_str(value)
}
});

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

resolve(&self) -> Value {
#[juniper::graphql_scalar(description = "A sample scalar, represented as an integer")]
impl GraphQLScalar for ScalarDescription {
fn resolve(&self) -> Value {
Value::scalar(self.0)
}

from_input_value(v: &InputValue) -> Option<ScalarDescription> {
fn from_input_value(v: &juniper::InputValue) -> Option<ScalarDescription> {
v.as_scalar_value::<i32>().map(|i| ScalarDescription(*i))
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> {
fn from_str<'a>(value: juniper::ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
<i32 as ParseScalarValue>::from_str(value)
}
});
}

#[crate::graphql_object_internal]
#[juniper::graphql_object]
impl Root {
fn default_name() -> DefaultName {
DefaultName(0)
Expand All @@ -106,7 +110,7 @@ where
EmptySubscription::<()>::new(),
);

let (result, errs) = crate::execute(doc, None, &schema, &Variables::new(), &())
let (result, errs) = juniper::execute(doc, None, &schema, &Variables::new(), &())
.await
.expect("Execution failed");

Expand All @@ -129,19 +133,22 @@ where
fn path_in_resolve_return_type() {
struct ResolvePath(i32);

graphql_scalar!(ResolvePath {
resolve(&self) -> self::Value {
#[juniper::graphql_scalar]
impl GraphQLScalar for ResolvePath {
fn resolve(&self) -> self::Value {
Value::scalar(self.0)
}

from_input_value(v: &InputValue) -> Option<ResolvePath> {
fn from_input_value(v: &juniper::InputValue) -> Option<ResolvePath> {
v.as_scalar_value::<i32>().map(|i| ResolvePath(*i))
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> {
fn from_str<'a>(
value: juniper::ScalarToken<'a>,
) -> ParseScalarResult<'a, DefaultScalarValue> {
<i32 as ParseScalarValue>::from_str(value)
}
});
}
}

#[tokio::test]
Expand Down
1 change: 1 addition & 0 deletions integration_tests/juniper_tests/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ mod derive_input_object;
mod derive_object;
mod derive_object_with_raw_idents;
mod derive_union;
mod impl_scalar;
mod impl_union;
mod scalar_value_transparent;
19 changes: 10 additions & 9 deletions integration_tests/juniper_tests/src/custom_scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,28 +133,29 @@ impl<'de> de::Visitor<'de> for MyScalarValueVisitor {
}
}

juniper::graphql_scalar!(i64 as "Long" where Scalar = MyScalarValue {
resolve(&self) -> Value {
#[juniper::graphql_scalar(name = "Long")]
impl GraphQLScalar for i64 {
fn resolve(&self) -> Value {
Value::scalar(*self)
}

from_input_value(v: &InputValue) -> Option<i64> {
fn from_input_value(v: &InputValue) -> Option<i64> {
match *v {
InputValue::Scalar(MyScalarValue::Long(i)) => Some(i),
_ => None,
}
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
if let ScalarToken::Int(v) = value {
v.parse()
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
.map(|s: i64| s.into())
v.parse()
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
.map(|s: i64| s.into())
} else {
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
}
}
});
}

struct TestType;

Expand Down
2 changes: 2 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ See [#618](https://github.com/graphql-rust/juniper/pull/618).

- remove old `graphql_object!` macro, rename `object` proc macro to `graphql_object`

- remove old `graphql_scalar!` macro, rename `scalar` proc macro to `graphql_scalar`

- Remove deprecated `ScalarValue` custom derive (renamed to GraphQLScalarValue)

- `graphql_union!` macro removed, replaced by `#[graphql_union]` proc macro
Expand Down
2 changes: 1 addition & 1 deletion juniper/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub type Document<'a, S> = Vec<Definition<'a, S>>;
/// 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 macro `graphql_scalar!` or by deriving GraphQLEnum.
/// automatically by the convenience proc macro `graphql_scalar` or by deriving GraphQLEnum.
///
/// Must be implemented manually when manually exposing new enums or scalars.
pub trait FromInputValue<S = DefaultScalarValue>: Sized {
Expand Down
13 changes: 7 additions & 6 deletions juniper/src/executor_tests/introspection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
executor::Variables,
schema::model::RootNode,
types::scalars::{EmptyMutation, EmptySubscription},
value::{ParseScalarResult, ParseScalarValue, Value},
value::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, Value},
};

#[derive(GraphQLEnum)]
Expand All @@ -27,19 +27,20 @@ struct Interface;

struct Root;

graphql_scalar!(Scalar as "SampleScalar" {
resolve(&self) -> Value {
#[crate::graphql_scalar_internal(name = "SampleScalar")]
impl GraphQLScalar for Scalar {
fn resolve(&self) -> Value {
Value::scalar(self.0)
}

from_input_value(v: &InputValue) -> Option<Scalar> {
fn from_input_value(v: &InputValue) -> Option<Scalar> {
v.as_scalar_value().map(|i: &i32| Scalar(*i))
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> {
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
<i32 as ParseScalarValue>::from_str(value)
}
});
}

graphql_interface!(Interface: () as "SampleInterface" |&self| {
description: "A sample interface"
Expand Down
13 changes: 7 additions & 6 deletions juniper/src/executor_tests/variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ struct TestComplexScalar;

struct TestType;

graphql_scalar!(TestComplexScalar {
resolve(&self) -> Value {
#[crate::graphql_scalar_internal]
impl GraphQLScalar for TestComplexScalar {
fn resolve(&self) -> Value {
Value::scalar(String::from("SerializedValue"))
}

from_input_value(v: &InputValue) -> Option<TestComplexScalar> {
fn from_input_value(v: &InputValue) -> Option<TestComplexScalar> {
if let Some(s) = v.as_scalar_value::<String>() {
if *s == "SerializedValue" {
return Some(TestComplexScalar);
Expand All @@ -31,10 +32,10 @@ graphql_scalar!(TestComplexScalar {
None
}

from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a> {
<String as ParseScalarValue<_>>::from_str(value)
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
<String as ParseScalarValue>::from_str(value)
}
});
}

#[derive(GraphQLInputObject, Debug)]
#[graphql(scalar = "DefaultScalarValue")]
Expand Down
Loading