#[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 is 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 of 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);
/// Increments [`Incremented`] before converting into a [`Value`].
fn to_output<S: ScalarValue>(v: &Incremented) -> Value<S> {
let inc = v.0 + 1;
Value::from(inc)
}
§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 [`InputValue`] is `String` beginning with `id: ` and
/// strips it.
fn from_input<S: ScalarValue>(
input: &InputValue<S>,
) -> Result<Self, String> {
// ^^^^^^ must implement `IntoFieldError`
input.as_string_value()
.ok_or_else(|| format!("Expected `String`, found: {input}"))
.and_then(|str| {
str.strip_prefix("id: ")
.ok_or_else(|| {
format!(
"Expected `UserId` to begin with `id: `, \
found: {input}",
)
})
})
.map(|id| Self(id.into()))
}
}
§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) -> Value<S> {
match v {
StringOrInt::String(s) => Value::scalar(s.to_owned()),
StringOrInt::Int(i) => Value::scalar(*i),
}
}
fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
v.as_string_value()
.map(|s| StringOrInt::String(s.into()))
.or_else(|| v.as_int_value().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
}
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) -> Value<S> {
match v {
StringOrInt::String(s) => Value::scalar(s.to_owned()),
StringOrInt::Int(i) => Value::scalar(*i),
}
}
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<StringOrInt, String> {
v.as_string_value()
.map(|s| StringOrInt::String(s.into()))
.or_else(|| v.as_int_value().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
}
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) -> Value<S> {
match self {
Self::String(s) => Value::scalar(s.to_owned()),
Self::Int(i) => Value::scalar(*i),
}
}
fn from_input<S>(v: &InputValue<S>) -> Result<Self, String>
where
S: ScalarValue
{
v.as_string_value()
.map(|s| Self::String(s.into()))
.or_else(|| v.as_int_value().map(Self::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
}
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>(v: &StringOrInt) -> Value<S>
where
S: ScalarValue,
{
match v {
StringOrInt::String(s) => Value::scalar(s.to_owned()),
StringOrInt::Int(i) => Value::scalar(*i),
}
}
pub(super) fn from_input<S>(v: &InputValue<S>) -> Result<StringOrInt, String>
where
S: ScalarValue,
{
v.as_string_value()
.map(|s| StringOrInt::String(s.into()))
.or_else(|| v.as_int_value().map(StringOrInt::Int))
.ok_or_else(|| format!("Expected `String` or `Int`, found: {v}"))
}
// 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
#[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))]
.