Redesign #[derive(ScalarValue)] macro to derive ScalarValue on enums (#1025)

This commit is contained in:
ilslv 2022-03-03 16:49:35 +03:00 committed by GitHub
parent 4862915e47
commit 5bbc73ad89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 894 additions and 572 deletions

View file

@ -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]`
|
= 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>>>
<[bool; LANES] as From<Mask<T, LANES>>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`

View file

@ -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]`
|
= 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>>>
<[bool; LANES] as From<Mask<T, LANES>>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`

View file

@ -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
|
3 | #[graphql_scalar(with = Self, transparent)]

View file

@ -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
|
4 | / struct Scalar {

View file

@ -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
|
4 | struct Scalar(i32, i32);

View file

@ -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
|
4 | struct ScalarSpecifiedByUrl;

View file

@ -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
|
4 | #[graphql(with = Self, transparent)]

View file

@ -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
|
4 | / #[graphql(transparent)]

View file

@ -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
|
4 | / #[graphql(transparent)]

View file

@ -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
|
4 | / #[graphql(transparent)]

View file

@ -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
|
6 | type CustomScalar = Scalar;

View file

@ -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
|
6 | type CustomScalar = Scalar;

View file

@ -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() {}

View file

@ -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 | | }
| |_^

View file

@ -0,0 +1,6 @@
#[derive(juniper::ScalarValue)]
enum ScalarValue {
Variant { first: i32, second: u64 },
}
fn main() {}

View file

@ -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 },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,6 @@
#[derive(juniper::ScalarValue)]
enum ScalarValue {
Variant(u32, i64),
}
fn main() {}

View file

@ -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),
| ^^^^^^^^^^

View file

@ -0,0 +1,4 @@
#[derive(juniper::ScalarValue)]
struct ScalarValue;
fn main() {}

View file

@ -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;
| ^^^^^^^^^^^^^^^^^^^

View file

@ -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]`
|
= 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>>>
<[bool; LANES] as From<Mask<T, LANES>>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`

View file

@ -7,6 +7,7 @@ mod object_derive;
mod scalar_attr_derive_input;
mod scalar_attr_type_alias;
mod scalar_derive;
mod scalar_value_derive;
mod subscription_attr;
mod union_attr;
mod union_derive;

View file

@ -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>());
}
}

View file

@ -10,204 +10,20 @@ use juniper::{
Value, Variables,
};
#[derive(Clone, Debug, PartialEq, Serialize)]
#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)]
#[serde(untagged)]
pub(crate) enum MyScalarValue {
#[value(as_float, as_int)]
Int(i32),
Long(i64),
#[value(as_float)]
Float(f64),
#[value(as_str, as_string, into_string)]
String(String),
#[value(as_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 {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct Visitor;

View file

@ -28,6 +28,7 @@
- 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).
- Rename `ScalarValue::as_boolean` to `ScalarValue::as_bool`. ([#1025](https://github.com/graphql-rust/juniper/pull/1025))
## 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 `__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))
- Implement `#[derive(ScalarValue)]` macro to derive `ScalarValue` on enums. ([#1025](https://github.com/graphql-rust/juniper/pull/1025))
## Fixes

View file

@ -153,7 +153,7 @@ where
if let LookAheadValue::Scalar(s) =
LookAheadValue::from_input_value(&v.item, vars)
{
s.as_boolean().unwrap_or(false)
s.as_bool().unwrap_or(false)
} else {
false
}
@ -168,7 +168,7 @@ where
if let LookAheadValue::Scalar(b) =
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 {
false
}

View file

@ -108,7 +108,7 @@ impl GraphQLParserTranslator {
ExternalValue::Int(ExternalNumber::from(v))
} else if let Some(v) = x.as_float() {
ExternalValue::Float(v)
} else if let Some(v) = x.as_boolean() {
} else if let Some(v) = x.as_bool() {
ExternalValue::Boolean(v)
} else {
panic!("unknown argument type")

View file

@ -281,7 +281,7 @@ mod impl_boolean_scalar {
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Boolean, String> {
v.as_scalar_value()
.and_then(ScalarValue::as_boolean)
.and_then(ScalarValue::as_bool)
.ok_or_else(|| format!("Expected `Boolean`, found: {}", v))
}

View file

@ -4,6 +4,8 @@ use serde::{de::DeserializeOwned, Serialize};
use crate::parser::{ParseError, ScalarToken};
pub use juniper_codegen::ScalarValue;
/// The result of converting a string into a scalar value
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>;
}
// TODO: Revisit this doc, once `GraphQLScalarValue` macro is re-implemented.
/// A trait marking a type that could be used as internal representation of
/// scalar values in juniper
///
/// The main objective of this abstraction is to allow other libraries to
/// replace the default representation with something that better fits their
/// needs.
/// There is a custom derive (`#[derive(juniper::GraphQLScalarValue)]`) available that implements
/// most of the required traits automatically for a enum representing a scalar value.
/// However, [`Serialize`](trait@serde::Serialize) and [`Deserialize`](trait@serde::Deserialize)
/// implementations are expected to be provided.
/// There is a custom derive (`#[derive(`[`ScalarValue`]`)]`) available that
/// implements most of the required traits automatically for a enum representing
/// a scalar value. However, [`Serialize`] and [`Deserialize`] implementations
/// are expected to be provided.
///
/// # Implementing a new scalar value representation
/// 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
/// at the lowest level.
/// The following example introduces an new variant that is able to store 64 bit integers.
/// defining a enum containing a variant for each type that needs to be
/// represented at the lowest level.
/// The following example introduces an new variant that is able to store 64 bit
/// integers.
///
/// ```rust
/// # use std::{fmt, convert::TryInto as _};
/// #
/// # use serde::{de, Deserialize, Deserializer, Serialize};
/// # use juniper::ScalarValue;
/// #
/// #[derive(Clone, Debug, PartialEq, Serialize)]
/// #[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(as_str, as_string, into_string)]
/// String(String),
/// #[value(as_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 {
/// fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
/// struct Visitor;
@ -298,6 +118,9 @@ pub trait ParseScalarValue<S = DefaultScalarValue> {
/// }
/// }
/// ```
///
/// [`Deserialize`]: trait@serde::Deserialize
/// [`Serialize`]: trait@serde::Serialize
pub trait ScalarValue:
fmt::Debug
+ fmt::Display
@ -383,7 +206,7 @@ pub trait ScalarValue:
/// all possible [`ScalarValue`]s.
///
/// [`GraphQLValue`]: crate::GraphQLValue
fn as_boolean(&self) -> Option<bool>;
fn as_bool(&self) -> Option<bool>;
/// Converts this [`ScalarValue`] into another one.
fn into_another<S: ScalarValue>(self) -> S {
@ -391,7 +214,7 @@ pub trait ScalarValue:
S::from(i)
} else if let Some(f) = self.as_float() {
S::from(f)
} else if let Some(b) = self.as_boolean() {
} else if let Some(b) = self.as_bool() {
S::from(b)
} else if let Some(s) = self.into_string() {
S::from(s)
@ -406,12 +229,13 @@ pub trait ScalarValue:
/// These types closely follow the [GraphQL specification][0].
///
/// [0]: https://spec.graphql.org/June2018
#[derive(Clone, Debug, PartialEq, Serialize)]
#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)]
#[serde(untagged)]
pub enum DefaultScalarValue {
/// [`Int` scalar][0] as a signed 32bit numeric nonfractional value.
///
/// [0]: https://spec.graphql.org/June2018/#sec-Int
#[value(as_float, as_int)]
Int(i32),
/// [`Float` scalar][0] as a signed doubleprecision fractional values as
@ -419,190 +243,23 @@ pub enum DefaultScalarValue {
///
/// [0]: https://spec.graphql.org/June2018/#sec-Float
/// [IEEE 754]: https://en.wikipedia.org/wiki/IEEE_floating_point
#[value(as_float)]
Float(f64),
/// [`String` scalar][0] as a textual data, represented as UTF8 character
/// sequences.
///
/// [0]: https://spec.graphql.org/June2018/#sec-String
#[value(as_str, as_string, into_string)]
String(String),
/// [`Boolean` scalar][0] as a `true` or `false` value.
///
/// [0]: https://spec.graphql.org/June2018/#sec-Boolean
#[value(as_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 {
fn from(s: &'a str) -> Self {
Self::String(s.into())

View file

@ -28,3 +28,4 @@ url = "2.0"
derive_more = "0.99.7"
futures = "0.3"
juniper = { version = "0.16.0-dev", path = "../juniper" }
serde = "1.0"

View file

@ -115,6 +115,7 @@ mod graphql_object;
mod graphql_scalar;
mod graphql_subscription;
mod graphql_union;
mod scalar_value;
use proc_macro::TokenStream;
use proc_macro_error::{proc_macro_error, ResultExt as _};
@ -549,6 +550,114 @@ pub fn graphql_scalar(attr: TokenStream, body: TokenStream) -> TokenStream {
.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]
/// implementation for traits and its implementers.
///

View file

@ -14,6 +14,7 @@ pub enum GraphQLScope {
ObjectDerive,
ScalarAttr,
ScalarDerive,
ScalarValueDerive,
UnionAttr,
UnionDerive,
DeriveInputObject,
@ -26,6 +27,7 @@ impl GraphQLScope {
Self::InterfaceAttr => "#sec-Interfaces",
Self::ObjectAttr | Self::ObjectDerive => "#sec-Objects",
Self::ScalarAttr | Self::ScalarDerive => "#sec-Scalars",
Self::ScalarValueDerive => "#sec-Scalars.Built-in-Scalars",
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
Self::DeriveInputObject => "#sec-Input-Objects",
Self::DeriveEnum => "#sec-Enums",
@ -39,6 +41,7 @@ impl fmt::Display for GraphQLScope {
Self::InterfaceAttr => "interface",
Self::ObjectAttr | Self::ObjectDerive => "object",
Self::ScalarAttr | Self::ScalarDerive => "scalar",
Self::ScalarValueDerive => "built-in scalars",
Self::UnionAttr | Self::UnionDerive => "union",
Self::DeriveInputObject => "input object",
Self::DeriveEnum => "enum",

View 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);
}
}
}
}