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§
Sourcefn is_type<T: Any + ?Sized>(&self) -> bool
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);Sourcefn downcast_type<T: Any>(&self) -> Option<&T>
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§
Sourcefn try_to<'a, T>(&'a self) -> Result<T, T::Error>where
T: FromScalarValue<'a, Self> + 'a,
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.
Sourcefn try_to_bool(&self) -> Option<bool>
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.
Sourcefn try_to_int(&self) -> Option<i32>
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.
Sourcefn try_to_float(&self) -> Option<f64>
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.
Sourcefn try_to_string(&self) -> Option<String>
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.
Sourcefn try_into_string(self) -> Option<String>
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.
Sourcefn try_as_str(&self) -> Option<&str>
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.
Sourcefn into_another<S: ScalarValue>(self) -> S
fn into_another<S: ScalarValue>(self) -> S
Sourcefn from_displayable<T: Display + Any + ?Sized>(value: &T) -> Self
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)
}
}Sourcefn from_displayable_non_static<T: Display + ?Sized>(value: &T) -> Self
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.