Import book from old stand-alone repo.
Imported from https://github.com/graphql-rust/graphql-rust.github.io.
This commit is contained in:
parent
8c5b86e1c6
commit
49297afab0
31 changed files with 4444 additions and 0 deletions
4
docs/book/.gitignore
vendored
Normal file
4
docs/book/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
_book
|
||||
_skeptic/target
|
||||
node_modules
|
||||
*.swp
|
38
docs/book/.travis.yml
Normal file
38
docs/book/.travis.yml
Normal file
|
@ -0,0 +1,38 @@
|
|||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "vaPicq7s2hHBZwtG5eZ1wSmlIYog8FBZ7OilJs6cXQ0fyP5FqGFdc+VG+FSNbEDqPct/v5ojrfbwhQWZVjzgyMZ+Ikrpd9QD0T2Ie4f5yHh2schpphiog7pAfRG1A56/JsGq7aZr76DsICYUGeU4d8BzjaeFC2ozoo5tE9NXpp5ENLFNuErYGwMcQ0vlLTrK2miyuDn18HasHeT5pmxZT1qN5KjxzqChTvEFbH9pQsVKv+dVQiWVifYt4beOfSxaZJmCyBJHv2MjUOyWmYPtqikVxz4dkTbS/Cyx9dK3u2AgrH2Trrl0RFa5VKQUA+06v9NC+oH8NJj72aw44JdryVTchfQw3VF27H/2xfeg3WJX87/1J1oWvCBBtFWU5UwWapXq4Tz7UjT75H7unmlnc11hwmgMklpqMpD52om8n/GLMY2wkS5/dPJpLbYWt6OnBCPtHdP2EO59Wxg1YJ73PZdsrC81z3t8c4SSUXCmzUCG7P8UrSjpBl0g3yXTtR1/fvvSU1qQLFIDN8ib4tl8KGEgbX1ipJkkgCExriuZ58wOPqOdioqNMfWxyGszqxALsL1qxcET8ZtVOzIRCGuVptV0cUujxUwM9LJBqWq4MqPVO9+98FtX6xZvMM5gUM2dq4gWI45KK/VcNEkgihoSKUyVR2OaW5sTs6d28OejOXs="
|
||||
|
||||
before_install:
|
||||
# Install node.
|
||||
- curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||
- sudo apt-get install -y nodejs
|
||||
# Install yarn.
|
||||
- curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
- echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
- sudo apt-get update && sudo apt-get install -y yarn
|
||||
|
||||
script:
|
||||
- yarn
|
||||
- yarn build
|
||||
- yarn test
|
||||
- touch _book/.nojekyll
|
||||
|
||||
branches:
|
||||
only:
|
||||
- source
|
||||
|
||||
deploy:
|
||||
provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $DEPLOY_TOKEN
|
||||
on:
|
||||
branch: source
|
||||
rust: stable
|
||||
target_branch: master
|
||||
local_dir: _book/
|
12
docs/book/README.md
Normal file
12
docs/book/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
Work in progress with improved documentation and main website for Juniper.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js
|
||||
- `yarn`
|
||||
|
||||
## Contributing
|
||||
|
||||
- Run `yarn build` to build your changes.
|
||||
- Run `yarn serve` to view your changes.
|
||||
- Run `yarn test` to test your changes.
|
3
docs/book/_skeptic/.gitignore
vendored
Normal file
3
docs/book/_skeptic/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
20
docs/book/_skeptic/Cargo.toml
Normal file
20
docs/book/_skeptic/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "_skeptic"
|
||||
version = "0.1.0"
|
||||
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper" }
|
||||
juniper_iron = { git = "https://github.com/graphql-rust/juniper" }
|
||||
|
||||
iron = "^0.5.0"
|
||||
mount = "^0.3.0"
|
||||
|
||||
skeptic = "0.13"
|
||||
|
||||
[build-dependencies]
|
||||
skeptic = "0.13"
|
||||
|
||||
[patch.crates-io]
|
||||
juniper_codegen = { git = "https://github.com/graphql-rust/juniper" }
|
6
docs/book/_skeptic/build.rs
Normal file
6
docs/book/_skeptic/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
extern crate skeptic;
|
||||
|
||||
fn main() {
|
||||
let files = skeptic::markdown_files_of_directory("../docs/");
|
||||
skeptic::generate_doc_tests(&files);
|
||||
}
|
1
docs/book/_skeptic/src/lib.rs
Normal file
1
docs/book/_skeptic/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs"));
|
11
docs/book/book.json
Normal file
11
docs/book/book.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"plugins": [
|
||||
"anchors",
|
||||
"codeblock-omit",
|
||||
"codeblock-rust",
|
||||
"sane-sidebar",
|
||||
"-sharing"
|
||||
],
|
||||
"root": "./docs"
|
||||
|
||||
}
|
74
docs/book/docs/README.md
Normal file
74
docs/book/docs/README.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Juniper
|
||||
|
||||
Juniper is a [GraphQL] server library for Rust. Build type-safe and fast API
|
||||
servers with minimal boilerplate and configuration.
|
||||
|
||||
[GraphQL][graphql] is a data query language developed by Facebook intended to
|
||||
serve mobile and web application frontends.
|
||||
|
||||
_Juniper_ makes it possible to write GraphQL servers in Rust that are
|
||||
type-safe and blazingly fast. We also try to make declaring and resolving
|
||||
GraphQL schemas as convenient as possible as Rust will allow.
|
||||
|
||||
Juniper does not include a web server - instead it provides building blocks to
|
||||
make integration with existing servers straightforward. It optionally provides a
|
||||
pre-built integration for the [Hyper][hyper], [Iron][iron], [Rocket], and [Warp][warp] frameworks, including
|
||||
embedded [Graphiql][graphiql] for easy debugging.
|
||||
|
||||
- [Cargo crate](https://crates.io/crates/juniper)
|
||||
- [API Reference][docsrs]
|
||||
|
||||
## Features
|
||||
|
||||
Juniper supports the full GraphQL query language according to the
|
||||
[specification][graphql_spec], including interfaces, unions, schema
|
||||
introspection, and validations.
|
||||
It does not, however, support the schema language.
|
||||
|
||||
As an exception to other GraphQL libraries for other languages, Juniper builds
|
||||
non-null types by default. A field of type `Vec<Episode>` will be converted into
|
||||
`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be
|
||||
`Option<Vec<Option<Episode>>>`.
|
||||
|
||||
## Integrations
|
||||
|
||||
### Data types
|
||||
|
||||
Juniper has automatic integration with some very common Rust crates to make
|
||||
building schemas a breeze. The types from these crates will be usable in
|
||||
your Schemas automatically.
|
||||
|
||||
- [uuid][uuid]
|
||||
- [url][url]
|
||||
- [chrono][chrono]
|
||||
|
||||
### Web Frameworks
|
||||
|
||||
- [hyper][hyper]
|
||||
- [rocket][rocket]
|
||||
- [iron][iron]
|
||||
- [warp][warp]
|
||||
|
||||
## API Stability
|
||||
|
||||
Juniper has not reached 1.0 yet, thus some API instability should be expected.
|
||||
|
||||
[graphql]: http://graphql.org
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[iron]: http://ironframework.io
|
||||
[graphql_spec]: http://facebook.github.io/graphql
|
||||
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
|
||||
[tokio]: https://github.com/tokio-rs/tokio
|
||||
[hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples
|
||||
[rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples
|
||||
[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples
|
||||
[hyper]: https://hyper.rs
|
||||
[rocket]: https://rocket.rs
|
||||
[book]: https://graphql-rust.github.io
|
||||
[book_quickstart]: https://graphql-rust.github.io/quickstart.html
|
||||
[docsrs]: https://docs.rs/juniper
|
||||
[warp]: https://github.com/seanmonstar/warp
|
||||
[warp_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_warp/examples
|
||||
[uuid]: https://crates.io/crates/uuid
|
||||
[url]: https://crates.io/crates/url
|
||||
[chrono]: https://crates.io/crates/chrono
|
41
docs/book/docs/SUMMARY.md
Normal file
41
docs/book/docs/SUMMARY.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](README.md)
|
||||
- [Quickstart](quickstart.md)
|
||||
|
||||
## Type System
|
||||
|
||||
- [Defining objects](types/objects/defining_objects.md)
|
||||
- [Complex fields](types/objects/complex_fields.md)
|
||||
- [Using contexts](types/objects/using_contexts.md)
|
||||
- [Error handling](types/objects/error_handling.md)
|
||||
- Other types
|
||||
- [Enums](types/enums.md)
|
||||
- [Interfaces](types/interfaces.md)
|
||||
- [Input objects](types/input_objects.md)
|
||||
- [Scalars](types/scalars.md)
|
||||
- [Unions](types/unions.md)
|
||||
|
||||
## Schema
|
||||
|
||||
- [Schemas and mutations](schema/schemas_and_mutations.md)
|
||||
|
||||
## Adding a server
|
||||
|
||||
- Integrations by Juniper
|
||||
- [Hyper](servers/hyper.md)
|
||||
- [Warp](servers/warp.md)
|
||||
- [Rocket](servers/rocket.md)
|
||||
- [Iron](servers/iron.md)
|
||||
- Integrations by others
|
||||
- [Actix-Web](https://github.com/actix/examples/tree/master/juniper)
|
||||
- [Finchers](https://github.com/finchers-rs/finchers-juniper)
|
||||
- [Tsukuyomi](https://github.com/tsukuyomi-rs/tsukuyomi/tree/master/examples/juniper)
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
- [Non-struct objects](advanced/non_struct_objects.md)
|
||||
- [Objects and generics](advanced/objects_and_generics.md)
|
||||
- [Context switching]
|
||||
- [Dynamic type system]
|
||||
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
|
73
docs/book/docs/advanced/multiple_ops_per_request.md
Normal file
73
docs/book/docs/advanced/multiple_ops_per_request.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
# Multiple operations per request
|
||||
|
||||
The GraphQL standard generally assumes there will be one server request for each client operation you want to perform (such as a query or mutation). This is conceptually simple but has the potential to be inefficent.
|
||||
|
||||
Some client libraries such as [apollo-link-batch-http](https://www.apollographql.com/docs/link/links/batch-http.html) have added the ability to batch operations in a single HTTP request to save network round-trips and potentially increase performance. There are some [tradeoffs](https://blog.apollographql.com/batching-client-graphql-queries-a685f5bcd41b) that should be considered before batching requests.
|
||||
|
||||
Juniper's server integration crates support multiple operations in a single HTTP request using JSON arrays. This makes them compatible with client libraries that support batch operations without any special configuration.
|
||||
|
||||
Server integration crates maintained by others are **not required** to support batch requests. Batch requests aren't part of the official GraphQL specification.
|
||||
|
||||
Assuming an integration supports batch requests, for the following GraphQL query:
|
||||
|
||||
```graphql
|
||||
{
|
||||
hero {
|
||||
name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The json data to POST to the server for an individual request would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "{hero{name}}"
|
||||
}
|
||||
```
|
||||
|
||||
And the response would be of the form:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"hero": {
|
||||
"name": "R2-D2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you wanted to run the same query twice in a single HTTP request, the batched json data to POST to the server would be:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"query": "{hero{name}}"
|
||||
},
|
||||
{
|
||||
"query": "{hero{name}}"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
And the response would be of the form:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"hero": {
|
||||
"name": "R2-D2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"hero": {
|
||||
"name": "R2-D2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
57
docs/book/docs/advanced/non_struct_objects.md
Normal file
57
docs/book/docs/advanced/non_struct_objects.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Non-struct objects
|
||||
|
||||
Up until now, we've only looked at mapping structs to GraphQL objects. However,
|
||||
any Rust type can be mapped into a GraphQL object. In this chapter, we'll look
|
||||
at enums, but traits will work too - they don't _have_ to be mapped into GraphQL
|
||||
interfaces.
|
||||
|
||||
Using `Result`-like enums can be a useful way of reporting e.g. validation
|
||||
errors from a mutation:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[derive(GraphQLObject)] struct User { name: String }
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct ValidationError {
|
||||
field: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
# #[allow(dead_code)]
|
||||
enum SignUpResult {
|
||||
Ok(User),
|
||||
Error(Vec<ValidationError>),
|
||||
}
|
||||
|
||||
graphql_object!(SignUpResult: () |&self| {
|
||||
field user() -> Option<&User> {
|
||||
match *self {
|
||||
SignUpResult::Ok(ref user) => Some(user),
|
||||
SignUpResult::Error(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
field error() -> Option<&Vec<ValidationError>> {
|
||||
match *self {
|
||||
SignUpResult::Ok(_) => None,
|
||||
SignUpResult::Error(ref errors) => Some(errors)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Here, we use an enum to decide whether a user's input data was valid or not, and
|
||||
it could be used as the result of e.g. a sign up mutation.
|
||||
|
||||
While this is an example of how you could use something other than a struct to
|
||||
represent a GraphQL object, it's also an example on how you could implement
|
||||
error handling for "expected" errors - errors like validation errors. There are
|
||||
no hard rules on how to represent errors in GraphQL, but there are
|
||||
[some](https://github.com/facebook/graphql/issues/117#issuecomment-170180628)
|
||||
[comments](https://github.com/graphql/graphql-js/issues/560#issuecomment-259508214)
|
||||
from one of the authors of GraphQL on how they intended "hard" field errors to
|
||||
be used, and how to model expected errors.
|
60
docs/book/docs/advanced/objects_and_generics.md
Normal file
60
docs/book/docs/advanced/objects_and_generics.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# 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](non_struct_objects.md):
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
# #[derive(GraphQLObject)] struct User { name: String }
|
||||
# #[derive(GraphQLObject)] struct ForumPost { title: String }
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct ValidationError {
|
||||
field: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
# #[allow(dead_code)]
|
||||
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
||||
|
||||
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()
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
191
docs/book/docs/quickstart.md
Normal file
191
docs/book/docs/quickstart.md
Normal file
|
@ -0,0 +1,191 @@
|
|||
# Quickstart
|
||||
|
||||
This page will give you a short introduction to the concepts in Juniper.
|
||||
|
||||
Once you are done here, head over to the [Tutorial][tutorial] to learn how to
|
||||
use Juniper by creating a full setup step by step, or consult the other chapters
|
||||
for more detailed information.
|
||||
|
||||
## Installation
|
||||
|
||||
!FILENAME Cargo.toml
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = "0.10"
|
||||
```
|
||||
|
||||
## Schema example
|
||||
|
||||
Exposing simple enums and structs as GraphQL is just a matter of adding a custom
|
||||
derive attribute to them. Juniper includes support for basic Rust types that
|
||||
naturally map to GraphQL features, such as `Option<T>`, `Vec<T>`, `Box<T>`,
|
||||
`String`, `f64`, and `i32`, references, and slices.
|
||||
|
||||
For more advanced mappings, Juniper provides multiple macros to map your Rust
|
||||
types to a GraphQL schema. The most important one is the
|
||||
[graphql_object!][jp_obj_macro] macro that is used for declaring an object with
|
||||
resolvers, which you will use for the `Query` and `Mutation` roots.
|
||||
|
||||
!FILENAME main.rs
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
use juniper::{FieldResult};
|
||||
|
||||
# struct DatabasePool;
|
||||
# impl DatabasePool {
|
||||
# fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
|
||||
# fn find_human(&self, id: &str) -> FieldResult<Human> { Err("")? }
|
||||
# fn insert_human(&self, human: &NewHuman) -> FieldResult<Human> { Err("")? }
|
||||
# }
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
enum Episode {
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(description="A humanoid creature in the Star Wars universe")]
|
||||
struct Human {
|
||||
id: String,
|
||||
name: String,
|
||||
appears_in: Vec<Episode>,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
// There is also a custom derive for mapping GraphQL input objects.
|
||||
|
||||
#[derive(GraphQLInputObject)]
|
||||
#[graphql(description="A humanoid creature in the Star Wars universe")]
|
||||
struct NewHuman {
|
||||
name: String,
|
||||
appears_in: Vec<Episode>,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
// Now, we create our root Query and Mutation types with resolvers by using the
|
||||
// graphql_object! macro.
|
||||
// Objects can have contexts that allow accessing shared state like a database
|
||||
// pool.
|
||||
|
||||
struct Context {
|
||||
// Use your real database pool here.
|
||||
pool: DatabasePool,
|
||||
}
|
||||
|
||||
// To make our context usable by Juniper, we have to implement a marker trait.
|
||||
impl juniper::Context for Context {}
|
||||
|
||||
struct Query;
|
||||
|
||||
graphql_object!(Query: Context |&self| {
|
||||
|
||||
field apiVersion() -> &str {
|
||||
"1.0"
|
||||
}
|
||||
|
||||
// Arguments to resolvers can either be simple types or input objects.
|
||||
// The executor is a special (optional) argument that allows accessing the context.
|
||||
field human(&executor, id: String) -> FieldResult<Human> {
|
||||
// Get the context from the executor.
|
||||
let context = executor.context();
|
||||
// Get a db connection.
|
||||
let connection = context.pool.get_connection()?;
|
||||
// Execute a db query.
|
||||
// Note the use of `?` to propagate errors.
|
||||
let human = connection.find_human(&id)?;
|
||||
// Return the result.
|
||||
Ok(human)
|
||||
}
|
||||
});
|
||||
|
||||
struct Mutation;
|
||||
|
||||
graphql_object!(Mutation: Context |&self| {
|
||||
|
||||
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
|
||||
let db = executor.context().pool.get_connection()?;
|
||||
let human: Human = db.insert_human(&new_human)?;
|
||||
Ok(human)
|
||||
}
|
||||
});
|
||||
|
||||
// A root schema consists of a query and a mutation.
|
||||
// Request queries can be executed against a RootNode.
|
||||
type Schema = juniper::RootNode<'static, Query, Mutation>;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
We now have a very simple but functional schema for a GraphQL server!
|
||||
|
||||
To actually serve the schema, see the guides for our [Hyper], [Rocket],
|
||||
[Warp], and [Iron] server integrations. Or you can invoke the executor directly:
|
||||
|
||||
## Executor
|
||||
|
||||
You can invoke `juniper::execute` directly to run a GraphQL query:
|
||||
|
||||
!FILENAME main.rs
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
use juniper::{FieldResult, Variables, EmptyMutation};
|
||||
|
||||
#[derive(GraphQLEnum, Clone, Copy)]
|
||||
enum Episode {
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
graphql_object!(Query: Ctx |&self| {
|
||||
field favoriteEpisode(&executor) -> FieldResult<Episode> {
|
||||
// Use the special &executor argument to fetch our fav episode.
|
||||
Ok(executor.context().0)
|
||||
}
|
||||
});
|
||||
|
||||
// Arbitrary context data.
|
||||
struct Ctx(Episode);
|
||||
|
||||
// A root schema consists of a query and a mutation.
|
||||
// Request queries can be executed against a RootNode.
|
||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
||||
|
||||
fn main() {
|
||||
// Create a context object.
|
||||
let ctx = Ctx(Episode::NewHope);
|
||||
|
||||
// Run the executor.
|
||||
let (res, _errors) = juniper::execute(
|
||||
"query { favoriteEpisode }",
|
||||
None,
|
||||
&Schema::new(Query, EmptyMutation::new()),
|
||||
&Variables::new(),
|
||||
&ctx,
|
||||
).unwrap();
|
||||
|
||||
// Ensure the value matches.
|
||||
assert_eq!(
|
||||
res.as_object_value().unwrap().get_field_value("favoriteEpisode").unwrap().as_string_value().unwrap(),
|
||||
"NEW_HOPE",
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
[hyper]: servers/hyper.md
|
||||
[warp]: servers/warp.md
|
||||
[rocket]: servers/rocket.md
|
||||
[iron]: servers/iron.md
|
||||
[tutorial]: ./tutorial.html
|
||||
[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.graphql_object.html
|
62
docs/book/docs/schema/schemas_and_mutations.md
Normal file
62
docs/book/docs/schema/schemas_and_mutations.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Schemas
|
||||
|
||||
A schema consists of two types: a query object and a mutation object (Juniper
|
||||
does not support subscriptions yet). These two define the root query fields
|
||||
and mutations of the schema, respectively.
|
||||
|
||||
Both query and mutation objects are regular GraphQL objects, defined like any
|
||||
other object in Juniper. The mutation object, however, is optional since schemas
|
||||
can be read-only.
|
||||
|
||||
In Juniper, the `RootNode` type represents a schema. You usually don't have to
|
||||
create this object yourself: see the framework integrations for [Iron](iron.md)
|
||||
and [Rocket](rocket.md) how schemas are created together with the handlers
|
||||
themselves.
|
||||
|
||||
When the schema is first created, Juniper will traverse the entire object graph
|
||||
and register all types it can find. This means that if you define a GraphQL
|
||||
object somewhere but never references it, it will not be exposed in a schema.
|
||||
|
||||
## The query root
|
||||
|
||||
The query root is just a GraphQL object. You define it like any other GraphQL
|
||||
object in Juniper, most commonly using the `graphql_object!` macro:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# use juniper::FieldResult;
|
||||
# #[derive(GraphQLObject)] struct User { name: String }
|
||||
struct Root;
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field userWithUsername(username: String) -> FieldResult<Option<User>> {
|
||||
// Look up user in database...
|
||||
# unimplemented!()
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
## Mutations
|
||||
|
||||
Mutations are _also_ just GraphQL objects. Each mutation is a single field that
|
||||
usually performs some mutating side-effect, such as updating a database.
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# use juniper::FieldResult;
|
||||
# #[derive(GraphQLObject)] struct User { name: String }
|
||||
struct Mutations;
|
||||
|
||||
graphql_object!(Mutations: () |&self| {
|
||||
field signUpUser(name: String, email: String) -> FieldResult<User> {
|
||||
// Validate inputs and save user in database...
|
||||
# unimplemented!()
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
28
docs/book/docs/servers/hyper.md
Normal file
28
docs/book/docs/servers/hyper.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Integrating with Hyper
|
||||
|
||||
[Hyper] is a is a fast HTTP implementation that many other Rust web frameworks
|
||||
leverage. It offers asynchronous I/O via the tokio runtime and works on
|
||||
Rust's stable channel.
|
||||
|
||||
Hyper is not a higher-level web framework and accordingly
|
||||
does not include ergonomic features such as simple endpoint routing,
|
||||
baked-in HTTP responses, or reusable middleware. For GraphQL, those aren't
|
||||
large downsides as all POSTs and GETs usually go through a single endpoint with
|
||||
a few clearly-defined response payloads.
|
||||
|
||||
Juniper's Hyper integration is contained in the [`juniper_hyper`][juniper_hyper] crate:
|
||||
|
||||
!FILENAME Cargo.toml
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = "0.10"
|
||||
juniper_hyper = "0.1.0"
|
||||
```
|
||||
|
||||
Included in the source is a [small example][example] which sets up a basic GraphQL and [GraphiQL] handler.
|
||||
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[hyper]: https://hyper.rs/
|
||||
[juniper_hyper]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper
|
||||
[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/examples/hyper_server.rs
|
125
docs/book/docs/servers/iron.md
Normal file
125
docs/book/docs/servers/iron.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
# Integrating with Iron
|
||||
|
||||
[Iron] is a library that's been around for a while in the Rust sphere but lately
|
||||
hasn't seen much of development. Nevertheless, it's still a solid library with a
|
||||
familiar request/response/middleware architecture that works on Rust's stable
|
||||
channel.
|
||||
|
||||
Juniper's Iron integration is contained in the `juniper_iron` crate:
|
||||
|
||||
!FILENAME Cargo.toml
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = "0.10"
|
||||
juniper_iron = "0.2.0"
|
||||
```
|
||||
|
||||
Included in the source is a [small
|
||||
example](https://github.com/graphql-rust/juniper_iron/blob/master/examples/iron_server.rs)
|
||||
which sets up a basic GraphQL and [GraphiQL] handler.
|
||||
|
||||
## Basic integration
|
||||
|
||||
Let's start with a minimal schema and just get a GraphQL endpoint up and
|
||||
running. We use [mount] to attach the GraphQL handler at `/graphql`.
|
||||
|
||||
The `context_factory` function will be executed on every request and can be used
|
||||
to set up database connections, read session token information from cookies, and
|
||||
set up other global data that the schema might require.
|
||||
|
||||
In this example, we won't use any global data so we just return an empty value.
|
||||
|
||||
```rust,ignore
|
||||
#[macro_use] extern crate juniper;
|
||||
extern crate juniper_iron;
|
||||
extern crate iron;
|
||||
extern crate mount;
|
||||
|
||||
use mount::Mount;
|
||||
use iron::prelude::*;
|
||||
use juniper::EmptyMutation;
|
||||
use juniper_iron::GraphQLHandler;
|
||||
|
||||
fn context_factory(_: &mut Request) -> IronResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Root;
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field foo() -> String {
|
||||
"Bar".to_owned()
|
||||
}
|
||||
});
|
||||
|
||||
# #[allow(unreachable_code, unused_variables)]
|
||||
fn main() {
|
||||
let mut mount = Mount::new();
|
||||
|
||||
let graphql_endpoint = GraphQLHandler::new(
|
||||
context_factory,
|
||||
Root,
|
||||
EmptyMutation::<()>::new(),
|
||||
);
|
||||
|
||||
mount.mount("/graphql", graphql_endpoint);
|
||||
|
||||
let chain = Chain::new(mount);
|
||||
|
||||
# return;
|
||||
Iron::new(chain).http("0.0.0.0:8080").unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
## Accessing data from the request
|
||||
|
||||
If you want to access e.g. the source IP address of the request from a field
|
||||
resolver, you need to pass this data using Juniper's [context
|
||||
feature](context.md).
|
||||
|
||||
```rust,ignore
|
||||
# #[macro_use] extern crate juniper;
|
||||
# extern crate juniper_iron;
|
||||
# extern crate iron;
|
||||
# use iron::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
struct Context {
|
||||
remote_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl juniper::Context for Context {}
|
||||
|
||||
fn context_factory(req: &mut Request) -> IronResult<Context> {
|
||||
Ok(Context {
|
||||
remote_addr: req.remote_addr
|
||||
})
|
||||
}
|
||||
|
||||
struct Root;
|
||||
|
||||
graphql_object!(Root: Context |&self| {
|
||||
field my_addr(&executor) -> String {
|
||||
let context = executor.context();
|
||||
|
||||
format!("Hello, you're coming from {}", context.remote_addr)
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {
|
||||
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
||||
# context_factory,
|
||||
# Root,
|
||||
# juniper::EmptyMutation::<Context>::new(),
|
||||
# );
|
||||
# }
|
||||
```
|
||||
|
||||
## Accessing global data
|
||||
|
||||
FIXME: Show how the `persistent` crate works with contexts using e.g. `r2d2`.
|
||||
|
||||
[iron]: http://ironframework.io
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[mount]: https://github.com/iron/mount
|
22
docs/book/docs/servers/rocket.md
Normal file
22
docs/book/docs/servers/rocket.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Integrating with Rocket
|
||||
|
||||
[Rocket] is a web framework for Rust that makes it simple to write fast web applications without sacrificing flexibility or type safety. All with minimal code. Rocket
|
||||
does not work on Rust's stable channel and instead requires the nightly
|
||||
channel.
|
||||
|
||||
Juniper's Rocket integration is contained in the [`juniper_rocket`][juniper_rocket] crate:
|
||||
|
||||
!FILENAME Cargo.toml
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = "0.10"
|
||||
juniper_rocket = "0.2.0"
|
||||
```
|
||||
|
||||
Included in the source is a [small example][example] which sets up a basic GraphQL and [GraphiQL] handler.
|
||||
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[rocket]: https://rocket.rs/
|
||||
[juniper_rocket]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket
|
||||
[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs
|
23
docs/book/docs/servers/warp.md
Normal file
23
docs/book/docs/servers/warp.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Integrating with Warp
|
||||
|
||||
[Warp] is a super-easy, composable, web server framework for warp speeds.
|
||||
The fundamental building block of warp is the Filter: they can be combined and composed to express rich requirements on requests. Warp is built on [Hyper] and works on
|
||||
Rust's stable channel.
|
||||
|
||||
Juniper's Warp integration is contained in the [`juniper_warp`][juniper_warp] crate:
|
||||
|
||||
!FILENAME Cargo.toml
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = "0.10"
|
||||
juniper_warp = "0.1.0"
|
||||
```
|
||||
|
||||
Included in the source is a [small example][example] which sets up a basic GraphQL and [GraphiQL] handler.
|
||||
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[hyper]: https://hyper.rs/
|
||||
[warp]: https://crates.io/crates/warp
|
||||
[juniper_warp]: https://github.com/graphql-rust/juniper/tree/master/juniper_warp
|
||||
[example]: https://github.com/graphql-rust/juniper/tree/master/juniper_warp/examples/warp_server
|
3
docs/book/docs/styles/website.css
Normal file
3
docs/book/docs/styles/website.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.markdown-section pre {
|
||||
line-height: 1.3;
|
||||
}
|
65
docs/book/docs/types/enums.md
Normal file
65
docs/book/docs/types/enums.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
# Enums
|
||||
|
||||
Enums in GraphQL are string constants grouped together to represent a set of
|
||||
possible values. Simple Rust enums can be converted to GraphQL enums by using a
|
||||
custom derive attribute:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper_codegen;
|
||||
extern crate juniper;
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
enum Episode {
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Juniper converts all enum variants to uppercase, so the corresponding string
|
||||
values for these variants are `NEWHOPE`, `EMPIRE`, and `JEDI`, respectively. If
|
||||
you want to override this, you can use the `graphql` attribute, similar to how
|
||||
it works when [defining objects](defining_objects.md):
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# extern crate juniper;
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
enum Episode {
|
||||
#[graphql(name="NEW_HOPE")]
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Documentation and deprecation
|
||||
|
||||
Just like when defining objects, the type itself can be renamed and documented,
|
||||
while individual enum variants can be renamed, documented, and deprecated:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# extern crate juniper;
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(name="Episode", description="An episode of Star Wars")]
|
||||
enum StarWarsEpisode {
|
||||
#[graphql(deprecated="We don't really talk about this one")]
|
||||
ThePhantomMenace,
|
||||
|
||||
#[graphql(name="NEW_HOPE")]
|
||||
NewHope,
|
||||
|
||||
#[graphql(description="Arguably the best one in the trilogy")]
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
60
docs/book/docs/types/input_objects.md
Normal file
60
docs/book/docs/types/input_objects.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Input objects
|
||||
|
||||
Input objects are complex data structures that can be used as arguments to
|
||||
GraphQL fields. In Juniper, you can define input objects using a custom derive
|
||||
attribute, similar to simple objects and enums:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper_codegen;
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
#[derive(GraphQLInputObject)]
|
||||
struct Coordinate {
|
||||
latitude: f64,
|
||||
longitude: f64
|
||||
}
|
||||
|
||||
struct Root;
|
||||
# #[derive(GraphQLObject)] struct User { name: String }
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
||||
// Send coordinate to database
|
||||
# unimplemented!()
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Documentation and renaming
|
||||
|
||||
Just like the [other](defining_objects.md) [derives](enums.md), you can rename
|
||||
and add documentation to both the type and the fields:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper_codegen;
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
#[derive(GraphQLInputObject)]
|
||||
#[graphql(name="Coordinate", description="A position on the globe")]
|
||||
struct WorldCoordinate {
|
||||
#[graphql(name="lat", description="The latitude")]
|
||||
latitude: f64,
|
||||
|
||||
#[graphql(name="long", description="The longitude")]
|
||||
longitude: f64
|
||||
}
|
||||
|
||||
struct Root;
|
||||
# #[derive(GraphQLObject)] struct User { name: String }
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
||||
// Send coordinate to database
|
||||
# unimplemented!()
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
223
docs/book/docs/types/interfaces.md
Normal file
223
docs/book/docs/types/interfaces.md
Normal file
|
@ -0,0 +1,223 @@
|
|||
# Interfaces
|
||||
|
||||
GraphQL interfaces map well to interfaces known from common object-oriented
|
||||
languages such as Java or C#, but Rust has unfortunately not a concept that maps
|
||||
perfectly to them. Because of this, defining interfaces in Juniper can require a
|
||||
little bit of boilerplate code, but on the other hand gives you full control
|
||||
over which type is backing your interface.
|
||||
|
||||
To highlight a couple of different ways you can implement interfaces in Rust,
|
||||
let's have a look at the same end-result from a few different implementations:
|
||||
|
||||
## Traits
|
||||
|
||||
Traits are maybe the most obvious concept you want to use when building
|
||||
interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
|
||||
have to manually specify how to convert a trait into a concrete type. This can
|
||||
be done in a couple of different ways:
|
||||
|
||||
### Downcasting via accessor methods
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
|
||||
// Downcast methods, each concrete class will need to implement one of these
|
||||
fn as_human(&self) -> Option<&Human> { None }
|
||||
fn as_droid(&self) -> Option<&Droid> { None }
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
fn as_human(&self) -> Option<&Human> { Some(&self) }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||
}
|
||||
|
||||
graphql_interface!(<'a> &'a Character: () as "Character" |&self| {
|
||||
field id() -> &str { self.id() }
|
||||
|
||||
instance_resolvers: |_| {
|
||||
// The left hand side indicates the concrete type T, the right hand
|
||||
// side should be an expression returning Option<T>
|
||||
&Human => self.as_human(),
|
||||
&Droid => self.as_droid(),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
The `instance_resolvers` declaration lists all the implementors of the given
|
||||
interface and how to resolve them.
|
||||
|
||||
As you can see, you lose a bit of the point with using traits: you need to list
|
||||
all the concrete types in the trait itself, and there's a bit of repetition
|
||||
going on.
|
||||
|
||||
### Using an extra database lookup
|
||||
|
||||
If you can afford an extra database lookup when the concrete class is requested,
|
||||
you can do away with the downcast methods and use the context instead. Here,
|
||||
we'll use two hashmaps, but this could be two tables and some SQL calls instead:
|
||||
|
||||
FIXME: This example does not compile at the moment
|
||||
|
||||
```rust,ignore
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||
field id() -> &str { self.id() }
|
||||
|
||||
instance_resolvers: |&context| {
|
||||
&Human => context.humans.get(self.id()),
|
||||
&Droid => context.droids.get(self.id()),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
This removes the need of downcast methods, but still requires some repetition.
|
||||
|
||||
## Placeholder objects
|
||||
|
||||
Continuing on from the last example, the trait itself seems a bit unneccesary.
|
||||
Maybe it can just be a struct containing the ID?
|
||||
|
||||
FIXME: This example does not compile at the moment
|
||||
|
||||
```rust,ignore
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
struct Character {
|
||||
id: String,
|
||||
}
|
||||
|
||||
graphql_interface!(Character: Database |&self| {
|
||||
field id() -> &str { self.id.as_str() }
|
||||
|
||||
instance_resolvers: |&context| {
|
||||
&Human => context.humans.get(&self.id),
|
||||
&Droid => context.droids.get(&self.id),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
This reduces repetition some more, but might be impractical if the interface's
|
||||
surface area is large.
|
||||
|
||||
## Enums
|
||||
|
||||
Using enums and pattern matching lies half-way between using traits and using
|
||||
placeholder objects. We don't need the extra database call in this case, so
|
||||
we'll remove it.
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
# #[allow(dead_code)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
|
||||
graphql_interface!(Character: () |&self| {
|
||||
field id() -> &str {
|
||||
match *self {
|
||||
Character::Human(Human { ref id, .. }) |
|
||||
Character::Droid(Droid { ref id, .. }) => id,
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {
|
||||
&Human => match *self { Character::Human(ref h) => Some(h), _ => None },
|
||||
&Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
98
docs/book/docs/types/objects/complex_fields.md
Normal file
98
docs/book/docs/types/objects/complex_fields.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
# Complex fields
|
||||
|
||||
If you've got a struct that can't be mapped directly to GraphQL, that contains
|
||||
computed fields or circular structures, you have to use a more powerful tool:
|
||||
the `graphql_object!` macro. This macro lets you define GraphQL objects similar
|
||||
to how you define methods in a Rust `impl` block for a type. Continuing with the
|
||||
example from the last chapter, this is how you would define `Person` using the
|
||||
macro:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
graphql_object!(Person: () |&self| {
|
||||
field name() -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
field age() -> i32 {
|
||||
self.age
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
While this is a bit more verbose, it lets you write any kind of function in the
|
||||
field resolver. With this syntax, fields can also take arguments:
|
||||
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
struct House {
|
||||
inhabitants: Vec<Person>,
|
||||
}
|
||||
|
||||
graphql_object!(House: () |&self| {
|
||||
// Creates the field inhabitantWithName(name), returning a nullable person
|
||||
field inhabitant_with_name(name: String) -> Option<&Person> {
|
||||
self.inhabitants.iter().find(|p| p.name == name)
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
To access global data such as database connections or authentication
|
||||
information, a _context_ is used. To learn more about this, see the next
|
||||
chapter: [Using contexts](using_contexts.md).
|
||||
|
||||
## Description, renaming, and deprecation
|
||||
|
||||
Like with the derive attribute, field names will be converted from `snake_case`
|
||||
to `camelCase`. If you need to override the conversion, you can simply rename
|
||||
the field. Also, the type name can be changed with an alias:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
struct Person {
|
||||
name: String,
|
||||
website_url: String,
|
||||
}
|
||||
|
||||
graphql_object!(Person: () as "PersonObject" |&self| {
|
||||
field name() -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
field websiteURL() -> &str {
|
||||
self.website_url.as_str()
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
## More features
|
||||
|
||||
GraphQL fields expose more features than Rust's standard method syntax gives us:
|
||||
|
||||
* Per-field description and deprecation messages
|
||||
* Per-argument default values
|
||||
* Per-argument descriptions
|
||||
|
||||
These, and more features, are described more thorougly in [the reference
|
||||
documentation](https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html).
|
209
docs/book/docs/types/objects/defining_objects.md
Normal file
209
docs/book/docs/types/objects/defining_objects.md
Normal file
|
@ -0,0 +1,209 @@
|
|||
# Defining objects
|
||||
|
||||
While any type in Rust can be exposed as a GraphQL object, the most common one
|
||||
is a struct.
|
||||
|
||||
There are two ways to create a GraphQL object in Juniper. If you've got a simple
|
||||
struct you want to expose, the easiest way is to use the custom derive
|
||||
attribute. The other way is described in the [Complex fields](complex_fields.md)
|
||||
chapter.
|
||||
|
||||
```rust
|
||||
extern crate juniper;
|
||||
#[macro_use] extern crate juniper_codegen;
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
This will create a GraphQL object type called `Person`, with two fields: `name`
|
||||
of type `String!`, and `age` of type `Int!`. Because of Rust's type system,
|
||||
everything is exported as non-null by default. If you need a nullable field, you
|
||||
can use `Option<T>`.
|
||||
|
||||
We should take advantage of the
|
||||
fact that GraphQL is self-documenting and add descriptions to the type and
|
||||
fields. Juniper will automatically use associated doc comments as GraphQL
|
||||
descriptions:
|
||||
|
||||
!FILENAME GraphQL descriptions via Rust doc comments
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
/// Information about a person
|
||||
struct Person {
|
||||
/// The person's full name, including both first and last names
|
||||
name: String,
|
||||
/// The person's age in years, rounded down
|
||||
age: i32,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Objects and fields without doc comments can instead set a `description`
|
||||
via the `graphql` attribute. The following example is equivalent to the above:
|
||||
|
||||
!FILENAME GraphQL descriptions via attribute
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(description="Information about a person")]
|
||||
struct Person {
|
||||
#[graphql(description="The person's full name, including both first and last names")]
|
||||
name: String,
|
||||
#[graphql(description="The person's age in years, rounded down")]
|
||||
age: i32,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Descriptions set via the `graphql` attribute take precedence over Rust
|
||||
doc comments. This enables internal Rust documentation and external GraphQL
|
||||
documentation to differ:
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(description="This description shows up in GraphQL")]
|
||||
/// This description shows up in RustDoc
|
||||
struct Person {
|
||||
#[graphql(description="This description shows up in GraphQL")]
|
||||
/// This description shows up in RustDoc
|
||||
name: String,
|
||||
/// This description shows up in both RustDoc and GraphQL
|
||||
age: i32,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Relationships
|
||||
|
||||
You can only use the custom derive attribute under these circumstances:
|
||||
|
||||
- The annotated type is a `struct`,
|
||||
- Every struct field is either
|
||||
- A primitive type (`i32`, `f64`, `bool`, `String`, `juniper::ID`), or
|
||||
- A valid custom GraphQL type, e.g. another struct marked with this attribute,
|
||||
or
|
||||
- A container/reference containing any of the above, e.g. `Vec<T>`, `Box<T>`,
|
||||
`Option<T>`
|
||||
|
||||
Let's see what that means for building relationships between objects:
|
||||
|
||||
```rust
|
||||
extern crate juniper;
|
||||
#[macro_use] extern crate juniper_codegen;
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct House {
|
||||
address: Option<String>, // Converted into String (nullable)
|
||||
inhabitants: Vec<Person>, // Converted into [Person!]!
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
Because `Person` is a valid GraphQL type, you can have a `Vec<Person>` in a
|
||||
struct and it'll be automatically converted into a list of non-nullable `Person`
|
||||
objects.
|
||||
|
||||
## Renaming fields
|
||||
|
||||
By default, struct fields are converted from Rust's standard `snake_case` naming
|
||||
convention into GraphQL's `camelCase` convention:
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
first_name: String, // Would be exposed as firstName in the GraphQL schema
|
||||
last_name: String, // Exposed as lastName
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
You can override the name by using the `graphql` attribute on individual struct
|
||||
fields:
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
#[graphql(name="websiteURL")]
|
||||
website_url: Option<String>, // Now exposed as websiteURL in the schema
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Deprecating fields
|
||||
|
||||
To deprecate a field, you specify a deprecation reason using the `graphql`
|
||||
attribute:
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
#[graphql(deprecation="Please use the name field instead")]
|
||||
first_name: String,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
The `name`, `description`, and `deprecation` arguments can of course be
|
||||
combined. Some restrictions from the GraphQL spec still applies though; you can
|
||||
only deprecate object fields and enum values.
|
||||
|
||||
## Skipping fields
|
||||
|
||||
By default all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(skip)]`:
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
#
|
||||
#[derive(GraphQLObject)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
#[graphql(skip)]
|
||||
password_hash: String, // This cannot be queried or modified from GraphQL
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
168
docs/book/docs/types/objects/error_handling.md
Normal file
168
docs/book/docs/types/objects/error_handling.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
# Error handling
|
||||
|
||||
Rust
|
||||
[provides](https://doc.rust-lang.org/book/second-edition/ch09-00-error-handling.html)
|
||||
two ways of dealing with errors: `Result<T, E>` for recoverable errors and
|
||||
`panic!` for unrecoverable errors. Juniper does not do anything about panicking;
|
||||
it will bubble up to the surrounding framework and hopefully be dealt with
|
||||
there.
|
||||
|
||||
For recoverable errors, Juniper works well with the built-in `Result` type, you
|
||||
can use the `?` operator or the `try!` macro and things will generally just work
|
||||
as you expect them to:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
use juniper::FieldResult;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::str;
|
||||
|
||||
struct Example {
|
||||
filename: PathBuf,
|
||||
}
|
||||
|
||||
graphql_object!(Example: () |&self| {
|
||||
field contents() -> FieldResult<String> {
|
||||
let mut file = File::open(&self.filename)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
}
|
||||
field foo() -> FieldResult<Option<String>> {
|
||||
// Some invalid bytes.
|
||||
let invalid = vec![128, 223];
|
||||
|
||||
match str::from_utf8(&invalid) {
|
||||
Ok(s) => Ok(Some(s.to_string())),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
`FieldResult<T>` is an alias for `Result<T, FieldError>`, which is the error
|
||||
type all fields must return. By using the `?` operator or `try!` macro, any type
|
||||
that implements the `Display` trait - which are most of the error types out
|
||||
there - those errors are automatically converted into `FieldError`.
|
||||
|
||||
When a field returns an error, the field's result is replaced by `null`, an
|
||||
additional `errors` object is created at the top level of the response, and the
|
||||
execution is resumed. For example, with the previous example and the following
|
||||
query:
|
||||
|
||||
```graphql
|
||||
{
|
||||
example {
|
||||
contents
|
||||
foo
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `str::from_utf8` resulted in a `std::str::Utf8Error`, the following would be
|
||||
returned:
|
||||
|
||||
!FILENAME Response for nullable field with error
|
||||
|
||||
```js
|
||||
{
|
||||
"data": {
|
||||
"example": {
|
||||
contents: "<Contents of the file>",
|
||||
foo: null,
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"message": "invalid utf-8 sequence of 2 bytes from index 0",
|
||||
"locations": [{ "line": 2, "column": 4 }])
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If an error is returned from a non-null field, such as the
|
||||
example above, the `null` value is propagated up to the first nullable parent
|
||||
field, or the root `data` object if there are no nullable fields.
|
||||
|
||||
For example, with the following query:
|
||||
|
||||
```graphql
|
||||
{
|
||||
example {
|
||||
contents
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `File::open()` above resulted in `std::io::ErrorKind::PermissionDenied`, the
|
||||
following would be returned:
|
||||
|
||||
!FILENAME Response for non-null field with error and no nullable parent
|
||||
|
||||
```js
|
||||
{
|
||||
"errors": [
|
||||
"message": "Permission denied (os error 13)",
|
||||
"locations": [{ "line": 2, "column": 4 }])
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Structured errors
|
||||
|
||||
Sometimes it is desirable to return additional structured error information
|
||||
to clients. This can be accomplished by implementing [`IntoFieldError`](https://docs.rs/juniper/latest/juniper/trait.IntoFieldError.html):
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
use juniper::{FieldError, IntoFieldError};
|
||||
|
||||
enum CustomError {
|
||||
WhateverNotSet,
|
||||
}
|
||||
|
||||
impl IntoFieldError for CustomError {
|
||||
fn into_field_error(self) -> FieldError {
|
||||
match self {
|
||||
CustomError::WhateverNotSet => FieldError::new(
|
||||
"Whatever does not exist",
|
||||
graphql_value!({
|
||||
"type": "NO_WHATEVER"
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Example {
|
||||
whatever: Option<bool>,
|
||||
}
|
||||
|
||||
graphql_object!(Example: () |&self| {
|
||||
field whatever() -> Result<bool, CustomError> {
|
||||
if let Some(value) = self.whatever {
|
||||
return Ok(value);
|
||||
}
|
||||
Err(CustomError::WhateverNotSet)
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
The specified structured error information is included in the [`extensions`](https://facebook.github.io/graphql/June2018/#sec-Errors) key:
|
||||
|
||||
```js
|
||||
{
|
||||
"errors": [
|
||||
"message": "Whatever does not exist",
|
||||
"locations": [{ "line": 2, "column": 4 }]),
|
||||
"extensions": {
|
||||
"type": "NO_WHATEVER"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
77
docs/book/docs/types/objects/using_contexts.md
Normal file
77
docs/book/docs/types/objects/using_contexts.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Using contexts
|
||||
|
||||
The context type is a feature in Juniper that lets field resolvers access global
|
||||
data, most commonly database connections or authentication information. The
|
||||
context is usually created from a _context factory_. How this is defined is
|
||||
specific to the framework integration you're using, so check out the
|
||||
documentation for either the [Iron](iron.md) or [Rocket](rocket.md)
|
||||
integration.
|
||||
|
||||
In this chapter, we'll show you how to define a context type and use it in field
|
||||
resolvers. Let's say that we have a simple user database in a `HashMap`:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# use std::collections::HashMap;
|
||||
|
||||
struct Database {
|
||||
users: HashMap<i32, User>,
|
||||
}
|
||||
|
||||
struct User {
|
||||
id: i32,
|
||||
name: String,
|
||||
friend_ids: Vec<i32>,
|
||||
}
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
We would like a `friends` field on `User` that returns a list of `User` objects.
|
||||
In order to write such a field though, the database must be queried.
|
||||
|
||||
To solve this, we mark the `Database` as a valid context type and assign it to
|
||||
the user object. Then, we use the special `&executor` argument to access the
|
||||
current context object:
|
||||
|
||||
```rust
|
||||
# use std::collections::HashMap;
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
struct Database {
|
||||
users: HashMap<i32, User>,
|
||||
}
|
||||
|
||||
struct User {
|
||||
id: i32,
|
||||
name: String,
|
||||
friend_ids: Vec<i32>,
|
||||
}
|
||||
|
||||
// 1. Mark the Database as a valid context type for Juniper
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
// 2. Assign Database as the context type for User
|
||||
graphql_object!(User: Database |&self| {
|
||||
// 3. Use the special executor argument
|
||||
field friends(&executor) -> Vec<&User> {
|
||||
// 4. Use the executor to access the context object
|
||||
let database = executor.context();
|
||||
|
||||
// 5. Use the database to lookup users
|
||||
self.friend_ids.iter()
|
||||
.map(|id| database.users.get(id).expect("Could not find user with ID"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
field name() -> &str { self.name.as_str() }
|
||||
field id() -> i32 { self.id }
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
You only get an immutable reference to the context, so if you want to affect
|
||||
change to the execution, you'll need to use [interior
|
||||
mutability](https://doc.rust-lang.org/book/first-edition/mutability.html#interior-vs-exterior-mutability)
|
||||
using e.g. `RwLock` or `RefCell`.
|
56
docs/book/docs/types/scalars.md
Normal file
56
docs/book/docs/types/scalars.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# 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. Typically, you represent your custom
|
||||
scalars as strings.
|
||||
|
||||
In Juniper, you use the `graphql_scalar!` macro to create a custom scalar. In
|
||||
this example, we're representing a user ID as a string wrapped in a custom type:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
use juniper::Value;
|
||||
|
||||
struct UserID(String);
|
||||
|
||||
graphql_scalar!(UserID {
|
||||
description: "An opaque identifier, represented as a string"
|
||||
|
||||
resolve(&self) -> Value {
|
||||
Value::string(&self.0)
|
||||
}
|
||||
|
||||
from_input_value(v: &InputValue) -> Option<UserID> {
|
||||
// If there's a parse error here, simply return None. Juniper will
|
||||
// present an error to the client.
|
||||
v.as_string_value().map(|s| UserID(s.to_owned()))
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Built-in scalars
|
||||
|
||||
Juniper has built-in support for:
|
||||
|
||||
* `i32` as `Int`
|
||||
* `f64` as `Float`
|
||||
* `String` and `&str` as `String`
|
||||
* `bool` as `Boolean`
|
||||
* `juniper::ID` as `ID`. This type is defined [in the
|
||||
spec](http://facebook.github.io/graphql/#sec-ID) as a type that is serialized
|
||||
as a string but can be parsed from both a string and an integer.
|
||||
|
||||
### Non-standard scalars
|
||||
|
||||
Juniper has built-in support for UUIDs from the [uuid
|
||||
crate](https://doc.rust-lang.org/uuid/uuid/index.html). This support is enabled
|
||||
by default, but can be disabled if you want to reduce the number of dependencies
|
||||
in your application.
|
180
docs/book/docs/types/unions.md
Normal file
180
docs/book/docs/types/unions.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
# Unions
|
||||
|
||||
From a server's point of view, GraphQL unions are similar to interfaces: the
|
||||
only exception is that they don't contain fields on their own.
|
||||
|
||||
In Juniper, the `graphql_union!` has identical syntax to the [interface
|
||||
macro](interfaces.md), but does not support defining fields. Therefore, the same
|
||||
considerations about using traits, placeholder types, or enums still apply to
|
||||
unions.
|
||||
|
||||
If we look at the same examples as in the interfaces chapter, we see the
|
||||
similarities and the tradeoffs:
|
||||
|
||||
## Traits
|
||||
|
||||
### Downcasting via accessor methods
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
trait Character {
|
||||
// Downcast methods, each concrete class will need to implement one of these
|
||||
fn as_human(&self) -> Option<&Human> { None }
|
||||
fn as_droid(&self) -> Option<&Droid> { None }
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn as_human(&self) -> Option<&Human> { Some(&self) }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||
}
|
||||
|
||||
graphql_union!(<'a> &'a Character: () as "Character" |&self| {
|
||||
instance_resolvers: |_| {
|
||||
// The left hand side indicates the concrete type T, the right hand
|
||||
// side should be an expression returning Option<T>
|
||||
&Human => self.as_human(),
|
||||
&Droid => self.as_droid(),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
### Using an extra database lookup
|
||||
|
||||
FIXME: This example does not compile at the moment
|
||||
|
||||
```rust,ignore
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
graphql_union!(<'a> &'a Character: Database as "Character" |&self| {
|
||||
instance_resolvers: |&context| {
|
||||
&Human => context.humans.get(self.id()),
|
||||
&Droid => context.droids.get(self.id()),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Placeholder objects
|
||||
|
||||
FIXME: This example does not compile at the moment
|
||||
|
||||
```rust,ignore
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
struct Character {
|
||||
id: String,
|
||||
}
|
||||
|
||||
graphql_union!(Character: Database |&self| {
|
||||
instance_resolvers: |&context| {
|
||||
&Human => context.humans.get(&self.id),
|
||||
&Droid => context.droids.get(&self.id),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## Enums
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper_codegen;
|
||||
# #[macro_use] extern crate juniper;
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
# #[allow(dead_code)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
|
||||
graphql_union!(Character: () |&self| {
|
||||
instance_resolvers: |_| {
|
||||
&Human => match *self { Character::Human(ref h) => Some(h), _ => None },
|
||||
&Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() {}
|
||||
```
|
17
docs/book/package.json
Normal file
17
docs/book/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "graphql-rust.github.io",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "gitbook install && gitbook build",
|
||||
"serve": "gitbook install && gitbook serve",
|
||||
"test": "(cd _skeptic && cargo test)"
|
||||
},
|
||||
"dependencies": {
|
||||
"gitbook-cli": "^2.3.2",
|
||||
"gitbook-plugin-codeblock-omit": "^0.0.5",
|
||||
"gitbook-plugin-codeblock-rust": "^0.0.4",
|
||||
"gitbook-plugin-sane-sidebar": "^1.0.0"
|
||||
}
|
||||
}
|
2437
docs/book/yarn.lock
Normal file
2437
docs/book/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue