Merge pull request #65 from Ejhfast/book-english-translation

Ongoing english translations
This commit is contained in:
Sunli 2020-05-10 08:56:11 +08:00 committed by GitHub
commit 064354fbb7
14 changed files with 430 additions and 4 deletions

View File

@ -1 +1,35 @@
# Apollo Federation
`Apollo Federation` is a `GraphQL` gateway API which can combine multiple GraphQL services, allowing each srvice 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.
- You can use the `extends` property declaration on `async_graphql::Object` and `async_graphql::Interface` to extend a type offered by another implementing service.
- The `external` property declares that a field comes from another service。
- The `provides` property indicates the fields provided by a service.
The definition of a root Query type is slighly different. An entity search function must be defined. For example:
```rust
struct Query;
#[Object]
impl Query {
#[entity]
async fn find_user_by_id(&self, id: ID) -> User {
User { id }
}
}
```
This is equivalent to:
```graphql
type User @key(id: ID!) {
id: ID!,
}
```
For a complete example, refer to: https://github.com/async-graphql/examples/tree/master/federation

View File

@ -1 +1,15 @@
# Apollo Tracing
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.
```rust
use async_graphql::*;
use async_graphql::extensions::ApolloTracing;
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
.extension(|| ApolloTracing::default()) // 启用ApolloTracing扩展
.finish();
```

View File

@ -1 +1,49 @@
# Cache control
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.
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`.
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.
```rust
#[Object(cache_control(max_age = 60))]
impl Query {
#[field(cache_control(max_age = 30))]
async fn value1(&self) -> i32 {
}
#[field(cache_control(private))]
async fn value2(&self) -> i32 {
}
async fn value3(&self) -> i32 {
}
}
```
The following are different queries corresponding to different cache control results:
```graphql
# max_age=30
{ value1 }
```
```graphql
# max_age=30, private
{ value1 value2 }
```
```graphql
# max_age=60
{ value3 }
```

View File

@ -1 +1,82 @@
# Cursor
# 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)。
It is simple to define a cursor connection in `Async-GraphQL`
1. Implement `async_graphql::DataSource` and write the `query_operation` function.
2. Call `DataSource::query` in the field's resolve function and return the result.
Here is a simple data source that returns continuous integers:
```rust
use async_graphql::*;
struct Integers;
#[DataSource]
impl DataSource for Integers {
// Type for response
type Element = i32;
// We don't need to extend the edge fields, so this can be empty
type EdgeFieldsObj = EmptyEdgeFields;
async fn query_operation(&self, _ctx: &Context<'_>, operation: &QueryOperation<'_>) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>> {
let (start, end) = match operation {
// Look from beginning up to limit
QueryOperation::First {limit} => {
let start = 0;
let end = start + *limit as i32;
(start, end)
}
QueryOperation::FirstAfter {after, limit} => {
// Look after number up to limit
let start = after.parse::<i32>()
.ok()
.map(|after| after + 1)
.unwrap_or(0);
(start, end + start + *limit)
}
// Look backward from last element up to limit
QueryOperation::Last {limit} => {
let end = 0;
let start = end - *limit as i32;
(start, end)
}
QueryOperation::LastBefore {before, limit} => {
// Look before number up to limit
let end = before.parse::<i32>()
.ok()
.unwrap_or(0);
(end - *limit, end)
}
// TODO: Need to handle all conditions
_ => (0, 10)
};
// Create nodes. Each node is a tuple containing three values: the cursor, extended edge object, and node value
let nodes = (start..end).into_iter().map(|n| (n.to_string(), EmptyEdgeFields, n)).collect();
Ok(Connection::new(None, true, true, nodes))
}
}
struct Query;
#[Object]
impl Query {
#[field]
async fn numbers(&self,
ctx: &Context<'_>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
) -> FieldResult<Connection<i32, EmptyEdgeFields>> {
// Make the query
Integers.query(ctx, after, before, first, last).await
}
}
```

View File

@ -1 +1,7 @@
# Custom extensions
A GraphQL extension object can receive events in various stages of a query's execution, and you can collect various kinds of data to be returned in the query results.
You can use `async_graphql::Extension` to define an extension object, and your application much call `Schema::extension` when your `Schema` is created.
You can refer to [Apollo Tracing](https://github.com/async-graphql/async-graphql/blob/master/src/extensions/tracing.rs) to implement your own extension types.

View File

@ -1 +1,37 @@
# Custom scalars
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.)
```rust
use async_graphql::*;
struct StringNumber(i64);
#[Scalar]
impl ScalarType for StringNumber {
fn type_name() -> &'static str {
// Name of type
"StringNumber"
}
fn parse(value: &Value) -> Option<Self> {
if let Value::String(value) = value {
// Parse the integer value
value.parse().ok().map(StringNumber)
} else {
// If the type does not match, return None
None
}
}
fn to_json(&self) -> Result<serde_json::Value> {
Ok(serde_json::to_value(self.0).unwrap())
}
}
```

View File

@ -1 +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.
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.

View File

@ -1 +1,73 @@
# 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.
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:
```rust
use async_graphql::*;
use async_graphql::validators::{Email, MAC};
struct Query;
#[Object]
impl Query {
async fn input(#[arg(validator(or(Email, MAC(colon = "false"))))] a: String) {
}
}
```
The following example verifies that the `i32` parameter `a` is greater than 10 and less than 100, or else equal to 0:
```rust
use async_graphql:*;
use async_graphql::validators::{IntGreaterThan, IntLessThan, IntEqual};
struct Query;
#[Object]
impl Query {
async fn input(#[validator(
or(
and(IntGreaterThan(value = "10"), IntLessThan(value = "100")),
IntEqual(value = "0")
))] a: String) {
} {
}
}
```
## Custom validator
Here is an example of a custom validator:
```rust
struct MustBeZero {}
impl InputValueValidator for InputValueValidator {
fn is_valid(&self, value: &Value) -> Option<String> {
if let Value::Int(n) = value {
if n.as_i64().unwrap() != 0 {
// Validation failed
Some(format!(
"the value is {}, but must be zero",
n.as_i64().unwrap(),
))
} else {
// Validation succeeded
None
}
} else {
// If the type does not match we can return None and built-in validations
// will pick up on the error
None
}
}
}
```

View File

@ -1 +1,8 @@
# Integrations
`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)
**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 +1,32 @@
# 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`
`WSSubscription` is an Actor that supports WebSocket subscriptions。
## Request example
When you define your `actix_web::App` you need to pass in the Schema as data.
```rust
async fn index(
// Schema now accessible here
schema: web::Data<Schema>,
gql_request: GQLRequest,
) -> web::Json<GQLResponse> {
web::Json(GQLResponse(gql_request.into_inner().execute(&schema).await))
}
```
## Subscription example
```rust
async fn index_ws(
schema: web::Data<Schema>,
req: HttpRequest,
payload: web::Payload,
) -> Result<HttpResponse> {
ws::start_with_protocols(WSSubscription::new(&schema), &["graphql-ws"], &req, payload)
}
```

