Redesign #[derive(ScalarValue)]
macro to derive ScalarValue
on enums (#1025)
This commit is contained in:
parent
4862915e47
commit
5bbc73ad89
33 changed files with 894 additions and 572 deletions
|
@ -5,6 +5,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
|
||||||
| ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
| ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
|
<&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
|
||||||
|
<&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
|
||||||
|
<&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
|
||||||
<[T; LANES] as From<Simd<T, LANES>>>
|
<[T; LANES] as From<Simd<T, LANES>>>
|
||||||
<[bool; LANES] as From<Mask<T, LANES>>>
|
<[bool; LANES] as From<Mask<T, LANES>>>
|
||||||
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
|
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
|
||||||
|
|
|
@ -5,6 +5,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
|
||||||
| ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
| ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
|
<&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
|
||||||
|
<&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
|
||||||
|
<&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
|
||||||
<[T; LANES] as From<Simd<T, LANES>>>
|
<[T; LANES] as From<Simd<T, LANES>>>
|
||||||
<[bool; LANES] as From<Mask<T, LANES>>>
|
<[bool; LANES] as From<Mask<T, LANES>>>
|
||||||
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
|
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar `with = <path>` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones.
|
error: GraphQL scalar `with = <path>` attribute argument cannot be combined with `transparent`. You can specify custom resolvers with `to_output_with`, `from_input_with`, `parse_token`/`parse_token_with` attribute arguments and still use `transparent` for unspecified ones.
|
||||||
--> fail/scalar/derive_input/attr_transparent_and_with.rs:3:25
|
--> fail/scalar/derive_input/attr_transparent_and_with.rs:3:25
|
||||||
|
|
|
|
||||||
3 | #[graphql_scalar(with = Self, transparent)]
|
3 | #[graphql_scalar(with = Self, transparent)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute
|
error: GraphQL scalar `transparent` attribute argument requires exactly 1 field
|
||||||
--> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:1
|
--> fail/scalar/derive_input/attr_transparent_multiple_named_fields.rs:4:1
|
||||||
|
|
|
|
||||||
4 | / struct Scalar {
|
4 | / struct Scalar {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute
|
error: GraphQL scalar `transparent` attribute argument requires exactly 1 field
|
||||||
--> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:1
|
--> fail/scalar/derive_input/attr_transparent_multiple_unnamed_fields.rs:4:1
|
||||||
|
|
|
|
||||||
4 | struct Scalar(i32, i32);
|
4 | struct Scalar(i32, i32);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute
|
error: GraphQL scalar `transparent` attribute argument requires exactly 1 field
|
||||||
--> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:1
|
--> fail/scalar/derive_input/attr_transparent_unit_struct.rs:4:1
|
||||||
|
|
|
|
||||||
4 | struct ScalarSpecifiedByUrl;
|
4 | struct ScalarSpecifiedByUrl;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar `with = <path>` attribute can\'t be combined with `transparent`. You can specify custom resolvers with `to_output`, `from_input`, `parse_token` attributes and still use `transparent` for unspecified ones.
|
error: GraphQL scalar `with = <path>` attribute argument cannot be combined with `transparent`. You can specify custom resolvers with `to_output_with`, `from_input_with`, `parse_token`/`parse_token_with` attribute arguments and still use `transparent` for unspecified ones.
|
||||||
--> fail/scalar/derive_input/derive_transparent_and_with.rs:4:18
|
--> fail/scalar/derive_input/derive_transparent_and_with.rs:4:18
|
||||||
|
|
|
|
||||||
4 | #[graphql(with = Self, transparent)]
|
4 | #[graphql(with = Self, transparent)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar expected exactly 1 field, e.g., Test { test: i32 } because of `transparent` attribute
|
error: GraphQL scalar `transparent` attribute argument requires exactly 1 field
|
||||||
--> fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs:4:1
|
--> fail/scalar/derive_input/derive_transparent_multiple_named_fields.rs:4:1
|
||||||
|
|
|
|
||||||
4 | / #[graphql(transparent)]
|
4 | / #[graphql(transparent)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar expected exactly 1 field, e.g., Test(i32) because of `transparent` attribute
|
error: GraphQL scalar `transparent` attribute argument requires exactly 1 field
|
||||||
--> fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs:4:1
|
--> fail/scalar/derive_input/derive_transparent_multiple_unnamed_fields.rs:4:1
|
||||||
|
|
|
|
||||||
4 | / #[graphql(transparent)]
|
4 | / #[graphql(transparent)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar expected exactly 1 field, e.g.: `Test(i32)`, `Test { test: i32 }` because of `transparent` attribute
|
error: GraphQL scalar `transparent` attribute argument requires exactly 1 field
|
||||||
--> fail/scalar/derive_input/derive_transparent_unit_struct.rs:4:1
|
--> fail/scalar/derive_input/derive_transparent_unit_struct.rs:4:1
|
||||||
|
|
|
|
||||||
4 | / #[graphql(transparent)]
|
4 | / #[graphql(transparent)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments
|
error: GraphQL scalar all the resolvers have to be provided via `with` attribute argument or a combination of `to_output_with`, `from_input_with`, `parse_token_with`/`parse_token` attribute arguments
|
||||||
--> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:1
|
--> fail/scalar/type_alias/attr_with_not_all_resolvers.rs:6:1
|
||||||
|
|
|
|
||||||
6 | type CustomScalar = Scalar;
|
6 | type CustomScalar = Scalar;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: GraphQL scalar all custom resolvers have to be provided via `with` or combination of `to_output_with`, `from_input_with`, `parse_token_with` attribute arguments
|
error: GraphQL scalar all the resolvers have to be provided via `with` attribute argument or a combination of `to_output_with`, `from_input_with`, `parse_token_with`/`parse_token` attribute arguments
|
||||||
--> fail/scalar/type_alias/attr_without_resolvers.rs:6:1
|
--> fail/scalar/type_alias/attr_without_resolvers.rs:6:1
|
||||||
|
|
|
|
||||||
6 | type CustomScalar = Scalar;
|
6 | type CustomScalar = Scalar;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
use juniper::ScalarValue;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, ScalarValue)]
|
||||||
|
pub enum DefaultScalarValue {
|
||||||
|
Int(i32),
|
||||||
|
Float(f64),
|
||||||
|
#[value(as_str, as_string, into_string)]
|
||||||
|
String(String),
|
||||||
|
#[value(as_bool)]
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,11 @@
|
||||||
|
error: GraphQL built-in scalars missing `#[value(as_int, as_float)]` attributes. In case you are sure that it\'s ok, use `#[value(allow_missing_attributes)]` to suppress this error.
|
||||||
|
--> fail/scalar_value/missing_attributes.rs:4:1
|
||||||
|
|
|
||||||
|
4 | / pub enum DefaultScalarValue {
|
||||||
|
5 | | Int(i32),
|
||||||
|
6 | | Float(f64),
|
||||||
|
7 | | #[value(as_str, as_string, into_string)]
|
||||||
|
... |
|
||||||
|
10 | | Boolean(bool),
|
||||||
|
11 | | }
|
||||||
|
| |_^
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[derive(juniper::ScalarValue)]
|
||||||
|
enum ScalarValue {
|
||||||
|
Variant { first: i32, second: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: GraphQL built-in scalars expected exactly 1 field
|
||||||
|
--> fail/scalar_value/multiple_named_fields.rs:3:13
|
||||||
|
|
|
||||||
|
3 | Variant { first: i32, second: u64 },
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[derive(juniper::ScalarValue)]
|
||||||
|
enum ScalarValue {
|
||||||
|
Variant(u32, i64),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: GraphQL built-in scalars expected exactly 1 field
|
||||||
|
--> fail/scalar_value/multiple_unnamed_fields.rs:3:12
|
||||||
|
|
|
||||||
|
3 | Variant(u32, i64),
|
||||||
|
| ^^^^^^^^^^
|
|
@ -0,0 +1,4 @@
|
||||||
|
#[derive(juniper::ScalarValue)]
|
||||||
|
struct ScalarValue;
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: GraphQL built-in scalars can only be derived for enums
|
||||||
|
--> fail/scalar_value/not_enum.rs:2:1
|
||||||
|
|
|
||||||
|
2 | struct ScalarValue;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
|
@ -5,6 +5,9 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
|
<&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
|
||||||
|
<&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
|
||||||
|
<&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
|
||||||
<[T; LANES] as From<Simd<T, LANES>>>
|
<[T; LANES] as From<Simd<T, LANES>>>
|
||||||
<[bool; LANES] as From<Mask<T, LANES>>>
|
<[bool; LANES] as From<Mask<T, LANES>>>
|
||||||
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
|
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
|
||||||
|
|
|
@ -7,6 +7,7 @@ mod object_derive;
|
||||||
mod scalar_attr_derive_input;
|
mod scalar_attr_derive_input;
|
||||||
mod scalar_attr_type_alias;
|
mod scalar_attr_type_alias;
|
||||||
mod scalar_derive;
|
mod scalar_derive;
|
||||||
|
mod scalar_value_derive;
|
||||||
mod subscription_attr;
|
mod subscription_attr;
|
||||||
mod union_attr;
|
mod union_attr;
|
||||||
mod union_derive;
|
mod union_derive;
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
//! Tests for `#[derive(ScalarValue)]` macro.
|
||||||
|
|
||||||
|
use juniper::{DefaultScalarValue, ScalarValue};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod trivial {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CustomScalarValue {
|
||||||
|
#[value(as_float, as_int)]
|
||||||
|
Int(i32),
|
||||||
|
#[value(as_float)]
|
||||||
|
Float(f64),
|
||||||
|
#[value(as_str, as_string, into_string)]
|
||||||
|
String(String),
|
||||||
|
#[value(as_bool)]
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_another() {
|
||||||
|
assert!(CustomScalarValue::from(5)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<i32>());
|
||||||
|
assert!(CustomScalarValue::from(0.5_f64)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<f64>());
|
||||||
|
assert!(CustomScalarValue::from("str".to_owned())
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<String>());
|
||||||
|
assert!(CustomScalarValue::from(true)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<bool>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod named_fields {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CustomScalarValue {
|
||||||
|
#[value(as_float, as_int)]
|
||||||
|
Int { int: i32 },
|
||||||
|
#[value(as_float)]
|
||||||
|
Float(f64),
|
||||||
|
#[value(as_str, as_string, into_string)]
|
||||||
|
String(String),
|
||||||
|
#[value(as_bool)]
|
||||||
|
Boolean { v: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_another() {
|
||||||
|
assert!(CustomScalarValue::from(5)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<i32>());
|
||||||
|
assert!(CustomScalarValue::from(0.5_f64)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<f64>());
|
||||||
|
assert!(CustomScalarValue::from("str".to_owned())
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<String>());
|
||||||
|
assert!(CustomScalarValue::from(true)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<bool>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod custom_fn {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CustomScalarValue {
|
||||||
|
#[value(as_float, as_int)]
|
||||||
|
Int(i32),
|
||||||
|
#[value(as_float)]
|
||||||
|
Float(f64),
|
||||||
|
#[value(
|
||||||
|
as_str,
|
||||||
|
as_string = str::to_owned,
|
||||||
|
into_string = std::convert::identity,
|
||||||
|
)]
|
||||||
|
String(String),
|
||||||
|
#[value(as_bool)]
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_another() {
|
||||||
|
assert!(CustomScalarValue::from(5)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<i32>());
|
||||||
|
assert!(CustomScalarValue::from(0.5_f64)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<f64>());
|
||||||
|
assert!(CustomScalarValue::from("str".to_owned())
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<String>());
|
||||||
|
assert!(CustomScalarValue::from(true)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<bool>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod allow_missing_attributes {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, ScalarValue, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
#[value(allow_missing_attributes)]
|
||||||
|
pub enum CustomScalarValue {
|
||||||
|
Int(i32),
|
||||||
|
#[value(as_float)]
|
||||||
|
Float(f64),
|
||||||
|
#[value(as_str, as_string, into_string)]
|
||||||
|
String(String),
|
||||||
|
#[value(as_bool)]
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_another() {
|
||||||
|
assert!(CustomScalarValue::Int(5).as_int().is_none());
|
||||||
|
assert!(CustomScalarValue::from(0.5_f64)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<f64>());
|
||||||
|
assert!(CustomScalarValue::from("str".to_owned())
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<String>());
|
||||||
|
assert!(CustomScalarValue::from(true)
|
||||||
|
.into_another::<DefaultScalarValue>()
|
||||||
|
.is_type::<bool>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,204 +10,20 @@ use juniper::{
|
||||||
Value, Variables,
|
Value, Variables,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub(crate) enum MyScalarValue {
|
pub(crate) enum MyScalarValue {
|
||||||
|
#[value(as_float, as_int)]
|
||||||
Int(i32),
|
Int(i32),
|
||||||
Long(i64),
|
Long(i64),
|
||||||
|
#[value(as_float)]
|
||||||
Float(f64),
|
Float(f64),
|
||||||
|
#[value(as_str, as_string, into_string)]
|
||||||
String(String),
|
String(String),
|
||||||
|
#[value(as_bool)]
|
||||||
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 {
|
|
||||||
fn as_int(&self) -> Option<i32> {
|
|
||||||
match self {
|
|
||||||
Self::Int(i) => Some(*i),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::String(s) => Some(s.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_string(self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::String(s) => Some(s),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_str(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Self::String(s) => Some(s.as_str()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_float(&self) -> Option<f64> {
|
|
||||||
match self {
|
|
||||||
Self::Int(i) => Some(f64::from(*i)),
|
|
||||||
Self::Float(f) => Some(*f),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_boolean(&self) -> Option<bool> {
|
|
||||||
match self {
|
|
||||||
Self::Boolean(b) => Some(*b),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for MyScalarValue {
|
impl<'de> Deserialize<'de> for MyScalarValue {
|
||||||
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||||
struct Visitor;
|
struct Visitor;
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014))
|
- Redesign `#[graphql_scalar]` macro: ([#1014](https://github.com/graphql-rust/juniper/pull/1014))
|
||||||
- Mirror `#[derive(GraphQLScalar)]` macro.
|
- 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).
|
- 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).
|
||||||
|
- Rename `ScalarValue::as_boolean` to `ScalarValue::as_bool`. ([#1025](https://github.com/graphql-rust/juniper/pull/1025))
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
- Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
- Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
||||||
- Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
- Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
||||||
- Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005))
|
- Support directives on variables definitions. ([#1005](https://github.com/graphql-rust/juniper/pull/1005))
|
||||||
|
- Implement `#[derive(ScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025))
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ where
|
||||||
if let LookAheadValue::Scalar(s) =
|
if let LookAheadValue::Scalar(s) =
|
||||||
LookAheadValue::from_input_value(&v.item, vars)
|
LookAheadValue::from_input_value(&v.item, vars)
|
||||||
{
|
{
|
||||||
s.as_boolean().unwrap_or(false)
|
s.as_bool().unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ where
|
||||||
if let LookAheadValue::Scalar(b) =
|
if let LookAheadValue::Scalar(b) =
|
||||||
LookAheadValue::from_input_value(&v.item, vars)
|
LookAheadValue::from_input_value(&v.item, vars)
|
||||||
{
|
{
|
||||||
b.as_boolean().map(::std::ops::Not::not).unwrap_or(false)
|
b.as_bool().map(::std::ops::Not::not).unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ impl GraphQLParserTranslator {
|
||||||
ExternalValue::Int(ExternalNumber::from(v))
|
ExternalValue::Int(ExternalNumber::from(v))
|
||||||
} else if let Some(v) = x.as_float() {
|
} else if let Some(v) = x.as_float() {
|
||||||
ExternalValue::Float(v)
|
ExternalValue::Float(v)
|
||||||
} else if let Some(v) = x.as_boolean() {
|
} else if let Some(v) = x.as_bool() {
|
||||||
ExternalValue::Boolean(v)
|
ExternalValue::Boolean(v)
|
||||||
} else {
|
} else {
|
||||||
panic!("unknown argument type")
|
panic!("unknown argument type")
|
||||||
|
|
|
@ -281,7 +281,7 @@ mod impl_boolean_scalar {
|
||||||
|
|
||||||
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Boolean, 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_bool)
|
||||||
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
|
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
use crate::parser::{ParseError, ScalarToken};
|
use crate::parser::{ParseError, ScalarToken};
|
||||||
|
|
||||||
|
pub use juniper_codegen::ScalarValue;
|
||||||
|
|
||||||
/// 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>>;
|
||||||
|
|
||||||
|
@ -13,226 +15,44 @@ 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
|
||||||
///
|
///
|
||||||
/// The main objective of this abstraction is to allow other libraries to
|
/// The main objective of this abstraction is to allow other libraries to
|
||||||
/// replace the default representation with something that better fits their
|
/// replace the default representation with something that better fits their
|
||||||
/// needs.
|
/// needs.
|
||||||
/// There is a custom derive (`#[derive(juniper::GraphQLScalarValue)]`) available that implements
|
/// There is a custom derive (`#[derive(`[`ScalarValue`]`)]`) available that
|
||||||
/// most of the required traits automatically for a enum representing a scalar value.
|
/// implements most of the required traits automatically for a enum representing
|
||||||
/// However, [`Serialize`](trait@serde::Serialize) and [`Deserialize`](trait@serde::Deserialize)
|
/// a scalar value. However, [`Serialize`] and [`Deserialize`] implementations
|
||||||
/// implementations are expected to be provided.
|
/// are expected to be provided.
|
||||||
///
|
///
|
||||||
/// # Implementing a new scalar value representation
|
/// # Implementing a new scalar value representation
|
||||||
/// The preferred way to define a new scalar value representation is
|
/// The preferred way to define a new scalar value representation is
|
||||||
/// defining a enum containing a variant for each type that needs to be represented
|
/// defining a enum containing a variant for each type that needs to be
|
||||||
/// at the lowest level.
|
/// represented at the lowest level.
|
||||||
/// The following example introduces an new variant that is able to store 64 bit integers.
|
/// The following example introduces an new variant that is able to store 64 bit
|
||||||
|
/// integers.
|
||||||
///
|
///
|
||||||
/// ```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::ScalarValue;
|
/// # use juniper::ScalarValue;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Clone, Debug, PartialEq, Serialize)]
|
/// #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)]
|
||||||
/// #[serde(untagged)]
|
/// #[serde(untagged)]
|
||||||
/// enum MyScalarValue {
|
/// enum MyScalarValue {
|
||||||
|
/// #[value(as_float, as_int)]
|
||||||
/// Int(i32),
|
/// Int(i32),
|
||||||
/// Long(i64),
|
/// Long(i64),
|
||||||
|
/// #[value(as_float)]
|
||||||
/// Float(f64),
|
/// Float(f64),
|
||||||
|
/// #[value(as_str, as_string, into_string)]
|
||||||
/// String(String),
|
/// String(String),
|
||||||
|
/// #[value(as_bool)]
|
||||||
/// 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 {
|
|
||||||
/// fn as_int(&self) -> Option<i32> {
|
|
||||||
/// match self {
|
|
||||||
/// Self::Int(i) => Some(*i),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn as_string(&self) -> Option<String> {
|
|
||||||
/// match self {
|
|
||||||
/// Self::String(s) => Some(s.clone()),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn into_string(self) -> Option<String> {
|
|
||||||
/// match self {
|
|
||||||
/// Self::String(s) => Some(s),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn as_str(&self) -> Option<&str> {
|
|
||||||
/// match self {
|
|
||||||
/// Self::String(s) => Some(s.as_str()),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn as_float(&self) -> Option<f64> {
|
|
||||||
/// match self {
|
|
||||||
/// Self::Int(i) => Some(f64::from(*i)),
|
|
||||||
/// Self::Float(f) => Some(*f),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn as_boolean(&self) -> Option<bool> {
|
|
||||||
/// match self {
|
|
||||||
/// Self::Boolean(b) => Some(*b),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl<'de> Deserialize<'de> for MyScalarValue {
|
/// impl<'de> Deserialize<'de> for MyScalarValue {
|
||||||
/// fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
/// fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||||
/// struct Visitor;
|
/// struct Visitor;
|
||||||
|
@ -298,6 +118,9 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Deserialize`]: trait@serde::Deserialize
|
||||||
|
/// [`Serialize`]: trait@serde::Serialize
|
||||||
pub trait ScalarValue:
|
pub trait ScalarValue:
|
||||||
fmt::Debug
|
fmt::Debug
|
||||||
+ fmt::Display
|
+ fmt::Display
|
||||||
|
@ -383,7 +206,7 @@ pub trait ScalarValue:
|
||||||
/// all possible [`ScalarValue`]s.
|
/// all possible [`ScalarValue`]s.
|
||||||
///
|
///
|
||||||
/// [`GraphQLValue`]: crate::GraphQLValue
|
/// [`GraphQLValue`]: crate::GraphQLValue
|
||||||
fn as_boolean(&self) -> Option<bool>;
|
fn as_bool(&self) -> Option<bool>;
|
||||||
|
|
||||||
/// Converts this [`ScalarValue`] into another one.
|
/// Converts this [`ScalarValue`] into another one.
|
||||||
fn into_another<S: ScalarValue>(self) -> S {
|
fn into_another<S: ScalarValue>(self) -> S {
|
||||||
|
@ -391,7 +214,7 @@ pub trait ScalarValue:
|
||||||
S::from(i)
|
S::from(i)
|
||||||
} else if let Some(f) = self.as_float() {
|
} else if let Some(f) = self.as_float() {
|
||||||
S::from(f)
|
S::from(f)
|
||||||
} else if let Some(b) = self.as_boolean() {
|
} else if let Some(b) = self.as_bool() {
|
||||||
S::from(b)
|
S::from(b)
|
||||||
} else if let Some(s) = self.into_string() {
|
} else if let Some(s) = self.into_string() {
|
||||||
S::from(s)
|
S::from(s)
|
||||||
|
@ -406,12 +229,13 @@ 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, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, ScalarValue, 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.
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/June2018/#sec-Int
|
/// [0]: https://spec.graphql.org/June2018/#sec-Int
|
||||||
|
#[value(as_float, as_int)]
|
||||||
Int(i32),
|
Int(i32),
|
||||||
|
|
||||||
/// [`Float` scalar][0] as a signed double‐precision fractional values as
|
/// [`Float` scalar][0] as a signed double‐precision fractional values as
|
||||||
|
@ -419,190 +243,23 @@ pub enum DefaultScalarValue {
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/June2018/#sec-Float
|
/// [0]: https://spec.graphql.org/June2018/#sec-Float
|
||||||
/// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point
|
/// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point
|
||||||
|
#[value(as_float)]
|
||||||
Float(f64),
|
Float(f64),
|
||||||
|
|
||||||
/// [`String` scalar][0] as a textual data, represented as UTF‐8 character
|
/// [`String` scalar][0] as a textual data, represented as UTF‐8 character
|
||||||
/// sequences.
|
/// sequences.
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/June2018/#sec-String
|
/// [0]: https://spec.graphql.org/June2018/#sec-String
|
||||||
|
#[value(as_str, as_string, into_string)]
|
||||||
String(String),
|
String(String),
|
||||||
|
|
||||||
/// [`Boolean` scalar][0] as a `true` or `false` value.
|
/// [`Boolean` scalar][0] as a `true` or `false` value.
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/June2018/#sec-Boolean
|
/// [0]: https://spec.graphql.org/June2018/#sec-Boolean
|
||||||
|
#[value(as_bool)]
|
||||||
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 {
|
|
||||||
fn as_int(&self) -> Option<i32> {
|
|
||||||
match self {
|
|
||||||
Self::Int(i) => Some(*i),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_float(&self) -> Option<f64> {
|
|
||||||
match self {
|
|
||||||
Self::Int(i) => Some(f64::from(*i)),
|
|
||||||
Self::Float(f) => Some(*f),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_str(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Self::String(s) => Some(s.as_str()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::String(s) => Some(s.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_string(self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::String(s) => Some(s),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_boolean(&self) -> Option<bool> {
|
|
||||||
match self {
|
|
||||||
Self::Boolean(b) => Some(*b),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_another<S: ScalarValue>(self) -> S {
|
|
||||||
match self {
|
|
||||||
Self::Int(i) => S::from(i),
|
|
||||||
Self::Float(f) => S::from(f),
|
|
||||||
Self::String(s) => S::from(s),
|
|
||||||
Self::Boolean(b) => S::from(b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for DefaultScalarValue {
|
impl<'a> From<&'a str> for DefaultScalarValue {
|
||||||
fn from(s: &'a str) -> Self {
|
fn from(s: &'a str) -> Self {
|
||||||
Self::String(s.into())
|
Self::String(s.into())
|
||||||
|
|
|
@ -28,3 +28,4 @@ url = "2.0"
|
||||||
derive_more = "0.99.7"
|
derive_more = "0.99.7"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
juniper = { version = "0.16.0-dev", path = "../juniper" }
|
juniper = { version = "0.16.0-dev", path = "../juniper" }
|
||||||
|
serde = "1.0"
|
||||||
|
|
|
@ -115,6 +115,7 @@ mod graphql_object;
|
||||||
mod graphql_scalar;
|
mod graphql_scalar;
|
||||||
mod graphql_subscription;
|
mod graphql_subscription;
|
||||||
mod graphql_union;
|
mod graphql_union;
|
||||||
|
mod scalar_value;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro_error::{proc_macro_error, ResultExt as _};
|
use proc_macro_error::{proc_macro_error, ResultExt as _};
|
||||||
|
@ -549,6 +550,114 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `#[derive(ScalarValue)]` macro for deriving a [`ScalarValue`]
|
||||||
|
/// implementation.
|
||||||
|
///
|
||||||
|
/// To derive a [`ScalarValue`] on enum you should mark the corresponding enum
|
||||||
|
/// variants with `as_int`, `as_float`, `as_string`, `into_string`, `as_str` and
|
||||||
|
/// `as_bool` attribute argumentes (names correspond to [`ScalarValue`] required
|
||||||
|
/// methods).
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::{fmt, convert::TryInto as _};
|
||||||
|
/// #
|
||||||
|
/// # use serde::{de, Deserialize, Deserializer, Serialize};
|
||||||
|
/// # use juniper::ScalarValue;
|
||||||
|
/// #
|
||||||
|
/// #[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)]
|
||||||
|
/// #[serde(untagged)]
|
||||||
|
/// enum MyScalarValue {
|
||||||
|
/// #[value(as_float, as_int)]
|
||||||
|
/// Int(i32),
|
||||||
|
/// Long(i64),
|
||||||
|
/// #[value(as_float)]
|
||||||
|
/// Float(f64),
|
||||||
|
/// #[value(
|
||||||
|
/// into_string,
|
||||||
|
/// as_str,
|
||||||
|
/// as_string = String::clone,
|
||||||
|
/// )]
|
||||||
|
/// // ^^^^^^^^^^^^^ custom resolvers may be provided
|
||||||
|
/// String(String),
|
||||||
|
/// #[value(as_bool)]
|
||||||
|
/// Boolean(bool),
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl<'de> Deserialize<'de> for MyScalarValue {
|
||||||
|
/// fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||||
|
/// struct Visitor;
|
||||||
|
///
|
||||||
|
/// impl<'de> de::Visitor<'de> for Visitor {
|
||||||
|
/// type Value = MyScalarValue;
|
||||||
|
///
|
||||||
|
/// fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
/// f.write_str("a valid input value")
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_bool<E: de::Error>(self, b: bool) -> Result<Self::Value, E> {
|
||||||
|
/// Ok(MyScalarValue::Boolean(b))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_i32<E: de::Error>(self, n: i32) -> Result<Self::Value, E> {
|
||||||
|
/// Ok(MyScalarValue::Int(n))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_i64<E: de::Error>(self, n: i64) -> Result<Self::Value, E> {
|
||||||
|
/// if n <= i64::from(i32::MAX) {
|
||||||
|
/// self.visit_i32(n.try_into().unwrap())
|
||||||
|
/// } else {
|
||||||
|
/// Ok(MyScalarValue::Long(n))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_u32<E: de::Error>(self, n: u32) -> Result<Self::Value, E> {
|
||||||
|
/// if n <= i32::MAX as u32 {
|
||||||
|
/// self.visit_i32(n.try_into().unwrap())
|
||||||
|
/// } else {
|
||||||
|
/// self.visit_u64(n.into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_u64<E: de::Error>(self, n: u64) -> Result<Self::Value, E> {
|
||||||
|
/// if n <= i64::MAX as u64 {
|
||||||
|
/// self.visit_i64(n.try_into().unwrap())
|
||||||
|
/// } else {
|
||||||
|
/// // Browser's `JSON.stringify()` serialize all numbers
|
||||||
|
/// // having no fractional part as integers (no decimal
|
||||||
|
/// // point), so we must parse large integers as floating
|
||||||
|
/// // point, otherwise we would error on transferring large
|
||||||
|
/// // floating point numbers.
|
||||||
|
/// Ok(MyScalarValue::Float(n as f64))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_f64<E: de::Error>(self, f: f64) -> Result<Self::Value, E> {
|
||||||
|
/// Ok(MyScalarValue::Float(f))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||||
|
/// self.visit_string(s.into())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn visit_string<E: de::Error>(self, s: String) -> Result<Self::Value, E> {
|
||||||
|
/// Ok(MyScalarValue::String(s))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// de.deserialize_any(Visitor)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
#[proc_macro_error]
|
||||||
|
#[proc_macro_derive(ScalarValue, attributes(value))]
|
||||||
|
pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
||||||
|
scalar_value::expand_derive(input.into())
|
||||||
|
.unwrap_or_abort()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
|
/// `#[graphql_interface]` macro for generating a [GraphQL interface][1]
|
||||||
/// implementation for traits and its implementers.
|
/// implementation for traits and its implementers.
|
||||||
///
|
///
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub enum GraphQLScope {
|
||||||
ObjectDerive,
|
ObjectDerive,
|
||||||
ScalarAttr,
|
ScalarAttr,
|
||||||
ScalarDerive,
|
ScalarDerive,
|
||||||
|
ScalarValueDerive,
|
||||||
UnionAttr,
|
UnionAttr,
|
||||||
UnionDerive,
|
UnionDerive,
|
||||||
DeriveInputObject,
|
DeriveInputObject,
|
||||||
|
@ -26,6 +27,7 @@ impl GraphQLScope {
|
||||||
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::ScalarAttr | Self::ScalarDerive => "#sec-Scalars",
|
||||||
|
Self::ScalarValueDerive => "#sec-Scalars.Built-in-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",
|
||||||
|
@ -39,6 +41,7 @@ impl fmt::Display for GraphQLScope {
|
||||||
Self::InterfaceAttr => "interface",
|
Self::InterfaceAttr => "interface",
|
||||||
Self::ObjectAttr | Self::ObjectDerive => "object",
|
Self::ObjectAttr | Self::ObjectDerive => "object",
|
||||||
Self::ScalarAttr | Self::ScalarDerive => "scalar",
|
Self::ScalarAttr | Self::ScalarDerive => "scalar",
|
||||||
|
Self::ScalarValueDerive => "built-in scalars",
|
||||||
Self::UnionAttr | Self::UnionDerive => "union",
|
Self::UnionAttr | Self::UnionDerive => "union",
|
||||||
Self::DeriveInputObject => "input object",
|
Self::DeriveInputObject => "input object",
|
||||||
Self::DeriveEnum => "enum",
|
Self::DeriveEnum => "enum",
|
||||||
|
|
531
juniper_codegen/src/scalar_value/mod.rs
Normal file
531
juniper_codegen/src/scalar_value/mod.rs
Normal file
|
@ -0,0 +1,531 @@
|
||||||
|
//! Code generation for `#[derive(ScalarValue)]` macro.
|
||||||
|
|
||||||
|
use std::{collections::HashMap, convert::TryFrom};
|
||||||
|
|
||||||
|
use proc_macro2::{Literal, TokenStream};
|
||||||
|
use quote::{quote, ToTokens, TokenStreamExt as _};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
parse_quote,
|
||||||
|
spanned::Spanned as _,
|
||||||
|
token,
|
||||||
|
visit::Visit,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::parse::{attr::err, ParseBufferExt as _},
|
||||||
|
util::{filter_attrs, span_container::SpanContainer},
|
||||||
|
GraphQLScope,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// [`GraphQLScope`] of errors for `#[derive(ScalarValue)]` macro.
|
||||||
|
const ERR: GraphQLScope = GraphQLScope::ScalarValueDerive;
|
||||||
|
|
||||||
|
/// Expands `#[derive(ScalarValue)]` macro into generated code.
|
||||||
|
pub fn expand_derive(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
let ast = syn::parse2::<syn::DeriveInput>(input)?;
|
||||||
|
let span = ast.span();
|
||||||
|
|
||||||
|
let data_enum = match ast.data {
|
||||||
|
syn::Data::Enum(e) => e,
|
||||||
|
_ => return Err(ERR.custom_error(ast.span(), "can only be derived for enums")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let attr = Attr::from_attrs("value", &ast.attrs)?;
|
||||||
|
|
||||||
|
let mut methods = HashMap::<Method, Vec<Variant>>::new();
|
||||||
|
for var in data_enum.variants.clone() {
|
||||||
|
let (ident, field) = (var.ident, Field::try_from(var.fields)?);
|
||||||
|
for attr in VariantAttr::from_attrs("value", &var.attrs)?.0 {
|
||||||
|
let (method, expr) = attr.into_inner();
|
||||||
|
methods.entry(method).or_default().push(Variant {
|
||||||
|
ident: ident.clone(),
|
||||||
|
field: field.clone(),
|
||||||
|
expr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let missing_methods = [
|
||||||
|
(Method::AsInt, "as_int"),
|
||||||
|
(Method::AsFloat, "as_float"),
|
||||||
|
(Method::AsStr, "as_str"),
|
||||||
|
(Method::AsString, "as_string"),
|
||||||
|
(Method::IntoString, "into_string"),
|
||||||
|
(Method::AsBool, "as_bool"),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(method, err)| (!methods.contains_key(method)).then(|| err))
|
||||||
|
.fold(None, |acc, &method| {
|
||||||
|
Some(
|
||||||
|
acc.map(|acc| format!("{}, {}", acc, method))
|
||||||
|
.unwrap_or_else(|| method.to_owned()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(|_| !attr.allow_missing_attrs);
|
||||||
|
if let Some(missing_methods) = missing_methods {
|
||||||
|
return Err(ERR.custom_error(
|
||||||
|
span,
|
||||||
|
format!(
|
||||||
|
"missing `#[value({})]` attributes. In case you are sure \
|
||||||
|
that it's ok, use `#[value(allow_missing_attributes)]` to \
|
||||||
|
suppress this error.",
|
||||||
|
missing_methods,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Definition {
|
||||||
|
ident: ast.ident,
|
||||||
|
generics: ast.generics,
|
||||||
|
variants: data_enum.variants.into_iter().collect(),
|
||||||
|
methods,
|
||||||
|
}
|
||||||
|
.into_token_stream())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Available arguments behind `#[value]` attribute when generating code for
|
||||||
|
/// an enum definition.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Attr {
|
||||||
|
/// Allows missing [`Method`]s.
|
||||||
|
allow_missing_attrs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Attr {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Attr> {
|
||||||
|
let mut out = Attr::default();
|
||||||
|
while !input.is_empty() {
|
||||||
|
let ident = input.parse::<syn::Ident>()?;
|
||||||
|
match ident.to_string().as_str() {
|
||||||
|
"allow_missing_attributes" => {
|
||||||
|
out.allow_missing_attrs = true;
|
||||||
|
}
|
||||||
|
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(mut self, another: Self) -> syn::Result<Self> {
|
||||||
|
self.allow_missing_attrs |= another.allow_missing_attrs;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s
|
||||||
|
/// placed on a enum variant.
|
||||||
|
fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
|
||||||
|
filter_attrs(name, attrs)
|
||||||
|
.map(|attr| attr.parse_args())
|
||||||
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible attribute names of the `#[derive(ScalarValue)]`.
|
||||||
|
#[derive(Eq, Hash, PartialEq)]
|
||||||
|
enum Method {
|
||||||
|
/// `#[value(as_int)]`.
|
||||||
|
AsInt,
|
||||||
|
|
||||||
|
/// `#[value(as_float)]`.
|
||||||
|
AsFloat,
|
||||||
|
|
||||||
|
/// `#[value(as_str)]`.
|
||||||
|
AsStr,
|
||||||
|
|
||||||
|
/// `#[value(as_string)]`.
|
||||||
|
AsString,
|
||||||
|
|
||||||
|
/// `#[value(into_string)]`.
|
||||||
|
IntoString,
|
||||||
|
|
||||||
|
/// `#[value(as_bool)]`.
|
||||||
|
AsBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Available arguments behind `#[value]` attribute when generating code for an
|
||||||
|
/// enum variant.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct VariantAttr(Vec<SpanContainer<(Method, Option<syn::ExprPath>)>>);
|
||||||
|
|
||||||
|
impl Parse for VariantAttr {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<VariantAttr> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
while !input.is_empty() {
|
||||||
|
let ident = input.parse::<syn::Ident>()?;
|
||||||
|
let method = match ident.to_string().as_str() {
|
||||||
|
"as_int" => Method::AsInt,
|
||||||
|
"as_float" => Method::AsFloat,
|
||||||
|
"as_str" => Method::AsStr,
|
||||||
|
"as_string" => Method::AsString,
|
||||||
|
"into_string" => Method::IntoString,
|
||||||
|
"as_bool" => Method::AsBool,
|
||||||
|
name => {
|
||||||
|
return Err(err::unknown_arg(&ident, name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let expr = input
|
||||||
|
.parse::<token::Eq>()
|
||||||
|
.ok()
|
||||||
|
.map(|_| input.parse::<syn::ExprPath>())
|
||||||
|
.transpose()?;
|
||||||
|
out.push(SpanContainer::new(
|
||||||
|
ident.span(),
|
||||||
|
expr.as_ref().map(|e| e.span()),
|
||||||
|
(method, expr),
|
||||||
|
));
|
||||||
|
input.try_parse::<token::Comma>()?;
|
||||||
|
}
|
||||||
|
Ok(VariantAttr(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VariantAttr {
|
||||||
|
/// Tries to merge two [`VariantAttr`]s into a single one, reporting about
|
||||||
|
/// duplicates, if any.
|
||||||
|
fn try_merge(mut self, mut another: Self) -> syn::Result<Self> {
|
||||||
|
let dup = another.0.iter().find(|m| self.0.contains(m));
|
||||||
|
if let Some(dup) = dup {
|
||||||
|
Err(err::dup_arg(dup.span_ident()))
|
||||||
|
} else {
|
||||||
|
self.0.append(&mut another.0);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses [`VariantAttr`] from the given multiple `name`d
|
||||||
|
/// [`syn::Attribute`]s placed on a enum variant.
|
||||||
|
fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
|
||||||
|
filter_attrs(name, attrs)
|
||||||
|
.map(|attr| attr.parse_args())
|
||||||
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Definition of a [`ScalarValue`] for code generation.
|
||||||
|
///
|
||||||
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
struct Definition {
|
||||||
|
/// [`syn::Ident`] of the enum representing this [`ScalarValue`].
|
||||||
|
///
|
||||||
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
ident: syn::Ident,
|
||||||
|
|
||||||
|
/// [`syn::Generics`] of the enum representing this [`ScalarValue`].
|
||||||
|
///
|
||||||
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
generics: syn::Generics,
|
||||||
|
|
||||||
|
/// [`syn::Variant`]s of the enum representing this [`ScalarValue`].
|
||||||
|
///
|
||||||
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
variants: Vec<syn::Variant>,
|
||||||
|
|
||||||
|
/// [`Variant`]s marked with a [`Method`] attribute.
|
||||||
|
methods: HashMap<Method, Vec<Variant>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Definition {
|
||||||
|
fn to_tokens(&self, into: &mut TokenStream) {
|
||||||
|
self.impl_scalar_value_tokens().to_tokens(into);
|
||||||
|
self.impl_from_tokens().to_tokens(into);
|
||||||
|
self.impl_display_tokens().to_tokens(into);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Definition {
|
||||||
|
/// Returns generated code implementing [`ScalarValue`].
|
||||||
|
///
|
||||||
|
/// [`ScalarValue`]: juniper::ScalarValue
|
||||||
|
fn impl_scalar_value_tokens(&self) -> TokenStream {
|
||||||
|
let ident = &self.ident;
|
||||||
|
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();
|
||||||
|
|
||||||
|
let methods = [
|
||||||
|
(
|
||||||
|
Method::AsInt,
|
||||||
|
quote! { fn as_int(&self) -> Option<i32> },
|
||||||
|
quote! { i32::from(*v) },
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Method::AsFloat,
|
||||||
|
quote! { fn as_float(&self) -> Option<f64> },
|
||||||
|
quote! { f64::from(*v) },
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Method::AsStr,
|
||||||
|
quote! { fn as_str(&self) -> Option<&str> },
|
||||||
|
quote! { ::std::convert::AsRef::as_ref(v) },
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Method::AsString,
|
||||||
|
quote! { fn as_string(&self) -> Option<String> },
|
||||||
|
quote! { ::std::string::ToString::to_string(v) },
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Method::IntoString,
|
||||||
|
quote! { fn into_string(self) -> Option<String> },
|
||||||
|
quote! { ::std::string::String::from(v) },
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Method::AsBool,
|
||||||
|
quote! { fn as_bool(&self) -> Option<bool> },
|
||||||
|
quote! { bool::from(*v) },
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let methods = methods.iter().map(|(m, sig, def)| {
|
||||||
|
let arms = self.methods.get(m).into_iter().flatten().map(|v| {
|
||||||
|
let arm = v.match_arm();
|
||||||
|
let call = v.expr.as_ref().map_or(def.clone(), |f| quote! { #f(v) });
|
||||||
|
quote! { #arm => Some(#call), }
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
#sig {
|
||||||
|
match self {
|
||||||
|
#(#arms)*
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl#impl_gens ::juniper::ScalarValue for #ident#ty_gens
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
#(#methods)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns generated code implementing:
|
||||||
|
/// - [`From`] each variant into enum itself.
|
||||||
|
/// - [`From`] enum into [`Option`] of each variant.
|
||||||
|
/// - [`From`] enum reference into [`Option`] of each variant reference.
|
||||||
|
fn impl_from_tokens(&self) -> TokenStream {
|
||||||
|
let ty_ident = &self.ident;
|
||||||
|
let (impl_gen, ty_gen, where_clause) = self.generics.split_for_impl();
|
||||||
|
|
||||||
|
// We don't impose additional bounds on generic parameters, because
|
||||||
|
// `ScalarValue` itself has `'static` bound.
|
||||||
|
let mut generics = self.generics.clone();
|
||||||
|
generics.params.push(parse_quote! { '___a });
|
||||||
|
let (lf_impl_gen, _, _) = generics.split_for_impl();
|
||||||
|
|
||||||
|
self.variants
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
let var_ident = &v.ident;
|
||||||
|
let field = v.fields.iter().next().unwrap();
|
||||||
|
let var_ty = &field.ty;
|
||||||
|
let var_field = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } });
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl#impl_gen ::std::convert::From<#var_ty> for #ty_ident#ty_gen
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
fn from(v: #var_ty) -> Self {
|
||||||
|
Self::#var_ident#var_field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[automatically_derived]
|
||||||
|
impl#impl_gen ::std::convert::From<#ty_ident#ty_gen> for Option<#var_ty>
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
fn from(ty: #ty_ident#ty_gen) -> Self {
|
||||||
|
if let #ty_ident::#var_ident#var_field = ty {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[automatically_derived]
|
||||||
|
impl#lf_impl_gen ::std::convert::From<&'___a #ty_ident#ty_gen> for
|
||||||
|
Option<&'___a #var_ty>
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
fn from(ty: &'___a #ty_ident#ty_gen) -> Self {
|
||||||
|
if let #ty_ident::#var_ident#var_field = ty {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns generated code implementing [`Display`] by matching over each
|
||||||
|
/// enum variant.
|
||||||
|
///
|
||||||
|
/// [`Display`]: std::fmt::Display
|
||||||
|
fn impl_display_tokens(&self) -> TokenStream {
|
||||||
|
let ident = &self.ident;
|
||||||
|
|
||||||
|
let mut generics = self.generics.clone();
|
||||||
|
generics.make_where_clause();
|
||||||
|
for var in &self.variants {
|
||||||
|
let var_ty = &var.fields.iter().next().unwrap().ty;
|
||||||
|
let mut check = IsVariantGeneric::new(&self.generics);
|
||||||
|
check.visit_type(var_ty);
|
||||||
|
if check.res {
|
||||||
|
generics
|
||||||
|
.where_clause
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.predicates
|
||||||
|
.push(parse_quote! { #var_ty: ::std::fmt::Display });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (impl_gen, ty_gen, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
|
let arms = self.variants.iter().map(|v| {
|
||||||
|
let var_ident = &v.ident;
|
||||||
|
let field = v.fields.iter().next().unwrap();
|
||||||
|
let var_field = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| quote! { (v) }, |i| quote! { { #i: v } });
|
||||||
|
|
||||||
|
quote! { Self::#var_ident#var_field => ::std::fmt::Display::fmt(v, f), }
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl#impl_gen ::std::fmt::Display for #ident#ty_gen
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
#(#arms)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Single-[`Field`] enum variant.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Variant {
|
||||||
|
/// [`Variant`] [`syn::Ident`].
|
||||||
|
ident: syn::Ident,
|
||||||
|
|
||||||
|
/// Single [`Variant`] [`Field`].
|
||||||
|
field: Field,
|
||||||
|
|
||||||
|
/// Optional resolver provided by [`VariantAttr`].
|
||||||
|
expr: Option<syn::ExprPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Variant {
|
||||||
|
/// Returns generated code for matching over this [`Variant`].
|
||||||
|
fn match_arm(&self) -> TokenStream {
|
||||||
|
let (ident, field) = (&self.ident, &self.field.match_arg());
|
||||||
|
quote! {
|
||||||
|
Self::#ident#field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enum [`Variant`] field.
|
||||||
|
#[derive(Clone)]
|
||||||
|
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 TryFrom<syn::Fields> for Field {
|
||||||
|
type Error = syn::Error;
|
||||||
|
|
||||||
|
fn try_from(value: syn::Fields) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
syn::Fields::Named(mut f) if f.named.len() == 1 => {
|
||||||
|
Ok(Self::Named(f.named.pop().unwrap().into_value()))
|
||||||
|
}
|
||||||
|
syn::Fields::Unnamed(mut f) if f.unnamed.len() == 1 => {
|
||||||
|
Ok(Self::Unnamed(f.unnamed.pop().unwrap().into_value()))
|
||||||
|
}
|
||||||
|
_ => Err(ERR.custom_error(value.span(), "expected exactly 1 field")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Field {
|
||||||
|
/// Returns a [`Field`] for constructing or matching over a [`Variant`].
|
||||||
|
fn match_arg(&self) -> TokenStream {
|
||||||
|
match self {
|
||||||
|
Self::Named(_) => quote! { { #self: v } },
|
||||||
|
Self::Unnamed(_) => quote! { (v) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Visit`]or checking whether a [`Variant`]'s [`Field`] contains generic
|
||||||
|
/// parameters.
|
||||||
|
struct IsVariantGeneric<'a> {
|
||||||
|
/// Indicates whether the checked [`Variant`]'s [`Field`] contains generic
|
||||||
|
/// parameters.
|
||||||
|
res: bool,
|
||||||
|
|
||||||
|
/// [`syn::Generics`] to search generic parameters in.
|
||||||
|
generics: &'a syn::Generics,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IsVariantGeneric<'a> {
|
||||||
|
/// Constructs a new [`IsVariantGeneric`] [`Visit`]or.
|
||||||
|
fn new(generics: &'a syn::Generics) -> Self {
|
||||||
|
Self {
|
||||||
|
res: false,
|
||||||
|
generics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast, 'gen> Visit<'ast> for IsVariantGeneric<'gen> {
|
||||||
|
fn visit_path(&mut self, path: &'ast syn::Path) {
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
let is_generic = self.generics.params.iter().any(|par| {
|
||||||
|
if let syn::GenericParam::Type(ty) = par {
|
||||||
|
ty.ident == *ident
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if is_generic {
|
||||||
|
self.res = true;
|
||||||
|
} else {
|
||||||
|
syn::visit::visit_path(self, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue