ScalarValue

Derive Macro ScalarValue 

Source
#[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)
    }
}