#[derive(ScalarValue)]
{
// Attributes available to this derive:
#[value]
}
Expand description
#[derive(ScalarValue)] macro for deriving a ScalarValue implementation.
To derive a ScalarValue on enum you either could mark the corresponding enum variants with
to_int, to_float, to_string, as_str and to_bool attribute arguments (names correspond
to the similar ScalarValue methods aliasing the required TryToPrimitive conversions), or
implement the required TryToPrimitive conversions by hand.
Additional from_displayable_with argument could be used to specify a custom function to
override the default ScalarValue::from_displayable() method.
use derive_more::with_trait::{Display, From, TryInto};
#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)]
#[serde(untagged)]
#[value(from_displayable_with = from_custom_str)]
enum MyScalarValue {
#[value(to_float, to_int)]
Int(i32),
Long(i64),
#[value(to_float)]
Float(f64),
#[value(
as_str,
to_string = String::clone,
)]
// ^^^^^^^^^^^^^ custom resolvers may be provided
String(String),
#[value(to_bool)]
Boolean(bool),
}
// Custom implementation of `ScalarValue::from_displayable()` method for
// possible efficient conversions into `MyScalarValue` from custom string types.
fn from_custom_str<Str: Display + Any + ?Sized>(s: &Str) -> MyScalarValue {
use juniper::AnyExt as _; // allows downcasting directly on types without `dyn`
// Imagine this is some custom optimized string type.
struct CustomString(String);
// We specialize the conversion for this type without going through expensive
// `ToString` -> `From<String>` conversion with allocation.
if let Some(s) = s.downcast_ref::<CustomString>() {
MyScalarValue::String(s.0.clone())
} else {
s.to_string().into()
}
}
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)
}
}