ScalarValue

Trait ScalarValue 

Source
pub trait ScalarValue:
    Debug
    + Display
    + PartialEq
    + Clone
    + DeserializeOwned
    + Serialize
    + From<String>
    + From<bool>
    + From<i32>
    + From<f64>
    + for<'a> TryToPrimitive<'a, bool, Error: Display + IntoFieldError<Self>>
    + for<'a> TryToPrimitive<'a, i32, Error: Display + IntoFieldError<Self>>
    + for<'a> TryToPrimitive<'a, f64, Error: Display + IntoFieldError<Self>>
    + for<'a> TryToPrimitive<'a, String, Error: Display + IntoFieldError<Self>>
    + for<'a> TryToPrimitive<'a, &'a str, Error: Display + IntoFieldError<Self>>
    + TryInto<String>
    + 'static {
    // Required methods
    fn is_type<T: Any + ?Sized>(&self) -> bool;
    fn downcast_type<T: Any>(&self) -> Option<&T>;

    // Provided methods
    fn try_to<'a, T>(&'a self) -> Result<T, T::Error>
       where T: FromScalarValue<'a, Self> + 'a { ... }
    fn try_to_bool(&self) -> Option<bool> { ... }
    fn try_to_int(&self) -> Option<i32> { ... }
    fn try_to_float(&self) -> Option<f64> { ... }
    fn try_to_string(&self) -> Option<String> { ... }
    fn try_into_string(self) -> Option<String> { ... }
    fn try_as_str(&self) -> Option<&str> { ... }
    fn into_another<S: ScalarValue>(self) -> S { ... }
    fn from_displayable<T: Display + Any + ?Sized>(value: &T) -> Self { ... }
    fn from_displayable_non_static<T: Display + ?Sized>(value: &T) -> Self { ... }
}
Expand description

Type that could be used as internal representation of scalar values (e.g. inside Value and [InputValue]).

This abstraction allows other libraries and user code to replace the default representation with something that better fits their needs than DefaultScalarValue.

§Deriving

There is a custom derive (#[derive(ScalarValue)]) available, that implements most of the required juniper traits automatically for an enum representing a ScalarValue. However, Serialize and Deserialize implementations are expected to be provided, as we as Display, From and TryInto ones (for which it’s convenient to use derive_more).

§Example

The preferred way to define a new ScalarValue representation is defining an enum containing a variant for each type that needs to be represented at the lowest level.

The following example introduces a new variant that is able to store 64-bit integers.

use derive_more::with_trait::{Display, From, TryInto};
use juniper::ScalarValue;
use serde::{de, Deserialize, Deserializer, Serialize};

#[derive(Clone, Debug, Display, From, PartialEq, ScalarValue, Serialize, TryInto)]
#[serde(untagged)]
enum MyScalarValue {
    #[value(to_float, to_int)]
    Int(i32),

    Long(i64),

    #[value(to_float)]
    Float(f64),

    #[value(as_str, to_string)]
    String(String),

    #[value(to_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)
    }
}

Required Methods§

Source

fn is_type<T: Any + ?Sized>(&self) -> bool

Checks whether this ScalarValue contains the value of the provided type T.

§Implementation

Implementations should implement this method.

This is usually an enum dispatch with calling AnyExt::is::<T>() method on each variant.

§Example
let value = DefaultScalarValue::Int(42);

assert_eq!(value.is_type::<i32>(), true);
assert_eq!(value.is_type::<f64>(), false);
Source

fn downcast_type<T: Any>(&self) -> Option<&T>

Downcasts this ScalarValue as the value of the provided type T, if this ScalarValue represents the one.

§Implementation

Implementations should implement this method.

This is usually an enum dispatch with calling AnyExt::downcast_ref::<T>() method on each variant.

§Example
let value = DefaultScalarValue::Int(42);

assert_eq!(value.downcast_type::<i32>(), Some(&42));
assert_eq!(value.downcast_type::<f64>(), None);
§GraphQLScalar implementation

This method is especially useful for performance, when a GraphQLScalar is implemented generically over a ScalarValue, but based on the type that is very likely could be used in an optimized ScalarValue implementation.

#[derive(GraphQLScalar)]
#[graphql(from_input_with = Self::from_input, transparent)]
struct Name(ArcStr);

impl Name {
    fn from_input<S: ScalarValue>(v: &Scalar<S>) -> FieldResult<Self, S> {
        // Check if our `ScalarValue` is represented by an `ArcStr` already, and if so,
        // do the cheap `Clone` instead of allocating a new `ArcStr` in its `From<&str>`
        // implementation.
        let s = if let Some(s) = v.downcast_type::<ArcStr>() {
            s.clone()
        } else {
            v.try_to::<&str>().map(ArcStr::from)?
        };
        if s.chars().next().is_some_and(char::is_uppercase) {
            Ok(Self(s))
        } else {
            Err("`Name` should start with a capital letter".into())
        }
    }
}

However, this method is needed only when the type doesn’t implement a GraphQLScalar itself, or does it in non-optimal way. In reality, the ArcStr already implements a GraphQLScalar and does the ScalarValue::downcast_type() check in its implementation, which can be naturally reused by calling the ScalarValue::try_to() method.

#[derive(GraphQLScalar)]
#[graphql(from_input_with = Self::from_input, transparent)]
struct Name(ArcStr);

impl Name {
    fn from_input(s: ArcStr) -> Result<Self, &'static str> {
        //           ^^^^^^ macro expansion will call the `ScalarValue::try_to()` method
        //                  to extract this type from the `ScalarValue` to this function
        if s.chars().next().is_some_and(char::is_uppercase) {
            Ok(Self(s))
        } else {
            Err("`Name` should start with a capital letter")
        }
    }
}

Provided Methods§

Source

fn try_to<'a, T>(&'a self) -> Result<T, T::Error>
where T: FromScalarValue<'a, Self> + 'a,

Tries to represent this ScalarValue as the specified type T.

This method is the recommended way to parse a defined GraphQLScalar type T from a ScalarValue.

This method could be used instead of other try_* helpers in case the FromScalarValue::Error is needed.

§Example

let v = DefaultScalarValue::Boolean(false);
assert_eq!(v.try_to::<bool>().unwrap(), false);
assert!(v.try_to::<f64>().is_err());

#[derive(Debug, GraphQLScalar, PartialEq)]
#[graphql(transparent)]
struct Name(String);

let v = DefaultScalarValue::String("John".into());
assert_eq!(v.try_to::<String>().unwrap(), "John");
assert_eq!(v.try_to::<&str>().unwrap(), "John");
assert_eq!(v.try_to::<Name>().unwrap(), Name("John".into()));
assert!(v.try_to::<i32>().is_err());
§Implementation

This method is an ergonomic alias for the FromScalarValue<T> conversion.

Implementations should not implement this method, but rather implement only the TryToPrimitive<T> conversion directly in case T is a primitive built-in GraphQL scalar type (bool, f64, i32, &str, or String), otherwise the FromScalarValue<T> conversion is provided when a GraphQLScalar is implemented.

Source

fn try_to_bool(&self) -> Option<bool>

Tries to represent this ScalarValue as a bool value.

Use the ScalarValue::try_to::<bool>() method in case the TryToPrimitive::Error is needed.

§Implementation

This method is an ergonomic alias for the TryToPrimitive<bool> conversion, which is used for implementing GraphQLValue for bool for all possible ScalarValues.

Implementations should not implement this method, but rather implement the TryToPrimitive<bool> conversions for all the supported boolean types.

Source

fn try_to_int(&self) -> Option<i32>

Tries to represent this ScalarValue as an i32 value.

Use the ScalarValue::try_to::<i32>() method in case the TryToPrimitive::Error is needed.

§Implementation

This method is an ergonomic alias for the TryToPrimitive<i32> conversion, which is used for implementing GraphQLValue for i32 for all possible ScalarValues.

Implementations should not implement this method, but rather implement the TryToPrimitive<i32> conversions for all the supported integer types with 32 bit or less to an integer, if requested.

Source

fn try_to_float(&self) -> Option<f64>

Tries to represent this ScalarValue as a f64 value.

Use the ScalarValue::try_to::<f64>() method in case the TryToPrimitive::Error is needed.

§Implementation

This method is an ergonomic alias for the TryToPrimitive<f64> conversion, which is used for implementing GraphQLValue for f64 for all possible ScalarValues.

Implementations should not implement this method, but rather implement the TryToPrimitive<f64> conversions for all the supported integer types with 64 bit and all floating point values with 64 bit or less to a float, if requested.

Source

fn try_to_string(&self) -> Option<String>

Tries to represent this ScalarValue as a String value.

Allocates every time is called. For read-only and non-owning use of the underlying String value, consider using the ScalarValue::try_as_str() method.

Use the ScalarValue::try_to::<String>() method in case the TryToPrimitive::Error is needed.

§Implementation

This method is an ergonomic alias for the TryToPrimitive<String> conversion, which is used for implementing GraphQLValue for String for all possible ScalarValues.

Implementations should not implement this method, but rather implement the TryToPrimitive<String> conversions for all the supported string types, if requested.

Source

fn try_into_string(self) -> Option<String>

Tries to convert this ScalarValue into a String value.

Similar to the ScalarValue::try_to_string() method, but takes ownership, so allows to omit redundant Cloneing.

Use the TryInto<String> conversion in case the TryInto::Error is needed.

§Implementation

This method is an ergonomic alias for the TryInto<String> conversion.

Implementations should not implement this method, but rather implement the TryInto<String> conversion for all the supported string types, if requested.

Source

fn try_as_str(&self) -> Option<&str>

Tries to represent this ScalarValue as a str value.

Use the ScalarValue::try_to::<&str>() method in case the TryToPrimitive::Error is needed.

§Implementation

This method is an ergonomic alias for the TryToPrimitive<&str> conversion, which is used for implementing GraphQLValue for String for all possible ScalarValues.

Implementations should not implement this method, but rather implement the TryToPrimitive<&str> conversions for all the supported string types, if requested.

Source

fn into_another<S: ScalarValue>(self) -> S

Converts this ScalarValue into another one via i32, f64, bool or String conversion.

§Panics

If this ScalarValue doesn’t represent at least one of i32, f64, bool or String.

Source

fn from_displayable<T: Display + Any + ?Sized>(value: &T) -> Self

Creates this ScalarValue from the provided Displayable type.

§Usage

This method cannot work with non-'static types due to Any 'static restriction. For non-'static types the ScalarValue::from_displayable_non_static() method should be used instead. However, the Any here allows implementors to specialize some conversions to be cheaper for their ScalarValue implementation, and so, using this method is preferred whenever is possible.

§Implementation

Default implementation allocates by converting ToString and From<String>.

This method should be implemented if ScalarValue implementation uses some custom string type inside to enable efficient conversion from values of this type.

use arcstr::ArcStr;
use derive_more::with_trait::{Display, From, TryInto};
use juniper::ScalarValue;
use serde::{Deserialize, Serialize};

#[derive(
    Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto,
)]
#[serde(untagged)]
#[value(from_displayable_with = from_arcstr)]
enum MyScalarValue {
    #[from]
    #[value(to_float, to_int)]
    Int(i32),
     
    #[from]
    #[value(to_float)]
    Float(f64),

    #[from(&str, String, ArcStr)]
    #[value(as_str, to_string)]
    String(ArcStr),
     
    #[from]
    #[value(to_bool)]
    Boolean(bool),
}

// Custom implementation of `ScalarValue::from_displayable()` method for specializing
// an efficient conversions from `ArcStr` into `MyScalarValue`.
fn from_arcstr<Str: Display + Any + ?Sized>(s: &Str) -> MyScalarValue {
    use juniper::AnyExt as _; // allows downcasting directly on types without `dyn`

    if let Some(s) = s.downcast_ref::<ArcStr>() {
        MyScalarValue::String(s.clone()) // `Clone`ing `ArcStr` is cheap
    } else {
        // We do not override `ScalarValue::from_displayable_non_static()` here,
        // since `arcstr` crate doesn't provide API for efficient conversion into
        // an `ArcStr` for any `Display`able type, unfortunately.
        // The closest possible way is to use `arcstr::format!("{s}")` expression.
        // However, it actually expands to `ArcStr::from(fmt::format(format_args!("{s}")))`,
        // where `fmt::format()` allocates a `String`, and thus, is fully equivalent to the
        // default implementation, which does `.to_string().into()` conversion.
        MyScalarValue::from_displayable_non_static(s)
    }
}
Source

fn from_displayable_non_static<T: Display + ?Sized>(value: &T) -> Self

Creates this ScalarValue from the provided non-'static Displayable type.

§Usage

This method exists solely because Any requires 'static, and so the ScalarValue::from_displayable() method cannot cover non-'static types. Always prefer to use the ScalarValue::from_displayable() method instead of this one, whenever it’s possible, to allow possible cheap conversion specialization.

§Implementation

Default implementation allocates by converting ToString and From<String>.

This method should be implemented if ScalarValue implementation uses some custom string type inside to create its values efficiently without intermediate String-conversion.

use compact_str::{CompactString, ToCompactString as _};
use derive_more::with_trait::{Display, From, TryInto};
use juniper::ScalarValue;
use serde::{Deserialize, Serialize};

#[derive(
    Clone, Debug, Deserialize, Display, From, PartialEq, ScalarValue, Serialize, TryInto,
)]
#[serde(untagged)]
#[value(from_displayable_non_static_with = to_compact_string)]
enum MyScalarValue {
    #[from]
    #[value(to_float, to_int)]
    Int(i32),
     
    #[from]
    #[value(to_float)]
    Float(f64),

    #[from(&str, String, CompactString)]
    #[value(as_str, to_string)]
    String(CompactString),
     
    #[from]
    #[value(to_bool)]
    Boolean(bool),
}

// Custom implementation of `ScalarValue::from_displayable_non_static()` method
// for efficient writing into a `CompactString` as a `MyScalarValue::String`.
fn to_compact_string<T: Display + ?Sized>(v: &T) -> MyScalarValue {
    v.to_compact_string().into()
}

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§