View File

@ -1 +1,29 @@
# Warp
For `Async-graphql-warp`, two `Filter` integrations are provided: `graphql` and `graphql_subscription`.
The `graphql` filter is used for execution `Query` and `Mutation` requests. It always asks for the POST method and outputs a `Schema` via `QueryBuilder`. You can combine other filters later, or directly call `QueryBuilder::execute` to execute the query.
`graphql_subscription` is used to implement WebSocket subscriptions. It outputs `warp::Reply`.
## Request example
```rust
let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
let filter = async_graphql_warp::graphql(schema).and_then(|(schema, builder): (_, QueryBuilder)| async move {
// Execute query
let resp = builder.execute(&schema).await;
// Return result
Ok::<_, Infallible>(warp::reply::json(&GQLResponse(resp)).into_response())
});
warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
```
## Subscription example
```rust
let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
let filter = async_graphql_warp::graphql_subscription(schema);
warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
```

View File

@ -1,12 +1,12 @@
# Introduction
`Async-graphql` is a GraphQL server-side library implemented in the Rust language. It is fully compatible with the GraphQL specification and most of its extensions, type-safe, and high-performance.
`Async-graphql` is a GraphQL server-side library implemented in Rust. It is fully compatible with the GraphQL specification and most of its extensions, and offers type safety and high performance.
You can define the Schema in the Rust language, the procedural macros automatically generate the framework code for the GraphQL query, and the lack of extending Rust's syntax means that Rustfmt can be used normally, which I value very much, which is one of the reasons why I developed `Async-graphql`.
You can define a Schema in Rust and procedural macros will automatically generate code for a GraphQL query. This library does not extend Rust's syntax, which means that Rustfmt can be used normally. I value this highly and it is one of the reasons why I developed `Async-graphql`.
## Why do this?
I like GraphQL and Rust. I've been using `Juniper` before, which solved my problem of implementing the GraphQL server with Rust, but it has some regrets, the most important of which is that it didn't support async/await at the time, so I decided to make one for myself.
I like GraphQL and Rust. I've been using `Juniper`, which solves the problem of implementing a GraphQL server with Rust. But Juniper had several problems, the most important of which is that it didn't support async/await at the time. So I decided to make this library for myself.
## Progress

View File

@ -1 +1,44 @@
# Query and Mutation
## Query root object
The query root object is a GraphQL object with a definition similar to other objects. Resolve functions for all fields of the query object are executed concurrently.
```rust
use async_graphql::*;
struct Query;
#[Object]
impl Query {
async fn user(&self, username: String) -> FieldResult<Option<User>> {
// Look up users from the database
}
}
```
## Mutation root object
The mutation root object is also a GraphQL object, but it executes sequentially. One mutation following from another will only be executed only after the first mutation is completed.
The following mutation root object provides an example of user registration and login:
```rust
use async_graphql::*;
struct Mutation;
#[Object]
impl Mutation {
async fn signup(&self, username: String, password: String) -> Result<bool> {
// User signup
}
async fn login(&self, username: String, password: String) -> Result<String> {
// User login (generate token)
}
}
```

View File

@ -1 +1,22 @@
# Subscription
The definition of the subscription root object is slightly different from other root objects. Its Resolve function always returns a Stream, 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
```rust
use async_graphql::*;
struct Subscription;
#[Subscription]
impl Subscription {
async fn integers(&self, #[arg(default = "1")] step: i32) -> impl Stream<Item = i32> {
let mut value = 0;
tokio::time::interval(Duration::from_secs(1)).map(move |_| {
value += step;
value
})
}
}
```