- support generic scalars - make it applicable to type aliases and struct/enums/unions
This commit is contained in:
parent
3a70403aba
commit
63198cdfcb
47 changed files with 4298 additions and 2042 deletions
|
@ -40,34 +40,61 @@ crates. They are enabled via features that are on by default.
|
|||
* url::Url
|
||||
* bson::oid::ObjectId
|
||||
|
||||
## newtype pattern
|
||||
|
||||
|
||||
|
||||
## Custom scalars
|
||||
|
||||
### `#[graphql(transparent)]` attribute
|
||||
|
||||
Often, you might need a custom scalar that just wraps an existing type.
|
||||
|
||||
This can be done with the newtype pattern and a custom derive, similar to how
|
||||
serde supports this pattern with `#[serde(transparent)]`.
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
# extern crate juniper;
|
||||
#[derive(juniper::GraphQLScalarValue)]
|
||||
#
|
||||
#[derive(juniper::GraphQLScalar)]
|
||||
#[graphql(transparent)]
|
||||
pub struct UserId(i32);
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
struct User {
|
||||
id: UserId,
|
||||
}
|
||||
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
That's it, you can now user `UserId` in your schema.
|
||||
`#[derive(GraphQLScalar)]` is mostly interchangeable with `#[graphql_scalar]`
|
||||
attribute:
|
||||
|
||||
```rust,ignore
|
||||
# extern crate juniper;
|
||||
# use juniper::graphql_scalar;
|
||||
#
|
||||
#[graphql_scalar(transparent)]
|
||||
pub struct UserId {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
struct User {
|
||||
id: UserId,
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
That's it, you can now use `UserId` in your schema.
|
||||
|
||||
The macro also allows for more customization:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
# extern crate juniper;
|
||||
/// You can use a doc comment to specify a description.
|
||||
#[derive(juniper::GraphQLScalarValue)]
|
||||
#[derive(juniper::GraphQLScalar)]
|
||||
#[graphql(
|
||||
transparent,
|
||||
// Overwrite the GraphQL type name.
|
||||
|
@ -77,37 +104,276 @@ The macro also allows for more customization:
|
|||
description = "My user id description",
|
||||
)]
|
||||
pub struct UserId(i32);
|
||||
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Custom scalars
|
||||
All the methods used from newtype's field can be replaced with attributes:
|
||||
|
||||
For more complex situations where you also need custom parsing or validation,
|
||||
you can use the `graphql_scalar` proc macro.
|
||||
### `#[graphql(to_output_with = <fn>)]` attribute
|
||||
|
||||
Typically, you represent your custom scalars as strings.
|
||||
```rust,ignore
|
||||
# use juniper::{GraphQLScalar, ScalarValue, Value};
|
||||
#
|
||||
#[derive(GraphQLScalar)]
|
||||
#[graphql(to_output_with = to_output, transparent)]
|
||||
struct Incremented(i32);
|
||||
|
||||
The example below implements a custom scalar for a custom `Date` type.
|
||||
/// Increments [`Incremented`] before converting into a [`Value`].
|
||||
fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
|
||||
Value::from(v.0 + 1)
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Note: juniper already has built-in support for the `chrono::DateTime` type
|
||||
via `chrono` feature, which is enabled by default and should be used for this
|
||||
purpose.
|
||||
### `#[graphql(from_input_with = <fn>)]` attribute
|
||||
|
||||
The example below is used just for illustration.
|
||||
```rust,ignore
|
||||
# use juniper::{GraphQLScalar, InputValue, ScalarValue};
|
||||
#
|
||||
#[derive(GraphQLScalar)]
|
||||
#[graphql(from_input_with = Self::from_input, transparent)]
|
||||
struct UserId(String);
|
||||
|
||||
**Note**: the example assumes that the `Date` type implements
|
||||
`std::fmt::Display` and `std::str::FromStr`.
|
||||
impl UserId {
|
||||
/// Checks whether [`InputValue`] is `String` beginning with `id: ` and
|
||||
/// strips it.
|
||||
fn from_input<S>(input: &InputValue<S>) -> Result<Self, String>
|
||||
where
|
||||
S: ScalarValue
|
||||
{
|
||||
input.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", input))
|
||||
.and_then(|str| {
|
||||
str.strip_prefix("id: ")
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Expected `UserId` to begin with `id: `, \
|
||||
found: {}",
|
||||
input,
|
||||
)
|
||||
})
|
||||
})
|
||||
.map(|id| Self(id.to_owned()))
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
### `#[graphql(parse_token_with = <fn>]` or `#[graphql(parse_token(<types>)]` attributes
|
||||
|
||||
```rust,ignore
|
||||
# use juniper::{
|
||||
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
|
||||
# ScalarValue, ScalarToken, Value
|
||||
# };
|
||||
#
|
||||
#[derive(GraphQLScalar)]
|
||||
#[graphql(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
parse_token_with = parse_token,
|
||||
// ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)`
|
||||
// which tries to parse as `String` and then as `i32`
|
||||
// if prior fails.
|
||||
)]
|
||||
enum StringOrInt {
|
||||
String(String),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
fn to_output<S>(v: &StringOrInt) -> Value<S>
|
||||
where
|
||||
S: ScalarValue
|
||||
{
|
||||
match v {
|
||||
StringOrInt::String(str) => Value::scalar(str.to_owned()),
|
||||
StringOrInt::Int(i) => Value::scalar(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String>
|
||||
where
|
||||
S: ScalarValue
|
||||
{
|
||||
v.as_string_value()
|
||||
.map(|s| StringOrInt::String(s.to_owned()))
|
||||
.or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>
|
||||
where
|
||||
S: ScalarValue
|
||||
{
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
> __NOTE:__ As you can see, once you provide all 3 custom resolvers, there
|
||||
> is no need to follow `newtype` pattern.
|
||||
|
||||
### `#[graphql(with = <path>)]` attribute
|
||||
|
||||
Instead of providing all custom resolvers, you can provide path to the `to_output`,
|
||||
`from_input`, `parse_token` functions.
|
||||
|
||||
Path can be simply `with = Self` (default path where macro expects resolvers to be),
|
||||
in case there is an impl block with custom resolvers:
|
||||
|
||||
```rust,ignore
|
||||
# use juniper::{
|
||||
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
|
||||
# ScalarValue, ScalarToken, Value
|
||||
# };
|
||||
#
|
||||
#[derive(GraphQLScalar)]
|
||||
// #[graphql(with = Self)] <- default behaviour
|
||||
enum StringOrInt {
|
||||
String(String),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
impl StringOrInt {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
match self {
|
||||
Self::String(str) => Value::scalar(str.to_owned()),
|
||||
Self::Int(i) => Value::scalar(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.map(|s| Self::String(s.to_owned()))
|
||||
.or_else(|| v.as_int_value().map(|i| Self::Int(i)))
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Or it can be path to a module, where custom resolvers are located.
|
||||
|
||||
```rust,ignore
|
||||
# use juniper::{
|
||||
# GraphQLScalar, InputValue, ParseScalarResult, ParseScalarValue,
|
||||
# ScalarValue, ScalarToken, Value
|
||||
# };
|
||||
#
|
||||
#[derive(GraphQLScalar)]
|
||||
#[graphql(with = string_or_int)]
|
||||
enum StringOrInt {
|
||||
String(String),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
mod string_or_int {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S>(v: &StringOrInt) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
match v {
|
||||
StringOrInt::String(str) => Value::scalar(str.to_owned()),
|
||||
StringOrInt::Int(i) => Value::scalar(*i),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.map(|s| StringOrInt::String(s.to_owned()))
|
||||
.or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
pub(super) fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Also, you can partially override `#[graphql(with)]` attribute with other custom scalars.
|
||||
|
||||
```rust,ignore
|
||||
# use juniper::{GraphQLScalar, InputValue, ParseScalarResult, ScalarValue, ScalarToken, Value};
|
||||
#
|
||||
#[derive(GraphQLScalar)]
|
||||
#[graphql(parse_token(String, i32))]
|
||||
enum StringOrInt {
|
||||
String(String),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
impl StringOrInt {
|
||||
fn to_output<S>(&self) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
match self {
|
||||
Self::String(str) => Value::scalar(str.to_owned()),
|
||||
Self::Int(i) => Value::scalar(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
v.as_string_value()
|
||||
.map(|s| Self::String(s.to_owned()))
|
||||
.or_else(|| v.as_int_value().map(|i| Self::Int(i)))
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
### Using foreign types as scalars
|
||||
|
||||
For implementing custom scalars on foreign types there is `#[graphql_scalar]` attribute macro.
|
||||
|
||||
> __NOTE:__ To satisfy [orphan rules] you should provide local [`ScalarValue`] implementation.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# mod date {
|
||||
# pub struct Date;
|
||||
# impl std::str::FromStr for Date {
|
||||
# type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
|
||||
# type Err = String;
|
||||
#
|
||||
# fn from_str(_value: &str) -> Result<Self, Self::Err> {
|
||||
# unimplemented!()
|
||||
# }
|
||||
# }
|
||||
# // And we define how to represent date as a string.
|
||||
#
|
||||
# impl std::fmt::Display for Date {
|
||||
# fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
# unimplemented!()
|
||||
|
@ -115,32 +381,34 @@ The example below is used just for illustration.
|
|||
# }
|
||||
# }
|
||||
#
|
||||
use juniper::{Value, ParseScalarResult, ParseScalarValue};
|
||||
use date::Date;
|
||||
# use juniper::DefaultScalarValue as CustomScalarValue;
|
||||
use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
#[juniper::graphql_scalar(description = "Date")]
|
||||
impl<S> GraphQLScalar for Date
|
||||
where
|
||||
S: ScalarValue
|
||||
{
|
||||
// Define how to convert your custom scalar into a primitive type.
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.to_string())
|
||||
#[graphql_scalar(
|
||||
with = date_scalar,
|
||||
parse_token(String),
|
||||
scalar = CustomScalarValue,
|
||||
// ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
|
||||
)]
|
||||
type Date = date::Date;
|
||||
// ^^^^^^^^^^ Type from another crate.
|
||||
|
||||
mod date_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output(v: &Date) -> Value<CustomScalarValue> {
|
||||
Value::scalar(v.to_string())
|
||||
}
|
||||
|
||||
// Define how to parse a primitive type into your custom scalar.
|
||||
// NOTE: The error type should implement `IntoFieldError<S>`.
|
||||
fn from_input_value(v: &InputValue) -> Result<Date, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
|
||||
}
|
||||
|
||||
// Define how to parse a string value.
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
[orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
|
||||
[`ScalarValue`]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
|
||||
|
|
|
@ -1,14 +1,3 @@
|
|||
error[E0119]: conflicting implementations of trait `<CharacterValueEnum<ObjA, ObjA> as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
|
||||
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
||||
|
|
||||
11 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| first implementation here
|
||||
| conflicting implementation for `ObjA`
|
||||
|
|
||||
= note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValueEnum<ObjA, ObjA>`
|
||||
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
||||
|
|
||||
|
@ -19,3 +8,14 @@ error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` fo
|
|||
| conflicting implementation for `CharacterValueEnum<ObjA, ObjA>`
|
||||
|
|
||||
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0119]: conflicting implementations of trait `<CharacterValueEnum<ObjA, ObjA> as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`
|
||||
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
||||
|
|
||||
11 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| first implementation here
|
||||
| conflicting implementation for `ObjA`
|
||||
|
|
||||
= note: this error originates in the macro `::juniper::sa::assert_type_ne_all` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
#[graphql_scalar(specified_by_url = "not an url", parse_token(i32))]
|
||||
struct ScalarSpecifiedByUrl(i32);
|
||||
|
||||
impl ScalarSpecifiedByUrl {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(_: &InputValue<S>) -> Result<Self, String> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,17 @@
|
|||
error: Invalid URL: relative URL without a base
|
||||
--> fail/scalar/derive_input/impl_invalid_url.rs:3:37
|
||||
|
|
||||
3 | #[graphql_scalar(specified_by_url = "not an url", parse_token(i32))]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error[E0412]: cannot find type `ScalarSpecifiedByUrl` in this scope
|
||||
--> fail/scalar/derive_input/impl_invalid_url.rs:6:6
|
||||
|
|
||||
6 | impl ScalarSpecifiedByUrl {
|
||||
| ^^^^^^^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error: the `Self` constructor can only be used with tuple or unit structs
|
||||
--> fail/scalar/derive_input/impl_invalid_url.rs:12:12
|
||||
|
|
||||
12 | Ok(Self)
|
||||
| ^^^^
|
|
@ -1,7 +0,0 @@
|
|||
use juniper::GraphQLScalarValue;
|
||||
|
||||
#[derive(GraphQLScalarValue)]
|
||||
#[graphql(specified_by_url = "not an url")]
|
||||
struct ScalarSpecifiedByUrl(i64);
|
||||
|
||||
fn main() {}
|
|
@ -1,5 +0,0 @@
|
|||
error: Invalid URL: relative URL without a base
|
||||
--> fail/scalar/derive_invalid_url.rs:4:30
|
||||
|
|
||||
4 | #[graphql(specified_by_url = "not an url")]
|
||||
| ^^^^^^^^^^^^
|
|
@ -1,22 +0,0 @@
|
|||
use juniper::graphql_scalar;
|
||||
|
||||
struct ScalarSpecifiedByUrl(i32);
|
||||
|
||||
#[graphql_scalar(specified_by_url = "not an url")]
|
||||
impl GraphQLScalar for ScalarSpecifiedByUrl {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ScalarSpecifiedByUrl, String> {
|
||||
v.as_int_value()
|
||||
.map(ScalarSpecifiedByUrl)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -1,5 +0,0 @@
|
|||
error: Invalid URL: relative URL without a base
|
||||
--> fail/scalar/impl_invalid_url.rs:5:18
|
||||
|
|
||||
5 | #[graphql_scalar(specified_by_url = "not an url")]
|
||||
| ^^^^^^^^^^^^^^^^
|
|
@ -0,0 +1,26 @@
|
|||
use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
struct ScalarSpecifiedByUrl;
|
||||
|
||||
#[graphql_scalar(
|
||||
specified_by_url = "not an url",
|
||||
with = scalar,
|
||||
parse_token(i32),
|
||||
)]
|
||||
type Scalar = ScalarSpecifiedByUrl;
|
||||
|
||||
mod scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(_: &ScalarSpecifiedByUrl) -> Value<S> {
|
||||
Value::scalar(0)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(
|
||||
_: &InputValue<S>,
|
||||
) -> Result<ScalarSpecifiedByUrl, String> {
|
||||
Ok(ScalarSpecifiedByUrl)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,5 @@
|
|||
error: Invalid URL: relative URL without a base
|
||||
--> fail/scalar/type_alias/impl_invalid_url.rs:6:24
|
||||
|
|
||||
6 | specified_by_url = "not an url",
|
||||
| ^^^^^^^^^^^^
|
|
@ -0,0 +1,14 @@
|
|||
use juniper::{graphql_scalar, Value};
|
||||
|
||||
struct Scalar;
|
||||
|
||||
#[graphql_scalar(to_output_with = Scalar::to_output)]
|
||||
type CustomScalar = Scalar;
|
||||
|
||||
impl Scalar {
|
||||
fn to_output(&self) -> Value {
|
||||
Value::scalar(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,5 @@
|
|||
error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes
|
||||
--> fail/scalar/type_alias/impl_with_not_all_resolvers.rs:6:1
|
||||
|
|
||||
6 | type CustomScalar = Scalar;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -0,0 +1,8 @@
|
|||
use juniper::graphql_scalar;
|
||||
|
||||
struct Scalar;
|
||||
|
||||
#[graphql_scalar]
|
||||
type CustomScalar = Scalar;
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,5 @@
|
|||
error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attributes
|
||||
--> fail/scalar/type_alias/impl_without_resolvers.rs:6:1
|
||||
|
|
||||
6 | type CustomScalar = Scalar;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -5,6 +5,7 @@ edition = "2018"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
derive_more = "0.99"
|
||||
futures = "0.3"
|
||||
juniper = { path = "../../juniper" }
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
use juniper::{
|
||||
execute, graphql_value, EmptyMutation, EmptySubscription, FromInputValue, InputValue, RootNode,
|
||||
ToInputValue, Value, Variables,
|
||||
};
|
||||
|
||||
use crate::custom_scalar::MyScalarValue;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalarValue)]
|
||||
#[graphql(
|
||||
transparent,
|
||||
scalar = MyScalarValue,
|
||||
specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
)]
|
||||
pub struct LargeId(i64);
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(scalar = MyScalarValue)]
|
||||
struct User {
|
||||
id: LargeId,
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[juniper::graphql_object(scalar = MyScalarValue)]
|
||||
impl Query {
|
||||
fn user() -> User {
|
||||
User { id: LargeId(0) }
|
||||
}
|
||||
}
|
||||
|
||||
struct Mutation;
|
||||
|
||||
#[juniper::graphql_object(scalar = MyScalarValue)]
|
||||
impl Mutation {
|
||||
fn change_user(id: LargeId) -> User {
|
||||
User { id }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scalar_value_large_id() {
|
||||
let num: i64 = 4294967297;
|
||||
|
||||
let input_integer: InputValue<MyScalarValue> =
|
||||
serde_json::from_value(serde_json::json!(num)).unwrap();
|
||||
|
||||
let output: LargeId =
|
||||
FromInputValue::<MyScalarValue>::from_input_value(&input_integer).unwrap();
|
||||
assert_eq!(output, LargeId(num));
|
||||
|
||||
let id = LargeId(num);
|
||||
let output = ToInputValue::<MyScalarValue>::to_input_value(&id);
|
||||
assert_eq!(output, InputValue::scalar(num));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scalar_value_large_specified_url() {
|
||||
let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
|
||||
Query,
|
||||
EmptyMutation::<()>::new(),
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
let doc = r#"{
|
||||
__type(name: "LargeId") {
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
assert_eq!(
|
||||
execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc4122"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scalar_value_large_query() {
|
||||
let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
|
||||
Query,
|
||||
EmptyMutation::<()>::new(),
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
let doc = r#"{
|
||||
user { id }
|
||||
}"#;
|
||||
|
||||
let val = Value::<MyScalarValue>::scalar(0_i64);
|
||||
assert_eq!(
|
||||
execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
|
||||
Ok((graphql_value!({"user": {"id": val}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scalar_value_large_mutation() {
|
||||
let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
|
||||
Query,
|
||||
Mutation,
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
let doc = r#"mutation {
|
||||
changeUser(id: 1) { id }
|
||||
}"#;
|
||||
|
||||
let val = Value::<MyScalarValue>::scalar(1_i64);
|
||||
assert_eq!(
|
||||
execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
|
||||
Ok((graphql_value!({"changeUser": {"id": val}}), vec![])),
|
||||
);
|
||||
|
||||
let doc = r#"mutation {
|
||||
changeUser(id: 4294967297) { id }
|
||||
}"#;
|
||||
|
||||
let val = Value::<MyScalarValue>::scalar(4294967297_i64);
|
||||
assert_eq!(
|
||||
execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
|
||||
Ok((graphql_value!({"changeUser": {"id": val}}), vec![])),
|
||||
);
|
||||
}
|
|
@ -1,405 +0,0 @@
|
|||
use juniper::{
|
||||
execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, DefaultScalarValue,
|
||||
EmptyMutation, EmptySubscription, Object, ParseScalarResult, ParseScalarValue, RootNode, Value,
|
||||
};
|
||||
|
||||
use crate::custom_scalar::MyScalarValue;
|
||||
|
||||
struct DefaultName(i32);
|
||||
struct OtherOrder(i32);
|
||||
struct Named(i32);
|
||||
struct ScalarDescription(i32);
|
||||
struct ScalarSpecifiedByUrl(i32);
|
||||
struct Generated(String);
|
||||
|
||||
struct Root;
|
||||
|
||||
/*
|
||||
|
||||
Syntax to validate:
|
||||
|
||||
* Default name vs. custom name
|
||||
* Description vs. no description on the scalar
|
||||
|
||||
*/
|
||||
|
||||
#[graphql_scalar]
|
||||
impl<S> GraphQLScalar for DefaultName
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<DefaultName, String> {
|
||||
v.as_int_value()
|
||||
.map(DefaultName)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar]
|
||||
impl GraphQLScalar for OtherOrder {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<OtherOrder, String> {
|
||||
v.as_int_value()
|
||||
.map(OtherOrder)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(name = "ANamedScalar")]
|
||||
impl GraphQLScalar for Named {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Named, String> {
|
||||
v.as_int_value()
|
||||
.map(Named)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(description = "A sample scalar, represented as an integer")]
|
||||
impl GraphQLScalar for ScalarDescription {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ScalarDescription, String> {
|
||||
v.as_int_value()
|
||||
.map(ScalarDescription)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")]
|
||||
impl GraphQLScalar for ScalarSpecifiedByUrl {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ScalarSpecifiedByUrl, String> {
|
||||
v.as_int_value()
|
||||
.map(ScalarSpecifiedByUrl)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_scalar {
|
||||
($name: ident) => {
|
||||
#[graphql_scalar]
|
||||
impl<S> GraphQLScalar for $name
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0.clone())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Self, &'static str> {
|
||||
v.as_scalar_value()
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| Some(Self(s.to_owned())))
|
||||
.ok_or_else(|| "Expected `String`")
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_scalar!(Generated);
|
||||
|
||||
#[graphql_object(scalar = DefaultScalarValue)]
|
||||
impl Root {
|
||||
fn default_name() -> DefaultName {
|
||||
DefaultName(0)
|
||||
}
|
||||
fn other_order() -> OtherOrder {
|
||||
OtherOrder(0)
|
||||
}
|
||||
fn named() -> Named {
|
||||
Named(0)
|
||||
}
|
||||
fn scalar_description() -> ScalarDescription {
|
||||
ScalarDescription(0)
|
||||
}
|
||||
fn scalar_specified_by_url() -> ScalarSpecifiedByUrl {
|
||||
ScalarSpecifiedByUrl(0)
|
||||
}
|
||||
fn generated() -> Generated {
|
||||
Generated("foo".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
struct WithCustomScalarValue(i32);
|
||||
|
||||
#[graphql_scalar]
|
||||
impl GraphQLScalar for WithCustomScalarValue {
|
||||
fn resolve(&self) -> Value<MyScalarValue> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue<MyScalarValue>) -> Result<WithCustomScalarValue, String> {
|
||||
v.as_int_value()
|
||||
.map(WithCustomScalarValue)
|
||||
.ok_or_else(|| format!("Expected Int, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
|
||||
<i32 as ParseScalarValue<MyScalarValue>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct RootWithCustomScalarValue;
|
||||
|
||||
#[graphql_object(scalar = MyScalarValue)]
|
||||
impl RootWithCustomScalarValue {
|
||||
fn with_custom_scalar_value() -> WithCustomScalarValue {
|
||||
WithCustomScalarValue(0)
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_type_info_query<F>(doc: &str, f: F)
|
||||
where
|
||||
F: Fn(&Object<DefaultScalarValue>) -> (),
|
||||
{
|
||||
let schema = RootNode::new(
|
||||
Root {},
|
||||
EmptyMutation::<()>::new(),
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
let (result, errs) = execute(doc, None, &schema, &graphql_vars! {}, &())
|
||||
.await
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
println!("Result: {:#?}", result);
|
||||
|
||||
let type_info = result
|
||||
.as_object_value()
|
||||
.expect("Result is not an object")
|
||||
.get_field_value("__type")
|
||||
.expect("__type field missing")
|
||||
.as_object_value()
|
||||
.expect("__type field not an object value");
|
||||
|
||||
f(type_info);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_in_resolve_return_type() {
|
||||
struct ResolvePath(i32);
|
||||
|
||||
#[graphql_scalar]
|
||||
impl GraphQLScalar for ResolvePath {
|
||||
fn resolve(&self) -> self::Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ResolvePath, String> {
|
||||
v.as_int_value()
|
||||
.map(ResolvePath)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn default_name_introspection() {
|
||||
let doc = r#"
|
||||
{
|
||||
__type(name: "DefaultName") {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("DefaultName")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("description"),
|
||||
Some(&graphql_value!(null)),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn other_order_introspection() {
|
||||
let doc = r#"
|
||||
{
|
||||
__type(name: "OtherOrder") {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("OtherOrder")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("description"),
|
||||
Some(&graphql_value!(null)),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn named_introspection() {
|
||||
let doc = r#"
|
||||
{
|
||||
__type(name: "ANamedScalar") {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("ANamedScalar")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("description"),
|
||||
Some(&graphql_value!(null)),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn scalar_description_introspection() {
|
||||
let doc = r#"
|
||||
{
|
||||
__type(name: "ScalarDescription") {
|
||||
name
|
||||
description
|
||||
specifiedByUrl
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("ScalarDescription")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("description"),
|
||||
Some(&graphql_value!(
|
||||
"A sample scalar, represented as an integer",
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("specifiedByUrl"),
|
||||
Some(&graphql_value!(null)),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn scalar_specified_by_url_introspection() {
|
||||
let doc = r#"{
|
||||
__type(name: "ScalarSpecifiedByUrl") {
|
||||
name
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("ScalarSpecifiedByUrl")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("specifiedByUrl"),
|
||||
Some(&graphql_value!("https://tools.ietf.org/html/rfc4122")),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generated_scalar_introspection() {
|
||||
let doc = r#"
|
||||
{
|
||||
__type(name: "Generated") {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("Generated")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("description"),
|
||||
Some(&graphql_value!(null)),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_with_custom_scalar_value() {
|
||||
const DOC: &str = r#"{ withCustomScalarValue }"#;
|
||||
|
||||
let schema = RootNode::<_, _, _, MyScalarValue>::new_with_scalar_value(
|
||||
RootWithCustomScalarValue,
|
||||
EmptyMutation::<()>::new(),
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"withCustomScalarValue": 0}), vec![])),
|
||||
);
|
||||
}
|
|
@ -2,34 +2,11 @@
|
|||
|
||||
use juniper::{
|
||||
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
||||
EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject,
|
||||
GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue,
|
||||
Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion,
|
||||
IntoFieldError, ScalarValue,
|
||||
};
|
||||
|
||||
fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
|
||||
where
|
||||
Q: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
|
||||
{
|
||||
RootNode::new(
|
||||
query_root,
|
||||
EmptyMutation::<C>::new(),
|
||||
EmptySubscription::<C>::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn schema_with_scalar<'q, S, C, Q>(
|
||||
query_root: Q,
|
||||
) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>, S>
|
||||
where
|
||||
Q: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
|
||||
S: ScalarValue + 'q,
|
||||
{
|
||||
RootNode::new_with_scalar_value(
|
||||
query_root,
|
||||
EmptyMutation::<C>::new(),
|
||||
EmptySubscription::<C>::new(),
|
||||
)
|
||||
}
|
||||
use crate::util::{schema, schema_with_scalar};
|
||||
|
||||
mod no_implers {
|
||||
use super::*;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
mod derive_enum;
|
||||
mod derive_input_object;
|
||||
mod derive_object_with_raw_idents;
|
||||
mod derive_scalar;
|
||||
mod impl_scalar;
|
||||
mod interface_attr;
|
||||
mod object_attr;
|
||||
mod object_derive;
|
||||
mod scalar_value_transparent;
|
||||
mod scalar_attr_derive_input;
|
||||
mod scalar_attr_type_alias;
|
||||
mod subscription_attr;
|
||||
mod union_attr;
|
||||
mod union_derive;
|
||||
|
|
|
@ -0,0 +1,972 @@
|
|||
use std::fmt;
|
||||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use juniper::{
|
||||
execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, InputValue,
|
||||
ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
custom_scalar::MyScalarValue,
|
||||
util::{schema, schema_with_scalar},
|
||||
};
|
||||
|
||||
mod trivial {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(t: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(t)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod all_custom_resolvers {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
)]
|
||||
#[graphql_scalar(parse_token_with = parse_token)]
|
||||
struct Counter(i32);
|
||||
|
||||
fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(Counter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod explicit_name {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(name = "Counter")]
|
||||
struct CustomCounter(i32);
|
||||
|
||||
impl CustomCounter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: CustomCounter) -> CustomCounter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod delegated_parse_token {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(parse_token(i32))]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod multiple_delegated_parse_token {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(parse_token(String, i32))]
|
||||
enum StringOrInt {
|
||||
String(String),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
impl StringOrInt {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
match self {
|
||||
Self::String(str) => Value::scalar(str.to_owned()),
|
||||
Self::Int(i) => Value::scalar(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_string_value()
|
||||
.map(|s| Self::String(s.to_owned()))
|
||||
.or_else(|| v.as_int_value().map(|i| Self::Int(i)))
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn string_or_int(value: StringOrInt) -> StringOrInt {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_string() {
|
||||
const DOC: &str = r#"{ stringOrInt(value: "test") }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"stringOrInt": "test"}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_int() {
|
||||
const DOC: &str = r#"{ stringOrInt(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"stringOrInt": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod where_attribute {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
parse_token(String),
|
||||
where(Tz: From<Utc>, Tz::Offset: fmt::Display),
|
||||
specified_by_url = "https://tools.ietf.org/html/rfc3339",
|
||||
)]
|
||||
struct CustomDateTime<Tz: TimeZone>(DateTime<Tz>);
|
||||
|
||||
fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
Value::scalar(v.0.to_rfc3339())
|
||||
}
|
||||
|
||||
fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
DateTime::parse_from_rfc3339(s)
|
||||
.map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc))))
|
||||
.map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_custom_date_time() {
|
||||
const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_specified_by_url() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "CustomDateTime") {
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod with_self {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(with = Self)]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod with_module {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(
|
||||
with = custom_date_time,
|
||||
parse_token(String),
|
||||
where(Tz: From<Utc>, Tz::Offset: fmt::Display),
|
||||
specified_by_url = "https://tools.ietf.org/html/rfc3339",
|
||||
)]
|
||||
struct CustomDateTime<Tz: TimeZone>(DateTime<Tz>);
|
||||
|
||||
mod custom_date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
Value::scalar(v.0.to_rfc3339())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
DateTime::parse_from_rfc3339(s)
|
||||
.map(|dt| CustomDateTime(dt.with_timezone(&Tz::from(Utc))))
|
||||
.map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_custom_date_time() {
|
||||
const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_specified_by_url() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "CustomDateTime") {
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod description_from_doc_comment {
|
||||
use super::*;
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(parse_token(i32))]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod description_from_attribute {
|
||||
use super::*;
|
||||
|
||||
/// Doc comment
|
||||
#[graphql_scalar(description = "Description from attribute", parse_token(i32))]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description from attribute"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod custom_scalar {
|
||||
use super::*;
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(scalar = MyScalarValue, parse_token(i32))]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object(scalar = MyScalarValue)]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod generic_scalar {
|
||||
use super::*;
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(scalar = S: ScalarValue, parse_token(i32))]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod bounded_generic_scalar {
|
||||
use super::*;
|
||||
|
||||
#[graphql_scalar(scalar = S: ScalarValue + Clone, parse_token(i32))]
|
||||
struct Counter(i32);
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,996 @@
|
|||
use std::fmt;
|
||||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use juniper::{
|
||||
execute, graphql_object, graphql_scalar, graphql_value, graphql_vars, InputValue,
|
||||
ParseScalarResult, ParseScalarValue, ScalarToken, ScalarValue, Value,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
custom_scalar::MyScalarValue,
|
||||
util::{schema, schema_with_scalar},
|
||||
};
|
||||
|
||||
mod all_custom_resolvers {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
#[graphql_scalar(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
)]
|
||||
#[graphql_scalar(
|
||||
parse_token_with = parse_token,
|
||||
)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod explicit_name {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
#[graphql_scalar(
|
||||
name = "Counter",
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
parse_token_with = parse_token,
|
||||
)]
|
||||
type CounterScalar = CustomCounter;
|
||||
|
||||
fn to_output<S: ScalarValue>(v: &CounterScalar) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<CounterScalar, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: CounterScalar) -> CounterScalar {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_custom_counter() {
|
||||
for name in ["CustomCounter", "CustomScalar"] {
|
||||
let doc = format!(
|
||||
r#"{{
|
||||
__type(name: "{}") {{
|
||||
kind
|
||||
}}
|
||||
}}"#,
|
||||
name,
|
||||
);
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(&doc, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!(null), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod delegated_parse_token {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
#[graphql_scalar(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
parse_token(i32),
|
||||
)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod multiple_delegated_parse_token {
|
||||
use super::*;
|
||||
|
||||
enum StringOrIntScalar {
|
||||
String(String),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
#[graphql_scalar(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
parse_token(String, i32),
|
||||
)]
|
||||
type StringOrInt = StringOrIntScalar;
|
||||
|
||||
fn to_output<S: ScalarValue>(v: &StringOrInt) -> Value<S> {
|
||||
match v {
|
||||
StringOrInt::String(str) => Value::scalar(str.to_owned()),
|
||||
StringOrInt::Int(i) => Value::scalar(*i),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
|
||||
v.as_string_value()
|
||||
.map(|s| StringOrInt::String(s.to_owned()))
|
||||
.or_else(|| v.as_int_value().map(|i| StringOrInt::Int(i)))
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn string_or_int(value: StringOrInt) -> StringOrInt {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_string() {
|
||||
const DOC: &str = r#"{ stringOrInt(value: "test") }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"stringOrInt": "test"}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_int() {
|
||||
const DOC: &str = r#"{ stringOrInt(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"stringOrInt": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod where_attribute {
|
||||
use super::*;
|
||||
|
||||
struct CustomDateTimeScalar<Tz: TimeZone>(DateTime<Tz>);
|
||||
|
||||
#[graphql_scalar(
|
||||
to_output_with = to_output,
|
||||
from_input_with = from_input,
|
||||
parse_token(String),
|
||||
where(Tz: From<Utc> + TimeZone, Tz::Offset: fmt::Display),
|
||||
specified_by_url = "https://tools.ietf.org/html/rfc3339",
|
||||
)]
|
||||
type CustomDateTime<Tz> = CustomDateTimeScalar<Tz>;
|
||||
|
||||
fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
Value::scalar(v.0.to_rfc3339())
|
||||
}
|
||||
|
||||
fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
DateTime::parse_from_rfc3339(s)
|
||||
.map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc))))
|
||||
.map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_custom_date_time() {
|
||||
const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_specified_by_url() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "CustomDateTime") {
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod with_self {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
#[graphql_scalar(with = Self)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
impl Counter {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
|
||||
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_no_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"description": null}}), vec![])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod with_module {
|
||||
use super::*;
|
||||
|
||||
struct CustomDateTimeScalar<Tz: TimeZone>(DateTime<Tz>);
|
||||
|
||||
#[graphql_scalar(
|
||||
with = custom_date_time,
|
||||
parse_token(String),
|
||||
where(Tz: From<Utc> + TimeZone, Tz::Offset: fmt::Display),
|
||||
specified_by_url = "https://tools.ietf.org/html/rfc3339",
|
||||
)]
|
||||
type CustomDateTime<Tz> = CustomDateTimeScalar<Tz>;
|
||||
|
||||
mod custom_date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S, Tz>(v: &CustomDateTime<Tz>) -> Value<S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
Value::scalar(v.0.to_rfc3339())
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S, Tz>(v: &InputValue<S>) -> Result<CustomDateTime<Tz>, String>
|
||||
where
|
||||
S: ScalarValue,
|
||||
Tz: From<Utc> + TimeZone,
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
DateTime::parse_from_rfc3339(s)
|
||||
.map(|dt| CustomDateTimeScalar(dt.with_timezone(&Tz::from(Utc))))
|
||||
.map_err(|e| format!("Failed to parse `CustomDateTime`: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn date_time(value: CustomDateTime<Utc>) -> CustomDateTime<Utc> {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_custom_date_time() {
|
||||
const DOC: &str = r#"{ dateTime(value: "1996-12-19T16:39:57-08:00") }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"dateTime": "1996-12-20T00:39:57+00:00"}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_specified_by_url() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "CustomDateTime") {
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod description_from_doc_comment {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(with = counter, parse_token(i32))]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
mod counter {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod description_from_attribute {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
/// Doc comment
|
||||
#[graphql_scalar(
|
||||
description = "Description from attribute",
|
||||
with = counter,
|
||||
parse_token(i32),
|
||||
)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
mod counter {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description from attribute"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod custom_scalar {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(
|
||||
scalar = MyScalarValue,
|
||||
with = counter,
|
||||
parse_token(i32),
|
||||
)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
mod counter {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object(scalar = MyScalarValue)]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod generic_scalar {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(
|
||||
scalar = S: ScalarValue,
|
||||
with = counter,
|
||||
parse_token(i32),
|
||||
)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
mod counter {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod bounded_generic_scalar {
|
||||
use super::*;
|
||||
|
||||
struct CustomCounter(i32);
|
||||
|
||||
/// Description
|
||||
#[graphql_scalar(
|
||||
scalar = S: ScalarValue + Clone,
|
||||
with = counter,
|
||||
parse_token(i32),
|
||||
)]
|
||||
type Counter = CustomCounter;
|
||||
|
||||
mod counter {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Counter) -> Value<S> {
|
||||
Value::scalar(v.0)
|
||||
}
|
||||
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Counter, String> {
|
||||
v.as_int_value()
|
||||
.map(CustomCounter)
|
||||
.ok_or_else(|| format!("Expected `Counter`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object]
|
||||
impl QueryRoot {
|
||||
fn counter(value: Counter) -> Counter {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn is_graphql_scalar() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
kind
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"__type": {"kind": "SCALAR"}}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_counter() {
|
||||
const DOC: &str = r#"{ counter(value: 0) }"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((graphql_value!({"counter": 0}), vec![])),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn has_description() {
|
||||
const DOC: &str = r#"{
|
||||
__type(name: "Counter") {
|
||||
description
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = schema_with_scalar::<MyScalarValue, _, _>(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"description": "Description"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
use fnv::FnvHashMap;
|
||||
use juniper::{
|
||||
graphql_input_value, graphql_object, DefaultScalarValue, FromInputValue, GraphQLObject,
|
||||
GraphQLScalarValue, GraphQLType, InputValue, Registry, ToInputValue,
|
||||
};
|
||||
|
||||
#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
|
||||
#[graphql(transparent)]
|
||||
struct UserId(String);
|
||||
|
||||
#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
|
||||
#[graphql(transparent, name = "MyUserId", description = "custom description...")]
|
||||
struct CustomUserId(String);
|
||||
|
||||
/// The doc comment...
|
||||
#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
|
||||
#[graphql(transparent, specified_by_url = "https://tools.ietf.org/html/rfc4122")]
|
||||
struct IdWithDocComment(i32);
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct User {
|
||||
id: UserId,
|
||||
id_custom: CustomUserId,
|
||||
}
|
||||
|
||||
struct User2;
|
||||
|
||||
#[graphql_object]
|
||||
impl User2 {
|
||||
fn id(&self) -> UserId {
|
||||
UserId("id".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scalar_value_simple() {
|
||||
assert_eq!(
|
||||
<UserId as GraphQLType<DefaultScalarValue>>::name(&()),
|
||||
Some("UserId")
|
||||
);
|
||||
|
||||
let mut registry: Registry = Registry::new(FnvHashMap::default());
|
||||
let meta = UserId::meta(&(), &mut registry);
|
||||
assert_eq!(meta.name(), Some("UserId"));
|
||||
assert_eq!(meta.description(), None);
|
||||
|
||||
let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
|
||||
let output: UserId = FromInputValue::from_input_value(&input).unwrap();
|
||||
assert_eq!(output, UserId("userId1".into()),);
|
||||
|
||||
let id = UserId("111".into());
|
||||
let output = ToInputValue::<DefaultScalarValue>::to_input_value(&id);
|
||||
assert_eq!(output, graphql_input_value!("111"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scalar_value_custom() {
|
||||
assert_eq!(
|
||||
<CustomUserId as GraphQLType<DefaultScalarValue>>::name(&()),
|
||||
Some("MyUserId")
|
||||
);
|
||||
|
||||
let mut registry: Registry = Registry::new(FnvHashMap::default());
|
||||
let meta = CustomUserId::meta(&(), &mut registry);
|
||||
assert_eq!(meta.name(), Some("MyUserId"));
|
||||
assert_eq!(meta.description(), Some("custom description..."));
|
||||
assert_eq!(meta.specified_by_url(), None);
|
||||
|
||||
let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
|
||||
let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap();
|
||||
assert_eq!(output, CustomUserId("userId1".into()),);
|
||||
|
||||
let id = CustomUserId("111".into());
|
||||
let output = ToInputValue::<DefaultScalarValue>::to_input_value(&id);
|
||||
assert_eq!(output, graphql_input_value!("111"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scalar_value_doc_comment() {
|
||||
let mut registry: Registry = Registry::new(FnvHashMap::default());
|
||||
let meta = IdWithDocComment::meta(&(), &mut registry);
|
||||
assert_eq!(meta.description(), Some("The doc comment..."));
|
||||
assert_eq!(
|
||||
meta.specified_by_url(),
|
||||
Some("https://tools.ietf.org/html/rfc4122"),
|
||||
);
|
||||
}
|
|
@ -6,11 +6,11 @@ use juniper::{
|
|||
graphql_vars,
|
||||
parser::{ParseError, ScalarToken, Token},
|
||||
serde::{de, Deserialize, Deserializer, Serialize},
|
||||
EmptyMutation, FieldResult, GraphQLScalarValue, InputValue, Object, ParseScalarResult,
|
||||
RootNode, ScalarValue, Value, Variables,
|
||||
EmptyMutation, FieldResult, InputValue, Object, ParseScalarResult, RootNode, ScalarValue,
|
||||
Value, Variables,
|
||||
};
|
||||
|
||||
#[derive(GraphQLScalarValue, Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum MyScalarValue {
|
||||
Int(i32),
|
||||
|
@ -20,6 +20,149 @@ pub(crate) enum MyScalarValue {
|
|||
Boolean(bool),
|
||||
}
|
||||
|
||||
// TODO: replace all underlying `From` impls with `GraphQLScalarValue` macro.
|
||||
impl From<i32> for MyScalarValue {
|
||||
fn from(v: i32) -> Self {
|
||||
Self::Int(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MyScalarValue> for Option<i32> {
|
||||
fn from(v: MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Int(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MyScalarValue> for Option<&'a i32> {
|
||||
fn from(v: &'a MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Int(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for MyScalarValue {
|
||||
fn from(v: i64) -> Self {
|
||||
Self::Long(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MyScalarValue> for Option<i64> {
|
||||
fn from(v: MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Long(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MyScalarValue> for Option<&'a i64> {
|
||||
fn from(v: &'a MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Long(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for MyScalarValue {
|
||||
fn from(v: f64) -> Self {
|
||||
Self::Float(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MyScalarValue> for Option<f64> {
|
||||
fn from(v: MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Float(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MyScalarValue> for Option<&'a f64> {
|
||||
fn from(v: &'a MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Float(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for MyScalarValue {
|
||||
fn from(v: String) -> Self {
|
||||
Self::String(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MyScalarValue> for Option<String> {
|
||||
fn from(v: MyScalarValue) -> Self {
|
||||
if let MyScalarValue::String(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MyScalarValue> for Option<&'a String> {
|
||||
fn from(v: &'a MyScalarValue) -> Self {
|
||||
if let MyScalarValue::String(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for MyScalarValue {
|
||||
fn from(v: bool) -> Self {
|
||||
Self::Boolean(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MyScalarValue> for Option<bool> {
|
||||
fn from(v: MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Boolean(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MyScalarValue> for Option<&'a bool> {
|
||||
fn from(v: &'a MyScalarValue) -> Self {
|
||||
if let MyScalarValue::Boolean(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MyScalarValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Int(v) => v.fmt(f),
|
||||
Self::Long(v) => v.fmt(f),
|
||||
Self::Float(v) => v.fmt(f),
|
||||
Self::String(v) => v.fmt(f),
|
||||
Self::Boolean(v) => v.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScalarValue for MyScalarValue {
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
match self {
|
||||
|
@ -132,19 +275,23 @@ impl<'de> Deserialize<'de> for MyScalarValue {
|
|||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(name = "Long")]
|
||||
impl GraphQLScalar for i64 {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(*self)
|
||||
#[graphql_scalar(with = long, scalar = MyScalarValue)]
|
||||
type Long = i64;
|
||||
|
||||
mod long {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output(v: &Long) -> Value<MyScalarValue> {
|
||||
Value::scalar(*v)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<i64, String> {
|
||||
pub(super) fn from_input(v: &InputValue<MyScalarValue>) -> Result<Long, String> {
|
||||
v.as_scalar_value::<i64>()
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, MyScalarValue> {
|
||||
pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, MyScalarValue> {
|
||||
if let ScalarToken::Int(v) = value {
|
||||
v.parse()
|
||||
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
|
|
|
@ -37,7 +37,37 @@ mod pre_parse;
|
|||
/// Common utilities used across tests.
|
||||
pub(crate) mod util {
|
||||
use futures::StreamExt as _;
|
||||
use juniper::{graphql_value, ExecutionError, GraphQLError, ScalarValue, Value, ValuesStream};
|
||||
use juniper::{
|
||||
graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, ExecutionError,
|
||||
GraphQLError, GraphQLType, RootNode, ScalarValue, Value, ValuesStream,
|
||||
};
|
||||
|
||||
pub(crate) fn schema<'q, C, Q>(
|
||||
query_root: Q,
|
||||
) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
|
||||
where
|
||||
Q: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
|
||||
{
|
||||
RootNode::new(
|
||||
query_root,
|
||||
EmptyMutation::<C>::new(),
|
||||
EmptySubscription::<C>::new(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn schema_with_scalar<'q, S, C, Q>(
|
||||
query_root: Q,
|
||||
) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>, S>
|
||||
where
|
||||
Q: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
|
||||
S: ScalarValue + 'q,
|
||||
{
|
||||
RootNode::new_with_scalar_value(
|
||||
query_root,
|
||||
EmptyMutation::<C>::new(),
|
||||
EmptySubscription::<C>::new(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Extracts a single next value from the result returned by
|
||||
/// [`juniper::resolve_into_stream()`] and transforms it into a regular
|
||||
|
|
|
@ -21,6 +21,13 @@
|
|||
- Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields).
|
||||
- Forbid default impls on non-ignored trait methods.
|
||||
- Support coercion of additional nullable arguments and return sub-typing on implementer.
|
||||
- Redesign `#[derive(GraphQLScalar)]` macro: ([#1017](https://github.com/graphql-rust/juniper/pull/1017))
|
||||
- Support generic scalars.
|
||||
- Support structs with single named field.
|
||||
- Support overriding resolvers.
|
||||
- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014))
|
||||
- Mirror `#[derive(GraphQLScalar)]` macro.
|
||||
- Support usage on type aliases in case `#[derive(GraphQLScalar)]` isn't applicable because of [orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules).
|
||||
|
||||
## Features
|
||||
|
||||
|
|
|
@ -1252,7 +1252,7 @@ impl<'r, S: 'r> Registry<'r, S> {
|
|||
/// Creates a [`ScalarMeta`] type.
|
||||
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
|
||||
where
|
||||
T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S> + 'r,
|
||||
T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S>,
|
||||
T::Error: IntoFieldError<S>,
|
||||
S: ScalarValue,
|
||||
{
|
||||
|
|
|
@ -9,8 +9,7 @@ use crate::{
|
|||
graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::{ParseScalarResult, ParseScalarValue, Value},
|
||||
GraphQLEnum,
|
||||
GraphQLEnum, InputValue, ScalarValue, Value,
|
||||
};
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
|
@ -20,23 +19,20 @@ enum Sample {
|
|||
Two,
|
||||
}
|
||||
|
||||
// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
|
||||
#[graphql_scalar(name = "SampleScalar", parse_token(i32))]
|
||||
struct Scalar(i32);
|
||||
|
||||
#[graphql_scalar(name = "SampleScalar")]
|
||||
impl<S: ScalarValue> GraphQLScalar for Scalar {
|
||||
fn resolve(&self) -> Value {
|
||||
impl Scalar {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Scalar, String> {
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_int_value()
|
||||
.map(Scalar)
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<i32 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A sample interface
|
||||
|
|
|
@ -5,30 +5,27 @@ use crate::{
|
|||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
validation::RuleError,
|
||||
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue},
|
||||
value::{DefaultScalarValue, Object},
|
||||
GraphQLError::ValidationError,
|
||||
GraphQLInputObject,
|
||||
GraphQLInputObject, InputValue, ScalarValue, Value,
|
||||
};
|
||||
|
||||
// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
|
||||
#[derive(Debug)]
|
||||
#[graphql_scalar(parse_token(String))]
|
||||
struct TestComplexScalar;
|
||||
|
||||
#[graphql_scalar]
|
||||
impl<S: ScalarValue> GraphQLScalar for TestComplexScalar {
|
||||
fn resolve(&self) -> Value {
|
||||
impl TestComplexScalar {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
graphql_value!("SerializedValue")
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<TestComplexScalar, String> {
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_string_value()
|
||||
.filter(|s| *s == "SerializedValue")
|
||||
.map(|_| TestComplexScalar)
|
||||
.map(|_| Self)
|
||||
.ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
|
|
|
@ -1,66 +1,46 @@
|
|||
//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
|
||||
|
||||
use bson::{oid::ObjectId, DateTime as UtcDateTime};
|
||||
use chrono::prelude::*;
|
||||
|
||||
use crate::{
|
||||
graphql_scalar,
|
||||
parser::{ParseError, ScalarToken, Token},
|
||||
value::ParseScalarResult,
|
||||
Value,
|
||||
};
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
#[graphql_scalar(description = "ObjectId")]
|
||||
impl<S> GraphQLScalar for ObjectId
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.to_hex())
|
||||
#[graphql_scalar(with = object_id, parse_token(String))]
|
||||
type ObjectId = bson::oid::ObjectId;
|
||||
|
||||
mod object_id {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &ObjectId) -> Value<S> {
|
||||
Value::scalar(v.to_hex())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ObjectId, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<ObjectId, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
Self::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e))
|
||||
ObjectId::parse_str(s).map_err(|e| format!("Failed to parse `ObjectId`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(val) = value {
|
||||
Ok(S::from(val.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(description = "UtcDateTime")]
|
||||
impl<S> GraphQLScalar for UtcDateTime
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar((*self).to_chrono().to_rfc3339())
|
||||
#[graphql_scalar(with = utc_date_time, parse_token(String))]
|
||||
type UtcDateTime = bson::DateTime;
|
||||
|
||||
mod utc_date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &UtcDateTime) -> Value<S> {
|
||||
Value::scalar((*v).to_chrono().to_rfc3339())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<UtcDateTime, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcDateTime, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
s.parse::<DateTime<Utc>>()
|
||||
.map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e))
|
||||
})
|
||||
.map(Self::from_chrono)
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(val) = value {
|
||||
Ok(S::from(val.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
.map(UtcDateTime::from_chrono)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +52,7 @@ mod test {
|
|||
use crate::{graphql_input_value, FromInputValue, InputValue};
|
||||
|
||||
#[test]
|
||||
fn objectid_from_input_value() {
|
||||
fn objectid_from_input() {
|
||||
let raw = "53e37d08776f724e42000000";
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
|
||||
|
@ -83,7 +63,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn utcdatetime_from_input_value() {
|
||||
fn utcdatetime_from_input() {
|
||||
let raw = "2020-03-23T17:38:32.446+00:00";
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
|
||||
|
|
|
@ -16,66 +16,48 @@
|
|||
|
||||
*/
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
use chrono::prelude::*;
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
use crate::{
|
||||
parser::{ParseError, ScalarToken, Token},
|
||||
value::{ParseScalarResult, ParseScalarValue},
|
||||
Value,
|
||||
};
|
||||
#[graphql_scalar(with = date_time_fixed_offset, parse_token(String))]
|
||||
type DateTimeFixedOffset = chrono::DateTime<chrono::FixedOffset>;
|
||||
|
||||
#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")]
|
||||
impl<S> GraphQLScalar for DateTime<FixedOffset>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.to_rfc3339())
|
||||
mod date_time_fixed_offset {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &DateTimeFixedOffset) -> Value<S> {
|
||||
Value::scalar(v.to_rfc3339())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<DateTime<FixedOffset>, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(
|
||||
v: &InputValue<S>,
|
||||
) -> Result<DateTimeFixedOffset, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
DateTime::parse_from_rfc3339(s)
|
||||
DateTimeFixedOffset::parse_from_rfc3339(s)
|
||||
.map_err(|e| format!("Failed to parse `DateTimeFixedOffset`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(value) = value {
|
||||
Ok(S::from(value.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_scalar(name = "DateTimeUtc", description = "DateTime")]
|
||||
impl<S> GraphQLScalar for DateTime<Utc>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.to_rfc3339())
|
||||
#[graphql_scalar(with = date_time_utc, parse_token(String))]
|
||||
type DateTimeUtc = chrono::DateTime<chrono::Utc>;
|
||||
|
||||
mod date_time_utc {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &DateTimeUtc) -> Value<S> {
|
||||
Value::scalar(v.to_rfc3339())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<DateTime<Utc>, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTimeUtc, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
s.parse::<DateTime<Utc>>()
|
||||
s.parse::<DateTimeUtc>()
|
||||
.map_err(|e| format!("Failed to parse `DateTimeUtc`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(value) = value {
|
||||
Ok(S::from(value.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use `Date` as the docs say:
|
||||
|
@ -83,16 +65,17 @@ where
|
|||
// inherent lack of precision required for the time zone resolution.
|
||||
// For serialization and deserialization uses, it is best to use
|
||||
// `NaiveDate` instead."
|
||||
#[crate::graphql_scalar(description = "NaiveDate")]
|
||||
impl<S> GraphQLScalar for NaiveDate
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.format("%Y-%m-%d").to_string())
|
||||
#[graphql_scalar(with = naive_date, parse_token(String))]
|
||||
type NaiveDate = chrono::NaiveDate;
|
||||
|
||||
mod naive_date {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &NaiveDate) -> Value<S> {
|
||||
Value::scalar(v.format("%Y-%m-%d").to_string())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<NaiveDate, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<NaiveDate, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
|
@ -100,27 +83,21 @@ where
|
|||
.map_err(|e| format!("Failed to parse `NaiveDate`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(value) = value {
|
||||
Ok(S::from(value.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "scalar-naivetime")]
|
||||
#[crate::graphql_scalar(description = "NaiveTime")]
|
||||
impl<S> GraphQLScalar for NaiveTime
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.format("%H:%M:%S").to_string())
|
||||
#[graphql_scalar(with = naive_time, parse_token(String))]
|
||||
type NaiveTime = chrono::NaiveTime;
|
||||
|
||||
#[cfg(feature = "scalar-naivetime")]
|
||||
mod naive_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &NaiveTime) -> Value<S> {
|
||||
Value::scalar(v.format("%H:%M:%S").to_string())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<NaiveTime, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<NaiveTime, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
|
@ -128,28 +105,21 @@ where
|
|||
.map_err(|e| format!("Failed to parse `NaiveTime`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(value) = value {
|
||||
Ok(S::from(value.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond
|
||||
// datetimes. Values will be truncated to microsecond resolution.
|
||||
#[crate::graphql_scalar(description = "NaiveDateTime")]
|
||||
impl<S> GraphQLScalar for NaiveDateTime
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.timestamp() as f64)
|
||||
#[graphql_scalar(with = naive_date_time, parse_token(f64))]
|
||||
type NaiveDateTime = chrono::NaiveDateTime;
|
||||
|
||||
mod naive_date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &NaiveDateTime) -> Value<S> {
|
||||
Value::scalar(v.timestamp() as f64)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<NaiveDateTime, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<NaiveDateTime, String> {
|
||||
v.as_float_value()
|
||||
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
|
||||
.and_then(|f| {
|
||||
|
@ -158,10 +128,6 @@ where
|
|||
.ok_or_else(|| format!("Out-of-range number of seconds: {}", secs))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<f64 as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -180,17 +146,17 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_fixedoffset_from_input_value() {
|
||||
fn datetime_fixedoffset_from_input() {
|
||||
datetime_fixedoffset_test("2014-11-28T21:00:09+09:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_fixedoffset_from_input_value_with_z_timezone() {
|
||||
fn datetime_fixedoffset_from_input_with_z_timezone() {
|
||||
datetime_fixedoffset_test("2014-11-28T21:00:09Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_fixedoffset_from_input_value_with_fractional_seconds() {
|
||||
fn datetime_fixedoffset_from_input_with_fractional_seconds() {
|
||||
datetime_fixedoffset_test("2014-11-28T21:00:09.05+09:00");
|
||||
}
|
||||
|
||||
|
@ -206,22 +172,22 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_utc_from_input_value() {
|
||||
fn datetime_utc_from_input() {
|
||||
datetime_utc_test("2014-11-28T21:00:09+09:00")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_utc_from_input_value_with_z_timezone() {
|
||||
fn datetime_utc_from_input_with_z_timezone() {
|
||||
datetime_utc_test("2014-11-28T21:00:09Z")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datetime_utc_from_input_value_with_fractional_seconds() {
|
||||
fn datetime_utc_from_input_with_fractional_seconds() {
|
||||
datetime_utc_test("2014-11-28T21:00:09.005+09:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn naivedate_from_input_value() {
|
||||
fn naivedate_from_input() {
|
||||
let input: InputValue = graphql_input_value!("1996-12-19");
|
||||
let y = 1996;
|
||||
let m = 12;
|
||||
|
@ -239,7 +205,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
#[cfg(feature = "scalar-naivetime")]
|
||||
fn naivetime_from_input_value() {
|
||||
fn naivetime_from_input() {
|
||||
let input: InputValue = graphql_input_value!("21:12:19");
|
||||
let [h, m, s] = [21, 12, 19];
|
||||
let parsed: NaiveTime = FromInputValue::from_input_value(&input).unwrap();
|
||||
|
@ -251,7 +217,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn naivedatetime_from_input_value() {
|
||||
fn naivedatetime_from_input() {
|
||||
let raw = 1_000_000_000_f64;
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
|
||||
|
|
|
@ -3,25 +3,19 @@
|
|||
//! [`Tz`]: chrono_tz::Tz
|
||||
//! [1]: http://www.iana.org/time-zones
|
||||
|
||||
use chrono_tz::Tz;
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
use crate::{
|
||||
graphql_scalar,
|
||||
parser::{ParseError, ScalarToken, Token},
|
||||
value::ParseScalarResult,
|
||||
Value,
|
||||
};
|
||||
#[graphql_scalar(with = tz, parse_token(String))]
|
||||
type Tz = chrono_tz::Tz;
|
||||
|
||||
#[graphql_scalar(name = "Tz", description = "Timezone")]
|
||||
impl<S> GraphQLScalar for Tz
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.name().to_owned())
|
||||
mod tz {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Tz) -> Value<S> {
|
||||
Value::scalar(v.name().to_owned())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Tz, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Tz, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
|
@ -29,32 +23,22 @@ where
|
|||
.map_err(|e| format!("Failed to parse `Tz`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(val: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(s) = val {
|
||||
Ok(S::from(s.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(val)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod from_input_value {
|
||||
use std::ops::Deref;
|
||||
|
||||
mod from_input {
|
||||
use chrono_tz::Tz;
|
||||
|
||||
use crate::{graphql_input_value, FromInputValue, InputValue};
|
||||
use crate::{graphql_input_value, FromInputValue, InputValue, IntoFieldError};
|
||||
|
||||
fn tz_input_test(raw: &'static str, expected: Result<Tz, &str>) {
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
let parsed = FromInputValue::from_input_value(&input);
|
||||
|
||||
assert_eq!(
|
||||
parsed.as_ref().map_err(Deref::deref),
|
||||
expected.as_ref().map_err(Deref::deref),
|
||||
parsed.as_ref(),
|
||||
expected.map_err(IntoFieldError::into_field_error).as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,59 +26,45 @@ use time::{
|
|||
macros::format_description,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
graphql_scalar,
|
||||
parser::{ParseError, ScalarToken, Token},
|
||||
value::ParseScalarResult,
|
||||
Value,
|
||||
};
|
||||
|
||||
pub use time::{
|
||||
Date, OffsetDateTime as DateTime, PrimitiveDateTime as LocalDateTime, Time as LocalTime,
|
||||
UtcOffset,
|
||||
};
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
/// Format of a [`Date` scalar][1].
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]");
|
||||
|
||||
/// Date in the proleptic Gregorian calendar (without time zone).
|
||||
///
|
||||
/// Represents a description of the date (as used for birthdays, for example).
|
||||
/// It cannot represent an instant on the time-line.
|
||||
///
|
||||
/// [`Date` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`time::Date`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||
/// [2]: https://docs.rs/time/*/time/struct.Date.html
|
||||
#[graphql_scalar(
|
||||
description = "Date in the proleptic Gregorian calendar (without time \
|
||||
zone).\
|
||||
\n\n\
|
||||
Represents a description of the date (as used for birthdays,
|
||||
for example). It cannot represent an instant on the \
|
||||
time-line.\
|
||||
\n\n\
|
||||
[`Date` scalar][1] compliant.\
|
||||
\n\n\
|
||||
See also [`time::Date`][2] for details.\
|
||||
\n\n\
|
||||
[1]: https://graphql-scalars.dev/docs/scalars/date\n\
|
||||
[2]: https://docs.rs/time/*/time/struct.Date.html",
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date"
|
||||
with = date,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date",
|
||||
)]
|
||||
impl<S: ScalarValue> GraphQLScalar for Date {
|
||||
fn resolve(&self) -> Value {
|
||||
pub type Date = time::Date;
|
||||
|
||||
mod date {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Date) -> Value<S> {
|
||||
Value::scalar(
|
||||
self.format(DATE_FORMAT)
|
||||
v.format(DATE_FORMAT)
|
||||
.unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)),
|
||||
)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Self, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Date, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(s) = value {
|
||||
Ok(S::from(s.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
.and_then(|s| Date::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,137 +85,120 @@ const LOCAL_TIME_FORMAT_NO_MILLIS: &[FormatItem<'_>] =
|
|||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour]:[minute]");
|
||||
|
||||
#[graphql_scalar(
|
||||
description = "Clock time within a given date (without time zone) in \
|
||||
`HH:mm[:ss[.SSS]]` format.\
|
||||
\n\n\
|
||||
All minutes are assumed to have exactly 60 seconds; no \
|
||||
attempt is made to handle leap seconds (either positive or \
|
||||
negative).\
|
||||
\n\n\
|
||||
[`LocalTime` scalar][1] compliant.\
|
||||
\n\n\
|
||||
See also [`time::Time`][2] for details.\
|
||||
\n\n\
|
||||
[1]: https://graphql-scalars.dev/docs/scalars/local-time\n\
|
||||
[2]: https://docs.rs/time/*/time/struct.Time.html",
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time"
|
||||
)]
|
||||
impl<S: ScalarValue> GraphQLScalar for LocalTime {
|
||||
fn resolve(&self) -> Value {
|
||||
/// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]`
|
||||
/// format.
|
||||
///
|
||||
/// All minutes are assumed to have exactly 60 seconds; no attempt is made to
|
||||
/// handle leap seconds (either positive or negative).
|
||||
///
|
||||
/// [`LocalTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`time::Time`][2] for details.
|
||||
///
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||
/// [2]: https://docs.rs/time/*/time/struct.Time.html
|
||||
#[graphql_scalar(with = local_time, parse_token(String))]
|
||||
pub type LocalTime = time::Time;
|
||||
|
||||
mod local_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &LocalTime) -> Value<S> {
|
||||
Value::scalar(
|
||||
if self.millisecond() == 0 {
|
||||
self.format(LOCAL_TIME_FORMAT_NO_MILLIS)
|
||||
if v.millisecond() == 0 {
|
||||
v.format(LOCAL_TIME_FORMAT_NO_MILLIS)
|
||||
} else {
|
||||
self.format(LOCAL_TIME_FORMAT)
|
||||
v.format(LOCAL_TIME_FORMAT)
|
||||
}
|
||||
.unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {}", e)),
|
||||
)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Self, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalTime, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
// First, try to parse the most used format.
|
||||
// At the end, try to parse the full format for the parsing
|
||||
// error to be most informative.
|
||||
Self::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS)
|
||||
.or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT_NO_SECS))
|
||||
.or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT))
|
||||
LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS)
|
||||
.or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_SECS))
|
||||
.or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT))
|
||||
.map_err(|e| format!("Invalid `LocalTime`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(s) = value {
|
||||
Ok(S::from(s.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format of a [`LocalDateTime`] scalar.
|
||||
const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
|
||||
#[graphql_scalar(
|
||||
description = "Combined date and time (without time zone) in `yyyy-MM-dd \
|
||||
HH:mm:ss` format.\
|
||||
\n\n\
|
||||
See also [`time::PrimitiveDateTime`][2] for details.\
|
||||
\n\n\
|
||||
[2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html"
|
||||
)]
|
||||
impl<S: ScalarValue> GraphQLScalar for LocalDateTime {
|
||||
fn resolve(&self) -> Value {
|
||||
/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format.
|
||||
///
|
||||
/// See also [`time::PrimitiveDateTime`][2] for details.
|
||||
///
|
||||
/// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html
|
||||
#[graphql_scalar(with = local_date_time, parse_token(String))]
|
||||
pub type LocalDateTime = time::PrimitiveDateTime;
|
||||
|
||||
mod local_date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &LocalDateTime) -> Value<S> {
|
||||
Value::scalar(
|
||||
self.format(LOCAL_DATE_TIME_FORMAT)
|
||||
v.format(LOCAL_DATE_TIME_FORMAT)
|
||||
.unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)),
|
||||
)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Self, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<LocalDateTime, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
Self::parse(s, LOCAL_DATE_TIME_FORMAT)
|
||||
LocalDateTime::parse(s, LOCAL_DATE_TIME_FORMAT)
|
||||
.map_err(|e| format!("Invalid `LocalDateTime`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(s) = value {
|
||||
Ok(S::from(s.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Combined date and time (with time zone) in [RFC 3339][0] format.
|
||||
///
|
||||
/// Represents a description of an exact instant on the time-line (such as the
|
||||
/// instant that a user account was created).
|
||||
///
|
||||
/// [`DateTime` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`time::OffsetDateTime`][2] for details.
|
||||
///
|
||||
/// [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date-time
|
||||
/// [2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html
|
||||
#[graphql_scalar(
|
||||
description = "Combined date and time (with time zone) in [RFC 3339][0] \
|
||||
format.\
|
||||
\n\n\
|
||||
Represents a description of an exact instant on the \
|
||||
time-line (such as the instant that a user account was \
|
||||
created).\
|
||||
\n\n\
|
||||
[`DateTime` scalar][1] compliant.\
|
||||
\n\n\
|
||||
See also [`time::OffsetDateTime`][2] for details.\
|
||||
\n\n\
|
||||
[0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6\n\
|
||||
[1]: https://graphql-scalars.dev/docs/scalars/date-time\n\
|
||||
[2]: https://docs.rs/time/*/time/struct.OffsetDateTime.html",
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time"
|
||||
with = date_time,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
|
||||
)]
|
||||
impl<S: ScalarValue> GraphQLScalar for DateTime {
|
||||
fn resolve(&self) -> Value {
|
||||
pub type DateTime = time::OffsetDateTime;
|
||||
|
||||
mod date_time {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &DateTime) -> Value<S> {
|
||||
Value::scalar(
|
||||
self.to_offset(UtcOffset::UTC)
|
||||
v.to_offset(UtcOffset::UTC)
|
||||
.format(&Rfc3339)
|
||||
.unwrap_or_else(|e| panic!("Failed to format `DateTime`: {}", e)),
|
||||
)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Self, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<DateTime, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
Self::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e))
|
||||
DateTime::parse(s, &Rfc3339).map_err(|e| format!("Invalid `DateTime`: {}", e))
|
||||
})
|
||||
.map(|dt| dt.to_offset(UtcOffset::UTC))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(s) = value {
|
||||
Ok(S::from(s.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format of a [`UtcOffset` scalar][1].
|
||||
|
@ -238,42 +207,40 @@ impl<S: ScalarValue> GraphQLScalar for DateTime {
|
|||
const UTC_OFFSET_FORMAT: &[FormatItem<'_>] =
|
||||
format_description!("[offset_hour sign:mandatory]:[offset_minute]");
|
||||
|
||||
/// Offset from UTC in `±hh:mm` format. See [list of database time zones][0].
|
||||
///
|
||||
/// [`UtcOffset` scalar][1] compliant.
|
||||
///
|
||||
/// See also [`time::UtcOffset`][2] for details.
|
||||
///
|
||||
/// [0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
/// [1]: https://graphql-scalars.dev/docs/scalars/utc-offset
|
||||
/// [2]: https://docs.rs/time/*/time/struct.UtcOffset.html
|
||||
#[graphql_scalar(
|
||||
description = "Offset from UTC in `±hh:mm` format. See [list of database \
|
||||
time zones][0].\
|
||||
\n\n\
|
||||
[`UtcOffset` scalar][1] compliant.\
|
||||
\n\n\
|
||||
See also [`time::UtcOffset`][2] for details.\
|
||||
\n\n\
|
||||
[0]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\
|
||||
[1]: https://graphql-scalars.dev/docs/scalars/utc-offset\n\
|
||||
[2]: https://docs.rs/time/*/time/struct.UtcOffset.html",
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset"
|
||||
with = utc_offset,
|
||||
parse_token(String),
|
||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset",
|
||||
)]
|
||||
impl<S: ScalarValue> GraphQLScalar for UtcOffset {
|
||||
fn resolve(&self) -> Value {
|
||||
pub type UtcOffset = time::UtcOffset;
|
||||
|
||||
mod utc_offset {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &UtcOffset) -> Value<S> {
|
||||
Value::scalar(
|
||||
self.format(UTC_OFFSET_FORMAT)
|
||||
v.format(UTC_OFFSET_FORMAT)
|
||||
.unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)),
|
||||
)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Self, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcOffset, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| {
|
||||
Self::parse(s, UTC_OFFSET_FORMAT).map_err(|e| format!("Invalid `UtcOffset`: {}", e))
|
||||
UtcOffset::parse(s, UTC_OFFSET_FORMAT)
|
||||
.map_err(|e| format!("Invalid `UtcOffset`: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(s) = value {
|
||||
Ok(S::from(s.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -295,7 +262,7 @@ mod date_test {
|
|||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{}`: {}",
|
||||
"failed to parse `{}`: {:?}",
|
||||
raw,
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
|
@ -364,7 +331,7 @@ mod local_time_test {
|
|||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{}`: {}",
|
||||
"failed to parse `{}`: {:?}",
|
||||
raw,
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
|
@ -436,7 +403,7 @@ mod local_date_time_test {
|
|||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{}`: {}",
|
||||
"failed to parse `{}`: {:?}",
|
||||
raw,
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
|
@ -524,7 +491,7 @@ mod date_time_test {
|
|||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{}`: {}",
|
||||
"failed to parse `{}`: {:?}",
|
||||
raw,
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
|
@ -608,7 +575,7 @@ mod utc_offset_test {
|
|||
|
||||
assert!(
|
||||
parsed.is_ok(),
|
||||
"failed to parse `{}`: {}",
|
||||
"failed to parse `{}`: {:?}",
|
||||
raw,
|
||||
parsed.unwrap_err(),
|
||||
);
|
||||
|
|
|
@ -1,30 +1,22 @@
|
|||
//! GraphQL support for [url](https://github.com/servo/rust-url) types.
|
||||
|
||||
use url::Url;
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
use crate::{
|
||||
value::{ParseScalarResult, ParseScalarValue},
|
||||
Value,
|
||||
};
|
||||
#[graphql_scalar(with = url_scalar, parse_token(String))]
|
||||
type Url = url::Url;
|
||||
|
||||
#[crate::graphql_scalar(description = "Url")]
|
||||
impl<S> GraphQLScalar for Url
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.as_str().to_owned())
|
||||
mod url_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Url) -> Value<S> {
|
||||
Value::scalar(v.as_str().to_owned())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Url, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Url, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e)))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
<String as ParseScalarValue<S>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -34,7 +26,7 @@ mod test {
|
|||
use crate::{graphql_input_value, InputValue};
|
||||
|
||||
#[test]
|
||||
fn url_from_input_value() {
|
||||
fn url_from_input() {
|
||||
let raw = "https://example.net/";
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
|
||||
|
|
|
@ -2,36 +2,23 @@
|
|||
|
||||
#![allow(clippy::needless_lifetimes)]
|
||||
|
||||
use uuid::Uuid;
|
||||
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
|
||||
use crate::{
|
||||
parser::{ParseError, ScalarToken, Token},
|
||||
value::ParseScalarResult,
|
||||
Value,
|
||||
};
|
||||
#[graphql_scalar(with = uuid_scalar, parse_token(String))]
|
||||
type Uuid = uuid::Uuid;
|
||||
|
||||
#[crate::graphql_scalar(description = "Uuid")]
|
||||
impl<S> GraphQLScalar for Uuid
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.to_string())
|
||||
mod uuid_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Uuid) -> Value<S> {
|
||||
Value::scalar(v.to_string())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<Uuid, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Uuid, String> {
|
||||
v.as_string_value()
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
.and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e)))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
if let ScalarToken::String(value) = value {
|
||||
Ok(S::from(value.to_owned()))
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -41,7 +28,7 @@ mod test {
|
|||
use crate::{graphql_input_value, FromInputValue, InputValue};
|
||||
|
||||
#[test]
|
||||
fn uuid_from_input_value() {
|
||||
fn uuid_from_input() {
|
||||
let raw = "123e4567-e89b-12d3-a456-426655440000";
|
||||
let input: InputValue = graphql_input_value!((raw));
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture};
|
|||
// functionality automatically.
|
||||
pub use juniper_codegen::{
|
||||
graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
|
||||
GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
|
||||
GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLUnion,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -168,7 +168,7 @@ pub use crate::{
|
|||
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
||||
AsDynGraphQLValue,
|
||||
},
|
||||
parser::{ParseError, Spanning},
|
||||
parser::{ParseError, ScalarToken, Spanning},
|
||||
schema::{
|
||||
meta,
|
||||
model::{RootNode, SchemaType},
|
||||
|
|
|
@ -599,7 +599,7 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod coercion {
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue};
|
||||
use crate::{graphql_input_value, FromInputValue as _, InputValue, IntoFieldError as _};
|
||||
|
||||
use super::{FromInputValueArrayError, FromInputValueVecError};
|
||||
|
||||
|
@ -685,13 +685,13 @@ mod coercion {
|
|||
assert_eq!(
|
||||
<Vec<i32>>::from_input_value(&v),
|
||||
Err(FromInputValueVecError::Item(
|
||||
"Expected `Int`, found: null".to_owned(),
|
||||
"Expected `Int`, found: null".into_field_error(),
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
<Option<Vec<i32>>>::from_input_value(&v),
|
||||
Err(FromInputValueVecError::Item(
|
||||
"Expected `Int`, found: null".to_owned(),
|
||||
"Expected `Int`, found: null".into_field_error(),
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -795,13 +795,13 @@ mod coercion {
|
|||
assert_eq!(
|
||||
<[i32; 3]>::from_input_value(&v),
|
||||
Err(FromInputValueArrayError::Item(
|
||||
"Expected `Int`, found: null".to_owned(),
|
||||
"Expected `Int`, found: null".into_field_error(),
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
<Option<[i32; 3]>>::from_input_value(&v),
|
||||
Err(FromInputValueArrayError::Item(
|
||||
"Expected `Int`, found: null".to_owned(),
|
||||
"Expected `Int`, found: null".into_field_error(),
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::{
|
||||
ast::{InputValue, Selection, ToInputValue},
|
||||
executor::{ExecutionResult, Executor, Registry},
|
||||
graphql_scalar,
|
||||
macros::reflect,
|
||||
parser::{LexerError, ParseError, ScalarToken, Token},
|
||||
schema::meta::MetaType,
|
||||
|
@ -21,9 +22,25 @@ use crate::{
|
|||
/// An ID as defined by the GraphQL specification
|
||||
///
|
||||
/// Represented as a string, but can be converted _to_ from an integer as well.
|
||||
// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[graphql_scalar(parse_token(String, i32))]
|
||||
pub struct ID(String);
|
||||
|
||||
impl ID {
|
||||
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||
Value::scalar(self.0.clone())
|
||||
}
|
||||
|
||||
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Self, String> {
|
||||
v.as_string_value()
|
||||
.map(str::to_owned)
|
||||
.or_else(|| v.as_int_value().map(|i| i.to_string()))
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ID {
|
||||
fn from(s: String) -> ID {
|
||||
ID(s)
|
||||
|
@ -51,47 +68,23 @@ impl fmt::Display for ID {
|
|||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_scalar(name = "ID")]
|
||||
impl<S> GraphQLScalar for ID
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0.clone())
|
||||
#[graphql_scalar(with = impl_string_scalar)]
|
||||
type String = std::string::String;
|
||||
|
||||
mod impl_string_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &str) -> Value<S> {
|
||||
Value::scalar(v.to_owned())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ID, String> {
|
||||
v.as_string_value()
|
||||
.map(str::to_owned)
|
||||
.or_else(|| v.as_int_value().map(|i| i.to_string()))
|
||||
.map(ID)
|
||||
.ok_or_else(|| format!("Expected `String` or `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
match value {
|
||||
ScalarToken::String(value) | ScalarToken::Int(value) => Ok(S::from(value.to_owned())),
|
||||
_ => Err(ParseError::UnexpectedToken(Token::Scalar(value))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_scalar(name = "String")]
|
||||
impl<S> GraphQLScalar for String
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.clone())
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<String, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<String, String> {
|
||||
v.as_string_value()
|
||||
.map(str::to_owned)
|
||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
if let ScalarToken::String(value) = value {
|
||||
let mut ret = String::with_capacity(value.len());
|
||||
let mut char_iter = value.chars();
|
||||
|
@ -276,42 +269,44 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_scalar(name = "Boolean")]
|
||||
impl<S> GraphQLScalar for bool
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(*self)
|
||||
#[graphql_scalar(with = impl_boolean_scalar)]
|
||||
type Boolean = bool;
|
||||
|
||||
mod impl_boolean_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Boolean) -> Value<S> {
|
||||
Value::scalar(*v)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<bool, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Boolean, String> {
|
||||
v.as_scalar_value()
|
||||
.and_then(ScalarValue::as_boolean)
|
||||
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
// Bools are parsed separately - they shouldn't reach this code path
|
||||
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
// `Boolean`s are parsed separately, they shouldn't reach this code path.
|
||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_scalar(name = "Int")]
|
||||
impl<S> GraphQLScalar for i32
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(*self)
|
||||
#[graphql_scalar(with = impl_int_scalar)]
|
||||
type Int = i32;
|
||||
|
||||
mod impl_int_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Int) -> Value<S> {
|
||||
Value::scalar(*v)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<i32, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Int, String> {
|
||||
v.as_int_value()
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
if let ScalarToken::Int(v) = value {
|
||||
v.parse()
|
||||
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||
|
@ -322,21 +317,22 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_scalar(name = "Float")]
|
||||
impl<S> GraphQLScalar for f64
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(*self)
|
||||
#[graphql_scalar(with = impl_float_scalar)]
|
||||
type Float = f64;
|
||||
|
||||
mod impl_float_scalar {
|
||||
use super::*;
|
||||
|
||||
pub(super) fn to_output<S: ScalarValue>(v: &Float) -> Value<S> {
|
||||
Value::scalar(*v)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<f64, String> {
|
||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Float, String> {
|
||||
v.as_float_value()
|
||||
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
||||
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||
match value {
|
||||
ScalarToken::Int(v) => v
|
||||
.parse()
|
||||
|
|
|
@ -2,10 +2,7 @@ use std::{borrow::Cow, fmt};
|
|||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::{
|
||||
parser::{ParseError, ScalarToken},
|
||||
GraphQLScalarValue,
|
||||
};
|
||||
use crate::parser::{ParseError, ScalarToken};
|
||||
|
||||
/// The result of converting a string into a scalar value
|
||||
pub type ParseScalarResult<'a, S = DefaultScalarValue> = Result<S, ParseError<'a>>;
|
||||
|
@ -16,6 +13,7 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
|
|||
fn from_str(value: ScalarToken<'_>) -> ParseScalarResult<'_, S>;
|
||||
}
|
||||
|
||||
// TODO: Revisit this doc, once `GraphQLScalarValue` macro is re-implemented.
|
||||
/// A trait marking a type that could be used as internal representation of
|
||||
/// scalar values in juniper
|
||||
///
|
||||
|
@ -36,9 +34,9 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
|
|||
/// ```rust
|
||||
/// # use std::{fmt, convert::TryInto as _};
|
||||
/// # use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
/// # use juniper::{GraphQLScalarValue, ScalarValue};
|
||||
/// # use juniper::ScalarValue;
|
||||
/// #
|
||||
/// #[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)]
|
||||
/// #[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
/// #[serde(untagged)]
|
||||
/// enum MyScalarValue {
|
||||
/// Int(i32),
|
||||
|
@ -48,6 +46,148 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
|
|||
/// Boolean(bool),
|
||||
/// }
|
||||
///
|
||||
/// impl From<i32> for MyScalarValue {
|
||||
/// fn from(v: i32) -> Self {
|
||||
/// Self::Int(v)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<MyScalarValue> for Option<i32> {
|
||||
/// fn from(v: MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Int(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> From<&'a MyScalarValue> for Option<&'a i32> {
|
||||
/// fn from(v: &'a MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Int(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<i64> for MyScalarValue {
|
||||
/// fn from(v: i64) -> Self {
|
||||
/// Self::Long(v)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<MyScalarValue> for Option<i64> {
|
||||
/// fn from(v: MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Long(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> From<&'a MyScalarValue> for Option<&'a i64> {
|
||||
/// fn from(v: &'a MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Long(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<f64> for MyScalarValue {
|
||||
/// fn from(v: f64) -> Self {
|
||||
/// Self::Float(v)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<MyScalarValue> for Option<f64> {
|
||||
/// fn from(v: MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Float(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> From<&'a MyScalarValue> for Option<&'a f64> {
|
||||
/// fn from(v: &'a MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Float(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<String> for MyScalarValue {
|
||||
/// fn from(v: String) -> Self {
|
||||
/// Self::String(v)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<MyScalarValue> for Option<String> {
|
||||
/// fn from(v: MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::String(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> From<&'a MyScalarValue> for Option<&'a String> {
|
||||
/// fn from(v: &'a MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::String(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<bool> for MyScalarValue {
|
||||
/// fn from(v: bool) -> Self {
|
||||
/// Self::Boolean(v)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl From<MyScalarValue> for Option<bool> {
|
||||
/// fn from(v: MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Boolean(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> From<&'a MyScalarValue> for Option<&'a bool> {
|
||||
/// fn from(v: &'a MyScalarValue) -> Self {
|
||||
/// if let MyScalarValue::Boolean(v) = v {
|
||||
/// Some(v)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl fmt::Display for MyScalarValue {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// match self {
|
||||
/// Self::Int(v) => v.fmt(f),
|
||||
/// Self::Long(v) => v.fmt(f),
|
||||
/// Self::Float(v) => v.fmt(f),
|
||||
/// Self::String(v) => v.fmt(f),
|
||||
/// Self::Boolean(v) => v.fmt(f),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl ScalarValue for MyScalarValue {
|
||||
/// fn as_int(&self) -> Option<i32> {
|
||||
/// match self {
|
||||
|
@ -266,7 +406,7 @@ pub trait ScalarValue:
|
|||
/// These types closely follow the [GraphQL specification][0].
|
||||
///
|
||||
/// [0]: https://spec.graphql.org/June2018
|
||||
#[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DefaultScalarValue {
|
||||
/// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value.
|
||||
|
@ -293,6 +433,122 @@ pub enum DefaultScalarValue {
|
|||
Boolean(bool),
|
||||
}
|
||||
|
||||
// TODO: Revisit these impls, once `GraphQLScalarValue` macro is re-implemented.
|
||||
impl From<i32> for DefaultScalarValue {
|
||||
fn from(v: i32) -> Self {
|
||||
Self::Int(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefaultScalarValue> for Option<i32> {
|
||||
fn from(v: DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::Int(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a DefaultScalarValue> for Option<&'a i32> {
|
||||
fn from(v: &'a DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::Int(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for DefaultScalarValue {
|
||||
fn from(v: f64) -> Self {
|
||||
Self::Float(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefaultScalarValue> for Option<f64> {
|
||||
fn from(v: DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::Float(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a DefaultScalarValue> for Option<&'a f64> {
|
||||
fn from(v: &'a DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::Float(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DefaultScalarValue {
|
||||
fn from(v: String) -> Self {
|
||||
Self::String(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefaultScalarValue> for Option<String> {
|
||||
fn from(v: DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::String(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a DefaultScalarValue> for Option<&'a String> {
|
||||
fn from(v: &'a DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::String(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for DefaultScalarValue {
|
||||
fn from(v: bool) -> Self {
|
||||
Self::Boolean(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefaultScalarValue> for Option<bool> {
|
||||
fn from(v: DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::Boolean(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a DefaultScalarValue> for Option<&'a bool> {
|
||||
fn from(v: &'a DefaultScalarValue) -> Self {
|
||||
if let DefaultScalarValue::Boolean(v) = v {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DefaultScalarValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Int(v) => v.fmt(f),
|
||||
Self::Float(v) => v.fmt(f),
|
||||
Self::String(v) => v.fmt(f),
|
||||
Self::Boolean(v) => v.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScalarValue for DefaultScalarValue {
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
match self {
|
||||
|
|
|
@ -159,7 +159,7 @@ impl TypeExt for syn::Type {
|
|||
ty.lifetimes_iter_mut(func)
|
||||
}
|
||||
if let syn::ReturnType::Type(_, ty) = &mut args.output {
|
||||
(&mut *ty).lifetimes_iter_mut(func)
|
||||
(*ty).lifetimes_iter_mut(func)
|
||||
}
|
||||
}
|
||||
syn::PathArguments::None => {}
|
||||
|
@ -172,7 +172,7 @@ impl TypeExt for syn::Type {
|
|||
| T::Group(syn::TypeGroup { elem, .. })
|
||||
| T::Paren(syn::TypeParen { elem, .. })
|
||||
| T::Ptr(syn::TypePtr { elem, .. })
|
||||
| T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_iter_mut(func),
|
||||
| T::Slice(syn::TypeSlice { elem, .. }) => (*elem).lifetimes_iter_mut(func),
|
||||
|
||||
T::Tuple(syn::TypeTuple { elems, .. }) => {
|
||||
for ty in elems.iter_mut() {
|
||||
|
@ -199,7 +199,7 @@ impl TypeExt for syn::Type {
|
|||
if let Some(lt) = ref_ty.lifetime.as_mut() {
|
||||
func(lt)
|
||||
}
|
||||
(&mut *ref_ty.elem).lifetimes_iter_mut(func)
|
||||
(*ref_ty.elem).lifetimes_iter_mut(func)
|
||||
}
|
||||
|
||||
T::Path(ty) => iter_path(&mut ty.path, func),
|
||||
|
@ -228,7 +228,7 @@ impl TypeExt for syn::Type {
|
|||
fn topmost_ident(&self) -> Option<&syn::Ident> {
|
||||
match self.unparenthesized() {
|
||||
syn::Type::Path(p) => Some(&p.path),
|
||||
syn::Type::Reference(r) => match (&*r.elem).unparenthesized() {
|
||||
syn::Type::Reference(r) => match (*r.elem).unparenthesized() {
|
||||
syn::Type::Path(p) => Some(&p.path),
|
||||
syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() {
|
||||
syn::TypeParamBound::Trait(b) => Some(&b.path),
|
||||
|
|
|
@ -1,347 +0,0 @@
|
|||
use crate::{
|
||||
common::parse::ParseBufferExt as _,
|
||||
result::GraphQLScope,
|
||||
util::{self, span_container::SpanContainer},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TransparentAttributes {
|
||||
transparent: Option<bool>,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
specified_by_url: Option<Url>,
|
||||
scalar: Option<syn::Type>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for TransparentAttributes {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
|
||||
let mut output = Self {
|
||||
transparent: None,
|
||||
name: None,
|
||||
description: None,
|
||||
specified_by_url: None,
|
||||
scalar: None,
|
||||
};
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: syn::Ident = input.parse()?;
|
||||
match ident.to_string().as_str() {
|
||||
"name" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.name = Some(val.value());
|
||||
}
|
||||
"description" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.description = Some(val.value());
|
||||
}
|
||||
"specified_by_url" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let val: syn::LitStr = input.parse::<syn::LitStr>()?;
|
||||
output.specified_by_url =
|
||||
Some(val.value().parse().map_err(|e| {
|
||||
syn::Error::new(val.span(), format!("Invalid URL: {}", e))
|
||||
})?);
|
||||
}
|
||||
"transparent" => {
|
||||
output.transparent = Some(true);
|
||||
}
|
||||
"scalar" | "Scalar" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::Type>()?;
|
||||
output.scalar = Some(val);
|
||||
}
|
||||
_ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
|
||||
}
|
||||
input.try_parse::<token::Comma>()?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransparentAttributes {
|
||||
fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result<Self> {
|
||||
match util::find_graphql_attr(attrs) {
|
||||
Some(attr) => {
|
||||
let mut parsed: TransparentAttributes = attr.parse_args()?;
|
||||
if parsed.description.is_none() {
|
||||
parsed.description =
|
||||
util::get_doc_comment(attrs).map(SpanContainer::into_inner);
|
||||
}
|
||||
Ok(parsed)
|
||||
}
|
||||
None => Ok(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_scalar_value(ast: &syn::DeriveInput, error: GraphQLScope) -> syn::Result<TokenStream> {
|
||||
let ident = &ast.ident;
|
||||
|
||||
match ast.data {
|
||||
Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, error),
|
||||
Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, error),
|
||||
Data::Union(_) => Err(error.custom_error(ast.span(), "may not be applied to unions")),
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_scalar_struct(
|
||||
ast: &syn::DeriveInput,
|
||||
data: &syn::DataStruct,
|
||||
error: GraphQLScope,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let field = match data.fields {
|
||||
syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||
fields.unnamed.first().unwrap()
|
||||
}
|
||||
_ => {
|
||||
return Err(error.custom_error(
|
||||
data.fields.span(),
|
||||
"requires exact one field, e.g., Test(i32)",
|
||||
))
|
||||
}
|
||||
};
|
||||
let ident = &ast.ident;
|
||||
let attrs = TransparentAttributes::from_attrs(&ast.attrs)?;
|
||||
let inner_ty = &field.ty;
|
||||
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
||||
|
||||
let description = attrs.description.map(|val| quote!(.description(#val)));
|
||||
let specified_by_url = attrs.specified_by_url.map(|url| {
|
||||
let url_lit = url.as_str();
|
||||
quote!(.specified_by_url(#url_lit))
|
||||
});
|
||||
|
||||
let scalar = attrs
|
||||
.scalar
|
||||
.as_ref()
|
||||
.map(|s| quote!( #s ))
|
||||
.unwrap_or_else(|| quote!(__S));
|
||||
|
||||
let impl_generics = attrs
|
||||
.scalar
|
||||
.as_ref()
|
||||
.map(|_| quote!())
|
||||
.unwrap_or_else(|| quote!(<__S>));
|
||||
|
||||
let _async = quote!(
|
||||
impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ident
|
||||
where
|
||||
Self: Sync,
|
||||
Self::TypeInfo: Sync,
|
||||
Self::Context: Sync,
|
||||
#scalar: ::juniper::ScalarValue + Send + Sync,
|
||||
{
|
||||
fn resolve_async<'a>(
|
||||
&'a self,
|
||||
info: &'a Self::TypeInfo,
|
||||
selection_set: Option<&'a [::juniper::Selection<#scalar>]>,
|
||||
executor: &'a ::juniper::Executor<Self::Context, #scalar>,
|
||||
) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#scalar>> {
|
||||
use ::juniper::futures::future;
|
||||
let v = ::juniper::GraphQLValue::<#scalar>::resolve(self, info, selection_set, executor);
|
||||
Box::pin(future::ready(v))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let content = quote!(
|
||||
#_async
|
||||
|
||||
impl#impl_generics ::juniper::GraphQLType<#scalar> for #ident
|
||||
where
|
||||
#scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
fn name(_: &Self::TypeInfo) -> Option<&'static str> {
|
||||
Some(#name)
|
||||
}
|
||||
|
||||
fn meta<'r>(
|
||||
info: &Self::TypeInfo,
|
||||
registry: &mut ::juniper::Registry<'r, #scalar>,
|
||||
) -> ::juniper::meta::MetaType<'r, #scalar>
|
||||
where
|
||||
#scalar: 'r,
|
||||
{
|
||||
registry.build_scalar_type::<Self>(info)
|
||||
#description
|
||||
#specified_by_url
|
||||
.into_meta()
|
||||
}
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ident
|
||||
where
|
||||
#scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
|
||||
<Self as ::juniper::GraphQLType<#scalar>>::name(info)
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
info: &(),
|
||||
selection: Option<&[::juniper::Selection<#scalar>]>,
|
||||
executor: &::juniper::Executor<Self::Context, #scalar>,
|
||||
) -> ::juniper::ExecutionResult<#scalar> {
|
||||
::juniper::GraphQLValue::<#scalar>::resolve(&self.0, info, selection, executor)
|
||||
}
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::ToInputValue<#scalar> for #ident
|
||||
where
|
||||
#scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
|
||||
::juniper::ToInputValue::<#scalar>::to_input_value(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::FromInputValue<#scalar> for #ident
|
||||
where
|
||||
#scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
type Error = <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error;
|
||||
|
||||
fn from_input_value(
|
||||
v: &::juniper::InputValue<#scalar>
|
||||
) -> Result<#ident, <#inner_ty as ::juniper::FromInputValue<#scalar>>::Error> {
|
||||
let inner: #inner_ty = ::juniper::FromInputValue::<#scalar>::from_input_value(v)?;
|
||||
Ok(#ident(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::ParseScalarValue<#scalar> for #ident
|
||||
where
|
||||
#scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
fn from_str<'a>(
|
||||
value: ::juniper::parser::ScalarToken<'a>,
|
||||
) -> ::juniper::ParseScalarResult<'a, #scalar> {
|
||||
<#inner_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ident
|
||||
where #scalar: ::juniper::ScalarValue,
|
||||
{ }
|
||||
impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident
|
||||
where #scalar: ::juniper::ScalarValue,
|
||||
{ }
|
||||
|
||||
impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident
|
||||
where #scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
const NAME: ::juniper::macros::reflect::Type = #name;
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident
|
||||
where #scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
const NAMES: ::juniper::macros::reflect::Types =
|
||||
&[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
|
||||
}
|
||||
|
||||
impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident
|
||||
where #scalar: ::juniper::ScalarValue,
|
||||
{
|
||||
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
fn impl_scalar_enum(
|
||||
ident: &syn::Ident,
|
||||
data: &syn::DataEnum,
|
||||
error: GraphQLScope,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let froms = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(|v| derive_from_variant(v, ident, &error))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let display = derive_display(data.variants.iter(), ident);
|
||||
|
||||
Ok(quote! {
|
||||
#(#froms)*
|
||||
|
||||
#display
|
||||
})
|
||||
}
|
||||
|
||||
fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream
|
||||
where
|
||||
I: Iterator<Item = &'a Variant>,
|
||||
{
|
||||
let arms = variants.map(|v| {
|
||||
let variant = &v.ident;
|
||||
quote!(#ident::#variant(ref v) => write!(f, "{}", v),)
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl std::fmt::Display for #ident {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_from_variant(
|
||||
variant: &Variant,
|
||||
ident: &Ident,
|
||||
error: &GraphQLScope,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let ty = match variant.fields {
|
||||
Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty,
|
||||
|
||||
_ => {
|
||||
return Err(error.custom_error(
|
||||
variant.fields.span(),
|
||||
"requires exact one field, e.g., Test(i32)",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let variant = &variant.ident;
|
||||
|
||||
Ok(quote! {
|
||||
impl ::std::convert::From<#ty> for #ident {
|
||||
fn from(t: #ty) -> Self {
|
||||
#ident::#variant(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ::std::convert::From<&'a #ident> for std::option::Option<&'a #ty> {
|
||||
fn from(t: &'a #ident) -> Self {
|
||||
match *t {
|
||||
#ident::#variant(ref t) => std::option::Option::Some(t),
|
||||
_ => std::option::Option::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::From<#ident> for std::option::Option<#ty> {
|
||||
fn from(t: #ident) -> Self {
|
||||
match t {
|
||||
#ident::#variant(t) => std::option::Option::Some(t),
|
||||
_ => std::option::Option::None
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
154
juniper_codegen/src/graphql_scalar/attr.rs
Normal file
154
juniper_codegen/src/graphql_scalar/attr.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
//! Code generation for `#[graphql_scalar]` macro.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_quote, spanned::Spanned};
|
||||
|
||||
use crate::{
|
||||
common::{parse, scalar},
|
||||
graphql_scalar::TypeOrIdent,
|
||||
GraphQLScope,
|
||||
};
|
||||
|
||||
use super::{Attr, Definition, GraphQLScalarMethods, ParseToken};
|
||||
|
||||
const ERR: GraphQLScope = GraphQLScope::ScalarAttr;
|
||||
|
||||
/// Expands `#[graphql_scalar]` macro into generated code.
|
||||
pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
||||
if let Ok(mut ast) = syn::parse2::<syn::ItemType>(body.clone()) {
|
||||
let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs);
|
||||
ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs);
|
||||
return expand_on_type_alias(attrs, ast);
|
||||
} else if let Ok(mut ast) = syn::parse2::<syn::DeriveInput>(body) {
|
||||
let attrs = parse::attr::unite(("graphql_scalar", &attr_args), &ast.attrs);
|
||||
ast.attrs = parse::attr::strip("graphql_scalar", ast.attrs);
|
||||
return expand_on_derive_input(attrs, ast);
|
||||
}
|
||||
|
||||
Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
"#[graphql_scalar] attribute is applicable to type aliases, structs, \
|
||||
enums and unions only",
|
||||
))
|
||||
}
|
||||
|
||||
/// Expands `#[graphql_scalar]` macro placed on a type alias.
|
||||
fn expand_on_type_alias(
|
||||
attrs: Vec<syn::Attribute>,
|
||||
ast: syn::ItemType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
|
||||
|
||||
let field = match (
|
||||
attr.to_output.as_deref().cloned(),
|
||||
attr.from_input.as_deref().cloned(),
|
||||
attr.parse_token.as_deref().cloned(),
|
||||
attr.with.as_deref().cloned(),
|
||||
) {
|
||||
(Some(to_output), Some(from_input), Some(parse_token), None) => {
|
||||
GraphQLScalarMethods::Custom {
|
||||
to_output,
|
||||
from_input,
|
||||
parse_token,
|
||||
}
|
||||
}
|
||||
(to_output, from_input, parse_token, Some(module)) => GraphQLScalarMethods::Custom {
|
||||
to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
|
||||
from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
|
||||
parse_token: parse_token
|
||||
.unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
|
||||
},
|
||||
_ => {
|
||||
return Err(ERR.custom_error(
|
||||
ast.span(),
|
||||
"all custom resolvers have to be provided via `with` or \
|
||||
combination of `to_output_with`, `from_input_with`, \
|
||||
`parse_token_with` attribute arguments",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
||||
|
||||
let def = Definition {
|
||||
ty: TypeOrIdent::Type(ast.ty.clone()),
|
||||
where_clause: attr
|
||||
.where_clause
|
||||
.map_or_else(Vec::new, |cl| cl.into_inner()),
|
||||
generics: ast.generics.clone(),
|
||||
methods: field,
|
||||
name: attr
|
||||
.name
|
||||
.as_deref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ast.ident.to_string()),
|
||||
description: attr.description.as_deref().cloned(),
|
||||
specified_by_url: attr.specified_by_url.as_deref().cloned(),
|
||||
scalar,
|
||||
}
|
||||
.to_token_stream();
|
||||
|
||||
Ok(quote! {
|
||||
#ast
|
||||
#def
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Support `#[graphql(transparent)]`.
|
||||
/// Expands `#[graphql_scalar]` macro placed on a struct/enum/union.
|
||||
fn expand_on_derive_input(
|
||||
attrs: Vec<syn::Attribute>,
|
||||
ast: syn::DeriveInput,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let attr = Attr::from_attrs("graphql_scalar", &attrs)?;
|
||||
|
||||
let field = match (
|
||||
attr.to_output.as_deref().cloned(),
|
||||
attr.from_input.as_deref().cloned(),
|
||||
attr.parse_token.as_deref().cloned(),
|
||||
attr.with.as_deref().cloned(),
|
||||
) {
|
||||
(Some(to_output), Some(from_input), Some(parse_token), None) => {
|
||||
GraphQLScalarMethods::Custom {
|
||||
to_output,
|
||||
from_input,
|
||||
parse_token,
|
||||
}
|
||||
}
|
||||
(to_output, from_input, parse_token, module) => {
|
||||
let module = module.unwrap_or_else(|| parse_quote! { Self });
|
||||
GraphQLScalarMethods::Custom {
|
||||
to_output: to_output.unwrap_or_else(|| parse_quote! { #module::to_output }),
|
||||
from_input: from_input.unwrap_or_else(|| parse_quote! { #module::from_input }),
|
||||
parse_token: parse_token
|
||||
.unwrap_or_else(|| ParseToken::Custom(parse_quote! { #module::parse_token })),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
||||
|
||||
let def = Definition {
|
||||
ty: TypeOrIdent::Ident(ast.ident.clone()),
|
||||
where_clause: attr
|
||||
.where_clause
|
||||
.map_or_else(Vec::new, |cl| cl.into_inner()),
|
||||
generics: ast.generics.clone(),
|
||||
methods: field,
|
||||
name: attr
|
||||
.name
|
||||
.as_deref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ast.ident.to_string()),
|
||||
description: attr.description.as_deref().cloned(),
|
||||
specified_by_url: attr.specified_by_url.as_deref().cloned(),
|
||||
scalar,
|
||||
}
|
||||
.to_token_stream();
|
||||
|
||||
Ok(quote! {
|
||||
#ast
|
||||
#def
|
||||
})
|
||||
}
|
888
juniper_codegen/src/graphql_scalar/mod.rs
Normal file
888
juniper_codegen/src/graphql_scalar/mod.rs
Normal file
|
@ -0,0 +1,888 @@
|
|||
//! Code generation for [GraphQL scalar][1].
|
||||
//!
|
||||
//! [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
ext::IdentExt as _,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
spanned::Spanned as _,
|
||||
token,
|
||||
visit_mut::VisitMut,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
parse::{
|
||||
attr::{err, OptionExt as _},
|
||||
ParseBufferExt as _,
|
||||
},
|
||||
scalar,
|
||||
},
|
||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer},
|
||||
};
|
||||
|
||||
pub mod attr;
|
||||
|
||||
/// Available arguments behind `#[graphql]`/`#[graphql_scalar]` attributes when
|
||||
/// generating code for [GraphQL scalar][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
#[derive(Debug, Default)]
|
||||
struct Attr {
|
||||
/// Name of this [GraphQL scalar][1] in GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
name: Option<SpanContainer<String>>,
|
||||
|
||||
/// Description of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
description: Option<SpanContainer<String>>,
|
||||
|
||||
/// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
specified_by_url: Option<SpanContainer<Url>>,
|
||||
|
||||
/// Explicitly specified type (or type parameter with its bounds) of
|
||||
/// [`ScalarValue`] to use for resolving this [GraphQL scalar][1] type with.
|
||||
///
|
||||
/// If [`None`], then generated code will be generic over any
|
||||
/// [`ScalarValue`] type, which, in turn, requires all [scalar][1] fields to
|
||||
/// be generic over any [`ScalarValue`] type too. That's why this type
|
||||
/// should be specified only if one of the variants implements
|
||||
/// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type.
|
||||
///
|
||||
/// [`GraphQLType`]: juniper::GraphQLType
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
scalar: Option<SpanContainer<scalar::AttrValue>>,
|
||||
|
||||
/// Explicitly specified function to be used as
|
||||
/// [`ToInputValue::to_input_value`] implementation.
|
||||
///
|
||||
/// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value
|
||||
to_output: Option<SpanContainer<syn::ExprPath>>,
|
||||
|
||||
/// Explicitly specified function to be used as
|
||||
/// [`FromInputValue::from_input_value`] implementation.
|
||||
///
|
||||
/// [`FromInputValue::from_input_value`]: juniper::FromInputValue::from_input_value
|
||||
from_input: Option<SpanContainer<syn::ExprPath>>,
|
||||
|
||||
/// Explicitly specified resolver to be used as
|
||||
/// [`ParseScalarValue::from_str`] implementation.
|
||||
///
|
||||
/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
|
||||
parse_token: Option<SpanContainer<ParseToken>>,
|
||||
|
||||
/// Explicitly specified module with all custom resolvers for
|
||||
/// [`Self::to_output`], [`Self::from_input`] and [`Self::parse_token`].
|
||||
with: Option<SpanContainer<syn::ExprPath>>,
|
||||
|
||||
/// Explicit where clause added to [`syn::WhereClause`].
|
||||
where_clause: Option<SpanContainer<Vec<syn::WherePredicate>>>,
|
||||
}
|
||||
|
||||
impl Parse for Attr {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
let mut out = Self::default();
|
||||
while !input.is_empty() {
|
||||
let ident = input.parse_any_ident()?;
|
||||
match ident.to_string().as_str() {
|
||||
"name" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let name = input.parse::<syn::LitStr>()?;
|
||||
out.name
|
||||
.replace(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(name.span()),
|
||||
name.value(),
|
||||
))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"desc" | "description" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let desc = input.parse::<syn::LitStr>()?;
|
||||
out.description
|
||||
.replace(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(desc.span()),
|
||||
desc.value(),
|
||||
))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"specified_by_url" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let lit = input.parse::<syn::LitStr>()?;
|
||||
let url = lit.value().parse::<Url>().map_err(|err| {
|
||||
syn::Error::new(lit.span(), format!("Invalid URL: {}", err))
|
||||
})?;
|
||||
out.specified_by_url
|
||||
.replace(SpanContainer::new(ident.span(), Some(lit.span()), url))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"scalar" | "Scalar" | "ScalarValue" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let scl = input.parse::<scalar::AttrValue>()?;
|
||||
out.scalar
|
||||
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"to_output_with" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let scl = input.parse::<syn::ExprPath>()?;
|
||||
out.to_output
|
||||
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"from_input_with" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let scl = input.parse::<syn::ExprPath>()?;
|
||||
out.from_input
|
||||
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"parse_token_with" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let scl = input.parse::<syn::ExprPath>()?;
|
||||
out.parse_token
|
||||
.replace(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(scl.span()),
|
||||
ParseToken::Custom(scl),
|
||||
))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"parse_token" => {
|
||||
let types;
|
||||
let _ = syn::parenthesized!(types in input);
|
||||
let parsed_types =
|
||||
types.parse_terminated::<_, token::Comma>(syn::Type::parse)?;
|
||||
|
||||
if parsed_types.is_empty() {
|
||||
return Err(syn::Error::new(ident.span(), "expected at least 1 type."));
|
||||
}
|
||||
|
||||
out.parse_token
|
||||
.replace(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(parsed_types.span()),
|
||||
ParseToken::Delegated(parsed_types.into_iter().collect()),
|
||||
))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"with" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let scl = input.parse::<syn::ExprPath>()?;
|
||||
out.with
|
||||
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"where" => {
|
||||
let (span, parsed_predicates) = if input.parse::<token::Eq>().is_ok() {
|
||||
let pred = input.parse::<syn::WherePredicate>()?;
|
||||
(pred.span(), vec![pred])
|
||||
} else {
|
||||
let predicates;
|
||||
let _ = syn::parenthesized!(predicates in input);
|
||||
let parsed_predicates = predicates
|
||||
.parse_terminated::<_, token::Comma>(syn::WherePredicate::parse)?;
|
||||
|
||||
if parsed_predicates.is_empty() {
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
"expected at least 1 where predicate.",
|
||||
));
|
||||
}
|
||||
|
||||
(
|
||||
parsed_predicates.span(),
|
||||
parsed_predicates.into_iter().collect(),
|
||||
)
|
||||
};
|
||||
|
||||
out.where_clause
|
||||
.replace(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(span),
|
||||
parsed_predicates,
|
||||
))
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
name => {
|
||||
return Err(err::unknown_arg(&ident, name));
|
||||
}
|
||||
}
|
||||
input.try_parse::<token::Comma>()?;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
/// Tries to merge two [`Attr`]s into a single one, reporting about
|
||||
/// duplicates, if any.
|
||||
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
name: try_merge_opt!(name: self, another),
|
||||
description: try_merge_opt!(description: self, another),
|
||||
specified_by_url: try_merge_opt!(specified_by_url: self, another),
|
||||
scalar: try_merge_opt!(scalar: self, another),
|
||||
to_output: try_merge_opt!(to_output: self, another),
|
||||
from_input: try_merge_opt!(from_input: self, another),
|
||||
parse_token: try_merge_opt!(parse_token: self, another),
|
||||
with: try_merge_opt!(with: self, another),
|
||||
where_clause: try_merge_opt!(where_clause: self, another),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
|
||||
/// placed on a trait definition.
|
||||
fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
|
||||
let mut attr = filter_attrs(name, attrs)
|
||||
.map(|attr| attr.parse_args())
|
||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||
|
||||
if attr.description.is_none() {
|
||||
attr.description = get_doc_comment(attrs);
|
||||
}
|
||||
|
||||
Ok(attr)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`syn::Type`] in case of `#[graphql_scalar]` or [`syn::Ident`] in case of
|
||||
/// `#[derive(GraphQLScalar)]`.
|
||||
#[derive(Clone)]
|
||||
enum TypeOrIdent {
|
||||
/// [`syn::Type`].
|
||||
Type(Box<syn::Type>),
|
||||
|
||||
/// [`syn::Ident`].
|
||||
Ident(syn::Ident),
|
||||
}
|
||||
|
||||
/// Definition of [GraphQL scalar][1] for code generation.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
struct Definition {
|
||||
/// Name of this [GraphQL scalar][1] in GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
name: String,
|
||||
|
||||
/// [`TypeOrIdent`] of this [GraphQL scalar][1] in GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
ty: TypeOrIdent,
|
||||
|
||||
/// Additional [`Self::generics`] [`syn::WhereClause`] predicates.
|
||||
where_clause: Vec<syn::WherePredicate>,
|
||||
|
||||
/// Generics of the Rust type that this [GraphQL scalar][1] is implemented
|
||||
/// for.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
generics: syn::Generics,
|
||||
|
||||
/// [`GraphQLScalarMethods`] representing [GraphQL scalar][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
methods: GraphQLScalarMethods,
|
||||
|
||||
/// Description of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
description: Option<String>,
|
||||
|
||||
/// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
specified_by_url: Option<Url>,
|
||||
|
||||
/// [`ScalarValue`] parametrization to generate [`GraphQLType`]
|
||||
/// implementation with for this [GraphQL scalar][1].
|
||||
///
|
||||
/// [`GraphQLType`]: juniper::GraphQLType
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
scalar: scalar::Type,
|
||||
}
|
||||
|
||||
impl ToTokens for Definition {
|
||||
fn to_tokens(&self, into: &mut TokenStream) {
|
||||
self.impl_output_and_input_type_tokens().to_tokens(into);
|
||||
self.impl_type_tokens().to_tokens(into);
|
||||
self.impl_value_tokens().to_tokens(into);
|
||||
self.impl_value_async_tokens().to_tokens(into);
|
||||
self.impl_to_input_value_tokens().to_tokens(into);
|
||||
self.impl_from_input_value_tokens().to_tokens(into);
|
||||
self.impl_parse_scalar_value_tokens().to_tokens(into);
|
||||
self.impl_reflection_traits_tokens().to_tokens(into);
|
||||
}
|
||||
}
|
||||
|
||||
impl Definition {
|
||||
/// Returns generated code implementing [`marker::IsInputType`] and
|
||||
/// [`marker::IsOutputType`] trait for this [GraphQL scalar][1].
|
||||
///
|
||||
/// [`marker::IsInputType`]: juniper::marker::IsInputType
|
||||
/// [`marker::IsOutputType`]: juniper::marker::IsOutputType
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
#[must_use]
|
||||
fn impl_output_and_input_type_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::marker::IsInputType<#scalar> for #ty
|
||||
#where_clause { }
|
||||
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::marker::IsOutputType<#scalar> for #ty
|
||||
#where_clause { }
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`GraphQLType`] trait for this
|
||||
/// [GraphQL scalar][1].
|
||||
///
|
||||
/// [`GraphQLType`]: juniper::GraphQLType
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_type_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
let name = &self.name;
|
||||
|
||||
let description = self
|
||||
.description
|
||||
.as_ref()
|
||||
.map(|val| quote! { .description(#val) });
|
||||
let specified_by_url = self.specified_by_url.as_ref().map(|url| {
|
||||
let url_lit = url.as_str();
|
||||
quote! { .specified_by_url(#url_lit) }
|
||||
});
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::GraphQLType<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
fn name(_: &Self::TypeInfo) -> Option<&'static str> {
|
||||
Some(#name)
|
||||
}
|
||||
|
||||
fn meta<'r>(
|
||||
info: &Self::TypeInfo,
|
||||
registry: &mut ::juniper::Registry<'r, #scalar>,
|
||||
) -> ::juniper::meta::MetaType<'r, #scalar>
|
||||
where
|
||||
#scalar: 'r,
|
||||
{
|
||||
registry.build_scalar_type::<Self>(info)
|
||||
#description
|
||||
#specified_by_url
|
||||
.into_meta()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`GraphQLValue`] trait for this
|
||||
/// [GraphQL scalar][1].
|
||||
///
|
||||
/// [`GraphQLValue`]: juniper::GraphQLValue
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_value_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
|
||||
let resolve = self.methods.expand_resolve(scalar);
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::GraphQLValue<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
|
||||
<Self as ::juniper::GraphQLType<#scalar>>::name(info)
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
info: &(),
|
||||
selection: Option<&[::juniper::Selection<#scalar>]>,
|
||||
executor: &::juniper::Executor<Self::Context, #scalar>,
|
||||
) -> ::juniper::ExecutionResult<#scalar> {
|
||||
#resolve
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`GraphQLValueAsync`] trait for this
|
||||
/// [GraphQL scalar][1].
|
||||
///
|
||||
/// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_value_async_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(true);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::GraphQLValueAsync<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
fn resolve_async<'b>(
|
||||
&'b self,
|
||||
info: &'b Self::TypeInfo,
|
||||
selection_set: Option<&'b [::juniper::Selection<#scalar>]>,
|
||||
executor: &'b ::juniper::Executor<Self::Context, #scalar>,
|
||||
) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> {
|
||||
use ::juniper::futures::future;
|
||||
let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor);
|
||||
Box::pin(future::ready(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`InputValue`] trait for this
|
||||
/// [GraphQL scalar][1].
|
||||
///
|
||||
/// [`InputValue`]: juniper::InputValue
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_to_input_value_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
|
||||
let to_input_value = self.methods.expand_to_input_value(scalar);
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::ToInputValue<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
fn to_input_value(&self) -> ::juniper::InputValue<#scalar> {
|
||||
#to_input_value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`FromInputValue`] trait for this
|
||||
/// [GraphQL scalar][1].
|
||||
///
|
||||
/// [`FromInputValue`]: juniper::FromInputValue
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_from_input_value_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
|
||||
let from_input_value = self.methods.expand_from_input_value(scalar);
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::FromInputValue<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
type Error = ::juniper::executor::FieldError<#scalar>;
|
||||
|
||||
fn from_input_value(input: &::juniper::InputValue<#scalar>) -> Result<Self, Self::Error> {
|
||||
#from_input_value
|
||||
.map_err(::juniper::executor::IntoFieldError::<#scalar>::into_field_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`ParseScalarValue`] trait for this
|
||||
/// [GraphQL scalar][1].
|
||||
///
|
||||
/// [`ParseScalarValue`]: juniper::ParseScalarValue
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_parse_scalar_value_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
|
||||
let from_str = self.methods.expand_parse_scalar_value(scalar);
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::ParseScalarValue<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
fn from_str(
|
||||
token: ::juniper::parser::ScalarToken<'_>,
|
||||
) -> ::juniper::ParseScalarResult<'_, #scalar> {
|
||||
#from_str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and
|
||||
/// [`WrappedType`] traits for this [GraphQL scalar][1].
|
||||
///
|
||||
/// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes
|
||||
/// [`BaseType`]: juniper::macros::reflection::BaseType
|
||||
/// [`WrappedType`]: juniper::macros::reflection::WrappedType
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn impl_reflection_traits_tokens(&self) -> TokenStream {
|
||||
let scalar = &self.scalar;
|
||||
let name = &self.name;
|
||||
|
||||
let (ty, generics) = self.impl_self_and_generics(false);
|
||||
let (impl_gens, _, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::macros::reflect::BaseType<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
const NAME: ::juniper::macros::reflect::Type = #name;
|
||||
}
|
||||
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
const NAMES: ::juniper::macros::reflect::Types =
|
||||
&[<Self as ::juniper::macros::reflect::BaseType<#scalar>>::NAME];
|
||||
}
|
||||
|
||||
#[automatically_derived]
|
||||
impl#impl_gens ::juniper::macros::reflect::WrappedType<#scalar> for #ty
|
||||
#where_clause
|
||||
{
|
||||
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns prepared self type and [`syn::Generics`] for [`GraphQLType`]
|
||||
/// trait (and similar) implementation.
|
||||
///
|
||||
/// If `for_async` is `true`, then additional predicates are added to suit
|
||||
/// the [`GraphQLAsyncValue`] trait (and similar) requirements.
|
||||
///
|
||||
/// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue
|
||||
/// [`GraphQLType`]: juniper::GraphQLType
|
||||
#[must_use]
|
||||
fn impl_self_and_generics(&self, for_async: bool) -> (TokenStream, syn::Generics) {
|
||||
let mut generics = self.generics.clone();
|
||||
|
||||
let ty = match &self.ty {
|
||||
TypeOrIdent::Type(ty) => ty.into_token_stream(),
|
||||
TypeOrIdent::Ident(ident) => {
|
||||
let (_, ty_gen, _) = self.generics.split_for_impl();
|
||||
quote! { #ident#ty_gen }
|
||||
}
|
||||
};
|
||||
|
||||
if !self.where_clause.is_empty() {
|
||||
generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.extend(self.where_clause.clone())
|
||||
}
|
||||
|
||||
let scalar = &self.scalar;
|
||||
if scalar.is_implicit_generic() {
|
||||
generics.params.push(parse_quote! { #scalar });
|
||||
}
|
||||
if scalar.is_generic() {
|
||||
generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { #scalar: ::juniper::ScalarValue });
|
||||
}
|
||||
if let Some(bound) = scalar.bounds() {
|
||||
generics.make_where_clause().predicates.push(bound);
|
||||
}
|
||||
|
||||
if for_async {
|
||||
let self_ty = if self.generics.lifetimes().next().is_some() {
|
||||
let mut generics = self.generics.clone();
|
||||
ModifyLifetimes.visit_generics_mut(&mut generics);
|
||||
|
||||
let lifetimes = generics.lifetimes().map(|lt| <.lifetime);
|
||||
let ty = match self.ty.clone() {
|
||||
TypeOrIdent::Type(mut ty) => {
|
||||
ModifyLifetimes.visit_type_mut(&mut ty);
|
||||
ty.into_token_stream()
|
||||
}
|
||||
TypeOrIdent::Ident(ident) => {
|
||||
let (_, ty_gens, _) = generics.split_for_impl();
|
||||
quote! { #ident#ty_gens }
|
||||
}
|
||||
};
|
||||
|
||||
quote! { for<#( #lifetimes ),*> #ty }
|
||||
} else {
|
||||
quote! { Self }
|
||||
};
|
||||
generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { #self_ty: Sync });
|
||||
|
||||
if scalar.is_generic() {
|
||||
generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { #scalar: Send + Sync });
|
||||
}
|
||||
}
|
||||
|
||||
(ty, generics)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds `__fa__` prefix to all lifetimes to avoid "lifetime name `'a` shadows a
|
||||
/// lifetime name that is already in scope" error.
|
||||
struct ModifyLifetimes;
|
||||
|
||||
impl VisitMut for ModifyLifetimes {
|
||||
fn visit_lifetime_mut(&mut self, lf: &mut syn::Lifetime) {
|
||||
lf.ident = format_ident!("__fa__{}", lf.ident.unraw());
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods representing [GraphQL scalar][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
#[allow(dead_code)]
|
||||
enum GraphQLScalarMethods {
|
||||
/// [GraphQL scalar][1] represented with only custom resolvers.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
Custom {
|
||||
/// Function provided with `#[graphql(to_output_with = ...)]`.
|
||||
to_output: syn::ExprPath,
|
||||
|
||||
/// Function provided with `#[graphql(from_input_with = ...)]`.
|
||||
from_input: syn::ExprPath,
|
||||
|
||||
/// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]`
|
||||
/// or `#[graphql(parse_token(...))]`.
|
||||
parse_token: ParseToken,
|
||||
},
|
||||
|
||||
/// [GraphQL scalar][1] maybe partially represented with custom resolver.
|
||||
/// Other methods are used from [`Field`].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
Delegated {
|
||||
/// Function provided with `#[graphql(to_output_with = ...)]`.
|
||||
to_output: Option<syn::ExprPath>,
|
||||
|
||||
/// Function provided with `#[graphql(from_input_with = ...)]`.
|
||||
from_input: Option<syn::ExprPath>,
|
||||
|
||||
/// [`ParseToken`] provided with `#[graphql(parse_token_with = ...)]`
|
||||
/// or `#[graphql(parse_token(...))]`.
|
||||
parse_token: Option<ParseToken>,
|
||||
|
||||
/// [`Field`] to resolve not provided methods.
|
||||
field: Box<Field>,
|
||||
},
|
||||
}
|
||||
|
||||
impl GraphQLScalarMethods {
|
||||
/// Expands [`GraphQLValue::resolve`] method.
|
||||
///
|
||||
/// [`GraphQLValue::resolve`]: juniper::GraphQLValue::resolve
|
||||
fn expand_resolve(&self, scalar: &scalar::Type) -> TokenStream {
|
||||
match self {
|
||||
Self::Custom { to_output, .. }
|
||||
| Self::Delegated {
|
||||
to_output: Some(to_output),
|
||||
..
|
||||
} => {
|
||||
quote! { Ok(#to_output(self)) }
|
||||
}
|
||||
Self::Delegated { field, .. } => {
|
||||
quote! {
|
||||
::juniper::GraphQLValue::<#scalar>::resolve(
|
||||
&self.#field,
|
||||
info,
|
||||
selection,
|
||||
executor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands [`ToInputValue::to_input_value`] method.
|
||||
///
|
||||
/// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value
|
||||
fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream {
|
||||
match self {
|
||||
Self::Custom { to_output, .. }
|
||||
| Self::Delegated {
|
||||
to_output: Some(to_output),
|
||||
..
|
||||
} => {
|
||||
quote! {
|
||||
let v = #to_output(self);
|
||||
::juniper::ToInputValue::to_input_value(&v)
|
||||
}
|
||||
}
|
||||
Self::Delegated { field, .. } => {
|
||||
quote! { ::juniper::ToInputValue::<#scalar>::to_input_value(&self.#field) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands [`FromInputValue::from_input_value`][1] method.
|
||||
///
|
||||
/// [1]: juniper::FromInputValue::from_input_value
|
||||
fn expand_from_input_value(&self, scalar: &scalar::Type) -> TokenStream {
|
||||
match self {
|
||||
Self::Custom { from_input, .. }
|
||||
| Self::Delegated {
|
||||
from_input: Some(from_input),
|
||||
..
|
||||
} => {
|
||||
quote! { #from_input(input) }
|
||||
}
|
||||
Self::Delegated { field, .. } => {
|
||||
let field_ty = field.ty();
|
||||
let self_constructor = field.closure_constructor();
|
||||
quote! {
|
||||
<#field_ty as ::juniper::FromInputValue<#scalar>>::from_input_value(input)
|
||||
.map(#self_constructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands [`ParseScalarValue::from_str`] method.
|
||||
///
|
||||
/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
|
||||
fn expand_parse_scalar_value(&self, scalar: &scalar::Type) -> TokenStream {
|
||||
match self {
|
||||
Self::Custom { parse_token, .. }
|
||||
| Self::Delegated {
|
||||
parse_token: Some(parse_token),
|
||||
..
|
||||
} => {
|
||||
let parse_token = parse_token.expand_from_str(scalar);
|
||||
quote! { #parse_token }
|
||||
}
|
||||
Self::Delegated { field, .. } => {
|
||||
let field_ty = field.ty();
|
||||
quote! { <#field_ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of [`ParseScalarValue::from_str`] method.
|
||||
///
|
||||
/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
|
||||
#[derive(Clone, Debug)]
|
||||
enum ParseToken {
|
||||
/// Custom method.
|
||||
Custom(syn::ExprPath),
|
||||
|
||||
/// Tries to parse using [`syn::Type`]s [`ParseScalarValue`] impls until
|
||||
/// first success.
|
||||
///
|
||||
/// [`ParseScalarValue`]: juniper::ParseScalarValue
|
||||
Delegated(Vec<syn::Type>),
|
||||
}
|
||||
|
||||
impl ParseToken {
|
||||
/// Expands [`ParseScalarValue::from_str`] method.
|
||||
///
|
||||
/// [`ParseScalarValue::from_str`]: juniper::ParseScalarValue::from_str
|
||||
fn expand_from_str(&self, scalar: &scalar::Type) -> TokenStream {
|
||||
match self {
|
||||
Self::Custom(parse_token) => {
|
||||
quote! { #parse_token(token) }
|
||||
}
|
||||
Self::Delegated(delegated) => delegated
|
||||
.iter()
|
||||
.fold(None, |acc, ty| {
|
||||
acc.map_or_else(
|
||||
|| Some(quote! { <#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token) }),
|
||||
|prev| {
|
||||
Some(quote! {
|
||||
#prev.or_else(|_| {
|
||||
<#ty as ::juniper::ParseScalarValue<#scalar>>::from_str(token)
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct field to resolve not provided methods.
|
||||
#[allow(dead_code)]
|
||||
enum Field {
|
||||
/// Named [`Field`].
|
||||
Named(syn::Field),
|
||||
|
||||
/// Unnamed [`Field`].
|
||||
Unnamed(syn::Field),
|
||||
}
|
||||
|
||||
impl ToTokens for Field {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Self::Named(f) => f.ident.to_tokens(tokens),
|
||||
Self::Unnamed(_) => tokens.append(Literal::u8_unsuffixed(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Field {
|
||||
/// [`syn::Type`] of this [`Field`].
|
||||
fn ty(&self) -> &syn::Type {
|
||||
match self {
|
||||
Self::Named(f) | Self::Unnamed(f) => &f.ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Closure to construct [GraphQL scalar][1] struct from [`Field`].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||
fn closure_constructor(&self) -> TokenStream {
|
||||
match self {
|
||||
Field::Named(syn::Field { ident, .. }) => {
|
||||
quote! { |v| Self { #ident: v } }
|
||||
}
|
||||
Field::Unnamed(_) => quote! { Self },
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
#![allow(clippy::collapsible_if)]
|
||||
|
||||
use crate::{
|
||||
result::GraphQLScope,
|
||||
util::{self, span_container::SpanContainer},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ScalarCodegenInput {
|
||||
impl_for_type: Option<syn::PathSegment>,
|
||||
custom_data_type: Option<syn::PathSegment>,
|
||||
custom_data_type_is_struct: bool,
|
||||
resolve_body: Option<syn::Block>,
|
||||
from_input_value_arg: Option<syn::Ident>,
|
||||
from_input_value_body: Option<syn::Block>,
|
||||
from_input_value_result: Option<syn::Type>,
|
||||
from_str_arg: Option<syn::Ident>,
|
||||
from_str_body: Option<syn::Block>,
|
||||
from_str_result: Option<syn::Type>,
|
||||
}
|
||||
|
||||
fn get_first_method_arg(
|
||||
inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
|
||||
) -> Option<syn::Ident> {
|
||||
if let Some(syn::FnArg::Typed(pat_type)) = inputs.first() {
|
||||
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||
return Some(pat_ident.ident.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_method_return_type(output: syn::ReturnType) -> Option<syn::Type> {
|
||||
match output {
|
||||
syn::ReturnType::Type(_, return_type) => Some(*return_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Find the enum type by inspecting the type parameter on the return value
|
||||
fn get_enum_type(return_type: &Option<syn::Type>) -> Option<syn::PathSegment> {
|
||||
if let Some(syn::Type::Path(type_path)) = return_type {
|
||||
let path_segment = type_path
|
||||
.path
|
||||
.segments
|
||||
.iter()
|
||||
.find(|ps| matches!(ps.arguments, syn::PathArguments::AngleBracketed(_)));
|
||||
|
||||
if let Some(path_segment) = path_segment {
|
||||
if let syn::PathArguments::AngleBracketed(generic_args) = &path_segment.arguments {
|
||||
let generic_type_arg = generic_args.args.iter().find(|generic_type_arg| {
|
||||
matches!(generic_type_arg, syn::GenericArgument::Type(_))
|
||||
});
|
||||
|
||||
if let Some(syn::GenericArgument::Type(syn::Type::Path(type_path))) =
|
||||
generic_type_arg
|
||||
{
|
||||
if let Some(path_segment) = type_path.path.segments.first() {
|
||||
return Some(path_segment.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for ScalarCodegenInput {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
|
||||
let mut impl_for_type: Option<syn::PathSegment> = None;
|
||||
let mut enum_data_type: Option<syn::PathSegment> = None;
|
||||
let mut resolve_body: Option<syn::Block> = None;
|
||||
let mut from_input_value_arg: Option<syn::Ident> = None;
|
||||
let mut from_input_value_body: Option<syn::Block> = None;
|
||||
let mut from_input_value_result: Option<syn::Type> = None;
|
||||
let mut from_str_arg: Option<syn::Ident> = None;
|
||||
let mut from_str_body: Option<syn::Block> = None;
|
||||
let mut from_str_result: Option<syn::Type> = None;
|
||||
|
||||
let parse_custom_scalar_value_impl: syn::ItemImpl = input.parse()?;
|
||||
// To implement a custom scalar for a struct, it's required to
|
||||
// specify a generic type and a type bound
|
||||
let custom_data_type_is_struct: bool =
|
||||
!parse_custom_scalar_value_impl.generics.params.is_empty();
|
||||
|
||||
let mut self_ty = *parse_custom_scalar_value_impl.self_ty;
|
||||
|
||||
while let syn::Type::Group(type_group) = self_ty {
|
||||
self_ty = *type_group.elem;
|
||||
}
|
||||
|
||||
if let syn::Type::Path(type_path) = self_ty {
|
||||
if let Some(path_segment) = type_path.path.segments.first() {
|
||||
impl_for_type = Some(path_segment.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for impl_item in parse_custom_scalar_value_impl.items {
|
||||
if let syn::ImplItem::Method(method) = impl_item {
|
||||
match method.sig.ident.to_string().as_str() {
|
||||
"resolve" => {
|
||||
resolve_body = Some(method.block);
|
||||
}
|
||||
"from_input_value" => {
|
||||
from_input_value_arg = get_first_method_arg(method.sig.inputs);
|
||||
from_input_value_result = get_method_return_type(method.sig.output);
|
||||
from_input_value_body = Some(method.block);
|
||||
}
|
||||
"from_str" => {
|
||||
from_str_arg = get_first_method_arg(method.sig.inputs);
|
||||
from_str_result = get_method_return_type(method.sig.output);
|
||||
|
||||
if !custom_data_type_is_struct {
|
||||
enum_data_type = get_enum_type(&from_str_result);
|
||||
}
|
||||
|
||||
from_str_body = Some(method.block);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let custom_data_type = if custom_data_type_is_struct {
|
||||
impl_for_type.clone()
|
||||
} else {
|
||||
enum_data_type
|
||||
};
|
||||
|
||||
Ok(ScalarCodegenInput {
|
||||
impl_for_type,
|
||||
custom_data_type,
|
||||
custom_data_type_is_struct,
|
||||
resolve_body,
|
||||
from_input_value_arg,
|
||||
from_input_value_body,
|
||||
from_input_value_result,
|
||||
from_str_arg,
|
||||
from_str_body,
|
||||
from_str_result,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code for the juniper::graphql_scalar proc macro.
|
||||
pub fn build_scalar(
|
||||
attributes: TokenStream,
|
||||
body: TokenStream,
|
||||
error: GraphQLScope,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let body_span = body.span();
|
||||
|
||||
let attrs = syn::parse2::<util::FieldAttributes>(attributes)?;
|
||||
let input = syn::parse2::<ScalarCodegenInput>(body)?;
|
||||
|
||||
let impl_for_type = input.impl_for_type.ok_or_else(|| {
|
||||
error.custom_error(
|
||||
body_span,
|
||||
"unable to find target for implementation target for `GraphQLScalar`",
|
||||
)
|
||||
})?;
|
||||
let custom_data_type = input
|
||||
.custom_data_type
|
||||
.ok_or_else(|| error.custom_error(body_span, "unable to find custom scalar data type"))?;
|
||||
let resolve_body = input
|
||||
.resolve_body
|
||||
.ok_or_else(|| error.custom_error(body_span, "unable to find body of `resolve` method"))?;
|
||||
let from_input_value_arg = input.from_input_value_arg.ok_or_else(|| {
|
||||
error.custom_error(
|
||||
body_span,
|
||||
"unable to find argument for `from_input_value` method",
|
||||
)
|
||||
})?;
|
||||
let from_input_value_body = input.from_input_value_body.ok_or_else(|| {
|
||||
error.custom_error(
|
||||
body_span,
|
||||
"unable to find body of `from_input_value` method",
|
||||
)
|
||||
})?;
|
||||
let from_input_value_result = input.from_input_value_result.ok_or_else(|| {
|
||||
error.custom_error(
|
||||
body_span,
|
||||
"unable to find return type of `from_input_value` method",
|
||||
)
|
||||
})?;
|
||||
let from_str_arg = input.from_str_arg.ok_or_else(|| {
|
||||
error.custom_error(body_span, "unable to find argument for `from_str` method")
|
||||
})?;
|
||||
let from_str_body = input
|
||||
.from_str_body
|
||||
.ok_or_else(|| error.custom_error(body_span, "unable to find body of `from_str` method"))?;
|
||||
let from_str_result = input.from_str_result.ok_or_else(|| {
|
||||
error.custom_error(body_span, "unable to find return type of `from_str` method")
|
||||
})?;
|
||||
|
||||
let name = attrs
|
||||
.name
|
||||
.map(SpanContainer::into_inner)
|
||||
.unwrap_or_else(|| impl_for_type.ident.to_string());
|
||||
let description = attrs.description.map(|val| quote!(.description(#val)));
|
||||
let specified_by_url = attrs.specified_by_url.map(|url| {
|
||||
let url_lit = url.as_str();
|
||||
quote!(.specified_by_url(#url_lit))
|
||||
});
|
||||
let async_generic_type = match input.custom_data_type_is_struct {
|
||||
true => quote!(__S),
|
||||
_ => quote!(#custom_data_type),
|
||||
};
|
||||
let async_generic_type_decl = match input.custom_data_type_is_struct {
|
||||
true => quote!(<#async_generic_type>),
|
||||
_ => quote!(),
|
||||
};
|
||||
let generic_type = match input.custom_data_type_is_struct {
|
||||
true => quote!(S),
|
||||
_ => quote!(#custom_data_type),
|
||||
};
|
||||
let generic_type_decl = match input.custom_data_type_is_struct {
|
||||
true => quote!(<#generic_type>),
|
||||
_ => quote!(),
|
||||
};
|
||||
let generic_type_bound = match input.custom_data_type_is_struct {
|
||||
true => quote!(where #generic_type: ::juniper::ScalarValue,),
|
||||
_ => quote!(),
|
||||
};
|
||||
|
||||
let _async = quote!(
|
||||
impl#async_generic_type_decl ::juniper::GraphQLValueAsync<#async_generic_type> for #impl_for_type
|
||||
where
|
||||
Self: Sync,
|
||||
Self::TypeInfo: Sync,
|
||||
Self::Context: Sync,
|
||||
#async_generic_type: ::juniper::ScalarValue + Send + Sync,
|
||||
{
|
||||
fn resolve_async<'a>(
|
||||
&'a self,
|
||||
info: &'a Self::TypeInfo,
|
||||
selection_set: Option<&'a [::juniper::Selection<#async_generic_type>]>,
|
||||
executor: &'a ::juniper::Executor<Self::Context, #async_generic_type>,
|
||||
) -> ::juniper::BoxFuture<'a, ::juniper::ExecutionResult<#async_generic_type>> {
|
||||
use ::juniper::futures::future;
|
||||
let v = ::juniper::GraphQLValue::resolve(self, info, selection_set, executor);
|
||||
Box::pin(future::ready(v))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let content = quote!(
|
||||
#_async
|
||||
|
||||
impl#generic_type_decl ::juniper::marker::IsInputType<#generic_type> for #impl_for_type
|
||||
#generic_type_bound { }
|
||||
|
||||
impl#generic_type_decl ::juniper::marker::IsOutputType<#generic_type> for #impl_for_type
|
||||
#generic_type_bound { }
|
||||
|
||||
impl#generic_type_decl ::juniper::GraphQLType<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
fn name(_: &Self::TypeInfo) -> Option<&'static str> {
|
||||
Some(#name)
|
||||
}
|
||||
|
||||
fn meta<'r>(
|
||||
info: &Self::TypeInfo,
|
||||
registry: &mut ::juniper::Registry<'r, #generic_type>,
|
||||
) -> ::juniper::meta::MetaType<'r, #generic_type>
|
||||
where
|
||||
#generic_type: 'r,
|
||||
{
|
||||
registry.build_scalar_type::<Self>(info)
|
||||
#description
|
||||
#specified_by_url
|
||||
.into_meta()
|
||||
}
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::GraphQLValue<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> {
|
||||
<Self as ::juniper::GraphQLType<#generic_type>>::name(info)
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
info: &(),
|
||||
selection: Option<&[::juniper::Selection<#generic_type>]>,
|
||||
executor: &::juniper::Executor<Self::Context, #generic_type>,
|
||||
) -> ::juniper::ExecutionResult<#generic_type> {
|
||||
Ok(#resolve_body)
|
||||
}
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::ToInputValue<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
fn to_input_value(&self) -> ::juniper::InputValue<#generic_type> {
|
||||
let v = #resolve_body;
|
||||
::juniper::ToInputValue::to_input_value(&v)
|
||||
}
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::FromInputValue<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
type Error = <#from_input_value_result as ::juniper::macros::helper::ExtractError>::Error;
|
||||
|
||||
fn from_input_value(#from_input_value_arg: &::juniper::InputValue<#generic_type>) -> #from_input_value_result {
|
||||
#from_input_value_body
|
||||
}
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::ParseScalarValue<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
fn from_str<'a>(
|
||||
#from_str_arg: ::juniper::parser::ScalarToken<'a>,
|
||||
) -> #from_str_result {
|
||||
#from_str_body
|
||||
}
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::macros::reflect::BaseType<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
const NAME: ::juniper::macros::reflect::Type = #name;
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::macros::reflect::BaseSubTypes<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
const NAMES: ::juniper::macros::reflect::Types =
|
||||
&[<Self as ::juniper::macros::reflect::BaseType<#generic_type>>::NAME];
|
||||
}
|
||||
|
||||
impl#generic_type_decl ::juniper::macros::reflect::WrappedType<#generic_type> for #impl_for_type
|
||||
#generic_type_bound
|
||||
{
|
||||
const VALUE: ::juniper::macros::reflect::WrappedValue = 1;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(content)
|
||||
}
|
|
@ -108,12 +108,11 @@ macro_rules! try_merge_hashset {
|
|||
|
||||
mod derive_enum;
|
||||
mod derive_input_object;
|
||||
mod derive_scalar_value;
|
||||
mod impl_scalar;
|
||||
|
||||
mod common;
|
||||
mod graphql_interface;
|
||||
mod graphql_object;
|
||||
mod graphql_scalar;
|
||||
mod graphql_subscription;
|
||||
mod graphql_union;
|
||||
|
||||
|
@ -143,126 +142,116 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
|
||||
/// derive.
|
||||
/// `#[graphql_scalar]` is interchangeable with `#[derive(`[`GraphQLScalar`]`)]`
|
||||
/// macro:
|
||||
///
|
||||
/// This can be used for two purposes.
|
||||
///
|
||||
/// ## Transparent Newtype Wrapper
|
||||
///
|
||||
/// Sometimes, you want to create a custerm scalar type by wrapping
|
||||
/// an existing type. In Rust, this is often called the "newtype" pattern.
|
||||
/// Thanks to this custom derive, this becomes really easy:
|
||||
///
|
||||
/// ```rust
|
||||
/// // Deriving GraphQLScalar is all that is required.
|
||||
/// #[derive(juniper::GraphQLScalarValue)]
|
||||
/// struct UserId(String);
|
||||
///
|
||||
/// #[derive(juniper::GraphQLObject)]
|
||||
/// struct User {
|
||||
/// id: UserId,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The type can also be customized.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// /// Doc comments are used for the GraphQL type description.
|
||||
/// #[derive(juniper::GraphQLScalarValue)]
|
||||
/// #[derive(juniper::GraphQLScalar)]
|
||||
/// #[graphql(
|
||||
/// transparent,
|
||||
/// // Set a custom GraphQL name.
|
||||
/// name= "MyUserId",
|
||||
/// // A description can also specified in the attribute.
|
||||
/// // This will the doc comment, if one exists.
|
||||
/// description = "...",
|
||||
/// // A specification URL.
|
||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
/// // Set a custom GraphQL name.
|
||||
/// name = "MyUserId",
|
||||
/// // A description can also specified in the attribute.
|
||||
/// // This will the doc comment, if one exists.
|
||||
/// description = "...",
|
||||
/// // A specification URL.
|
||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
/// // Explicit generic scalar.
|
||||
/// scalar = S: juniper::ScalarValue,
|
||||
/// transparent,
|
||||
/// )]
|
||||
/// struct UserId(String);
|
||||
/// ```
|
||||
///
|
||||
/// ### Base ScalarValue Enum
|
||||
/// Is transformed into:
|
||||
///
|
||||
/// TODO: write documentation.
|
||||
/// ```rust,ignore
|
||||
/// /// Doc comments are used for the GraphQL type description.
|
||||
/// #[juniper::graphql_scalar(
|
||||
/// // Set a custom GraphQL name.
|
||||
/// name = "MyUserId",
|
||||
/// // A description can also specified in the attribute.
|
||||
/// // This will the doc comment, if one exists.
|
||||
/// description = "...",
|
||||
/// // A specification URL.
|
||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
/// // Explicit generic scalar.
|
||||
/// scalar = S: juniper::ScalarValue,
|
||||
/// transparent,
|
||||
/// )]
|
||||
/// struct UserId(String);
|
||||
/// ```
|
||||
///
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))]
|
||||
pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||
let gen = derive_scalar_value::impl_scalar_value(&ast, GraphQLScope::DeriveScalar);
|
||||
match gen {
|
||||
Ok(gen) => gen.into(),
|
||||
Err(err) => proc_macro_error::abort!(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expose GraphQL scalars
|
||||
/// In addition to that `#[graphql_scalar]` can be used in case
|
||||
/// [`GraphQLScalar`] isn't applicable because type located in other crate and
|
||||
/// you don't want to wrap it in a newtype. This is done by placing
|
||||
/// `#[graphql_scalar]` on a type alias.
|
||||
///
|
||||
/// The GraphQL language defines a number of built-in scalars: strings, numbers, and
|
||||
/// booleans. This macro can be used either to define new types of scalars (e.g.
|
||||
/// timestamps), or expose other types as one of the built-in scalars (e.g. bigints
|
||||
/// as numbers or strings).
|
||||
/// All attributes are mirroring [`GraphQLScalar`] derive macro.
|
||||
///
|
||||
/// Since the preferred transport protocol for GraphQL responses is JSON, most
|
||||
/// custom scalars will be transferred as strings. You therefore need to ensure that
|
||||
/// the client library you are sending data to can parse the custom value into a
|
||||
/// datatype appropriate for that platform.
|
||||
///
|
||||
/// By default the trait is implemented in terms of the default scalar value
|
||||
/// representation provided by juniper. If that does not fit your needs it is
|
||||
/// possible to specify a custom representation.
|
||||
/// > __NOTE:__ To satisfy [orphan rules] you should provide local
|
||||
/// > [`ScalarValue`] implementation.
|
||||
///
|
||||
/// ```rust
|
||||
/// // The data type
|
||||
/// struct UserID(String);
|
||||
/// # mod date {
|
||||
/// # pub struct Date;
|
||||
/// #
|
||||
/// # impl std::str::FromStr for Date {
|
||||
/// # type Err = String;
|
||||
/// #
|
||||
/// # fn from_str(_value: &str) -> Result<Self, Self::Err> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl std::fmt::Display for Date {
|
||||
/// # fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # use juniper::DefaultScalarValue as CustomScalarValue;
|
||||
/// use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||
///
|
||||
/// #[juniper::graphql_scalar(
|
||||
/// // You can rename the type for GraphQL by specifying the name here.
|
||||
/// name = "MyName",
|
||||
/// // You can also specify a description here.
|
||||
/// // If present, doc comments will be ignored.
|
||||
/// description = "An opaque identifier, represented as a string",
|
||||
/// // A specification URL.
|
||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
/// #[graphql_scalar(
|
||||
/// with = date_scalar,
|
||||
/// parse_token(String),
|
||||
/// scalar = CustomScalarValue,
|
||||
/// // ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
|
||||
/// )]
|
||||
/// impl<S> GraphQLScalar for UserID
|
||||
/// where
|
||||
/// S: juniper::ScalarValue
|
||||
/// {
|
||||
/// fn resolve(&self) -> juniper::Value {
|
||||
/// juniper::Value::scalar(self.0.to_owned())
|
||||
/// type Date = date::Date;
|
||||
/// // ^^^^^^^^^^ Type from another crate.
|
||||
///
|
||||
/// mod date_scalar {
|
||||
/// use super::*;
|
||||
///
|
||||
/// // Define how to convert your custom scalar into a primitive type.
|
||||
/// pub(super) fn to_output(v: &Date) -> Value<CustomScalarValue> {
|
||||
/// Value::scalar(v.to_string())
|
||||
/// }
|
||||
///
|
||||
/// // Define how to parse a primitive type into your custom scalar.
|
||||
/// // NOTE: The error type should implement `IntoFieldError<S>`.
|
||||
/// fn from_input_value(value: &juniper::InputValue) -> Result<UserID, String> {
|
||||
/// value.as_string_value()
|
||||
/// .map(|s| UserID(s.to_owned()))
|
||||
/// .ok_or_else(|| format!("Expected `String`, found: {}", value))
|
||||
/// }
|
||||
///
|
||||
/// fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
|
||||
/// <String as juniper::ParseScalarValue<S>>::from_str(value)
|
||||
/// pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
|
||||
/// v.as_string_value()
|
||||
/// .ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||
/// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// 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.
|
||||
/// [orphan rules]: https://bit.ly/3glAGC2
|
||||
/// [`GraphQLScalar`]: juniper::GraphQLScalar
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let args = proc_macro2::TokenStream::from(args);
|
||||
let input = proc_macro2::TokenStream::from(input);
|
||||
let gen = impl_scalar::build_scalar(args, input, GraphQLScope::ImplScalar);
|
||||
match gen {
|
||||
Ok(gen) => gen.into(),
|
||||
Err(err) => proc_macro_error::abort!(err),
|
||||
}
|
||||
pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream {
|
||||
graphql_scalar::attr::expand(attr.into(), body.into())
|
||||
.unwrap_or_abort()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
|
||||
|
|
|
@ -13,12 +13,13 @@ pub enum GraphQLScope {
|
|||
InterfaceAttr,
|
||||
ObjectAttr,
|
||||
ObjectDerive,
|
||||
ScalarAttr,
|
||||
#[allow(dead_code)]
|
||||
ScalarDerive,
|
||||
UnionAttr,
|
||||
UnionDerive,
|
||||
DeriveInputObject,
|
||||
DeriveEnum,
|
||||
DeriveScalar,
|
||||
ImplScalar,
|
||||
}
|
||||
|
||||
impl GraphQLScope {
|
||||
|
@ -26,10 +27,10 @@ impl GraphQLScope {
|
|||
match self {
|
||||
Self::InterfaceAttr => "#sec-Interfaces",
|
||||
Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
|
||||
Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars",
|
||||
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
|
||||
Self::DeriveInputObject => "#sec-Input-Objects",
|
||||
Self::DeriveEnum => "#sec-Enums",
|
||||
Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +40,11 @@ impl fmt::Display for GraphQLScope {
|
|||
let name = match self {
|
||||
Self::InterfaceAttr => "interface",
|
||||
Self::ObjectAttr | Self::ObjectDerive => "object",
|
||||
Self::ScalarAttr | Self::ScalarDerive => "scalar",
|
||||
Self::UnionAttr | Self::UnionDerive => "union",
|
||||
Self::DeriveInputObject => "input object",
|
||||
Self::DeriveEnum => "enum",
|
||||
Self::DeriveScalar | Self::ImplScalar => "scalar",
|
||||
};
|
||||
|
||||
write!(f, "GraphQL {}", name)
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ impl GraphQLScope {
|
|||
duplicates
|
||||
.into_iter()
|
||||
.for_each(|dup| {
|
||||
(&dup.spanned[1..])
|
||||
dup.spanned[1..]
|
||||
.iter()
|
||||
.for_each(|spanned| {
|
||||
Diagnostic::spanned(
|
||||
|
|
|
@ -17,7 +17,6 @@ use syn::{
|
|||
spanned::Spanned,
|
||||
token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::common::parse::ParseBufferExt as _;
|
||||
|
||||
|
@ -455,7 +454,6 @@ pub enum FieldAttributeParseMode {
|
|||
enum FieldAttribute {
|
||||
Name(SpanContainer<syn::LitStr>),
|
||||
Description(SpanContainer<syn::LitStr>),
|
||||
SpecifiedByUrl(SpanContainer<syn::LitStr>),
|
||||
Deprecation(SpanContainer<DeprecationAttr>),
|
||||
Skip(SpanContainer<syn::Ident>),
|
||||
Arguments(HashMap<String, FieldAttributeArgument>),
|
||||
|
@ -490,15 +488,6 @@ impl Parse for FieldAttribute {
|
|||
lit,
|
||||
)))
|
||||
}
|
||||
"specified_by_url" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let lit = input.parse::<syn::LitStr>()?;
|
||||
Ok(FieldAttribute::SpecifiedByUrl(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(lit.span()),
|
||||
lit,
|
||||
)))
|
||||
}
|
||||
"deprecated" | "deprecation" => {
|
||||
let reason = if input.peek(token::Eq) {
|
||||
input.parse::<token::Eq>()?;
|
||||
|
@ -553,8 +542,6 @@ pub struct FieldAttributes {
|
|||
pub name: Option<SpanContainer<String>>,
|
||||
pub description: Option<SpanContainer<String>>,
|
||||
pub deprecation: Option<SpanContainer<DeprecationAttr>>,
|
||||
/// Only relevant for scalar impl macro.
|
||||
pub specified_by_url: Option<SpanContainer<Url>>,
|
||||
/// Only relevant for GraphQLObject derive.
|
||||
pub skip: Option<SpanContainer<syn::Ident>>,
|
||||
/// Only relevant for object macro.
|
||||
|
@ -577,18 +564,6 @@ impl Parse for FieldAttributes {
|
|||
FieldAttribute::Description(name) => {
|
||||
output.description = Some(name.map(|val| val.value()));
|
||||
}
|
||||
FieldAttribute::SpecifiedByUrl(url) => {
|
||||
output.specified_by_url = Some(
|
||||
url.map(|val| Url::parse(&val.value()))
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
syn::Error::new(
|
||||
e.span_ident(),
|
||||
format!("Invalid URL: {}", e.inner()),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
}
|
||||
FieldAttribute::Deprecation(attr) => {
|
||||
output.deprecation = Some(attr);
|
||||
}
|
||||
|
|
|
@ -58,15 +58,6 @@ impl<T> SpanContainer<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, E> SpanContainer<Result<T, E>> {
|
||||
pub fn transpose(self) -> Result<SpanContainer<T>, SpanContainer<E>> {
|
||||
match self.val {
|
||||
Ok(v) => Ok(SpanContainer::new(self.ident, self.expr, v)),
|
||||
Err(e) => Err(SpanContainer::new(self.ident, self.expr, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for SpanContainer<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.val
|
||||
|
|
Loading…
Reference in a new issue