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 + } + } +} +```