From d0b4e4db87d1b25fc09fb9c591818730ad03de84 Mon Sep 17 00:00:00 2001 From: Ethan Fast Date: Sat, 9 May 2020 13:56:15 -0700 Subject: [PATCH 1/4] translate section 4 (schema) --- docs/en/src/define_schema.md | 5 ++++ docs/en/src/introduction.md | 6 ++--- docs/en/src/query_and_mutation.md | 43 +++++++++++++++++++++++++++++++ docs/en/src/subscription.md | 21 +++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/docs/en/src/define_schema.md b/docs/en/src/define_schema.md index 07d8c082..a38c4a94 100644 --- a/docs/en/src/define_schema.md +++ b/docs/en/src/define_schema.md @@ -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. + diff --git a/docs/en/src/introduction.md b/docs/en/src/introduction.md index 20b0e348..9c003f78 100644 --- a/docs/en/src/introduction.md +++ b/docs/en/src/introduction.md @@ -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 diff --git a/docs/en/src/query_and_mutation.md b/docs/en/src/query_and_mutation.md index 60aa8069..251313fc 100644 --- a/docs/en/src/query_and_mutation.md +++ b/docs/en/src/query_and_mutation.md @@ -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> { + // 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 { + // User signup + } + + async fn login(&self, username: String, password: String) -> Result { + // User login (generate token) + } +} +``` + diff --git a/docs/en/src/subscription.md b/docs/en/src/subscription.md index 13dc747f..b7adb20a 100644 --- a/docs/en/src/subscription.md +++ b/docs/en/src/subscription.md @@ -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 { + let mut value = 0; + tokio::time::interval(Duration::from_secs(1)).map(move |_| { + value += step; + value + }) + } +} +``` From 5e7a7f0df11347028f8e015a3cd294486381760e Mon Sep 17 00:00:00 2001 From: Ethan Fast Date: Sat, 9 May 2020 14:39:04 -0700 Subject: [PATCH 2/4] translate section 5 (extensions) --- docs/en/src/apollo_tracing.md | 14 +++++ docs/en/src/cache_control.md | 48 ++++++++++++++++ docs/en/src/cursor_connections.md | 83 ++++++++++++++++++++++++++- docs/en/src/input_value_validators.md | 72 +++++++++++++++++++++++ 4 files changed, 216 insertions(+), 1 deletion(-) diff --git a/docs/en/src/apollo_tracing.md b/docs/en/src/apollo_tracing.md index b552068f..1316f121 100644 --- a/docs/en/src/apollo_tracing.md +++ b/docs/en/src/apollo_tracing.md @@ -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(); + +``` \ No newline at end of file diff --git a/docs/en/src/cache_control.md b/docs/en/src/cache_control.md index 217fedf8..d5c8177b 100644 --- a/docs/en/src/cache_control.md +++ b/docs/en/src/cache_control.md @@ -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 } +``` + diff --git a/docs/en/src/cursor_connections.md b/docs/en/src/cursor_connections.md index 0b56908f..ce68d142 100644 --- a/docs/en/src/cursor_connections.md +++ b/docs/en/src/cursor_connections.md @@ -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> { + 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::() + .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::() + .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, + before: Option, + first: Option, + last: Option, + ) -> FieldResult> { + // Make the query + Integers.query(ctx, after, before, first, last).await + } +} + +``` \ No newline at end of file diff --git a/docs/en/src/input_value_validators.md b/docs/en/src/input_value_validators.md index a122067e..3bdc0711 100644 --- a/docs/en/src/input_value_validators.md +++ b/docs/en/src/input_value_validators.md @@ -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 { + 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 + } + } +} +``` From ddceb05e69ea5f9062eac09a9b5f79acc3fb54b4 Mon Sep 17 00:00:00 2001 From: Ethan Fast Date: Sat, 9 May 2020 14:56:45 -0700 Subject: [PATCH 3/4] translate section 6 (integrations) --- docs/en/src/integrations.md | 7 ++++++ docs/en/src/integrations_to_actix_web.md | 31 ++++++++++++++++++++++++ docs/en/src/integrations_to_warp.md | 28 +++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/docs/en/src/integrations.md b/docs/en/src/integrations.md index e20ec321..fd622b8e 100644 --- a/docs/en/src/integrations.md +++ b/docs/en/src/integrations.md @@ -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** \ No newline at end of file diff --git a/docs/en/src/integrations_to_actix_web.md b/docs/en/src/integrations_to_actix_web.md index efacadad..6db435fd 100644 --- a/docs/en/src/integrations_to_actix_web.md +++ b/docs/en/src/integrations_to_actix_web.md @@ -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, + gql_request: GQLRequest, +) -> web::Json { + web::Json(GQLResponse(gql_request.into_inner().execute(&schema).await)) +} + +``` + +## Subscription example + +```rust +async fn index_ws( + schema: web::Data, + req: HttpRequest, + payload: web::Payload, +) -> Result { + ws::start_with_protocols(WSSubscription::new(&schema), &["graphql-ws"], &req, payload) +} +``` diff --git a/docs/en/src/integrations_to_warp.md b/docs/en/src/integrations_to_warp.md index 0f90660e..52c95aac 100644 --- a/docs/en/src/integrations_to_warp.md +++ b/docs/en/src/integrations_to_warp.md @@ -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; +``` From 015e7ff6b5b8c16597580ce0cd67b4e1bf3c8b09 Mon Sep 17 00:00:00 2001 From: Ethan Fast Date: Sat, 9 May 2020 15:37:31 -0700 Subject: [PATCH 4/4] translate section 7 (advanced topics) --- docs/en/src/apollo_federation.md | 34 ++++++++++++++++++++++++++++++ docs/en/src/custom_extensions.md | 6 ++++++ docs/en/src/custom_scalars.md | 36 ++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/docs/en/src/apollo_federation.md b/docs/en/src/apollo_federation.md index 414729ff..640b8623 100644 --- a/docs/en/src/apollo_federation.md +++ b/docs/en/src/apollo_federation.md @@ -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 diff --git a/docs/en/src/custom_extensions.md b/docs/en/src/custom_extensions.md index 2e68a925..1fabdb67 100644 --- a/docs/en/src/custom_extensions.md +++ b/docs/en/src/custom_extensions.md @@ -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. \ No newline at end of file diff --git a/docs/en/src/custom_scalars.md b/docs/en/src/custom_scalars.md index 7a3d76ea..8b86e290 100644 --- a/docs/en/src/custom_scalars.md +++ b/docs/en/src/custom_scalars.md @@ -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 { + 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 { + Ok(serde_json::to_value(self.0).unwrap()) + } +} + +```