Improve book and fix serde dependency

This commit is contained in:
Koxiaet 2020-09-01 06:47:22 +01:00
parent b2a0871798
commit 1ef34f2c39
29 changed files with 94 additions and 111 deletions

View File

@ -22,7 +22,7 @@ async-graphql-derive = { path = "async-graphql-derive", version = "1.17.11" }
anyhow = "1.0.26"
thiserror = "1.0.11"
async-trait = "0.1.30"
serde = { version = "1.0.104", features = ["serde_derive"] }
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48"
bytes = "0.5.4"
Inflector = "0.11.4"

View File

@ -2,5 +2,5 @@
authors = ["sunli"]
description = "Async-graphql Book"
src = "src"
language = "zh-CN"
language = "en"
title = "Async-graphql Book"

View File

@ -1,8 +1,8 @@
# Apollo Federation
`Apollo Federation` is a `GraphQL` API gateway which can combine multiple GraphQL services, allowing each service to implement the subset of the API it is responsible for. You can read more in the [official documentation](https://www.apollographql.com/docs/apollo-server/federation/introduction)
`Apollo Federation` is a `GraphQL` API gateway which can combine multiple GraphQL services, allowing each service to implement the subset of the API it is responsible for. You can read more in the [official documentation](https://www.apollographql.com/docs/apollo-server/federation/introduction).
`Async-GraphQL` supports all the functionality of `Apollo Federation`, but some modifications to your `Schema` are required.
`Async-graphql` supports all the functionality of `Apollo Federation`, but some modifications to your `Schema` are required.
- You can use the `extends` property declaration on `async_graphql::Object` and `async_graphql::Interface` to extend a type offered by another implementing service.
@ -36,16 +36,16 @@ impl Query {
**Notice the difference between these three lookup functions, which are all looking for the `User` object.**
- find_user_by_id
- `find_user_by_id`
Use `id` to find an `User` object, the key for `User` is `id`
Use `id` to find an `User` object, the key for `User` is `id`.
- find_user_by_id_with_username
- `find_user_by_id_with_username`
Use `id` to find an `User` object, the key for `User` is `id`, and the `username` field value of the `User` object is requested
Use `id` to find an `User` object, the key for `User` is `id`, and the `username` field value of the `User` object is requested.
- find_user_by_id_and_username
- `find_user_by_id_and_username`
Use `id` and `username` to find an `User` object, the keys for `User` are `id` and `username`
Use `id` and `username` to find an `User` object, the keys for `User` are `id` and `username`.
For a complete example, refer to: https://github.com/async-graphql/examples/tree/master/federation
For a complete example, refer to: <https://github.com/async-graphql/examples/tree/master/federation>.

View File

@ -2,14 +2,13 @@
Apollo Tracing provides performance analysis results for each step of query. This is an extension to `Schema`, and the performance analysis results are stored in `QueryResponse`.
To enable the Apollo Tracing extension, add the extension when a `Schema` is created.
To enable the Apollo Tracing extension, add the extension when the `Schema` is created.
```rust
use async_graphql::*;
use async_graphql::extensions::ApolloTracing;
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
.extension(|| ApolloTracing::default()) // Enable ApolloTracing extension
.extension(ApolloTracing::default) // Enable ApolloTracing extension
.finish();
```
```

View File

@ -4,13 +4,13 @@ Production environments often rely on caching to improve performance.
A GraphQL query will call multiple resolver functions and each resolver can have a different cache definition. Some may cache for a few seconds, some may cache for a few hours, some may be the same for all users, and some may be different for each session.
`Async-Graphql` provides a mechanism that allows you to define the cache time and scope for each resolver.
`Async-graphql` provides a mechanism that allows you to define the cache time and scope for each resolver.
You can define cache parameters on the object or on its fields. The following example shows two uses of cache control parameters.
You can use `max_age` parameters to control the age of the cache (in seconds), and you can also use `public` and `private` to control the scope of the cache. When you do not specify it, the scope will default to `public`.
lWhen querying multiple resolvers, the results of all cache control parameters will be combined and the `max_age` minimum value will be taken. If the scope of any object or field is `private`, the result will be `private`.
when querying multiple resolvers, the results of all cache control parameters will be combined and the `max_age` minimum value will be taken. If the scope of any object or field is `private`, the result will be `private`.
We can use `QueryResponse` to get a merged cache control result from a query result, and call `CacheControl::value` to get the corresponding HTTP header.

View File

@ -1,6 +1,6 @@
# Context
The main goal of `Context` is to acquire global data attached to Schema. **Note that if the return value of resolver function is borrowed from `Context`, you need to explicitly state the lifetime of the argument.**
The main goal of `Context` is to acquire global data attached to Schema. **Note that if the return value of resolver function is borrowed from `Context`, you will need to explicitly state the lifetime of the argument.**
The following example shows how to borrow data in `Context`.

View File

@ -1,8 +1,8 @@
# Cursor connections
Relay's cursor connection specification is defined to provide a consistent method for query paging. For more details on the specification see the [GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm)。
Relay's cursor connection specification is designed to provide a consistent method for query paging. For more details on the specification see the [GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm)。
Define a cursor connection in `async-graphql` is very simple, you just call the `connection::query` function and query data in closure.
Defining a cursor connection in `async-graphql` is very simple, you just call the `connection::query` function and query data in the closure.
```rust
use async_graphql::*;
@ -42,4 +42,4 @@ impl Query {
}
}
```
```

View File

@ -1,15 +1,14 @@
# Custom scalars
In `Async-GraphQL` most common scalar types are built in, but you can also create your own scalar types.
In `Async-graphql` most common scalar types are built in, but you can also create your own scalar types.
Using `async-graphql::Scalar`, you can add support for a scalar when you implement it. You only need to implement parsing and output functions.
The following example defines a 64-bit integer scalar where its input and output are strings. (Note: `Async-graphQL` already supports 64-bit integers and uses strings as input and output.)
The following example defines a 64-bit integer scalar where its input and output are strings. (Note: `Async-graphql` already supports 64-bit integers and uses strings as input and output.)
```rust
use async_graphql::*;
struct StringNumber(i64);
#[Scalar]
@ -28,5 +27,4 @@ impl ScalarType for StringNumber {
Value::String(self.0.to_string())
}
}
```

View File

@ -1,7 +1,7 @@
# Default value
You can define default values for input value types.
The following shows how to define default values for different types.
Below are some examples.
## Object field
@ -57,4 +57,4 @@ struct MyInputObject {
#[field(default = "my_default()")]
value3: i32,
}
```
```

View File

@ -1,14 +1,14 @@
# Object
Different from `SimpleObject`, `Object` must have Resolve defined for each field in `impl`.
Different from `SimpleObject`, `Object` must have a resolver defined for each field in its `impl`.
**A resolver function has to be asynchronous. The first argument has to be `&self`, second being optional `Context` and followed by field arguments.**
**A resolver function has to be asynchronous. The first argument has to be `&self`, the second is an optional `Context` and it is followed by field arguments.**
Resolve is used to get the value of the field. You can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::FieldResult` so to return an error if it occurs an error message will be sent to query result.
The resolvers is used to get the value of the field. For example, you can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::FieldResult` to return an error if it occurs. The error message will then be sent to query result.
When querying a database, you may need a global data base connection pool.
When creating `Schema`, you can use `SchemaBuilder::data` to setup `Schema` data, and `Context::data` to setup `Context`data.
The following `value_from_db` function showed how to retrieve a database connection from `Context`.
You may need access to global data in your query, for example a database connection pool.
When creating your `Schema`, you can use `SchemaBuilder::data` to configure the global data, and `Context::data` to configure `Context` data.
The following `value_from_db` function shows how to retrieve a database connection from `Context`.
```rust
use async_graphql::*;

View File

@ -2,7 +2,7 @@
It's easy to define an `Enum`, here we have an example:
**Async-graphql can automatically change the name of each item to GraphQL's CONSTANT_CASE convension, you can also use `name` to rename.**
**Async-graphql will automatically change the name of each item to GraphQL's CONSTANT_CASE convention. You can use `name` to rename.**
```rust
use async_graphql::*;
@ -19,4 +19,4 @@ pub enum Episode {
#[item(name="AAA", desc = "Released in 1983.")]
Jedi,
}
```
```

View File

@ -1,12 +1,12 @@
# InputObject
<!--Input Object and SimpleObject inconsistant space.-->
You can define an `Object` as argument, GraphQL calls it `InputObject`.
The definition of `InputObject` is similar to [SimpleObject](define_simple_object.md).
However, `SimpleObject` can only be used for output and `InputObject` can only be used as input.
You can use an `Object` as an argument, and GraphQL calls it an `InputObject`.
The definition of `InputObject` is similar to [SimpleObject](define_simple_object.md), but
`SimpleObject` can only be used as output and `InputObject` can only be used as input.
`InputObject` don't need a `#[field]` for each field, every field is `InputValue`.
But you can add optional `#[field]` to add description or rename the field.
`InputObject` doesn't need a `#[field]` for each field, every field is an `InputValue`.
You can add optional `#[field]` attributes to add descriptions or rename the field.
```rust
use async_graphql::*;
@ -28,4 +28,4 @@ impl Mutation {
// ...
}
}
```
```

View File

@ -1,16 +1,16 @@
# Interface
`Interface` is used to abstract `Object`s with common fields.
`Async-graphql` implemented it as a wrapper.
The wrapper will forward Resolve to the `Object` that implemented this `Interface`.
Therefore, the `Object`'s fields' type, arguments must match with the `Interface`'s.
`Async-graphql` implements it as a wrapper.
The wrapper will forward field resolution to the `Object` that implements this `Interface`.
Therefore, the `Object`'s fields' type and arguments must match with the `Interface`'s.
`Async-graphql` implemented auto conversion from `Object` to `Interface`, you only need to call `Into::into`.
`Async-graphql` implements auto conversion from `Object` to `Interface`, you only need to call `Into::into`.
Interface fields names transforms to camelCase in schema definition.
If you need e.g. snake_cased GraphQL field name, you can use both the `name` and `method` attribute.
Interface field names are transformed to camelCase for the schema definition.
If you need e.g. a snake_cased GraphQL field name, you can use both the `name` and `method` attributes.
- When the `name` and `method` exist together, the `name` is the GraphQL field name and the `method` is the resolver function name.
- When `name` and `method` exist together, `name` is the GraphQL field name and the `method` is the resolver function name.
- When only `name` exists, `name.to_camel_case()` is the GraphQL field name and the `name` is the resolver function name.
```rust

View File

@ -1,6 +1,6 @@
# Schema
After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, mutation object, and subscription object, where the mutation object and subscription object are optional.
After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, a mutation object, and a subscription object, where the mutation object and subscription object are optional.
When the schema is created, `Async-Graphql` will traverse all object graphs and register all types. This means that if a GraphQL object is defined but never referenced, then this object will not be exposed in the schema.
When the schema is created, `Async-graphql` will traverse all object graphs and register all types. This means that if a GraphQL object is defined but never referenced, this object will not be exposed in the schema.

View File

@ -1,8 +1,8 @@
# SimpleObject
`SimpleObject` directly map all field of a struct to GraphQL object, you cannot define a resolver function on it.
`SimpleObject` directly maps all the fields of a struct to GraphQL object. You cannot define a resolver function on it - for that, see [Object](define_complex_object.html).
The example below defined an object `MyObject`, including field `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[field(skip)]`
The example below defines an object `MyObject` which includes the fields `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[field(skip)]`
```rust
use async_graphql::*;

View File

@ -1,8 +1,7 @@
# Union
The definition of `Union` is similar to `Interface`'s, **but no field allowed.**.
The implemention is quite similar for `Async-graphql`.
From `Async-graphql`'s perspective, `Union` is a subset of `Interface`.
The definition of a `Union` is similar to an `Interface`, **but with no fields allowed.**.
The implementation is quite similar for `Async-graphql`; from `Async-graphql`'s perspective, `Union` is a subset of `Interface`.
The following example modified the definition of `Interface` a little bit and removed fields.

View File

@ -1,7 +1,7 @@
# Error extensions
To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18):
> GraphQL services may provide an additional entry to errors with key extensions.
> This entry, if set, must have a map as its value. This entry is reserved for implementors to add
> This entry, if set, must have a map as its value. This entry is reserved for implementer to add
> additional information to errors however they see fit, and there are no additional restrictions on
> its contents.
@ -9,11 +9,11 @@ To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18):
I would recommend on checking out this [async-graphql example](https://github.com/async-graphql/examples/blob/master/actix-web/error-extensions/src/main.rs) as a quickstart.
## General Concept
In async-graphql all user-facing errors are cast to the `FieldError` type which by default provides
the error message exposed by `std::fmt::Display`. However `FieldError` actually provides an additional
field `Option<serde_json::Value>` which - if some valid `serde_json::Map` - will be exposed as the extensions key to any error.
In `async-graphql` all user-facing errors are cast to the `FieldError` type which by default provides
the error message exposed by `std::fmt::Display`. However `FieldError` also provides an additional
field `Option<serde_json::Value>` which - if given some valid `serde_json::Map` - will be exposed as the extensions key to any error.
A resolver like this:
A resolver looks like this:
```rust
async fn parse_with_extensions(&self) -> Result<i32, FieldError> {
@ -41,7 +41,7 @@ may then return a response like this:
## ErrorExtensions
Constructing new `FieldError`s by hand quickly becomes tedious. That is why async_graphql provides
Constructing new `FieldError`s by hand quickly becomes tedious. That is why `async-graphql` provides
two convenience traits for casting your errors to the appropriate `FieldError` with
extensions.
@ -62,8 +62,7 @@ If you find yourself attaching extensions to your errors all over the place you
implementing the trait on your custom error type directly.
```rust
#[macro_use]
extern crate thiserror;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MyError {
@ -102,7 +101,6 @@ async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
// OR
Err(MyError::NotFound.extend_with(|_| json!({ "on_the_fly": "some_more_info" })))
}
```
```json
@ -119,12 +117,8 @@ async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
}
]
}
```
## ResultExt
This trait enables you to call `extend_err` directly on results. So the above code becomes less verbose.
@ -176,7 +170,7 @@ Expected response:
### Pitfalls
Rust does not provide stable trait specialization yet.
That is why `ErrorExtensions` is actually implemented for `&E where E: std::fmt::Display`
instead of `E: std::fmt::Display` to provide some specialization through
instead of `E: std::fmt::Display`. Some specialization is provided through
[Autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md).
The disadvantage is that the below code does **NOT** compile:

View File

@ -1,15 +1,15 @@
# Error handling
Resolve can return a `FieldResult`, following is the definition:
Resolve can return a `FieldResult`, which has the following definition:
```rust
type FieldResult<T> = std::result::Result<T, FieldError>;
```
Any `Error` that implements `std::fmt::Display` can be converted to `FieldError` and you can extend error message.
Any `Error` that implements `std::fmt::Display` can be converted to `FieldError` and you can extend the error message.
Following example shows how to parse an input string to integer. When parsing failed, it would return error and attach error message.
See [ErrorExtensions](error_extensions.md) sections of this book for more details.
The following example shows how to parse an input string to an integer. When parsing fails, it will return an error and attach an error message.
See the [Error Extensions](error_extensions.md) section of this book for more details.
```rust
use async_graphql::*;

View File

@ -1,11 +1,10 @@
# Input value validators
Arguments to a query ([InputObject](define_input_object.md)) are called `Input Objects` in GraphQL. If the provided input type does not match for a query, the query will return a type mismatch error. But sometimes we want to provide more restrictions on specific types of values. For example, we might want to require that an argument is a valid email address. Async-graphql provides an input validators to solve this problem.
Arguments to a query ([InputObject](define_input_object.md)) are called `Input Objects` in GraphQL. If the provided input type does not match for a query, the query will return a type mismatch error. But sometimes we want to provide more restrictions on specific types of values. For example, we might want to require that an argument is a valid email address. `Async-graphql` provides an input validators to solve this problem.
An input validator can be combined via `and` and `or` operators.
The following is an input validator which checks that a `String` is a valid Email or MAC address:
@ -22,7 +21,6 @@ impl Query {
}
```
The following example verifies that the `i32` parameter `a` is greater than 10 and less than 100, or else equal to 0:
```rust

View File

@ -1,9 +1,9 @@
# Integrations
`Async-Graphql` supports several common Rust web servers.
`Async-graphql` supports several common Rust web servers.
- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web)
- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp)
- Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide)
**Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself**
**Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself.**

View File

@ -1,8 +1,8 @@
# Actix-web
`Async-graphql-actix-web` provides an implementation of `actix_web::FromRequest` for `GQLRequest`. This is actually an abstraction around `QueryBuilder` and you can call `GQLRequest::into_inner` to convert it into a `QueryBuilder`
`Async-graphql-actix-web` provides an implementation of `actix_web::FromRequest` for `GQLRequest`. This is actually an abstraction around `QueryBuilder` and you can call `GQLRequest::into_inner` to convert it into a `QueryBuilder`.
`WSSubscription` is an Actor that supports WebSocket subscriptions
`WSSubscription` is an Actor that supports WebSocket subscriptions.
## Request example

View File

@ -35,4 +35,4 @@ cd benchmark
cargo bench
```
Now HTML report is available at `benchmark/target/criterion/report`
Now a HTML report is available at `benchmark/target/criterion/report`.

View File

@ -18,9 +18,9 @@ impl Query {
}
```
Instead, the `#[derive(GQLMergedObject)]` macro allows you to split an object's resolvers across multiple file by merging 2 or more `#[Object]` implementations into one.
Instead, the `#[derive(GQLMergedObject)]`/`#[MergedObject]` macro allows you to split an object's resolvers across multiple modules or files by merging 2 or more `#[Object]` implementations into one.
**Tip:** Every `#[Object]` needs a unique name even in a GQLMergedObject so make sure to give each object your merging it's own name.
**Tip:** Every `#[Object]` needs a unique name, even in a `GQLMergedObject`, so make sure to give each object you're merging its own name.
**Note:** This works for queries and mutations. For subscriptions, see "Merging Subscriptions" below.
@ -51,7 +51,7 @@ let schema = Schema::new(
# Merging Subscriptions
Along with `GQLMergedObject`, you can derive `GQLMergedSubscription` to merge separate `#[Subscription]` blocks.
Along with `GQLMergedObject`, you can derive `GQLMergedSubscription` or use `#[MergedSubscription]` to merge separate `#[Subscription]` blocks.
Like merging Objects, each subscription block requires a unique name.

View File

@ -1,6 +1,6 @@
# Two ways to define types
I think you have discovered that defining a GraphqlQL type can be use attribute macro or derive.
I think you have discovered that GraphQL types can be defined using both an attribute macro and a derive.
The following is the corresponding table:
@ -14,7 +14,7 @@ The following is the corresponding table:
|Merged Object|MergedObject|GQLMergedObject|
|Merged Subscription|MergedSubscription|GQLMergedSubscription|
The advantage of attribute macro is that you can provide some parameters at the same time, for example
The advantage of the attribute macro is that you can provide parameters at the same time, for example
```rust
#[SimpleObject(name = "ABC")]
@ -23,7 +23,7 @@ struct MyObject {
}
```
**But it does not support conditional compilation**, for example:
**However, attribute macros do not support conditional compilation**. The following does not work:
```rust
#[SimpleObject]
@ -36,7 +36,7 @@ struct MyObject {
}
```
**Derive can support conditional compilation**, but it needs to provide parameters separately, for example:
Deriving, on the other hand, does support conditional compilation, but as derive macros can't take parameters you need to provide them separately. For example:
```rust
#[derive(SimpleObject)]
@ -46,4 +46,4 @@ struct MyObject {
}
```
_Which way to define the type is up to you, I prefer to use derive._
_Which way you use to define types is up to you, personally I prefer to use derive._

View File

@ -2,8 +2,7 @@
## Query root object
The query root object is a GraphQL object with a definition similar to other objects. resolver functions for all fields of the query object are executed concurrently.
The query root object is a GraphQL object with a definition similar to other objects. Resolver functions for all fields of the query object are executed concurrently.
```rust
use async_graphql::*;

View File

@ -4,10 +4,10 @@
```toml
[dependencies]
async-graphql = "1.11.0"
async-graphql-actix-web = "1.3.0" # If you need to integrate into actix-web
async-graphql-warp = "1.3.0" # If you need to integrate into warp
async-graphql-tide = "1.2.0" # If you need to integrate into tide
async-graphql = "1.17.15"
async-graphql-actix-web = "1.17.3" # If you need to integrate into actix-web
async-graphql-warp = "1.17.3" # If you need to integrate into warp
async-graphql-tide = "1.17.9" # If you need to integrate into tide
```
## Write a Schema
@ -30,12 +30,11 @@ impl Query {
a + b
}
}
```
## Execute the query
In our example, there is only Query without Mutation and Subscription, so we create the Schema with `EmptyMutation` and `EmptySubscription`, and then call `Schema::execute` to execute the Query.
In our example, there is only a Query without a Mutation or Subscription, so we create the Schema with `EmptyMutation` and `EmptySubscription`, and then call `Schema::execute` to execute the Query.
```rust
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
@ -44,7 +43,7 @@ let res = schema.execute("{ add(a: 10, b: 20) }");
## Output the query results as JSON
Query returns `async_graphql::Result` with `async_graphql::http::GQLResponse ` wrapped, can be directly converted to JSON.
`Schema::execute` returns `async_graphql::Result` with `async_graphql::http::GQLResponse` wrapped, and it can be directly converted to JSON.
```rust
let json = serde_json::to_string(&async_graphql::http::GQLResponse(res));
@ -52,4 +51,4 @@ let json = serde_json::to_string(&async_graphql::http::GQLResponse(res));
## Web server integration
Please refer to https://github.com/async-graphql/examples.
Please refer to <https://github.com/async-graphql/examples>.

View File

@ -1,8 +1,8 @@
# Subscription
The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a Stream, and the field parameters are usually used as data filtering conditions.
The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a [Stream](https://docs.rs/futures-core/~0.3/futures_core/stream/trait.Stream.html), and the field parameters are usually used as data filtering conditions.
The following example subscribes to an integer stream, which generates one integer per second. The parameter step specifies the integer step size with a default of 1
The following example subscribes to an integer stream, which generates one integer per second. The parameter `step` specifies the integer step size with a default of 1.
```rust
use async_graphql::*;

View File

@ -1,3 +1,3 @@
# Type System
`Async-graphql` implemented conversion from GraphQL Object to Rust struct, and it's easy to use.
`Async-graphql` implements conversions from GraphQL Objects to Rust structs, and it's easy to use.

View File

@ -12,14 +12,11 @@
</style>
</head>
<body>
<div>
<a href="en/index.html">English</a>
</div>
<div>
<a href="zh-CN/index.html">简体中文</a>
</div>
<h1>Async-graphql Book</h1>
<p>This book is available in multiple languages:</p>
<ul>
<li><a href="en/index.html">English</a></li>
<li><a href="zh-CN/index.html">简体中文</a></li>
</ul>
</body>
</html>