- 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
|
* url::Url
|
||||||
* bson::oid::ObjectId
|
* bson::oid::ObjectId
|
||||||
|
|
||||||
## newtype pattern
|
|
||||||
|
|
||||||
|
|
||||||
|
## Custom scalars
|
||||||
|
|
||||||
|
### `#[graphql(transparent)]` attribute
|
||||||
|
|
||||||
Often, you might need a custom scalar that just wraps an existing type.
|
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
|
This can be done with the newtype pattern and a custom derive, similar to how
|
||||||
serde supports this pattern with `#[serde(transparent)]`.
|
serde supports this pattern with `#[serde(transparent)]`.
|
||||||
|
|
||||||
```rust
|
```rust,ignore
|
||||||
# extern crate juniper;
|
# extern crate juniper;
|
||||||
#[derive(juniper::GraphQLScalarValue)]
|
#
|
||||||
|
#[derive(juniper::GraphQLScalar)]
|
||||||
|
#[graphql(transparent)]
|
||||||
pub struct UserId(i32);
|
pub struct UserId(i32);
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
struct User {
|
struct User {
|
||||||
id: UserId,
|
id: UserId,
|
||||||
}
|
}
|
||||||
|
#
|
||||||
# fn main() {}
|
# 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:
|
The macro also allows for more customization:
|
||||||
|
|
||||||
```rust
|
```rust,ignore
|
||||||
# extern crate juniper;
|
# extern crate juniper;
|
||||||
/// You can use a doc comment to specify a description.
|
/// You can use a doc comment to specify a description.
|
||||||
#[derive(juniper::GraphQLScalarValue)]
|
#[derive(juniper::GraphQLScalar)]
|
||||||
#[graphql(
|
#[graphql(
|
||||||
transparent,
|
transparent,
|
||||||
// Overwrite the GraphQL type name.
|
// Overwrite the GraphQL type name.
|
||||||
|
@ -77,37 +104,276 @@ The macro also allows for more customization:
|
||||||
description = "My user id description",
|
description = "My user id description",
|
||||||
)]
|
)]
|
||||||
pub struct UserId(i32);
|
pub struct UserId(i32);
|
||||||
|
#
|
||||||
# fn main() {}
|
# 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,
|
### `#[graphql(to_output_with = <fn>)]` attribute
|
||||||
you can use the `graphql_scalar` proc macro.
|
|
||||||
|
|
||||||
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
|
### `#[graphql(from_input_with = <fn>)]` attribute
|
||||||
via `chrono` feature, which is enabled by default and should be used for this
|
|
||||||
purpose.
|
|
||||||
|
|
||||||
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
|
impl UserId {
|
||||||
`std::fmt::Display` and `std::str::FromStr`.
|
/// 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
|
```rust
|
||||||
# extern crate juniper;
|
# extern crate juniper;
|
||||||
# mod date {
|
# mod date {
|
||||||
# pub struct Date;
|
# pub struct Date;
|
||||||
# impl std::str::FromStr for 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 {
|
# impl std::fmt::Display for Date {
|
||||||
# fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
# fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
|
@ -115,32 +381,34 @@ The example below is used just for illustration.
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
use juniper::{Value, ParseScalarResult, ParseScalarValue};
|
# use juniper::DefaultScalarValue as CustomScalarValue;
|
||||||
use date::Date;
|
use juniper::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
#[juniper::graphql_scalar(description = "Date")]
|
#[graphql_scalar(
|
||||||
impl<S> GraphQLScalar for Date
|
with = date_scalar,
|
||||||
where
|
parse_token(String),
|
||||||
S: ScalarValue
|
scalar = CustomScalarValue,
|
||||||
{
|
// ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
|
||||||
// Define how to convert your custom scalar into a primitive type.
|
)]
|
||||||
fn resolve(&self) -> Value {
|
type Date = date::Date;
|
||||||
Value::scalar(self.to_string())
|
// ^^^^^^^^^^ 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.
|
pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
|
||||||
// NOTE: The error type should implement `IntoFieldError<S>`.
|
v.as_string_value()
|
||||||
fn from_input_value(v: &InputValue) -> Result<Date, String> {
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
v.as_string_value()
|
.and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#
|
#
|
||||||
# fn main() {}
|
# 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>`
|
error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValueEnum<ObjA, ObjA>`
|
||||||
--> fail/interface/implementers_duplicate_ugly.rs:11:1
|
--> 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>`
|
| 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)
|
= 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
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = "0.4"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
juniper = { path = "../../juniper" }
|
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::{
|
use juniper::{
|
||||||
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue,
|
||||||
EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject,
|
Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLUnion,
|
||||||
GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue,
|
IntoFieldError, ScalarValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation<C>, EmptySubscription<C>>
|
use crate::util::{schema, schema_with_scalar};
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod no_implers {
|
mod no_implers {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
mod derive_enum;
|
mod derive_enum;
|
||||||
mod derive_input_object;
|
mod derive_input_object;
|
||||||
mod derive_object_with_raw_idents;
|
mod derive_object_with_raw_idents;
|
||||||
mod derive_scalar;
|
|
||||||
mod impl_scalar;
|
|
||||||
mod interface_attr;
|
mod interface_attr;
|
||||||
mod object_attr;
|
mod object_attr;
|
||||||
mod object_derive;
|
mod object_derive;
|
||||||
mod scalar_value_transparent;
|
mod scalar_attr_derive_input;
|
||||||
|
mod scalar_attr_type_alias;
|
||||||
mod subscription_attr;
|
mod subscription_attr;
|
||||||
mod union_attr;
|
mod union_attr;
|
||||||
mod union_derive;
|
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,
|
graphql_vars,
|
||||||
parser::{ParseError, ScalarToken, Token},
|
parser::{ParseError, ScalarToken, Token},
|
||||||
serde::{de, Deserialize, Deserializer, Serialize},
|
serde::{de, Deserialize, Deserializer, Serialize},
|
||||||
EmptyMutation, FieldResult, GraphQLScalarValue, InputValue, Object, ParseScalarResult,
|
EmptyMutation, FieldResult, InputValue, Object, ParseScalarResult, RootNode, ScalarValue,
|
||||||
RootNode, ScalarValue, Value, Variables,
|
Value, Variables,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(GraphQLScalarValue, Clone, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub(crate) enum MyScalarValue {
|
pub(crate) enum MyScalarValue {
|
||||||
Int(i32),
|
Int(i32),
|
||||||
|
@ -20,6 +20,149 @@ pub(crate) enum MyScalarValue {
|
||||||
Boolean(bool),
|
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 {
|
impl ScalarValue for MyScalarValue {
|
||||||
fn as_int(&self) -> Option<i32> {
|
fn as_int(&self) -> Option<i32> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -132,19 +275,23 @@ impl<'de> Deserialize<'de> for MyScalarValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[graphql_scalar(name = "Long")]
|
#[graphql_scalar(with = long, scalar = MyScalarValue)]
|
||||||
impl GraphQLScalar for i64 {
|
type Long = i64;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(*self)
|
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>()
|
v.as_scalar_value::<i64>()
|
||||||
.copied()
|
.copied()
|
||||||
.ok_or_else(|| format!("Expected `MyScalarValue::Long`, found: {}", v))
|
.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 {
|
if let ScalarToken::Int(v) = value {
|
||||||
v.parse()
|
v.parse()
|
||||||
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
|
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||||
|
|
|
@ -37,7 +37,37 @@ mod pre_parse;
|
||||||
/// Common utilities used across tests.
|
/// Common utilities used across tests.
|
||||||
pub(crate) mod util {
|
pub(crate) mod util {
|
||||||
use futures::StreamExt as _;
|
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
|
/// Extracts a single next value from the result returned by
|
||||||
/// [`juniper::resolve_into_stream()`] and transforms it into a regular
|
/// [`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).
|
- 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.
|
- Forbid default impls on non-ignored trait methods.
|
||||||
- Support coercion of additional nullable arguments and return sub-typing on implementer.
|
- 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
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -1252,7 +1252,7 @@ impl<'r, S: 'r> Registry<'r, S> {
|
||||||
/// Creates a [`ScalarMeta`] type.
|
/// Creates a [`ScalarMeta`] type.
|
||||||
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
|
pub fn build_scalar_type<T>(&mut self, info: &T::TypeInfo) -> ScalarMeta<'r, S>
|
||||||
where
|
where
|
||||||
T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S> + 'r,
|
T: GraphQLType<S> + FromInputValue<S> + ParseScalarValue<S>,
|
||||||
T::Error: IntoFieldError<S>,
|
T::Error: IntoFieldError<S>,
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,8 +9,7 @@ use crate::{
|
||||||
graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars,
|
graphql_interface, graphql_object, graphql_scalar, graphql_value, graphql_vars,
|
||||||
schema::model::RootNode,
|
schema::model::RootNode,
|
||||||
types::scalars::{EmptyMutation, EmptySubscription},
|
types::scalars::{EmptyMutation, EmptySubscription},
|
||||||
value::{ParseScalarResult, ParseScalarValue, Value},
|
GraphQLEnum, InputValue, ScalarValue, Value,
|
||||||
GraphQLEnum,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(GraphQLEnum)]
|
#[derive(GraphQLEnum)]
|
||||||
|
@ -20,23 +19,20 @@ enum Sample {
|
||||||
Two,
|
Two,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
|
||||||
|
#[graphql_scalar(name = "SampleScalar", parse_token(i32))]
|
||||||
struct Scalar(i32);
|
struct Scalar(i32);
|
||||||
|
|
||||||
#[graphql_scalar(name = "SampleScalar")]
|
impl Scalar {
|
||||||
impl<S: ScalarValue> GraphQLScalar for Scalar {
|
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.0)
|
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()
|
v.as_int_value()
|
||||||
.map(Scalar)
|
.map(Self)
|
||||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
.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
|
/// A sample interface
|
||||||
|
|
|
@ -5,30 +5,27 @@ use crate::{
|
||||||
schema::model::RootNode,
|
schema::model::RootNode,
|
||||||
types::scalars::{EmptyMutation, EmptySubscription},
|
types::scalars::{EmptyMutation, EmptySubscription},
|
||||||
validation::RuleError,
|
validation::RuleError,
|
||||||
value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue},
|
value::{DefaultScalarValue, Object},
|
||||||
GraphQLError::ValidationError,
|
GraphQLError::ValidationError,
|
||||||
GraphQLInputObject,
|
GraphQLInputObject, InputValue, ScalarValue, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Use `#[derive(GraphQLScalar)]` once implemented.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[graphql_scalar(parse_token(String))]
|
||||||
struct TestComplexScalar;
|
struct TestComplexScalar;
|
||||||
|
|
||||||
#[graphql_scalar]
|
impl TestComplexScalar {
|
||||||
impl<S: ScalarValue> GraphQLScalar for TestComplexScalar {
|
fn to_output<S: ScalarValue>(&self) -> Value<S> {
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
graphql_value!("SerializedValue")
|
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()
|
v.as_string_value()
|
||||||
.filter(|s| *s == "SerializedValue")
|
.filter(|s| *s == "SerializedValue")
|
||||||
.map(|_| TestComplexScalar)
|
.map(|_| Self)
|
||||||
.ok_or_else(|| format!(r#"Expected "SerializedValue" string, found: {}"#, v))
|
.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)]
|
#[derive(GraphQLInputObject, Debug)]
|
||||||
|
|
|
@ -1,66 +1,46 @@
|
||||||
//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
|
//! GraphQL support for [bson](https://github.com/mongodb/bson-rust) types.
|
||||||
|
|
||||||
use bson::{oid::ObjectId, DateTime as UtcDateTime};
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
graphql_scalar,
|
|
||||||
parser::{ParseError, ScalarToken, Token},
|
|
||||||
value::ParseScalarResult,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[graphql_scalar(description = "ObjectId")]
|
#[graphql_scalar(with = object_id, parse_token(String))]
|
||||||
impl<S> GraphQLScalar for ObjectId
|
type ObjectId = bson::oid::ObjectId;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod object_id {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.to_hex())
|
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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.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")]
|
#[graphql_scalar(with = utc_date_time, parse_token(String))]
|
||||||
impl<S> GraphQLScalar for UtcDateTime
|
type UtcDateTime = bson::DateTime;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod utc_date_time {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar((*self).to_chrono().to_rfc3339())
|
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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
s.parse::<DateTime<Utc>>()
|
s.parse::<DateTime<Utc>>()
|
||||||
.map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e))
|
.map_err(|e| format!("Failed to parse `UtcDateTime`: {}", e))
|
||||||
})
|
})
|
||||||
.map(Self::from_chrono)
|
.map(UtcDateTime::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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +52,7 @@ mod test {
|
||||||
use crate::{graphql_input_value, FromInputValue, InputValue};
|
use crate::{graphql_input_value, FromInputValue, InputValue};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn objectid_from_input_value() {
|
fn objectid_from_input() {
|
||||||
let raw = "53e37d08776f724e42000000";
|
let raw = "53e37d08776f724e42000000";
|
||||||
let input: InputValue = graphql_input_value!((raw));
|
let input: InputValue = graphql_input_value!((raw));
|
||||||
|
|
||||||
|
@ -83,7 +63,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn utcdatetime_from_input_value() {
|
fn utcdatetime_from_input() {
|
||||||
let raw = "2020-03-23T17:38:32.446+00:00";
|
let raw = "2020-03-23T17:38:32.446+00:00";
|
||||||
let input: InputValue = graphql_input_value!((raw));
|
let input: InputValue = graphql_input_value!((raw));
|
||||||
|
|
||||||
|
|
|
@ -16,66 +16,48 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
#![allow(clippy::needless_lifetimes)]
|
#![allow(clippy::needless_lifetimes)]
|
||||||
use chrono::prelude::*;
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
use crate::{
|
#[graphql_scalar(with = date_time_fixed_offset, parse_token(String))]
|
||||||
parser::{ParseError, ScalarToken, Token},
|
type DateTimeFixedOffset = chrono::DateTime<chrono::FixedOffset>;
|
||||||
value::{ParseScalarResult, ParseScalarValue},
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[crate::graphql_scalar(name = "DateTimeFixedOffset", description = "DateTime")]
|
mod date_time_fixed_offset {
|
||||||
impl<S> GraphQLScalar for DateTime<FixedOffset>
|
use super::*;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
pub(super) fn to_output<S: ScalarValue>(v: &DateTimeFixedOffset) -> Value<S> {
|
||||||
{
|
Value::scalar(v.to_rfc3339())
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
DateTime::parse_from_rfc3339(s)
|
DateTimeFixedOffset::parse_from_rfc3339(s)
|
||||||
.map_err(|e| format!("Failed to parse `DateTimeFixedOffset`: {}", e))
|
.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")]
|
#[graphql_scalar(with = date_time_utc, parse_token(String))]
|
||||||
impl<S> GraphQLScalar for DateTime<Utc>
|
type DateTimeUtc = chrono::DateTime<chrono::Utc>;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod date_time_utc {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.to_rfc3339())
|
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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
s.parse::<DateTime<Utc>>()
|
s.parse::<DateTimeUtc>()
|
||||||
.map_err(|e| format!("Failed to parse `DateTimeUtc`: {}", e))
|
.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:
|
// Don't use `Date` as the docs say:
|
||||||
|
@ -83,16 +65,17 @@ where
|
||||||
// inherent lack of precision required for the time zone resolution.
|
// inherent lack of precision required for the time zone resolution.
|
||||||
// For serialization and deserialization uses, it is best to use
|
// For serialization and deserialization uses, it is best to use
|
||||||
// `NaiveDate` instead."
|
// `NaiveDate` instead."
|
||||||
#[crate::graphql_scalar(description = "NaiveDate")]
|
#[graphql_scalar(with = naive_date, parse_token(String))]
|
||||||
impl<S> GraphQLScalar for NaiveDate
|
type NaiveDate = chrono::NaiveDate;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod naive_date {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.format("%Y-%m-%d").to_string())
|
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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
|
@ -100,27 +83,21 @@ where
|
||||||
.map_err(|e| format!("Failed to parse `NaiveDate`: {}", e))
|
.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")]
|
#[cfg(feature = "scalar-naivetime")]
|
||||||
#[crate::graphql_scalar(description = "NaiveTime")]
|
#[graphql_scalar(with = naive_time, parse_token(String))]
|
||||||
impl<S> GraphQLScalar for NaiveTime
|
type NaiveTime = chrono::NaiveTime;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
#[cfg(feature = "scalar-naivetime")]
|
||||||
{
|
mod naive_time {
|
||||||
fn resolve(&self) -> Value {
|
use super::*;
|
||||||
Value::scalar(self.format("%H:%M:%S").to_string())
|
|
||||||
|
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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
|
@ -128,28 +105,21 @@ where
|
||||||
.map_err(|e| format!("Failed to parse `NaiveTime`: {}", e))
|
.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
|
// JSON numbers (i.e. IEEE doubles) are not precise enough for nanosecond
|
||||||
// datetimes. Values will be truncated to microsecond resolution.
|
// datetimes. Values will be truncated to microsecond resolution.
|
||||||
#[crate::graphql_scalar(description = "NaiveDateTime")]
|
#[graphql_scalar(with = naive_date_time, parse_token(f64))]
|
||||||
impl<S> GraphQLScalar for NaiveDateTime
|
type NaiveDateTime = chrono::NaiveDateTime;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod naive_date_time {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.timestamp() as f64)
|
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()
|
v.as_float_value()
|
||||||
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
|
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
|
||||||
.and_then(|f| {
|
.and_then(|f| {
|
||||||
|
@ -158,10 +128,6 @@ where
|
||||||
.ok_or_else(|| format!("Out-of-range number of seconds: {}", secs))
|
.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)]
|
#[cfg(test)]
|
||||||
|
@ -180,17 +146,17 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn datetime_fixedoffset_from_input_value() {
|
fn datetime_fixedoffset_from_input() {
|
||||||
datetime_fixedoffset_test("2014-11-28T21:00:09+09:00");
|
datetime_fixedoffset_test("2014-11-28T21:00:09+09:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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");
|
datetime_fixedoffset_test("2014-11-28T21:00:09Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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");
|
datetime_fixedoffset_test("2014-11-28T21:00:09.05+09:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,22 +172,22 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn datetime_utc_from_input_value() {
|
fn datetime_utc_from_input() {
|
||||||
datetime_utc_test("2014-11-28T21:00:09+09:00")
|
datetime_utc_test("2014-11-28T21:00:09+09:00")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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")
|
datetime_utc_test("2014-11-28T21:00:09Z")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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");
|
datetime_utc_test("2014-11-28T21:00:09.005+09:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn naivedate_from_input_value() {
|
fn naivedate_from_input() {
|
||||||
let input: InputValue = graphql_input_value!("1996-12-19");
|
let input: InputValue = graphql_input_value!("1996-12-19");
|
||||||
let y = 1996;
|
let y = 1996;
|
||||||
let m = 12;
|
let m = 12;
|
||||||
|
@ -239,7 +205,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "scalar-naivetime")]
|
#[cfg(feature = "scalar-naivetime")]
|
||||||
fn naivetime_from_input_value() {
|
fn naivetime_from_input() {
|
||||||
let input: InputValue = graphql_input_value!("21:12:19");
|
let input: InputValue = graphql_input_value!("21:12:19");
|
||||||
let [h, m, s] = [21, 12, 19];
|
let [h, m, s] = [21, 12, 19];
|
||||||
let parsed: NaiveTime = FromInputValue::from_input_value(&input).unwrap();
|
let parsed: NaiveTime = FromInputValue::from_input_value(&input).unwrap();
|
||||||
|
@ -251,7 +217,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn naivedatetime_from_input_value() {
|
fn naivedatetime_from_input() {
|
||||||
let raw = 1_000_000_000_f64;
|
let raw = 1_000_000_000_f64;
|
||||||
let input: InputValue = graphql_input_value!((raw));
|
let input: InputValue = graphql_input_value!((raw));
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,19 @@
|
||||||
//! [`Tz`]: chrono_tz::Tz
|
//! [`Tz`]: chrono_tz::Tz
|
||||||
//! [1]: http://www.iana.org/time-zones
|
//! [1]: http://www.iana.org/time-zones
|
||||||
|
|
||||||
use chrono_tz::Tz;
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
use crate::{
|
#[graphql_scalar(with = tz, parse_token(String))]
|
||||||
graphql_scalar,
|
type Tz = chrono_tz::Tz;
|
||||||
parser::{ParseError, ScalarToken, Token},
|
|
||||||
value::ParseScalarResult,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[graphql_scalar(name = "Tz", description = "Timezone")]
|
mod tz {
|
||||||
impl<S> GraphQLScalar for Tz
|
use super::*;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
pub(super) fn to_output<S: ScalarValue>(v: &Tz) -> Value<S> {
|
||||||
{
|
Value::scalar(v.name().to_owned())
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
|
@ -29,32 +23,22 @@ where
|
||||||
.map_err(|e| format!("Failed to parse `Tz`: {}", e))
|
.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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
mod from_input_value {
|
mod from_input {
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use chrono_tz::Tz;
|
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>) {
|
fn tz_input_test(raw: &'static str, expected: Result<Tz, &str>) {
|
||||||
let input: InputValue = graphql_input_value!((raw));
|
let input: InputValue = graphql_input_value!((raw));
|
||||||
let parsed = FromInputValue::from_input_value(&input);
|
let parsed = FromInputValue::from_input_value(&input);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed.as_ref().map_err(Deref::deref),
|
parsed.as_ref(),
|
||||||
expected.as_ref().map_err(Deref::deref),
|
expected.map_err(IntoFieldError::into_field_error).as_ref(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,59 +26,45 @@ use time::{
|
||||||
macros::format_description,
|
macros::format_description,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
graphql_scalar,
|
|
||||||
parser::{ParseError, ScalarToken, Token},
|
|
||||||
value::ParseScalarResult,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use time::{
|
|
||||||
Date, OffsetDateTime as DateTime, PrimitiveDateTime as LocalDateTime, Time as LocalTime,
|
|
||||||
UtcOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Format of a [`Date` scalar][1].
|
/// Format of a [`Date` scalar][1].
|
||||||
///
|
///
|
||||||
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
/// [1]: https://graphql-scalars.dev/docs/scalars/date
|
||||||
const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]");
|
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(
|
#[graphql_scalar(
|
||||||
description = "Date in the proleptic Gregorian calendar (without time \
|
with = date,
|
||||||
zone).\
|
parse_token(String),
|
||||||
\n\n\
|
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date",
|
||||||
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"
|
|
||||||
)]
|
)]
|
||||||
impl<S: ScalarValue> GraphQLScalar for Date {
|
pub type Date = time::Date;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
|
mod date {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &Date) -> Value<S> {
|
||||||
Value::scalar(
|
Value::scalar(
|
||||||
self.format(DATE_FORMAT)
|
v.format(DATE_FORMAT)
|
||||||
.unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)),
|
.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| Self::parse(s, DATE_FORMAT).map_err(|e| format!("Invalid `Date`: {}", e)))
|
.and_then(|s| Date::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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,137 +85,120 @@ const LOCAL_TIME_FORMAT_NO_MILLIS: &[FormatItem<'_>] =
|
||||||
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||||
const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour]:[minute]");
|
const LOCAL_TIME_FORMAT_NO_SECS: &[FormatItem<'_>] = format_description!("[hour]:[minute]");
|
||||||
|
|
||||||
#[graphql_scalar(
|
/// Clock time within a given date (without time zone) in `HH:mm[:ss[.SSS]]`
|
||||||
description = "Clock time within a given date (without time zone) in \
|
/// format.
|
||||||
`HH:mm[:ss[.SSS]]` format.\
|
///
|
||||||
\n\n\
|
/// All minutes are assumed to have exactly 60 seconds; no attempt is made to
|
||||||
All minutes are assumed to have exactly 60 seconds; no \
|
/// handle leap seconds (either positive or negative).
|
||||||
attempt is made to handle leap seconds (either positive or \
|
///
|
||||||
negative).\
|
/// [`LocalTime` scalar][1] compliant.
|
||||||
\n\n\
|
///
|
||||||
[`LocalTime` scalar][1] compliant.\
|
/// See also [`time::Time`][2] for details.
|
||||||
\n\n\
|
///
|
||||||
See also [`time::Time`][2] for details.\
|
/// [1]: https://graphql-scalars.dev/docs/scalars/local-time
|
||||||
\n\n\
|
/// [2]: https://docs.rs/time/*/time/struct.Time.html
|
||||||
[1]: https://graphql-scalars.dev/docs/scalars/local-time\n\
|
#[graphql_scalar(with = local_time, parse_token(String))]
|
||||||
[2]: https://docs.rs/time/*/time/struct.Time.html",
|
pub type LocalTime = time::Time;
|
||||||
specified_by_url = "https://graphql-scalars.dev/docs/scalars/local-time"
|
|
||||||
)]
|
mod local_time {
|
||||||
impl<S: ScalarValue> GraphQLScalar for LocalTime {
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &LocalTime) -> Value<S> {
|
||||||
Value::scalar(
|
Value::scalar(
|
||||||
if self.millisecond() == 0 {
|
if v.millisecond() == 0 {
|
||||||
self.format(LOCAL_TIME_FORMAT_NO_MILLIS)
|
v.format(LOCAL_TIME_FORMAT_NO_MILLIS)
|
||||||
} else {
|
} else {
|
||||||
self.format(LOCAL_TIME_FORMAT)
|
v.format(LOCAL_TIME_FORMAT)
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|e| panic!("Failed to format `LocalTime`: {}", e)),
|
.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
// First, try to parse the most used format.
|
// First, try to parse the most used format.
|
||||||
// At the end, try to parse the full format for the parsing
|
// At the end, try to parse the full format for the parsing
|
||||||
// error to be most informative.
|
// error to be most informative.
|
||||||
Self::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS)
|
LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_MILLIS)
|
||||||
.or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT_NO_SECS))
|
.or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT_NO_SECS))
|
||||||
.or_else(|_| Self::parse(s, LOCAL_TIME_FORMAT))
|
.or_else(|_| LocalTime::parse(s, LOCAL_TIME_FORMAT))
|
||||||
.map_err(|e| format!("Invalid `LocalTime`: {}", e))
|
.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.
|
/// Format of a [`LocalDateTime`] scalar.
|
||||||
const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
const LOCAL_DATE_TIME_FORMAT: &[FormatItem<'_>] =
|
||||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||||
|
|
||||||
#[graphql_scalar(
|
/// Combined date and time (without time zone) in `yyyy-MM-dd HH:mm:ss` format.
|
||||||
description = "Combined date and time (without time zone) in `yyyy-MM-dd \
|
///
|
||||||
HH:mm:ss` format.\
|
/// See also [`time::PrimitiveDateTime`][2] for details.
|
||||||
\n\n\
|
///
|
||||||
See also [`time::PrimitiveDateTime`][2] for details.\
|
/// [2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html
|
||||||
\n\n\
|
#[graphql_scalar(with = local_date_time, parse_token(String))]
|
||||||
[2]: https://docs.rs/time/*/time/struct.PrimitiveDateTime.html"
|
pub type LocalDateTime = time::PrimitiveDateTime;
|
||||||
)]
|
|
||||||
impl<S: ScalarValue> GraphQLScalar for LocalDateTime {
|
mod local_date_time {
|
||||||
fn resolve(&self) -> Value {
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &LocalDateTime) -> Value<S> {
|
||||||
Value::scalar(
|
Value::scalar(
|
||||||
self.format(LOCAL_DATE_TIME_FORMAT)
|
v.format(LOCAL_DATE_TIME_FORMAT)
|
||||||
.unwrap_or_else(|e| panic!("Failed to format `LocalDateTime`: {}", e)),
|
.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
Self::parse(s, LOCAL_DATE_TIME_FORMAT)
|
LocalDateTime::parse(s, LOCAL_DATE_TIME_FORMAT)
|
||||||
.map_err(|e| format!("Invalid `LocalDateTime`: {}", e))
|
.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(
|
#[graphql_scalar(
|
||||||
description = "Combined date and time (with time zone) in [RFC 3339][0] \
|
with = date_time,
|
||||||
format.\
|
parse_token(String),
|
||||||
\n\n\
|
specified_by_url = "https://graphql-scalars.dev/docs/scalars/date-time",
|
||||||
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"
|
|
||||||
)]
|
)]
|
||||||
impl<S: ScalarValue> GraphQLScalar for DateTime {
|
pub type DateTime = time::OffsetDateTime;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
|
mod date_time {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &DateTime) -> Value<S> {
|
||||||
Value::scalar(
|
Value::scalar(
|
||||||
self.to_offset(UtcOffset::UTC)
|
v.to_offset(UtcOffset::UTC)
|
||||||
.format(&Rfc3339)
|
.format(&Rfc3339)
|
||||||
.unwrap_or_else(|e| panic!("Failed to format `DateTime`: {}", e)),
|
.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.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))
|
.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].
|
/// Format of a [`UtcOffset` scalar][1].
|
||||||
|
@ -238,42 +207,40 @@ impl<S: ScalarValue> GraphQLScalar for DateTime {
|
||||||
const UTC_OFFSET_FORMAT: &[FormatItem<'_>] =
|
const UTC_OFFSET_FORMAT: &[FormatItem<'_>] =
|
||||||
format_description!("[offset_hour sign:mandatory]:[offset_minute]");
|
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(
|
#[graphql_scalar(
|
||||||
description = "Offset from UTC in `±hh:mm` format. See [list of database \
|
with = utc_offset,
|
||||||
time zones][0].\
|
parse_token(String),
|
||||||
\n\n\
|
specified_by_url = "https://graphql-scalars.dev/docs/scalars/utc-offset",
|
||||||
[`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"
|
|
||||||
)]
|
)]
|
||||||
impl<S: ScalarValue> GraphQLScalar for UtcOffset {
|
pub type UtcOffset = time::UtcOffset;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
|
mod utc_offset {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &UtcOffset) -> Value<S> {
|
||||||
Value::scalar(
|
Value::scalar(
|
||||||
self.format(UTC_OFFSET_FORMAT)
|
v.format(UTC_OFFSET_FORMAT)
|
||||||
.unwrap_or_else(|e| panic!("Failed to format `UtcOffset`: {}", e)),
|
.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| {
|
.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)]
|
#[cfg(test)]
|
||||||
|
@ -295,7 +262,7 @@ mod date_test {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
parsed.is_ok(),
|
parsed.is_ok(),
|
||||||
"failed to parse `{}`: {}",
|
"failed to parse `{}`: {:?}",
|
||||||
raw,
|
raw,
|
||||||
parsed.unwrap_err(),
|
parsed.unwrap_err(),
|
||||||
);
|
);
|
||||||
|
@ -364,7 +331,7 @@ mod local_time_test {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
parsed.is_ok(),
|
parsed.is_ok(),
|
||||||
"failed to parse `{}`: {}",
|
"failed to parse `{}`: {:?}",
|
||||||
raw,
|
raw,
|
||||||
parsed.unwrap_err(),
|
parsed.unwrap_err(),
|
||||||
);
|
);
|
||||||
|
@ -436,7 +403,7 @@ mod local_date_time_test {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
parsed.is_ok(),
|
parsed.is_ok(),
|
||||||
"failed to parse `{}`: {}",
|
"failed to parse `{}`: {:?}",
|
||||||
raw,
|
raw,
|
||||||
parsed.unwrap_err(),
|
parsed.unwrap_err(),
|
||||||
);
|
);
|
||||||
|
@ -524,7 +491,7 @@ mod date_time_test {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
parsed.is_ok(),
|
parsed.is_ok(),
|
||||||
"failed to parse `{}`: {}",
|
"failed to parse `{}`: {:?}",
|
||||||
raw,
|
raw,
|
||||||
parsed.unwrap_err(),
|
parsed.unwrap_err(),
|
||||||
);
|
);
|
||||||
|
@ -608,7 +575,7 @@ mod utc_offset_test {
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
parsed.is_ok(),
|
parsed.is_ok(),
|
||||||
"failed to parse `{}`: {}",
|
"failed to parse `{}`: {:?}",
|
||||||
raw,
|
raw,
|
||||||
parsed.unwrap_err(),
|
parsed.unwrap_err(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,30 +1,22 @@
|
||||||
//! GraphQL support for [url](https://github.com/servo/rust-url) types.
|
//! GraphQL support for [url](https://github.com/servo/rust-url) types.
|
||||||
|
|
||||||
use url::Url;
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
use crate::{
|
#[graphql_scalar(with = url_scalar, parse_token(String))]
|
||||||
value::{ParseScalarResult, ParseScalarValue},
|
type Url = url::Url;
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[crate::graphql_scalar(description = "Url")]
|
mod url_scalar {
|
||||||
impl<S> GraphQLScalar for Url
|
use super::*;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
pub(super) fn to_output<S: ScalarValue>(v: &Url) -> Value<S> {
|
||||||
{
|
Value::scalar(v.as_str().to_owned())
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| Url::parse(s).map_err(|e| format!("Failed to parse `Url`: {}", e)))
|
.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)]
|
#[cfg(test)]
|
||||||
|
@ -34,7 +26,7 @@ mod test {
|
||||||
use crate::{graphql_input_value, InputValue};
|
use crate::{graphql_input_value, InputValue};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn url_from_input_value() {
|
fn url_from_input() {
|
||||||
let raw = "https://example.net/";
|
let raw = "https://example.net/";
|
||||||
let input: InputValue = graphql_input_value!((raw));
|
let input: InputValue = graphql_input_value!((raw));
|
||||||
|
|
||||||
|
|
|
@ -2,36 +2,23 @@
|
||||||
|
|
||||||
#![allow(clippy::needless_lifetimes)]
|
#![allow(clippy::needless_lifetimes)]
|
||||||
|
|
||||||
use uuid::Uuid;
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
use crate::{
|
#[graphql_scalar(with = uuid_scalar, parse_token(String))]
|
||||||
parser::{ParseError, ScalarToken, Token},
|
type Uuid = uuid::Uuid;
|
||||||
value::ParseScalarResult,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[crate::graphql_scalar(description = "Uuid")]
|
mod uuid_scalar {
|
||||||
impl<S> GraphQLScalar for Uuid
|
use super::*;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
pub(super) fn to_output<S: ScalarValue>(v: &Uuid) -> Value<S> {
|
||||||
{
|
Value::scalar(v.to_string())
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.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()
|
v.as_string_value()
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
.and_then(|s| Uuid::parse_str(s).map_err(|e| format!("Failed to parse `Uuid`: {}", e)))
|
.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)]
|
#[cfg(test)]
|
||||||
|
@ -41,7 +28,7 @@ mod test {
|
||||||
use crate::{graphql_input_value, FromInputValue, InputValue};
|
use crate::{graphql_input_value, FromInputValue, InputValue};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uuid_from_input_value() {
|
fn uuid_from_input() {
|
||||||
let raw = "123e4567-e89b-12d3-a456-426655440000";
|
let raw = "123e4567-e89b-12d3-a456-426655440000";
|
||||||
let input: InputValue = graphql_input_value!((raw));
|
let input: InputValue = graphql_input_value!((raw));
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub use futures::future::{BoxFuture, LocalBoxFuture};
|
||||||
// functionality automatically.
|
// functionality automatically.
|
||||||
pub use juniper_codegen::{
|
pub use juniper_codegen::{
|
||||||
graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
|
graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
|
||||||
GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
|
GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLUnion,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -168,7 +168,7 @@ pub use crate::{
|
||||||
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
||||||
AsDynGraphQLValue,
|
AsDynGraphQLValue,
|
||||||
},
|
},
|
||||||
parser::{ParseError, Spanning},
|
parser::{ParseError, ScalarToken, Spanning},
|
||||||
schema::{
|
schema::{
|
||||||
meta,
|
meta,
|
||||||
model::{RootNode, SchemaType},
|
model::{RootNode, SchemaType},
|
||||||
|
|
|
@ -599,7 +599,7 @@ where
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod coercion {
|
mod coercion {
|
||||||
use crate::{graphql_input_value, FromInputValue as _, InputValue};
|
use crate::{graphql_input_value, FromInputValue as _, InputValue, IntoFieldError as _};
|
||||||
|
|
||||||
use super::{FromInputValueArrayError, FromInputValueVecError};
|
use super::{FromInputValueArrayError, FromInputValueVecError};
|
||||||
|
|
||||||
|
@ -685,13 +685,13 @@ mod coercion {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<Vec<i32>>::from_input_value(&v),
|
<Vec<i32>>::from_input_value(&v),
|
||||||
Err(FromInputValueVecError::Item(
|
Err(FromInputValueVecError::Item(
|
||||||
"Expected `Int`, found: null".to_owned(),
|
"Expected `Int`, found: null".into_field_error(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<Option<Vec<i32>>>::from_input_value(&v),
|
<Option<Vec<i32>>>::from_input_value(&v),
|
||||||
Err(FromInputValueVecError::Item(
|
Err(FromInputValueVecError::Item(
|
||||||
"Expected `Int`, found: null".to_owned(),
|
"Expected `Int`, found: null".into_field_error(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -795,13 +795,13 @@ mod coercion {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<[i32; 3]>::from_input_value(&v),
|
<[i32; 3]>::from_input_value(&v),
|
||||||
Err(FromInputValueArrayError::Item(
|
Err(FromInputValueArrayError::Item(
|
||||||
"Expected `Int`, found: null".to_owned(),
|
"Expected `Int`, found: null".into_field_error(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<Option<[i32; 3]>>::from_input_value(&v),
|
<Option<[i32; 3]>>::from_input_value(&v),
|
||||||
Err(FromInputValueArrayError::Item(
|
Err(FromInputValueArrayError::Item(
|
||||||
"Expected `Int`, found: null".to_owned(),
|
"Expected `Int`, found: null".into_field_error(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{InputValue, Selection, ToInputValue},
|
ast::{InputValue, Selection, ToInputValue},
|
||||||
executor::{ExecutionResult, Executor, Registry},
|
executor::{ExecutionResult, Executor, Registry},
|
||||||
|
graphql_scalar,
|
||||||
macros::reflect,
|
macros::reflect,
|
||||||
parser::{LexerError, ParseError, ScalarToken, Token},
|
parser::{LexerError, ParseError, ScalarToken, Token},
|
||||||
schema::meta::MetaType,
|
schema::meta::MetaType,
|
||||||
|
@ -21,9 +22,25 @@ use crate::{
|
||||||
/// An ID as defined by the GraphQL specification
|
/// An ID as defined by the GraphQL specification
|
||||||
///
|
///
|
||||||
/// Represented as a string, but can be converted _to_ from an integer as well.
|
/// 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)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[graphql_scalar(parse_token(String, i32))]
|
||||||
pub struct ID(String);
|
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 {
|
impl From<String> for ID {
|
||||||
fn from(s: String) -> ID {
|
fn from(s: String) -> ID {
|
||||||
ID(s)
|
ID(s)
|
||||||
|
@ -51,47 +68,23 @@ impl fmt::Display for ID {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::graphql_scalar(name = "ID")]
|
#[graphql_scalar(with = impl_string_scalar)]
|
||||||
impl<S> GraphQLScalar for ID
|
type String = std::string::String;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod impl_string_scalar {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(self.0.clone())
|
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> {
|
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<String, 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> {
|
|
||||||
v.as_string_value()
|
v.as_string_value()
|
||||||
.map(str::to_owned)
|
.map(str::to_owned)
|
||||||
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
.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 {
|
if let ScalarToken::String(value) = value {
|
||||||
let mut ret = String::with_capacity(value.len());
|
let mut ret = String::with_capacity(value.len());
|
||||||
let mut char_iter = value.chars();
|
let mut char_iter = value.chars();
|
||||||
|
@ -276,42 +269,44 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::graphql_scalar(name = "Boolean")]
|
#[graphql_scalar(with = impl_boolean_scalar)]
|
||||||
impl<S> GraphQLScalar for bool
|
type Boolean = bool;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod impl_boolean_scalar {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(*self)
|
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()
|
v.as_scalar_value()
|
||||||
.and_then(ScalarValue::as_boolean)
|
.and_then(ScalarValue::as_boolean)
|
||||||
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
|
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
|
pub(super) fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<'_, S> {
|
||||||
// Bools are parsed separately - they shouldn't reach this code path
|
// `Boolean`s are parsed separately, they shouldn't reach this code path.
|
||||||
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
Err(ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::graphql_scalar(name = "Int")]
|
#[graphql_scalar(with = impl_int_scalar)]
|
||||||
impl<S> GraphQLScalar for i32
|
type Int = i32;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod impl_int_scalar {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(*self)
|
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()
|
v.as_int_value()
|
||||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
.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 {
|
if let ScalarToken::Int(v) = value {
|
||||||
v.parse()
|
v.parse()
|
||||||
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
|
.map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value)))
|
||||||
|
@ -322,21 +317,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::graphql_scalar(name = "Float")]
|
#[graphql_scalar(with = impl_float_scalar)]
|
||||||
impl<S> GraphQLScalar for f64
|
type Float = f64;
|
||||||
where
|
|
||||||
S: ScalarValue,
|
mod impl_float_scalar {
|
||||||
{
|
use super::*;
|
||||||
fn resolve(&self) -> Value {
|
|
||||||
Value::scalar(*self)
|
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()
|
v.as_float_value()
|
||||||
.ok_or_else(|| format!("Expected `Float`, found: {}", v))
|
.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 {
|
match value {
|
||||||
ScalarToken::Int(v) => v
|
ScalarToken::Int(v) => v
|
||||||
.parse()
|
.parse()
|
||||||
|
|
|
@ -2,10 +2,7 @@ use std::{borrow::Cow, fmt};
|
||||||
|
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::parser::{ParseError, ScalarToken};
|
||||||
parser::{ParseError, ScalarToken},
|
|
||||||
GraphQLScalarValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The result of converting a string into a scalar value
|
/// The result of converting a string into a scalar value
|
||||||
pub type ParseScalarResult<'a, S = DefaultScalarValue> = Result<S, ParseError<'a>>;
|
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>;
|
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
|
/// A trait marking a type that could be used as internal representation of
|
||||||
/// scalar values in juniper
|
/// scalar values in juniper
|
||||||
///
|
///
|
||||||
|
@ -36,9 +34,9 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use std::{fmt, convert::TryInto as _};
|
/// # use std::{fmt, convert::TryInto as _};
|
||||||
/// # use serde::{de, Deserialize, Deserializer, Serialize};
|
/// # 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)]
|
/// #[serde(untagged)]
|
||||||
/// enum MyScalarValue {
|
/// enum MyScalarValue {
|
||||||
/// Int(i32),
|
/// Int(i32),
|
||||||
|
@ -48,6 +46,148 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
|
||||||
/// Boolean(bool),
|
/// 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 {
|
/// impl ScalarValue for MyScalarValue {
|
||||||
/// fn as_int(&self) -> Option<i32> {
|
/// fn as_int(&self) -> Option<i32> {
|
||||||
/// match self {
|
/// match self {
|
||||||
|
@ -266,7 +406,7 @@ pub trait ScalarValue:
|
||||||
/// These types closely follow the [GraphQL specification][0].
|
/// These types closely follow the [GraphQL specification][0].
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/June2018
|
/// [0]: https://spec.graphql.org/June2018
|
||||||
#[derive(Clone, Debug, GraphQLScalarValue, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum DefaultScalarValue {
|
pub enum DefaultScalarValue {
|
||||||
/// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value.
|
/// [`Int` scalar][0] as a signed 32‐bit numeric non‐fractional value.
|
||||||
|
@ -293,6 +433,122 @@ pub enum DefaultScalarValue {
|
||||||
Boolean(bool),
|
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 {
|
impl ScalarValue for DefaultScalarValue {
|
||||||
fn as_int(&self) -> Option<i32> {
|
fn as_int(&self) -> Option<i32> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -159,7 +159,7 @@ impl TypeExt for syn::Type {
|
||||||
ty.lifetimes_iter_mut(func)
|
ty.lifetimes_iter_mut(func)
|
||||||
}
|
}
|
||||||
if let syn::ReturnType::Type(_, ty) = &mut args.output {
|
if let syn::ReturnType::Type(_, ty) = &mut args.output {
|
||||||
(&mut *ty).lifetimes_iter_mut(func)
|
(*ty).lifetimes_iter_mut(func)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syn::PathArguments::None => {}
|
syn::PathArguments::None => {}
|
||||||
|
@ -172,7 +172,7 @@ impl TypeExt for syn::Type {
|
||||||
| T::Group(syn::TypeGroup { elem, .. })
|
| T::Group(syn::TypeGroup { elem, .. })
|
||||||
| T::Paren(syn::TypeParen { elem, .. })
|
| T::Paren(syn::TypeParen { elem, .. })
|
||||||
| T::Ptr(syn::TypePtr { 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, .. }) => {
|
T::Tuple(syn::TypeTuple { elems, .. }) => {
|
||||||
for ty in elems.iter_mut() {
|
for ty in elems.iter_mut() {
|
||||||
|
@ -199,7 +199,7 @@ impl TypeExt for syn::Type {
|
||||||
if let Some(lt) = ref_ty.lifetime.as_mut() {
|
if let Some(lt) = ref_ty.lifetime.as_mut() {
|
||||||
func(lt)
|
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),
|
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> {
|
fn topmost_ident(&self) -> Option<&syn::Ident> {
|
||||||
match self.unparenthesized() {
|
match self.unparenthesized() {
|
||||||
syn::Type::Path(p) => Some(&p.path),
|
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::Path(p) => Some(&p.path),
|
||||||
syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() {
|
syn::Type::TraitObject(o) => match o.bounds.iter().next().unwrap() {
|
||||||
syn::TypeParamBound::Trait(b) => Some(&b.path),
|
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_enum;
|
||||||
mod derive_input_object;
|
mod derive_input_object;
|
||||||
mod derive_scalar_value;
|
|
||||||
mod impl_scalar;
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
mod graphql_interface;
|
mod graphql_interface;
|
||||||
mod graphql_object;
|
mod graphql_object;
|
||||||
|
mod graphql_scalar;
|
||||||
mod graphql_subscription;
|
mod graphql_subscription;
|
||||||
mod graphql_union;
|
mod graphql_union;
|
||||||
|
|
||||||
|
@ -143,126 +142,116 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This custom derive macro implements the #[derive(GraphQLScalarValue)]
|
/// `#[graphql_scalar]` is interchangeable with `#[derive(`[`GraphQLScalar`]`)]`
|
||||||
/// derive.
|
/// macro:
|
||||||
///
|
///
|
||||||
/// This can be used for two purposes.
|
/// ```rust,ignore
|
||||||
///
|
|
||||||
/// ## 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
|
|
||||||
/// /// Doc comments are used for the GraphQL type description.
|
/// /// Doc comments are used for the GraphQL type description.
|
||||||
/// #[derive(juniper::GraphQLScalarValue)]
|
/// #[derive(juniper::GraphQLScalar)]
|
||||||
/// #[graphql(
|
/// #[graphql(
|
||||||
/// transparent,
|
/// // Set a custom GraphQL name.
|
||||||
/// // Set a custom GraphQL name.
|
/// name = "MyUserId",
|
||||||
/// name= "MyUserId",
|
/// // A description can also specified in the attribute.
|
||||||
/// // A description can also specified in the attribute.
|
/// // This will the doc comment, if one exists.
|
||||||
/// // This will the doc comment, if one exists.
|
/// description = "...",
|
||||||
/// description = "...",
|
/// // A specification URL.
|
||||||
/// // A specification URL.
|
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
/// // Explicit generic scalar.
|
||||||
|
/// scalar = S: juniper::ScalarValue,
|
||||||
|
/// transparent,
|
||||||
/// )]
|
/// )]
|
||||||
/// struct UserId(String);
|
/// 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]
|
/// In addition to that `#[graphql_scalar]` can be used in case
|
||||||
#[proc_macro_derive(GraphQLScalarValue, attributes(graphql))]
|
/// [`GraphQLScalar`] isn't applicable because type located in other crate and
|
||||||
pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
/// you don't want to wrap it in a newtype. This is done by placing
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
/// `#[graphql_scalar]` on a type alias.
|
||||||
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
|
|
||||||
///
|
///
|
||||||
/// The GraphQL language defines a number of built-in scalars: strings, numbers, and
|
/// All attributes are mirroring [`GraphQLScalar`] derive macro.
|
||||||
/// 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).
|
|
||||||
///
|
///
|
||||||
/// Since the preferred transport protocol for GraphQL responses is JSON, most
|
/// > __NOTE:__ To satisfy [orphan rules] you should provide local
|
||||||
/// custom scalars will be transferred as strings. You therefore need to ensure that
|
/// > [`ScalarValue`] implementation.
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// // The data type
|
/// # mod date {
|
||||||
/// struct UserID(String);
|
/// # 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(
|
/// #[graphql_scalar(
|
||||||
/// // You can rename the type for GraphQL by specifying the name here.
|
/// with = date_scalar,
|
||||||
/// name = "MyName",
|
/// parse_token(String),
|
||||||
/// // You can also specify a description here.
|
/// scalar = CustomScalarValue,
|
||||||
/// // If present, doc comments will be ignored.
|
/// // ^^^^^^^^^^^^^^^^^ Local `ScalarValue` implementation.
|
||||||
/// description = "An opaque identifier, represented as a string",
|
|
||||||
/// // A specification URL.
|
|
||||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
|
||||||
/// )]
|
/// )]
|
||||||
/// impl<S> GraphQLScalar for UserID
|
/// type Date = date::Date;
|
||||||
/// where
|
/// // ^^^^^^^^^^ Type from another crate.
|
||||||
/// S: juniper::ScalarValue
|
///
|
||||||
/// {
|
/// mod date_scalar {
|
||||||
/// fn resolve(&self) -> juniper::Value {
|
/// use super::*;
|
||||||
/// juniper::Value::scalar(self.0.to_owned())
|
///
|
||||||
|
/// // 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>`.
|
/// // NOTE: The error type should implement `IntoFieldError<S>`.
|
||||||
/// fn from_input_value(value: &juniper::InputValue) -> Result<UserID, String> {
|
/// pub(super) fn from_input(v: &InputValue<CustomScalarValue>) -> Result<Date, String> {
|
||||||
/// value.as_string_value()
|
/// v.as_string_value()
|
||||||
/// .map(|s| UserID(s.to_owned()))
|
/// .ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
/// .ok_or_else(|| format!("Expected `String`, found: {}", value))
|
/// .and_then(|s| s.parse().map_err(|e| format!("Failed to parse `Date`: {}", e)))
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn from_str<'a>(value: juniper::ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
|
|
||||||
/// <String as juniper::ParseScalarValue<S>>::from_str(value)
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
/// #
|
||||||
/// # fn main() { }
|
/// # fn main() { }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In addition to implementing `GraphQLType` for the type in question,
|
/// [orphan rules]: https://bit.ly/3glAGC2
|
||||||
/// `FromInputValue` and `ToInputValue` is also implemented. This makes the type
|
/// [`GraphQLScalar`]: juniper::GraphQLScalar
|
||||||
/// usable as arguments and default values.
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
#[proc_macro_error]
|
#[proc_macro_error]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream {
|
||||||
let args = proc_macro2::TokenStream::from(args);
|
graphql_scalar::attr::expand(attr.into(), body.into())
|
||||||
let input = proc_macro2::TokenStream::from(input);
|
.unwrap_or_abort()
|
||||||
let gen = impl_scalar::build_scalar(args, input, GraphQLScope::ImplScalar);
|
.into()
|
||||||
match gen {
|
|
||||||
Ok(gen) => gen.into(),
|
|
||||||
Err(err) => proc_macro_error::abort!(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
|
/// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
|
||||||
|
|
|
@ -13,12 +13,13 @@ pub enum GraphQLScope {
|
||||||
InterfaceAttr,
|
InterfaceAttr,
|
||||||
ObjectAttr,
|
ObjectAttr,
|
||||||
ObjectDerive,
|
ObjectDerive,
|
||||||
|
ScalarAttr,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
ScalarDerive,
|
||||||
UnionAttr,
|
UnionAttr,
|
||||||
UnionDerive,
|
UnionDerive,
|
||||||
DeriveInputObject,
|
DeriveInputObject,
|
||||||
DeriveEnum,
|
DeriveEnum,
|
||||||
DeriveScalar,
|
|
||||||
ImplScalar,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLScope {
|
impl GraphQLScope {
|
||||||
|
@ -26,10 +27,10 @@ impl GraphQLScope {
|
||||||
match self {
|
match self {
|
||||||
Self::InterfaceAttr => "#sec-Interfaces",
|
Self::InterfaceAttr => "#sec-Interfaces",
|
||||||
Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
|
Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
|
||||||
|
Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars",
|
||||||
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
|
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
|
||||||
Self::DeriveInputObject => "#sec-Input-Objects",
|
Self::DeriveInputObject => "#sec-Input-Objects",
|
||||||
Self::DeriveEnum => "#sec-Enums",
|
Self::DeriveEnum => "#sec-Enums",
|
||||||
Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,12 +40,11 @@ impl fmt::Display for GraphQLScope {
|
||||||
let name = match self {
|
let name = match self {
|
||||||
Self::InterfaceAttr => "interface",
|
Self::InterfaceAttr => "interface",
|
||||||
Self::ObjectAttr | Self::ObjectDerive => "object",
|
Self::ObjectAttr | Self::ObjectDerive => "object",
|
||||||
|
Self::ScalarAttr | Self::ScalarDerive => "scalar",
|
||||||
Self::UnionAttr | Self::UnionDerive => "union",
|
Self::UnionAttr | Self::UnionDerive => "union",
|
||||||
Self::DeriveInputObject => "input object",
|
Self::DeriveInputObject => "input object",
|
||||||
Self::DeriveEnum => "enum",
|
Self::DeriveEnum => "enum",
|
||||||
Self::DeriveScalar | Self::ImplScalar => "scalar",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "GraphQL {}", name)
|
write!(f, "GraphQL {}", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ impl GraphQLScope {
|
||||||
duplicates
|
duplicates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|dup| {
|
.for_each(|dup| {
|
||||||
(&dup.spanned[1..])
|
dup.spanned[1..]
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|spanned| {
|
.for_each(|spanned| {
|
||||||
Diagnostic::spanned(
|
Diagnostic::spanned(
|
||||||
|
|
|
@ -17,7 +17,6 @@ use syn::{
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
|
token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::common::parse::ParseBufferExt as _;
|
use crate::common::parse::ParseBufferExt as _;
|
||||||
|
|
||||||
|
@ -455,7 +454,6 @@ pub enum FieldAttributeParseMode {
|
||||||
enum FieldAttribute {
|
enum FieldAttribute {
|
||||||
Name(SpanContainer<syn::LitStr>),
|
Name(SpanContainer<syn::LitStr>),
|
||||||
Description(SpanContainer<syn::LitStr>),
|
Description(SpanContainer<syn::LitStr>),
|
||||||
SpecifiedByUrl(SpanContainer<syn::LitStr>),
|
|
||||||
Deprecation(SpanContainer<DeprecationAttr>),
|
Deprecation(SpanContainer<DeprecationAttr>),
|
||||||
Skip(SpanContainer<syn::Ident>),
|
Skip(SpanContainer<syn::Ident>),
|
||||||
Arguments(HashMap<String, FieldAttributeArgument>),
|
Arguments(HashMap<String, FieldAttributeArgument>),
|
||||||
|
@ -490,15 +488,6 @@ impl Parse for FieldAttribute {
|
||||||
lit,
|
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" => {
|
"deprecated" | "deprecation" => {
|
||||||
let reason = if input.peek(token::Eq) {
|
let reason = if input.peek(token::Eq) {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
|
@ -553,8 +542,6 @@ pub struct FieldAttributes {
|
||||||
pub name: Option<SpanContainer<String>>,
|
pub name: Option<SpanContainer<String>>,
|
||||||
pub description: Option<SpanContainer<String>>,
|
pub description: Option<SpanContainer<String>>,
|
||||||
pub deprecation: Option<SpanContainer<DeprecationAttr>>,
|
pub deprecation: Option<SpanContainer<DeprecationAttr>>,
|
||||||
/// Only relevant for scalar impl macro.
|
|
||||||
pub specified_by_url: Option<SpanContainer<Url>>,
|
|
||||||
/// Only relevant for GraphQLObject derive.
|
/// Only relevant for GraphQLObject derive.
|
||||||
pub skip: Option<SpanContainer<syn::Ident>>,
|
pub skip: Option<SpanContainer<syn::Ident>>,
|
||||||
/// Only relevant for object macro.
|
/// Only relevant for object macro.
|
||||||
|
@ -577,18 +564,6 @@ impl Parse for FieldAttributes {
|
||||||
FieldAttribute::Description(name) => {
|
FieldAttribute::Description(name) => {
|
||||||
output.description = Some(name.map(|val| val.value()));
|
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) => {
|
FieldAttribute::Deprecation(attr) => {
|
||||||
output.deprecation = Some(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> {
|
impl<T> AsRef<T> for SpanContainer<T> {
|
||||||
fn as_ref(&self) -> &T {
|
fn as_ref(&self) -> &T {
|
||||||
&self.val
|
&self.val
|
||||||
|
|
Loading…
Reference in a new issue