#[derive(GraphQLScalar)]
{
// Attributes available to this derive:
#[graphql]
}
Expand description
#[derive(GraphQLScalar)]
macro for deriving a GraphQL scalar implementation.
§Transparent delegation
Quite often we want to create a custom GraphQL scalar type by just wrapping an existing
one, inheriting all its behavior. In Rust, this is often called as “newtype pattern”. This
could be achieved by annotating the definition with the #[graphql(transparent)]
attribute:
#[derive(GraphQLScalar)]
#[graphql(transparent)]
struct UserId(String);
#[derive(GraphQLScalar)]
#[graphql(transparent)]
struct DroidId {
value: String,
}
#[derive(GraphQLObject)]
struct Pair {
user_id: UserId,
droid_id: DroidId,
}
The inherited behaviour may also be customized:
/// Doc comments are used for the GraphQL type description.
#[derive(GraphQLScalar)]
#[graphql(
// Custom GraphQL name.
name = "MyUserId",
// Description can also specified in the attribute.
// This will the doc comment, if one exists.
description = "...",
// Optional specification URL.
specified_by_url = "https://tools.ietf.org/html/rfc4122",
// Explicit generic scalar.
scalar = S: juniper::ScalarValue,
transparent,
)]
struct UserId(String);
All the methods inherited from Newtype
’s field may also be overridden with the attributes
described below.
§Custom resolving
Customization of a GraphQL scalar type resolving is possible via
#[graphql(to_output_with = <fn path>)]
attribute:
#[derive(GraphQLScalar)]
#[graphql(to_output_with = to_output, transparent)]
struct Incremented(i32);
fn to_output(v: &Incremented) -> i32 {
// ^^^ any concrete type having `ToScalarValue` implementation
// could be used
v.0 + 1
}
The provided function is polymorphic by its output type:
#[derive(GraphQLScalar)]
#[graphql(to_output_with = Self::to_output, transparent)]
struct Incremented(i32);
impl Incremented {
fn to_output<S: ScalarValue>(v: &Incremented) -> S {
// ^^^^^^^^^^^^^^ returning generic or concrete `ScalarValue` is also OK
(v.0 + 1).into()
}
}
#[derive(GraphQLScalar)]
#[graphql(to_output_with = Self::to_output, transparent)]
struct CustomDateTime(jiff::Timestamp);
impl CustomDateTime {
fn to_output(&self) -> impl Display {
// ^^^^^^^^^^^^ in this case macro expansion uses the
// `ScalarValue::from_displayable_non_static()` conversion
self.0.strftime("%Y-%m-%d %H:%M:%S%.fZ")
}
}
§Custom parsing
Customization of a GraphQL scalar type parsing is possible via
#[graphql(from_input_with = <fn path>)]
attribute:
#[derive(GraphQLScalar)]
#[graphql(from_input_with = Self::from_input, transparent)]
struct UserId(String);
impl UserId {
/// Checks whether the [`ScalarValue`] is a [`String`] beginning with `id: ` and strips it.
fn from_input(
input: &str,
// ^^^^ any concrete type having `FromScalarValue` implementation could be used
) -> Result<Self, Box<str>> {
// ^^^^^^^^ must implement `IntoFieldError`
input
.strip_prefix("id: ")
.ok_or_else(|| {
format!("Expected `UserId` to begin with `id: `, found: {input}").into()
})
.map(|id| Self(id.into()))
}
}
The provided function is polymorphic by its input and output types:
#[derive(GraphQLScalar)]
#[graphql(from_input_with = Self::from_input, transparent)]
struct UserId(String);
impl UserId {
fn from_input(
input: &Scalar<impl ScalarValue>,
// ^^^^^^ for generic argument using `Scalar` transparent wrapper is required,
// otherwise Rust won't be able to infer the required type
) -> Self {
// ^^^^ if the result is infallible, it's OK to omit `Result`
Self(
input
.try_to_int().map(|i| i.to_string())
.or_else(|| input.try_to_bool().map(|f| f.to_string()))
.or_else(|| input.try_to_float().map(|b| b.to_string()))
.or_else(|| input.try_to_string())
.unwrap_or_else(|| {
unreachable!("`ScalarValue` is at least one of primitive GraphQL types")
}),
)
}
}
§Custom token parsing
Customization of which tokens a GraphQL scalar type should be parsed is possible via
#[graphql(parse_token_with = <fn path>)]
or #[graphql(parse_token(<types>)]
attributes:
#[derive(GraphQLScalar)]
#[graphql(
to_output_with = to_output,
from_input_with = from_input,
parse_token_with = parse_token,
)]
// ^^^^^^^^^^^^^^^^ Can be replaced with `parse_token(String, i32)`, which
// tries to parse as `String` first, and then as `i32` if
// prior fails.
enum StringOrInt {
String(String),
Int(i32),
}
fn to_output<S: ScalarValue>(v: &StringOrInt) -> S {
match v {
StringOrInt::String(s) => S::from_displayable(s),
// ^^^^^^^^^^^^^^^^^^^ preferable conversion for types
// represented by string token
StringOrInt::Int(i) => (*i).into(),
}
}
fn from_input(v: &Scalar<impl ScalarValue>) -> Result<StringOrInt, Box<str>> {
v.try_to_string()
.map(StringOrInt::String)
.or_else(|| v.try_to_int().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
}
fn parse_token<S: ScalarValue>(value: ScalarToken<'_>) -> ParseScalarResult<S> {
<String as ParseScalarValue<S>>::from_str(value)
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
}
NOTE: Once we provide all 3 custom functions, there is no sense in following the newtype pattern anymore.
§Full behavior
Instead of providing all custom functions separately, it’s possible to provide a module holding
the appropriate to_output()
, from_input()
and parse_token()
functions:
#[derive(GraphQLScalar)]
#[graphql(with = string_or_int)]
enum StringOrInt {
String(String),
Int(i32),
}
mod string_or_int {
use super::*;
pub(super) fn to_output<S: ScalarValue>(v: &StringOrInt) -> S {
match v {
StringOrInt::String(s) => S::from_displayable(s),
StringOrInt::Int(i) => (*i).into(),
}
}
pub(super) fn from_input(v: &Scalar<impl ScalarValue>) -> Result<StringOrInt, Box<str>> {
v.try_to_string()
.map(StringOrInt::String)
.or_else(|| v.try_to_int().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
}
pub(super) fn parse_token<S: ScalarValue>(t: ScalarToken<'_>) -> ParseScalarResult<S> {
<String as ParseScalarValue<S>>::from_str(t)
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(t))
}
}
A regular impl
block is also suitable for that:
#[derive(GraphQLScalar)]
// #[graphql(with = Self)] <- default behaviour, so can be omitted
enum StringOrInt {
String(String),
Int(i32),
}
impl StringOrInt {
fn to_output<S: ScalarValue>(&self) -> S {
match self {
Self::String(s) => S::from_displayable(s),
Self::Int(i) => (*i).into(),
}
}
fn from_input(v: &Scalar<impl ScalarValue>) -> Result<Self, Box<str>> {
v.try_to_string()
.map(Self::String)
.or_else(|| v.try_to_int().map(Self::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
}
fn parse_token<S>(value: ScalarToken<'_>) -> ParseScalarResult<S>
where
S: ScalarValue
{
<String as ParseScalarValue<S>>::from_str(value)
.or_else(|_| <i32 as ParseScalarValue<S>>::from_str(value))
}
}
At the same time, any custom function still may be specified separately:
#[derive(GraphQLScalar)]
#[graphql(
with = string_or_int,
parse_token(String, i32)
)]
enum StringOrInt {
String(String),
Int(i32),
}
mod string_or_int {
use super::*;
pub(super) fn to_output<S: ScalarValue>(v: &StringOrInt) -> S {
match v {
StringOrInt::String(s) => S::from_displayable(s),
StringOrInt::Int(i) => (*i).into(),
}
}
pub(super) fn from_input(v: &Scalar<impl ScalarValue>) -> Result<StringOrInt, Box<str>> {
v.try_to_string()
.map(StringOrInt::String)
.or_else(|| v.try_to_int().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}").into())
}
// No need in `parse_token()` function.
}
§Custom ScalarValue
By default, this macro generates code, which is generic over a ScalarValue
type. Concrete
ScalarValue
type may be specified via the #[graphql(scalar = <type>)]
attribute.
It also may be used to provide additional bounds to the ScalarValue
generic, like the
following: #[graphql(scalar = S: Trait)]
.
§Additional arbitrary trait bounds
GraphQL scalar type implementation may be bound with any additional trait bounds via
#[graphql(where(<bounds>))]
attribute, like the following:
#[graphql(where(S: Trait, Self: fmt::Debug + fmt::Display))]
.