Update book

This commit is contained in:
Sunli 2021-11-08 16:24:01 +08:00
parent b359b62976
commit ccb06019cc
10 changed files with 325 additions and 20 deletions

View File

@ -9,7 +9,6 @@
- [Error handling](error_handling.md)
- [Merging Objects / Subscriptions](merging_objects.md)
- [Derived fields](derived_fields.md)
- [Complex Object](define_complex_object.md)
- [Enum](define_enum.md)
- [Interface](define_interface.md)
- [Union](define_union.md)

View File

@ -1,16 +1,8 @@
# Derived fields
When you are working on a GraphQL project, you usually have to explain and share how your scalars should
be interpreted by your consumers. Sometimes, you event want to have the same data and the same logic exposing
the data in another type.
Sometimes two fields have the same query logic, but the output type is different. In `async-graphql`, you can create a derived field for it.
Within `async-graphql` you can create derived fields for objects to generate derived fields.
Consider you want to create a `Date` scalar, to represent an event of time.
How will you represent and format this date? You could create a scalar `Date` where you specified it's the RFCXXX
implemented to format it.
With derived fields there is a simple way to support multiple representation of a `Date` easily:
In the following example, you already have a `duration_rfc2822` field outputting the time format in `RFC2822` format, and then reuse it to derive a new `date_rfc3339` field.
```rust
struct DateRFC3339(chrono::DateTime);
@ -62,7 +54,9 @@ type Query {
## Wrapper types
A derived field won't be able to manage everythings easily: without the specialization from the Rust language, you won't be able to implement specialized trait like:
A derived field won't be able to manage everythings easily: Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) requires that either the
trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so the following code cannot be compiled:
```
impl From<Vec<U>> for Vec<T> {
...

View File

@ -24,9 +24,7 @@ pub trait CacheStorage: Send + Sync + Clone + 'static {
}
```
### References
[Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/)
References: [Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/)
## Apollo Tracing
*Available in the repository*

View File

@ -22,10 +22,10 @@ async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>)
}
```
As you have seen, a `Middleware` is only a function calling the next function at the end, but we could also do a middleware with the `next` function at the start. This is where it's becoming tricky: depending on where you put your logics and where is the `next` call, your logic won't have the same execution order.
As you have seen, a `Middleware` is only a function calling the next function at the end, but we could also do a middleware with the `next.run` function at the start. This is where it's becoming tricky: depending on where you put your logics and where is the `next.run` call, your logic won't have the same execution order.
Depending on your logic code, you'll want to process it before or after the `next` call. If you need more information about middlewares, there are a lot of things in the web.
Depending on your logic code, you'll want to process it before or after the `next.run` call. If you need more information about middlewares, there are a lot of things in the web.
## Processing of a query
@ -50,7 +50,7 @@ Depending on where you put your logic code, it'll be executed at the beginning o
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
// The code here will be run before the prepare_request is executed.
let result = next.run(ctx).await;
// The code after the completion of this futue will be after the processing, just before sending the result to the user.
// The code after the completion of this future will be after the processing, just before sending the result to the user.
result
}
```
@ -119,7 +119,7 @@ async fn execute(
operation_name: Option<&str>,
next: NextExecute<'_>,
) -> Response {
// Befoe starting resolving the whole query
// Before starting resolving the whole query
let result = next.run(ctx, operation_name).await;
// After resolving the whole query
result

View File

@ -8,6 +8,7 @@
- [查询上下文(Context)](context.md)
- [错误处理](error_handling.md)
- [合并对象(MergedObject)](merging_objects.md)
- [派生字段](derived_fields.md)
- [枚举(Enum)](define_enum.md)
- [接口(Interface)](define_interface.md)
- [联合(Union)](define_union.md)
@ -25,6 +26,9 @@
- [Apollo Tracing支持](apollo_tracing.md)
- [查询的深度和复杂度](depth_and_complexity.md)
- [在内省中隐藏内容](visibility.md)
- [扩展](extensions.md)
- [扩展如何工作](extensions_inner_working.md)
- [可用的扩展列表](extensions_available.md)
- [集成到WebServer](integrations.md)
- [Warp](integrations_to_warp.md)
- [Actix-web](integrations_to_actix_web.md)

View File

@ -85,7 +85,7 @@ impl Query {
// If multiple headers with the same key are `inserted` then the most recent
// one overwrites the previous. If you want multiple headers for the same key, use
// `append_http_header` for subsequent headers
let was_in_headers = ctx.insert_http_header("Custom-Header", "Hello World");
let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World");
String::from("Hello world")
}

View File

@ -0,0 +1,96 @@
# 派生字段
有时两个字段有一样的查询逻辑,仅仅是输出的类型不同,在 `async-graphql` 中,你可以为它创建派生字段。
在以下例子中,你已经有一个`duration_rfc2822`字段输出`RFC2822`格式的时间格式,然后复用它派生一个新的`date_rfc3339`字段。
```rust
struct DateRFC3339(chrono::DateTime);
struct DateRFC2822(chrono::DateTime);
#[Scalar]
impl ScalarType for DateRFC3339 {
fn parse(value: Value) -> InputValueResult { ... }
fn to_value(&self) -> Value {
Value::String(self.0.to_rfc3339())
}
}
#[Scalar]
impl ScalarType for DateRFC2822 {
fn parse(value: Value) -> InputValueResult { ... }
fn to_value(&self) -> Value {
Value::String(self.0.to_rfc2822())
}
}
impl From<DateRFC2822> for DateRFC3339 {
fn from(value: DateRFC2822) -> Self {
DateRFC3339(value.0)
}
}
struct Query;
#[Object]
impl Query {
#[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))]
async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 {
todo!()
}
}
```
它将呈现为如下GraphQL
```graphql
type Query {
duration_rfc2822(arg: String): DateRFC2822!
duration_rfc3339(arg: String): DateRFC3339!
}
```
## 包装类型
因为 [孤儿规则](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits),以下代码无法通过编译:
```
impl From<Vec<U>> for Vec<T> {
...
}
```
因此,你将无法为现有的包装类型结构(如`Vec`或`Option`)生成派生字段。
但是当你为 `T` 实现了 `From<U>` 后,你可以为 `Vec<T>` 实现 `From<Vec<U>>`,为 `Option<T>` 实现 `From<Option<U>>`.
使用 `with` 参数来定义一个转换函数,而不是用 `Into::into`
### Example
```rust
#[derive(Serialize, Deserialize, Clone)]
struct ValueDerived(String);
#[derive(Serialize, Deserialize, Clone)]
struct ValueDerived2(String);
scalar!(ValueDerived);
scalar!(ValueDerived2);
impl From<ValueDerived> for ValueDerived2 {
fn from(value: ValueDerived) -> Self {
ValueDerived2(value.0)
}
}
fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
value.map(|x| x.into())
}
#[derive(SimpleObject)]
struct TestObj {
#[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))]
pub value1: Option<ValueDerived>,
}
```

View File

@ -0,0 +1,3 @@
# 扩展
`async-graphql` 允许你不修改核心代码就能扩展它功能。

View File

@ -0,0 +1,53 @@
# 可用的扩展列表
`async-graphql` 中有很多可用的扩展用于增强你的 GraphQL 服务器。
## Analyzer
*Available in the repository*
`Analyzer` 扩展将在每个响应的扩展中输出 `complexity``depth` 字段。
## Apollo Persisted Queries
*Available in the repository*
要提高大型查询的性能你可以启用此扩展每个查询语句都将与一个唯一ID相关联因此客户端可以直接发送此ID查询以减少请求的大小。
这个扩展不会强迫你使用一些缓存策略,你可以选择你想要的缓存策略,你只需要实现 `CacheStorage` trait
```rust
#[async_trait::async_trait]
pub trait CacheStorage: Send + Sync + Clone + 'static {
/// Load the query by `key`.
async fn get(&self, key: String) -> Option<String>;
/// Save the query by `key`.
async fn set(&self, key: String, query: String);
}
```
References: [Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/)
## Apollo Tracing
*Available in the repository*
`Apollo Tracing` 扩展用于在响应中包含此查询分析数据。 此扩展程序遵循旧的且现已弃用的 [Apollo Tracing Spec](https://github.com/apollographql/apollo-tracing) 。
如果你想支持更新的 Apollo Reporting Protocol推荐使用 [async-graphql Apollo studio extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension) 。
## Apollo Studio
*Available at [async-graphql/async_graphql_apollo_studio_extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension)*
`async-graphql` 提供了实现官方 [Apollo Specification](https://www.apollographql.com/docs/studio/setup-analytics/#third-party-support) 的扩展,位于 [async-graphql-extension- apollo-tracing](https://github.com/async-graphql/async_graphql_apollo_studio_extension) 和 [crates.io](https://crates.io/crates/async-graphql-extension-apollo-tracing) 。
## Logger
*Available in the repository*
`Logger` 是一个简单的扩展,允许你向 `async-graphql` 添加一些日志记录功能。这也是学习如何创建自己的扩展的一个很好的例子。
## OpenTelemetry
*Available in the repository*
`OpenTelemetry` 扩展提供 [opentelemetry crate](https://crates.io/crates/opentelemetry) 的集成,以允许你的应用程序从 `async-graphql` 捕获分布式跟踪和指标。
## Tracing
*Available in the repository*
`Tracing` 扩展提供 [tracing crate](https://crates.io/crates/tracing) 的集成,允许您向 `async-graphql` 添加一些跟踪功能,有点像`Logger` 扩展。

View File

@ -0,0 +1,158 @@
# 如何定义扩展
`async-graphql` 扩展是通过实现 `Extension` trait 来定义的。 `Extension` trait 允许你将自定义代码插入到执行 GraphQL 查询的步骤中。
`Extensions` 很像来自其他框架的中间件,使用它们时要小心:当你使用扩展时**它对每个 GraphQL 请求生效**。
## 一句话解释什么是中间件
让我们了解什么是中间件:
```rust
async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
// 你的中间件代码
/*
* 调用next.run函数执行下个中间件的逻辑
*/
next.run(ctx).await
}
```
如你所见,`middleware` 只是在末尾调用 next 函数的函数。但我们也可以在开头使用 `next.run` 来实现中间件。 这就是它变得棘手的地方:根据你放置逻辑的位置以及`next.run`调用的位置,你的逻辑将不会具有相同的执行顺序。
根据你代码,你需要在 `next.run` 调用之前或之后处理它。 如果你需要更多关于中间件的信息,网上有很多。
## 查询的处理
查询的每个阶段都有回调,你将能够基于这些回调创建扩展。
### 请求
首先,当我们收到一个请求时,如果它不是订阅,第一个被调用的函数将是 `request`,它在传入请求时调用,并输出结果给客户端。
Default implementation for `request`:
```rust
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
next.run(ctx).await
}
```
根据你放置逻辑代码的位置,它将在正在查询执行的开头或结尾执行。
```rust
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
// 此处的代码将在执行 prepare_request 之前运行。
let result = next.run(ctx).await;
// 此处的代码将在把结果发送给客户端之前执行
result
}
```
### 准备查询
`request` 之后,将调用`prepare_request`,你可以在此处对请求做一些转换。
```rust
async fn prepare_request(
&self,
ctx: &ExtensionContext<'_>,
request: Request,
next: NextPrepareRequest<'_>,
) -> ServerResult<Request> {
// 此处的代码在 prepare_request 之前执行
let result = next.run(ctx, request).await;
// 此处的代码在 prepare_request 之后执行
result
}
```
### 解析查询
`parse_query` 将解析查询语句并生成 GraphQL `ExecutableDocument`,并且检查查询是否遵循 GraphQL 规范。 通常,`async-graphql` 遵循最后一个稳定的规范October2021
```rust
/// Called at parse query.
async fn parse_query(
&self,
ctx: &ExtensionContext<'_>,
// The raw query
query: &str,
// The variables
variables: &Variables,
next: NextParseQuery<'_>,
) -> ServerResult<ExecutableDocument> {
next.run(ctx, query, variables).await
}
```
### 校验
`validation` 步骤将执行查询校验(取决于你指定的 `validation_mode`),并向客户端提供有关查询无效的原因。
```rust
/// Called at validation query.
async fn validation(
&self,
ctx: &ExtensionContext<'_>,
next: NextValidation<'_>,
) -> Result<ValidationResult, Vec<ServerError>> {
next.run(ctx).await
}
```
### 执行
`execution` 步骤是一个很大的步骤,它将并发执行`Query`,或者顺序执行`Mutation`。
```rust
/// Called at execute query.
async fn execute(
&self,
ctx: &ExtensionContext<'_>,
operation_name: Option<&str>,
next: NextExecute<'_>,
) -> Response {
// 此处的代码在执行完整查询之前执行
let result = next.run(ctx, operation_name).await;
// 此处的代码在执行完整查询之后执行
result
}
````
### resolve
为每个字段执行`resolve`.
```rust
/// Called at resolve field.
async fn resolve(
&self,
ctx: &ExtensionContext<'_>,
info: ResolveInfo<'_>,
next: NextResolve<'_>,
) -> ServerResult<Option<Value>> {
// resolve字段之前
let result = next.run(ctx, info).await;
// resolve字段之后
result
}
```
### 订阅
`subscribe`的行为和`request`很像,只是专门用于订阅查询。
```rust
/// Called at subscribe request.
fn subscribe<'s>(
&self,
ctx: &ExtensionContext<'_>,
stream: BoxStream<'s, Response>,
next: NextSubscribe<'_>,
) -> BoxStream<'s, Response> {
next.run(ctx, stream)
}
```