Generics
Yet another point where GraphQL and Rust differs is in how generics work:
- In Rust, almost any type could be generic - that is, take type parameters.
- In GraphQL, there are only two generic types: lists and non-
null
ables.
This poses a restriction on what we can expose in GraphQL from Rust: no generic structs can be exposed - all type parameters must be bound. For example, we cannot expose Result<T, E>
as a GraphQL type, but we can expose Result<User, String>
as a GraphQL type.
Let's make a slightly more compact but generic implementation of the last schema error example:
extern crate juniper; use juniper::{graphql_object, GraphQLObject}; #[derive(GraphQLObject)] struct User { name: String, } #[derive(GraphQLObject)] struct ForumPost { title: String, } #[derive(GraphQLObject)] struct ValidationError { field: String, message: String, } struct MutationResult<T>(Result<T, Vec<ValidationError>>); #[graphql_object] #[graphql(name = "UserResult")] impl MutationResult<User> { fn user(&self) -> Option<&User> { self.0.as_ref().ok() } fn error(&self) -> Option<&[ValidationError]> { self.0.as_ref().err().map(Vec::as_slice) } } #[graphql_object] #[graphql(name = "ForumPostResult")] impl MutationResult<ForumPost> { fn forum_post(&self) -> Option<&ForumPost> { self.0.as_ref().ok() } fn error(&self) -> Option<&[ValidationError]> { self.0.as_ref().err().map(Vec::as_slice) } } fn main() {}
Here, we've made a wrapper around a Result
and exposed some concrete instantiations of Result<T, E>
as distinct GraphQL objects.
NOTE: The reason we needed the wrapper is of Rust's orphan rules (both the
Result
and Juniper's internal traits are from third-party sources).
NOTE: Because we're using generics, we also need to specify a
name
for our instantiated GraphQL types. Even if Juniper could figure out the name,MutationResult<User>
wouldn't be a valid GraphQL type name. And, also, two different GraphQL types cannot have the sameMutationResult
name, inferred by default.