commit
dded5fd05d
41 changed files with 5271 additions and 3 deletions
Cargo.tomlREADME.mdazure-pipelines.yml
docs/book
.gitignoreREADME.mdbook.tomlci-build.sh
content
tests
yarn.lockintegration_tests/juniper_tests/src/codegen
juniper_codegen/src
|
@ -10,3 +10,6 @@ members = [
|
||||||
"juniper_rocket",
|
"juniper_rocket",
|
||||||
"juniper_warp",
|
"juniper_warp",
|
||||||
]
|
]
|
||||||
|
exclude = [
|
||||||
|
"docs/book/tests",
|
||||||
|
]
|
||||||
|
|
|
@ -26,6 +26,9 @@ embedded [Graphiql][graphiql] and [GraphQL Playground][playground] for easy debu
|
||||||
- [API Reference][docsrs]
|
- [API Reference][docsrs]
|
||||||
- [Book][book]: Guides and Examples
|
- [Book][book]: Guides and Examples
|
||||||
|
|
||||||
|
The book is also available for the master branch and older versions published after 0.11.1. See the [book index][book_index].
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
The best place to get started is the [Juniper Book][book], which contains
|
The best place to get started is the [Juniper Book][book], which contains
|
||||||
|
@ -93,8 +96,9 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
|
||||||
[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples
|
[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples
|
||||||
[hyper]: https://hyper.rs
|
[hyper]: https://hyper.rs
|
||||||
[rocket]: https://rocket.rs
|
[rocket]: https://rocket.rs
|
||||||
[book]: https://graphql-rust.github.io
|
[book]: https://graphql-rust.github.io/juniper/current
|
||||||
[book_quickstart]: https://graphql-rust.github.io/quickstart.html
|
[book_index]: https://graphql-rust.github.io/juniper
|
||||||
|
[book_quickstart]: https://graphql-rust.github.io/juniper/current/quickstart.html
|
||||||
[docsrs]: https://docs.rs/juniper
|
[docsrs]: https://docs.rs/juniper
|
||||||
[warp]: https://github.com/seanmonstar/warp
|
[warp]: https://github.com/seanmonstar/warp
|
||||||
[warp_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_warp/examples
|
[warp_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_warp/examples
|
||||||
|
|
|
@ -13,6 +13,34 @@ jobs:
|
||||||
$HOME/.cargo/bin/cargo fmt -- --check
|
$HOME/.cargo/bin/cargo fmt -- --check
|
||||||
displayName: Run rustfmt
|
displayName: Run rustfmt
|
||||||
|
|
||||||
|
- job: run_book_tests
|
||||||
|
displayName: Book code example tests
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-16.04
|
||||||
|
steps:
|
||||||
|
- script: |
|
||||||
|
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
|
$HOME/.cargo/bin/rustup component add rustfmt
|
||||||
|
displayName: Install stable Rust
|
||||||
|
- script: |
|
||||||
|
cd docs/book/tests && $HOME/.cargo/bin/cargo test
|
||||||
|
displayName: Test book code examples via skeptic
|
||||||
|
|
||||||
|
- job: build_book_master
|
||||||
|
displayName: Build rendered book on master branch and push to Github
|
||||||
|
dependsOn: run_book_tests
|
||||||
|
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
|
variables:
|
||||||
|
- group: github-keys
|
||||||
|
steps:
|
||||||
|
# - task: InstallSSHKey@0
|
||||||
|
# inputs:
|
||||||
|
# hostName: $(secret.GHSshKnownHosts)
|
||||||
|
# sshPublicKey: $(secret.GHSshPub)
|
||||||
|
# sshKeySecureFile: $(secret.GHSshPriv)
|
||||||
|
- script: |
|
||||||
|
./docs/book/build.sh master
|
||||||
|
|
||||||
- template: _build/azure-pipelines-template.yml
|
- template: _build/azure-pipelines-template.yml
|
||||||
parameters:
|
parameters:
|
||||||
name: Linux
|
name: Linux
|
||||||
|
|
1
docs/book/.gitignore
vendored
Normal file
1
docs/book/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
_rendered
|
48
docs/book/README.md
Normal file
48
docs/book/README.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Juniper Book
|
||||||
|
|
||||||
|
Book containing the Juniper documentation.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
The book is built with [mdBook](https://github.com/rust-lang-nursery/mdBook).
|
||||||
|
|
||||||
|
You can install it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install mdbook
|
||||||
|
```
|
||||||
|
|
||||||
|
### Starting a local test server
|
||||||
|
|
||||||
|
To launch a local test server that continually re-builds the book and autoreloads the page, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdbook serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building the book
|
||||||
|
|
||||||
|
You can build the book to rendered HTML with this command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdbook build
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will be in the `./_rendered` directory.
|
||||||
|
|
||||||
|
### Running the tests
|
||||||
|
|
||||||
|
To run the tests validating all code examples in the book, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ./tests
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test setup
|
||||||
|
|
||||||
|
All Rust code examples in the book are compiled on the CI.
|
||||||
|
|
||||||
|
This is done using the [skeptic](https://github.com/budziq/rust-skeptic) library.
|
11
docs/book/book.toml
Normal file
11
docs/book/book.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[book]
|
||||||
|
title = "Juniper - GraphQL Server for Rust"
|
||||||
|
description = "Documentation for juniper, a GraphQL server library for Rust."
|
||||||
|
src = "content"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
build-dir = "_rendered"
|
||||||
|
create-missing = false
|
||||||
|
|
||||||
|
[output.html]
|
||||||
|
git_repository_url = "https://github.com/graphql-rs/juniper"
|
47
docs/book/ci-build.sh
Executable file
47
docs/book/ci-build.sh
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
#! /usr/bin/env sh
|
||||||
|
|
||||||
|
# Usage: ./ci-build.sh VERSION
|
||||||
|
#
|
||||||
|
# This script builds the book to HTML with mdbook
|
||||||
|
# commits and pushes the contents to the repo in the "gh-pages" branch.
|
||||||
|
#
|
||||||
|
# It is only inteded for use on the CI!
|
||||||
|
|
||||||
|
# Enable strict error checking.
|
||||||
|
set -exo pipefail
|
||||||
|
|
||||||
|
DIR=$(dirname $(readlink -f $0))
|
||||||
|
MDBOOK="mdbook"
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
# Verify version argument.
|
||||||
|
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
echo "Missing required argument 'version': cargo make build-book VERSION"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
# Download mdbook if not found.
|
||||||
|
$MDBOOK -h 2>&1 > /dev/null
|
||||||
|
if [ $? != 0]; then
|
||||||
|
echo "mdbook not found. Downloading..."
|
||||||
|
curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v0.2.0/mdbook-v0.2.0-x86_64-unknown-linux-gnu.tar.gz | tar xzf -
|
||||||
|
mv ./mdbook /tmp/mdbook
|
||||||
|
set MDBOOK="/tmp/mdbook"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$MDBOOK build
|
||||||
|
echo $VERSION > ./_rendered/VERSION
|
||||||
|
rm -rf /tmp/book-content
|
||||||
|
mv ./_rendered /tmp/book-content
|
||||||
|
|
||||||
|
cd $DIR/../..
|
||||||
|
git clean -fd
|
||||||
|
git checkout gh-pages
|
||||||
|
rm -rf $VERSION
|
||||||
|
mv /tmp/book-content ./$VERSION
|
||||||
|
git add -A $VERSION
|
||||||
|
git commit -m "Updated book for $VERSION"
|
||||||
|
git push origin gh-pages
|
74
docs/book/content/README.md
Normal file
74
docs/book/content/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
|
31
docs/book/content/SUMMARY.md
Normal file
31
docs/book/content/SUMMARY.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
- [Introduction](README.md)
|
||||||
|
- [Quickstart](quickstart.md)
|
||||||
|
|
||||||
|
- [Type System](types/index.md)
|
||||||
|
- [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](types/other-index.md)
|
||||||
|
- [Enums](types/enums.md)
|
||||||
|
- [Interfaces](types/interfaces.md)
|
||||||
|
- [Input objects](types/input_objects.md)
|
||||||
|
- [Scalars](types/scalars.md)
|
||||||
|
- [Unions](types/unions.md)
|
||||||
|
|
||||||
|
- [Schemas and mutations](schema/schemas_and_mutations.md)
|
||||||
|
|
||||||
|
- [Adding A Server](servers/index.md)
|
||||||
|
- [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md)
|
||||||
|
- [Warp](servers/warp.md)
|
||||||
|
- [Rocket](servers/rocket.md)
|
||||||
|
- [Iron](servers/iron.md)
|
||||||
|
- [Hyper](servers/hyper.md)
|
||||||
|
- [Third Party Integrations](servers/third-party.md)
|
||||||
|
|
||||||
|
- [Advanced Topics](advanced/index.md)
|
||||||
|
- [Non-struct objects](advanced/non_struct_objects.md)
|
||||||
|
- [Objects and generics](advanced/objects_and_generics.md)
|
||||||
|
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
|
||||||
|
# - [Context switching]
|
||||||
|
# - [Dynamic type system]
|
7
docs/book/content/advanced/index.md
Normal file
7
docs/book/content/advanced/index.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Advanced Topics
|
||||||
|
|
||||||
|
The chapters below cover some more advanced scenarios.
|
||||||
|
|
||||||
|
- [Non-struct objects](advanced/non_struct_objects.md)
|
||||||
|
- [Objects and generics](advanced/objects_and_generics.md)
|
||||||
|
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
|
73
docs/book/content/advanced/multiple_ops_per_request.md
Normal file
73
docs/book/content/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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
55
docs/book/content/advanced/non_struct_objects.md
Normal file
55
docs/book/content/advanced/non_struct_objects.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# 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
|
||||||
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct ValidationError {
|
||||||
|
field: String,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
# #[allow(dead_code)]
|
||||||
|
enum SignUpResult {
|
||||||
|
Ok(User),
|
||||||
|
Error(Vec<ValidationError>),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::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.
|
59
docs/book/content/advanced/objects_and_generics.md
Normal file
59
docs/book/content/advanced/objects_and_generics.md
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# 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
|
||||||
|
# #[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.
|
186
docs/book/content/quickstart.md
Normal file
186
docs/book/content/quickstart.md
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
# 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.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
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(juniper::GraphQLEnum)]
|
||||||
|
enum Episode {
|
||||||
|
NewHope,
|
||||||
|
Empire,
|
||||||
|
Jedi,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::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(juniper::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;
|
||||||
|
|
||||||
|
juniper::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;
|
||||||
|
|
||||||
|
juniper::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() { }
|
||||||
|
```
|
||||||
|
|
||||||
|
We now have a very simple but functional schema for a GraphQL server!
|
||||||
|
|
||||||
|
To actually serve the schema, see the guides for our various [server integrations](./servers/index.md).
|
||||||
|
|
||||||
|
You can also invoke the executor directly to get a result for a query:
|
||||||
|
|
||||||
|
## Executor
|
||||||
|
|
||||||
|
You can invoke `juniper::execute` directly to run a GraphQL query:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# // Only needed due to 2018 edition because the macro is not accessible.
|
||||||
|
# extern crate juniper;
|
||||||
|
use juniper::{FieldResult, Variables, EmptyMutation};
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
||||||
|
enum Episode {
|
||||||
|
NewHope,
|
||||||
|
Empire,
|
||||||
|
Jedi,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
juniper::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,
|
||||||
|
graphql_value!({
|
||||||
|
"favoriteEpisode": "NEW_HONE",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[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
|
58
docs/book/content/schema/schemas_and_mutations.md
Normal file
58
docs/book/content/schema/schemas_and_mutations.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# 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
|
||||||
|
# use juniper::FieldResult;
|
||||||
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
struct Root;
|
||||||
|
|
||||||
|
juniper::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
|
||||||
|
# use juniper::FieldResult;
|
||||||
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
struct Mutations;
|
||||||
|
|
||||||
|
juniper::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/content/servers/hyper.md
Normal file
28
docs/book/content/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
|
17
docs/book/content/servers/index.md
Normal file
17
docs/book/content/servers/index.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Adding A Server
|
||||||
|
|
||||||
|
To allow using Juniper with the HTTP server of your choice,
|
||||||
|
it does **not** come with a built in HTTP server.
|
||||||
|
|
||||||
|
To actually get a server up and running, there are multiple official and
|
||||||
|
third-party integration crates that will get you there.
|
||||||
|
|
||||||
|
- [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md)
|
||||||
|
- [Warp](servers/warp.md)
|
||||||
|
- [Rocket](servers/rocket.md)
|
||||||
|
- [Iron](servers/iron.md)
|
||||||
|
- [Hyper](servers/hyper.md)
|
||||||
|
- [Third Party Integrations](servers/third-party.md)
|
||||||
|
- [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)
|
125
docs/book/content/servers/iron.md
Normal file
125
docs/book/content/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
|
||||||
|
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
|
||||||
|
# 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
|
9
docs/book/content/servers/official.md
Normal file
9
docs/book/content/servers/official.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Official Server Integrations
|
||||||
|
|
||||||
|
Juniper provides official integration crates for several popular Rust server
|
||||||
|
libraries.
|
||||||
|
|
||||||
|
- [Hyper](./hyper.md)
|
||||||
|
- [Warp](./warp.md)
|
||||||
|
- [Rocket](./rocket.md)
|
||||||
|
- [Iron](./iron.md)
|
22
docs/book/content/servers/rocket.md
Normal file
22
docs/book/content/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
|
8
docs/book/content/servers/third-party.md
Normal file
8
docs/book/content/servers/third-party.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Third-Party Integrations
|
||||||
|
|
||||||
|
There are several examples or third party integration crates that are not
|
||||||
|
officially maintained by Juniper developers.
|
||||||
|
|
||||||
|
- [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)
|
23
docs/book/content/servers/warp.md
Normal file
23
docs/book/content/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/content/styles/website.css
Normal file
3
docs/book/content/styles/website.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.markdown-section pre {
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
56
docs/book/content/types/enums.md
Normal file
56
docs/book/content/types/enums.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# 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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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() {}
|
||||||
|
```
|
20
docs/book/content/types/index.md
Normal file
20
docs/book/content/types/index.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Type System
|
||||||
|
|
||||||
|
Most of the work in working with juniper consists of mapping the
|
||||||
|
GraphQL type system to the Rust types your application uses.
|
||||||
|
|
||||||
|
Juniper provides some convenient abstractions that try to make this process
|
||||||
|
as painless as possible.
|
||||||
|
|
||||||
|
Find out more in the individual chapters below.
|
||||||
|
|
||||||
|
- [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](types/other-index.md)
|
||||||
|
- [Enums](types/enums.md)
|
||||||
|
- [Interfaces](types/interfaces.md)
|
||||||
|
- [Input objects](types/input_objects.md)
|
||||||
|
- [Scalars](types/scalars.md)
|
||||||
|
- [Unions](types/unions.md)
|
54
docs/book/content/types/input_objects.md
Normal file
54
docs/book/content/types/input_objects.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# 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
|
||||||
|
#[derive(juniper::GraphQLInputObject)]
|
||||||
|
struct Coordinate {
|
||||||
|
latitude: f64,
|
||||||
|
longitude: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Root;
|
||||||
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
|
juniper::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
|
||||||
|
#[derive(juniper::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(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
|
juniper::graphql_object!(Root: () |&self| {
|
||||||
|
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
||||||
|
// Send coordinate to database
|
||||||
|
# unimplemented!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
# fn main() {}
|
||||||
|
```
|
215
docs/book/content/types/interfaces.md
Normal file
215
docs/book/content/types/interfaces.md
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
# 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
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S>|&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:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use std::collections::HashMap;
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(<'a> &'a Character: Database as "Character" where Scalar = <S> |&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?
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use std::collections::HashMap;
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: Database where Scalar = <S> |&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
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Droid {
|
||||||
|
id: String,
|
||||||
|
primary_function: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
# #[allow(dead_code)]
|
||||||
|
enum Character {
|
||||||
|
Human(Human),
|
||||||
|
Droid(Droid),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_interface!(Character: () where Scalar = <S> |&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() {}
|
||||||
|
```
|
93
docs/book/content/types/objects/complex_fields.md
Normal file
93
docs/book/content/types/objects/complex_fields.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::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
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct House {
|
||||||
|
inhabitants: Vec<Person>,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::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
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
website_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::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).
|
183
docs/book/content/types/objects/defining_objects.md
Normal file
183
docs/book/content/types/objects/defining_objects.md
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
# 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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::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
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: i32,
|
||||||
|
#[graphql(skip)]
|
||||||
|
# #[allow(dead_code)]
|
||||||
|
password_hash: String, // This cannot be queried or modified from GraphQL
|
||||||
|
}
|
||||||
|
|
||||||
|
# fn main() {}
|
||||||
|
```
|
168
docs/book/content/types/objects/error_handling.md
Normal file
168
docs/book/content/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
|
||||||
|
# extern crate juniper;
|
||||||
|
use std::{
|
||||||
|
str,
|
||||||
|
path::PathBuf,
|
||||||
|
fs::{File},
|
||||||
|
io::{Read},
|
||||||
|
};
|
||||||
|
use juniper::FieldResult;
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
filename: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::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;
|
||||||
|
enum CustomError {
|
||||||
|
WhateverNotSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl juniper::IntoFieldError for CustomError {
|
||||||
|
fn into_field_error(self) -> juniper::FieldError {
|
||||||
|
match self {
|
||||||
|
CustomError::WhateverNotSet => juniper::FieldError::new(
|
||||||
|
"Whatever does not exist",
|
||||||
|
juniper::graphql_value!({
|
||||||
|
"type": "NO_WHATEVER"
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
whatever: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::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/content/types/objects/using_contexts.md
Normal file
77
docs/book/content/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;
|
||||||
|
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
|
||||||
|
juniper::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`.
|
11
docs/book/content/types/other-index.md
Normal file
11
docs/book/content/types/other-index.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Other Types
|
||||||
|
|
||||||
|
The GraphQL type system provides several types in additon to objects.
|
||||||
|
|
||||||
|
Find out more about each type below:
|
||||||
|
|
||||||
|
- [Enums](./enums.md)
|
||||||
|
- [Interfaces](./interfaces.md)
|
||||||
|
- [Input objects](./input_objects.md)
|
||||||
|
- [Scalars](./scalars.md)
|
||||||
|
- [Unions](./unions.md)
|
58
docs/book/content/types/scalars.md
Normal file
58
docs/book/content/types/scalars.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# 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
|
||||||
|
use juniper::Value;
|
||||||
|
|
||||||
|
struct UserID(String);
|
||||||
|
|
||||||
|
juniper::graphql_scalar!(UserID {
|
||||||
|
description: "An opaque identifier, represented as a string"
|
||||||
|
|
||||||
|
resolve(&self) -> Value {
|
||||||
|
Value::scalar(self.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
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_scalar_value::<String>().map(|s| UserID(s.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
from_str<'a>(value: ScalarToken<'a>) -> juniper::ParseScalarResult<'a, juniper::DefaultScalarValue> {
|
||||||
|
<String as juniper::ParseScalarValue>::from_str(value)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
# 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.
|
174
docs/book/content/types/unions.md
Normal file
174
docs/book/content/types/unions.md
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
# 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
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&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
|
||||||
|
# use std::collections::HashMap;
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_union!(<'a> &'a Character: Database as "Character" where Scalar = <S> |&self| {
|
||||||
|
instance_resolvers: |&context| {
|
||||||
|
&Human => context.humans.get(self.id()),
|
||||||
|
&Droid => context.droids.get(self.id()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Placeholder objects
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use std::collections::HashMap;
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
#[graphql(Context = "Database")]
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_union!(Character: Database where Scalar = <S> |&self| {
|
||||||
|
instance_resolvers: |&context| {
|
||||||
|
&Human => context.humans.get(&self.id),
|
||||||
|
&Droid => context.droids.get(&self.id),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enums
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Human {
|
||||||
|
id: String,
|
||||||
|
home_planet: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLObject)]
|
||||||
|
struct Droid {
|
||||||
|
id: String,
|
||||||
|
primary_function: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
# #[allow(dead_code)]
|
||||||
|
enum Character {
|
||||||
|
Human(Human),
|
||||||
|
Droid(Droid),
|
||||||
|
}
|
||||||
|
|
||||||
|
juniper::graphql_union!(Character: () where Scalar = <S> |&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() {}
|
||||||
|
```
|
3
docs/book/tests/.gitignore
vendored
Normal file
3
docs/book/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
21
docs/book/tests/Cargo.toml
Normal file
21
docs/book/tests/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "juniper_book_tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
juniper = { version = "0.11", path = "../../../juniper" }
|
||||||
|
juniper_iron = { version = "0.3", path = "../../../juniper_iron" }
|
||||||
|
|
||||||
|
iron = "^0.5.0"
|
||||||
|
mount = "^0.3.0"
|
||||||
|
|
||||||
|
skeptic = "0.13"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
skeptic = "0.13"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
juniper_codegen = { path = "../../../juniper_codegen" }
|
6
docs/book/tests/build.rs
Normal file
6
docs/book/tests/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
extern crate skeptic;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let files = skeptic::markdown_files_of_directory("../content/types");
|
||||||
|
skeptic::generate_doc_tests(&files);
|
||||||
|
}
|
3
docs/book/tests/src/lib.rs
Normal file
3
docs/book/tests/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs"));
|
3185
docs/book/yarn.lock
Normal file
3185
docs/book/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
@ -70,6 +70,15 @@ struct SkippedFieldObj {
|
||||||
skipped: i32,
|
skipped: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Context;
|
||||||
|
impl juniper::Context for Context {}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject, Debug)]
|
||||||
|
#[graphql(Context = "Context")]
|
||||||
|
struct WithCustomContext {
|
||||||
|
a: bool,
|
||||||
|
}
|
||||||
|
|
||||||
graphql_object!(Query: () |&self| {
|
graphql_object!(Query: () |&self| {
|
||||||
field obj() -> Obj {
|
field obj() -> Obj {
|
||||||
Obj{
|
Obj{
|
||||||
|
|
|
@ -8,6 +8,7 @@ use util::*;
|
||||||
struct ObjAttrs {
|
struct ObjAttrs {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
context: Option<Ident>,
|
||||||
scalar: Option<Ident>,
|
scalar: Option<Ident>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +47,12 @@ impl ObjAttrs {
|
||||||
res.scalar = Some(Ident::new(&scalar as &str, Span::call_site()));
|
res.scalar = Some(Ident::new(&scalar as &str, Span::call_site()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if let Some(AttributeValue::String(ctx)) =
|
||||||
|
keyed_item_value(&item, "Context", AttributeValidation::String)
|
||||||
|
{
|
||||||
|
res.context = Some(Ident::new(&ctx as &str, Span::call_site()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
panic!(format!(
|
panic!(format!(
|
||||||
"Unknown struct attribute for #[derive(GraphQLObject)]: {:?}",
|
"Unknown struct attribute for #[derive(GraphQLObject)]: {:?}",
|
||||||
item
|
item
|
||||||
|
@ -223,13 +230,18 @@ pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
.scalar
|
.scalar
|
||||||
.unwrap_or_else(|| Ident::new("__S", Span::call_site()));
|
.unwrap_or_else(|| Ident::new("__S", Span::call_site()));
|
||||||
|
|
||||||
|
let ctx = attrs
|
||||||
|
.context
|
||||||
|
.map(|ident| quote!( #ident ))
|
||||||
|
.unwrap_or(quote!(()));
|
||||||
|
|
||||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
let body = quote! {
|
let body = quote! {
|
||||||
impl#impl_generics juniper::GraphQLType<#scalar> for #ident #ty_generics
|
impl#impl_generics juniper::GraphQLType<#scalar> for #ident #ty_generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
type Context = ();
|
type Context = #ctx;
|
||||||
type TypeInfo = ();
|
type TypeInfo = ();
|
||||||
|
|
||||||
fn name(_: &()) -> Option<&str> {
|
fn name(_: &()) -> Option<&str> {
|
||||||
|
|
Loading…
Add table
Reference in a new issue