Scalars
Scalars are the primitive types at the leaves of a GraphQL query: numbers, strings, and booleans. You can create custom scalars to other primitive values, but this often requires coordination with the client library intended to consume the API you're building.
Since any value going over the wire is eventually transformed into JSON, you're also limited in the data types you can use.
There are two ways to define custom scalars.
- For simple scalars that just wrap a primitive type, you can use the newtype pattern with a custom derive.
- For more advanced use cases with custom validation, you can use
the
graphql_scalar!
macro.
Built-in scalars
Juniper has built-in support for:
i32
asInt
f64
asFloat
String
and&str
asString
bool
asBoolean
juniper::ID
asID
. This type is defined in the spec as a type that is serialized as a string but can be parsed from both a string and an integer.
Third party types:
Juniper has built-in support for a few additional types from common third party crates. They are enabled via features that are on by default.
- uuid::Uuid
- chrono::DateTime
- url::Url
newtype pattern
Often, you might need a custom scalar that just wraps an existing type.
This can be done with the newtype pattern and a custom derive, similar to how
serde supports this pattern with #[serde(transparent)]
.
#[derive(juniper::GraphQLScalarValue)] pub struct UserId(i32); #[derive(juniper::GraphQLObject)] struct User { id: UserId, } # fn main() {}
That's it, you can now user UserId
in your schema.
The macro also allows for more customization:
/// You can use a doc comment to specify a description. #[derive(juniper::GraphQLScalarValue)] #[graphql( transparent, // Overwrite the GraphQL type name. name = "MyUserId", // Specify a custom description. // A description in the attribute will overwrite a doc comment. description = "My user id description", )] pub struct UserId(i32); # fn main() {}
Custom scalars
For more complex situations where you also need custom parsing or validation,
you can use the graphql_scalar!
macro.
Typically, you represent your custom scalars as strings.
The example below implements a custom scalar for a custom Date
type.
Note: juniper already has built-in support for the chrono::DateTime
type
via chrono
feature, which is enabled by default and should be used for this
purpose.
The example below is used just for illustration.
Note: the example assumes that the Date
type implements
std::fmt::Display
and std::str::FromStr
.
# mod date { # pub struct Date; # impl std::str::FromStr for Date{ # type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() } # } # // And we define how to represent date as a string. # impl std::fmt::Display for Date { # fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { # unimplemented!() # } # } # } use juniper::{Value, ParseScalarResult, ParseScalarValue}; use date::Date; juniper::graphql_scalar!(Date where Scalar = <S> { description: "Date" // Define how to convert your custom scalar into a primitive type. resolve(&self) -> Value { Value::scalar(self.to_string()) } // Define how to parse a primitive type into your custom scalar. from_input_value(v: &InputValue) -> Option<Date> { v.as_scalar_value::<String>() .and_then(|s| s.parse().ok()) } // Define how to parse a string value. from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> { <String as ParseScalarValue<S>>::from_str(value) } }); # fn main() {}