#[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))].