Update book
This commit is contained in:
parent
b359b62976
commit
ccb06019cc
|
@ -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)
|
||||
|
|
|
@ -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> {
|
||||
...
|
||||
|
|
|
@ -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*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
# 扩展
|
||||
|
||||
`async-graphql` 允许你不修改核心代码就能扩展它功能。
|
|
@ -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` 扩展。
|
|
@ -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)
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue