juniper/docs/book/content/advanced/objects_and_generics.md
Christoph Herzog 9623e4d326 (book) Update and fix book compilation and tests
* Use mdbook for building the book
* Update book config
* Update book hierarchy to work properly with mdbook
    This necessitated adding place-holder index pages
    since mdbook does not suppoert stand-alon menu items
* Update tests to use 2018 edition
* Fix various compilation errors in the tests
2019-03-08 11:47:13 +01:00

2 KiB

Objects and 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-nullables.

This poses a restriction on what you can expose in GraphQL from Rust: no generic structs can be exposed - all type parameters must be bound. For example, you can not make e.g. Result<T, E> into a GraphQL type, but you can make e.g. Result<User, String> into a GraphQL type.

Let's make a slightly more compact but generic implementation of the last chapter:

# #[derive(juniper::GraphQLObject)] struct User { name: String }
# #[derive(juniper::GraphQLObject)] struct ForumPost { title: String }

#[derive(juniper::GraphQLObject)]
struct ValidationError {
    field: String,
    message: String,
}

# #[allow(dead_code)]
struct MutationResult<T>(Result<T, Vec<ValidationError>>);

juniper::graphql_object!(MutationResult<User>: () as "UserResult" |&self| {
    field user() -> Option<&User> {
        self.0.as_ref().ok()
    }

    field error() -> Option<&Vec<ValidationError>> {
        self.0.as_ref().err()
    }
});

juniper::graphql_object!(MutationResult<ForumPost>: () as "ForumPostResult" |&self| {
    field forum_post() -> Option<&ForumPost> {
        self.0.as_ref().ok()
    }

    field error() -> Option<&Vec<ValidationError>> {
        self.0.as_ref().err()
    }
});

# fn main() {}

Here, we've made a wrapper around Result and exposed some concrete instantiations of Result<T, E> as distinct GraphQL objects. The reason we needed the wrapper is of Rust's rules for when you can derive a trait - in this case, both Result and Juniper's internal GraphQL trait are from third-party sources.

Because we're using generics, we also need to specify a name for our instantiated types. Even if Juniper could figure out the name, MutationResult<User> wouldn't be a valid GraphQL type name.