Derive Macro juniper_codegen_puff::GraphQLScalar
source · [−]#[derive(GraphQLScalar)]
{
// Attributes available to this derive:
#[graphql]
}
Expand description
#[derive(GraphQLScalar)]
macro for deriving a GraphQL scalar
implementation.
Transparent delegation
Sometimes, you 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 may be achieved by providing
a #[graphql(transparent)]
attribute to the definition:
#[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 to follow
Newtype
pattern anymore.
All at once
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))]
.