This commit is contained in:
Sunli 2020-09-01 19:34:05 +08:00
commit c3a4bf6e11
54 changed files with 690 additions and 373 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "async-graphql" name = "async-graphql"
version = "1.17.12" version = "1.17.15"
authors = ["sunli <scott_s829@163.com>"] authors = ["sunli <scott_s829@163.com>"]
edition = "2018" edition = "2018"
description = "A GraphQL server library implemented in Rust" description = "A GraphQL server library implemented in Rust"
@ -22,8 +22,7 @@ async-graphql-derive = { path = "async-graphql-derive", version = "1.17.11" }
anyhow = "1.0.26" anyhow = "1.0.26"
thiserror = "1.0.11" thiserror = "1.0.11"
async-trait = "0.1.30" async-trait = "0.1.30"
serde = "1.0.104" serde = { version = "1.0.104", features = ["derive"] }
serde_derive = "1.0.104"
serde_json = "1.0.48" serde_json = "1.0.48"
bytes = "0.5.4" bytes = "0.5.4"
Inflector = "0.11.4" Inflector = "0.11.4"
@ -55,8 +54,6 @@ smallvec = "1.4.2"
[dev-dependencies] [dev-dependencies]
async-std = { version = "1.5.0", features = ["attributes"] } async-std = { version = "1.5.0", features = ["attributes"] }
serde = "1.0.104"
serde_derive = "1.0.104"
[workspace] [workspace]
members = [ members = [
@ -65,6 +62,6 @@ members = [
"async-graphql-actix-web", "async-graphql-actix-web",
"async-graphql-warp", "async-graphql-warp",
"async-graphql-tide", "async-graphql-tide",
# "async-graphql-lambda", # "async-graphql-lambda",
"benchmark", "benchmark",
] ]

View File

@ -2,5 +2,5 @@
authors = ["sunli"] authors = ["sunli"]
description = "Async-graphql Book" description = "Async-graphql Book"
src = "src" src = "src"
language = "zh-CN" language = "en"
title = "Async-graphql Book" title = "Async-graphql Book"

View File

@ -1,8 +1,8 @@
# Apollo Federation # Apollo Federation
`Apollo Federation` is a `GraphQL` API gateway which can combine multiple GraphQL services, allowing each service 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) `Apollo Federation` is a `GraphQL` API gateway which can combine multiple GraphQL services, allowing each service 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. `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. - You can use the `extends` property declaration on `async_graphql::Object` and `async_graphql::Interface` to extend a type offered by another implementing service.
@ -36,16 +36,16 @@ impl Query {
**Notice the difference between these three lookup functions, which are all looking for the `User` object.** **Notice the difference between these three lookup functions, which are all looking for the `User` object.**
- find_user_by_id - `find_user_by_id`
Use `id` to find an `User` object, the key for `User` is `id` Use `id` to find an `User` object, the key for `User` is `id`.
- find_user_by_id_with_username - `find_user_by_id_with_username`
Use `id` to find an `User` object, the key for `User` is `id`, and the `username` field value of the `User` object is requested Use `id` to find an `User` object, the key for `User` is `id`, and the `username` field value of the `User` object is requested.
- find_user_by_id_and_username - `find_user_by_id_and_username`
Use `id` and `username` to find an `User` object, the keys for `User` are `id` and `username` Use `id` and `username` to find an `User` object, the keys for `User` are `id` and `username`.
For a complete example, refer to: https://github.com/async-graphql/examples/tree/master/federation For a complete example, refer to: <https://github.com/async-graphql/examples/tree/master/federation>.

View File

@ -2,14 +2,13 @@
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`. 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. To enable the Apollo Tracing extension, add the extension when the `Schema` is created.
```rust ```rust
use async_graphql::*; use async_graphql::*;
use async_graphql::extensions::ApolloTracing; use async_graphql::extensions::ApolloTracing;
let schema = Schema::build(Query, EmptyMutation, EmptySubscription) let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
.extension(|| ApolloTracing::default()) // Enable ApolloTracing extension .extension(ApolloTracing::default) // Enable ApolloTracing extension
.finish(); .finish();
```
```

View File

@ -4,13 +4,13 @@ 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. 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. `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 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`. 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`. when 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. 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.

View File

@ -1,6 +1,6 @@
# Context # Context
The main goal of `Context` is to acquire global data attached to Schema. **Note that if the return value of resolver function is borrowed from `Context`, you need to explicitly state the lifetime of the argument.** The main goal of `Context` is to acquire global data attached to Schema. **Note that if the return value of resolver function is borrowed from `Context`, you will need to explicitly state the lifetime of the argument.**
The following example shows how to borrow data in `Context`. The following example shows how to borrow data in `Context`.

View File

@ -1,8 +1,8 @@
# Cursor connections # 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)。 Relay's cursor connection specification is designed 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)。
Define a cursor connection in `async-graphql` is very simple, you just call the `connection::query` function and query data in closure. Defining a cursor connection in `async-graphql` is very simple, you just call the `connection::query` function and query data in the closure.
```rust ```rust
use async_graphql::*; use async_graphql::*;
@ -42,4 +42,4 @@ impl Query {
} }
} }
``` ```

View File

@ -1,15 +1,14 @@
# Custom scalars # Custom scalars
In `Async-GraphQL` most common scalar types are built in, but you can also create your own scalar types. 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. 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.) 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 ```rust
use async_graphql::*; use async_graphql::*;
struct StringNumber(i64); struct StringNumber(i64);
#[Scalar] #[Scalar]
@ -28,5 +27,4 @@ impl ScalarType for StringNumber {
Value::String(self.0.to_string()) Value::String(self.0.to_string())
} }
} }
``` ```

View File

@ -1,7 +1,7 @@
# Default value # Default value
You can define default values for input value types. You can define default values for input value types.
The following shows how to define default values for different types. Below are some examples.
## Object field ## Object field
@ -57,4 +57,4 @@ struct MyInputObject {
#[field(default = "my_default()")] #[field(default = "my_default()")]
value3: i32, value3: i32,
} }
``` ```

View File

@ -1,14 +1,14 @@
# Object # Object
Different from `SimpleObject`, `Object` must have Resolve defined for each field in `impl`. Different from `SimpleObject`, `Object` must have a resolver defined for each field in its `impl`.
**A resolver function has to be asynchronous. The first argument has to be `&self`, second being optional `Context` and followed by field arguments.** **A resolver function has to be asynchronous. The first argument has to be `&self`, the second is an optional `Context` and it is followed by field arguments.**
Resolve is used to get the value of the field. You can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::FieldResult` so to return an error if it occurs an error message will be sent to query result. The resolvers is used to get the value of the field. For example, you can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::FieldResult` to return an error if it occurs. The error message will then be sent to query result.
When querying a database, you may need a global data base connection pool. You may need access to global data in your query, for example a database connection pool.
When creating `Schema`, you can use `SchemaBuilder::data` to setup `Schema` data, and `Context::data` to setup `Context`data. When creating your `Schema`, you can use `SchemaBuilder::data` to configure the global data, and `Context::data` to configure `Context` data.
The following `value_from_db` function showed how to retrieve a database connection from `Context`. The following `value_from_db` function shows how to retrieve a database connection from `Context`.
```rust ```rust
use async_graphql::*; use async_graphql::*;

View File

@ -2,7 +2,7 @@
It's easy to define an `Enum`, here we have an example: It's easy to define an `Enum`, here we have an example:
**Async-graphql can automatically change the name of each item to GraphQL's CONSTANT_CASE convension, you can also use `name` to rename.** **Async-graphql will automatically change the name of each item to GraphQL's CONSTANT_CASE convention. You can use `name` to rename.**
```rust ```rust
use async_graphql::*; use async_graphql::*;
@ -19,4 +19,4 @@ pub enum Episode {
#[item(name="AAA", desc = "Released in 1983.")] #[item(name="AAA", desc = "Released in 1983.")]
Jedi, Jedi,
} }
``` ```

View File

@ -1,12 +1,12 @@
# InputObject # InputObject
<!--Input Object and SimpleObject inconsistant space.--> <!--Input Object and SimpleObject inconsistant space.-->
You can define an `Object` as argument, GraphQL calls it `InputObject`. You can use an `Object` as an argument, and GraphQL calls it an `InputObject`.
The definition of `InputObject` is similar to [SimpleObject](define_simple_object.md). The definition of `InputObject` is similar to [SimpleObject](define_simple_object.md), but
However, `SimpleObject` can only be used for output and `InputObject` can only be used as input. `SimpleObject` can only be used as output and `InputObject` can only be used as input.
`InputObject` don't need a `#[field]` for each field, every field is `InputValue`. `InputObject` doesn't need a `#[field]` for each field, every field is an `InputValue`.
But you can add optional `#[field]` to add description or rename the field. You can add optional `#[field]` attributes to add descriptions or rename the field.
```rust ```rust
use async_graphql::*; use async_graphql::*;
@ -28,4 +28,4 @@ impl Mutation {
// ... // ...
} }
} }
``` ```

View File

@ -1,16 +1,16 @@
# Interface # Interface
`Interface` is used to abstract `Object`s with common fields. `Interface` is used to abstract `Object`s with common fields.
`Async-graphql` implemented it as a wrapper. `Async-graphql` implements it as a wrapper.
The wrapper will forward Resolve to the `Object` that implemented this `Interface`. The wrapper will forward field resolution to the `Object` that implements this `Interface`.
Therefore, the `Object`'s fields' type, arguments must match with the `Interface`'s. Therefore, the `Object`'s fields' type and arguments must match with the `Interface`'s.
`Async-graphql` implemented auto conversion from `Object` to `Interface`, you only need to call `Into::into`. `Async-graphql` implements auto conversion from `Object` to `Interface`, you only need to call `Into::into`.
Interface fields names transforms to camelCase in schema definition. Interface field names are transformed to camelCase for the schema definition.
If you need e.g. snake_cased GraphQL field name, you can use both the `name` and `method` attribute. If you need e.g. a snake_cased GraphQL field name, you can use both the `name` and `method` attributes.
- When the `name` and `method` exist together, the `name` is the GraphQL field name and the `method` is the resolver function name. - When `name` and `method` exist together, `name` is the GraphQL field name and the `method` is the resolver function name.
- When only `name` exists, `name.to_camel_case()` is the GraphQL field name and the `name` is the resolver function name. - When only `name` exists, `name.to_camel_case()` is the GraphQL field name and the `name` is the resolver function name.
```rust ```rust

View File

@ -1,6 +1,6 @@
# Schema # 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. After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, a mutation object, and a 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. 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, this object will not be exposed in the schema.

View File

@ -1,8 +1,8 @@
# SimpleObject # SimpleObject
`SimpleObject` directly map all field of a struct to GraphQL object, you cannot define a resolver function on it. `SimpleObject` directly maps all the fields of a struct to GraphQL object. You cannot define a resolver function on it - for that, see [Object](define_complex_object.html).
The example below defined an object `MyObject`, including field `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[field(skip)]` The example below defines an object `MyObject` which includes the fields `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[field(skip)]`
```rust ```rust
use async_graphql::*; use async_graphql::*;

View File

@ -1,8 +1,7 @@
# Union # Union
The definition of `Union` is similar to `Interface`'s, **but no field allowed.**. The definition of a `Union` is similar to an `Interface`, **but with no fields allowed.**.
The implemention is quite similar for `Async-graphql`. The implementation is quite similar for `Async-graphql`; from `Async-graphql`'s perspective, `Union` is a subset of `Interface`.
From `Async-graphql`'s perspective, `Union` is a subset of `Interface`.
The following example modified the definition of `Interface` a little bit and removed fields. The following example modified the definition of `Interface` a little bit and removed fields.

View File

@ -1,7 +1,7 @@
# Error extensions # Error extensions
To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18): To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18):
> GraphQL services may provide an additional entry to errors with key extensions. > GraphQL services may provide an additional entry to errors with key extensions.
> This entry, if set, must have a map as its value. This entry is reserved for implementors to add > This entry, if set, must have a map as its value. This entry is reserved for implementer to add
> additional information to errors however they see fit, and there are no additional restrictions on > additional information to errors however they see fit, and there are no additional restrictions on
> its contents. > its contents.
@ -9,11 +9,11 @@ To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18):
I would recommend on checking out this [async-graphql example](https://github.com/async-graphql/examples/blob/master/actix-web/error-extensions/src/main.rs) as a quickstart. I would recommend on checking out this [async-graphql example](https://github.com/async-graphql/examples/blob/master/actix-web/error-extensions/src/main.rs) as a quickstart.
## General Concept ## General Concept
In async-graphql all user-facing errors are cast to the `FieldError` type which by default provides In `async-graphql` all user-facing errors are cast to the `FieldError` type which by default provides
the error message exposed by `std::fmt::Display`. However `FieldError` actually provides an additional the error message exposed by `std::fmt::Display`. However `FieldError` also provides an additional
field `Option<serde_json::Value>` which - if some valid `serde_json::Map` - will be exposed as the extensions key to any error. field `Option<serde_json::Value>` which - if given some valid `serde_json::Map` - will be exposed as the extensions key to any error.
A resolver like this: A resolver looks like this:
```rust ```rust
async fn parse_with_extensions(&self) -> Result<i32, FieldError> { async fn parse_with_extensions(&self) -> Result<i32, FieldError> {
@ -41,7 +41,7 @@ may then return a response like this:
## ErrorExtensions ## ErrorExtensions
Constructing new `FieldError`s by hand quickly becomes tedious. That is why async_graphql provides Constructing new `FieldError`s by hand quickly becomes tedious. That is why `async-graphql` provides
two convenience traits for casting your errors to the appropriate `FieldError` with two convenience traits for casting your errors to the appropriate `FieldError` with
extensions. extensions.
@ -62,8 +62,7 @@ If you find yourself attaching extensions to your errors all over the place you
implementing the trait on your custom error type directly. implementing the trait on your custom error type directly.
```rust ```rust
#[macro_use] use thiserror::Error;
extern crate thiserror;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum MyError { pub enum MyError {
@ -102,7 +101,6 @@ async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
// OR // OR
Err(MyError::NotFound.extend_with(|_| json!({ "on_the_fly": "some_more_info" }))) Err(MyError::NotFound.extend_with(|_| json!({ "on_the_fly": "some_more_info" })))
} }
``` ```
```json ```json
@ -119,12 +117,8 @@ async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
} }
] ]
} }
``` ```
## ResultExt ## ResultExt
This trait enables you to call `extend_err` directly on results. So the above code becomes less verbose. This trait enables you to call `extend_err` directly on results. So the above code becomes less verbose.
@ -176,7 +170,7 @@ Expected response:
### Pitfalls ### Pitfalls
Rust does not provide stable trait specialization yet. Rust does not provide stable trait specialization yet.
That is why `ErrorExtensions` is actually implemented for `&E where E: std::fmt::Display` That is why `ErrorExtensions` is actually implemented for `&E where E: std::fmt::Display`
instead of `E: std::fmt::Display` to provide some specialization through instead of `E: std::fmt::Display`. Some specialization is provided through
[Autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md). [Autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md).
The disadvantage is that the below code does **NOT** compile: The disadvantage is that the below code does **NOT** compile:

View File

@ -1,15 +1,15 @@
# Error handling # Error handling
Resolve can return a `FieldResult`, following is the definition: Resolve can return a `FieldResult`, which has the following definition:
```rust ```rust
type FieldResult<T> = std::result::Result<T, FieldError>; type FieldResult<T> = std::result::Result<T, FieldError>;
``` ```
Any `Error` that implements `std::fmt::Display` can be converted to `FieldError` and you can extend error message. Any `Error` that implements `std::fmt::Display` can be converted to `FieldError` and you can extend the error message.
Following example shows how to parse an input string to integer. When parsing failed, it would return error and attach error message. The following example shows how to parse an input string to an integer. When parsing fails, it will return an error and attach an error message.
See [ErrorExtensions](error_extensions.md) sections of this book for more details. See the [Error Extensions](error_extensions.md) section of this book for more details.
```rust ```rust
use async_graphql::*; use async_graphql::*;

View File

@ -1,11 +1,10 @@
# Input value validators # 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. 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. 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: The following is an input validator which checks that a `String` is a valid Email or MAC address:
@ -22,7 +21,6 @@ impl Query {
} }
``` ```
The following example verifies that the `i32` parameter `a` is greater than 10 and less than 100, or else equal to 0: The following example verifies that the `i32` parameter `a` is greater than 10 and less than 100, or else equal to 0:
```rust ```rust

View File

@ -1,9 +1,9 @@
# Integrations # Integrations
`Async-Graphql` supports several common Rust web servers. `Async-graphql` supports several common Rust web servers.
- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web) - 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) - Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp)
- Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide) - Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide)
**Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself** **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,8 +1,8 @@
# Actix-web # 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` `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 `WSSubscription` is an Actor that supports WebSocket subscriptions.
## Request example ## Request example

View File

@ -35,4 +35,4 @@ cd benchmark
cargo bench cargo bench
``` ```
Now HTML report is available at `benchmark/target/criterion/report` Now a HTML report is available at `benchmark/target/criterion/report`.

View File

@ -18,9 +18,9 @@ impl Query {
} }
``` ```
Instead, the `#[derive(GQLMergedObject)]` macro allows you to split an object's resolvers across multiple file by merging 2 or more `#[Object]` implementations into one. Instead, the `#[derive(GQLMergedObject)]`/`#[MergedObject]` macro allows you to split an object's resolvers across multiple modules or files by merging 2 or more `#[Object]` implementations into one.
**Tip:** Every `#[Object]` needs a unique name even in a GQLMergedObject so make sure to give each object your merging it's own name. **Tip:** Every `#[Object]` needs a unique name, even in a `GQLMergedObject`, so make sure to give each object you're merging its own name.
**Note:** This works for queries and mutations. For subscriptions, see "Merging Subscriptions" below. **Note:** This works for queries and mutations. For subscriptions, see "Merging Subscriptions" below.
@ -51,7 +51,7 @@ let schema = Schema::new(
# Merging Subscriptions # Merging Subscriptions
Along with `GQLMergedObject`, you can derive `GQLMergedSubscription` to merge separate `#[Subscription]` blocks. Along with `GQLMergedObject`, you can derive `GQLMergedSubscription` or use `#[MergedSubscription]` to merge separate `#[Subscription]` blocks.
Like merging Objects, each subscription block requires a unique name. Like merging Objects, each subscription block requires a unique name.

View File

@ -1,6 +1,6 @@
# Two ways to define types # Two ways to define types
I think you have discovered that defining a GraphqlQL type can be use attribute macro or derive. I think you have discovered that GraphQL types can be defined using both an attribute macro and a derive.
The following is the corresponding table: The following is the corresponding table:
@ -14,7 +14,7 @@ The following is the corresponding table:
|Merged Object|MergedObject|GQLMergedObject| |Merged Object|MergedObject|GQLMergedObject|
|Merged Subscription|MergedSubscription|GQLMergedSubscription| |Merged Subscription|MergedSubscription|GQLMergedSubscription|
The advantage of attribute macro is that you can provide some parameters at the same time, for example The advantage of the attribute macro is that you can provide parameters at the same time, for example
```rust ```rust
#[SimpleObject(name = "ABC")] #[SimpleObject(name = "ABC")]
@ -23,7 +23,7 @@ struct MyObject {
} }
``` ```
**But it does not support conditional compilation**, for example: **However, attribute macros do not support conditional compilation**. The following does not work:
```rust ```rust
#[SimpleObject] #[SimpleObject]
@ -36,7 +36,7 @@ struct MyObject {
} }
``` ```
**Derive can support conditional compilation**, but it needs to provide parameters separately, for example: Deriving, on the other hand, does support conditional compilation, but as derive macros can't take parameters you need to provide them separately. For example:
```rust ```rust
#[derive(SimpleObject)] #[derive(SimpleObject)]
@ -46,4 +46,4 @@ struct MyObject {
} }
``` ```
_Which way to define the type is up to you, I prefer to use derive._ _Which way you use to define types is up to you, personally I prefer to use derive._

View File

@ -2,8 +2,7 @@
## Query root object ## Query root object
The query root object is a GraphQL object with a definition similar to other objects. resolver functions for all fields of the query object are executed concurrently. The query root object is a GraphQL object with a definition similar to other objects. Resolver functions for all fields of the query object are executed concurrently.
```rust ```rust
use async_graphql::*; use async_graphql::*;

View File

@ -4,10 +4,10 @@
```toml ```toml
[dependencies] [dependencies]
async-graphql = "1.11.0" async-graphql = "1.17.15"
async-graphql-actix-web = "1.3.0" # If you need to integrate into actix-web async-graphql-actix-web = "1.17.3" # If you need to integrate into actix-web
async-graphql-warp = "1.3.0" # If you need to integrate into warp async-graphql-warp = "1.17.3" # If you need to integrate into warp
async-graphql-tide = "1.2.0" # If you need to integrate into tide async-graphql-tide = "1.17.9" # If you need to integrate into tide
``` ```
## Write a Schema ## Write a Schema
@ -30,12 +30,11 @@ impl Query {
a + b a + b
} }
} }
``` ```
## Execute the query ## Execute the query
In our example, there is only Query without Mutation and Subscription, so we create the Schema with `EmptyMutation` and `EmptySubscription`, and then call `Schema::execute` to execute the Query. In our example, there is only a Query without a Mutation or Subscription, so we create the Schema with `EmptyMutation` and `EmptySubscription`, and then call `Schema::execute` to execute the Query.
```rust ```rust
let schema = Schema::new(Query, EmptyMutation, EmptySubscription); let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
@ -44,7 +43,7 @@ let res = schema.execute("{ add(a: 10, b: 20) }");
## Output the query results as JSON ## Output the query results as JSON
Query returns `async_graphql::Result` with `async_graphql::http::GQLResponse ` wrapped, can be directly converted to JSON. `Schema::execute` returns `async_graphql::Result` with `async_graphql::http::GQLResponse` wrapped, and it can be directly converted to JSON.
```rust ```rust
let json = serde_json::to_string(&async_graphql::http::GQLResponse(res)); let json = serde_json::to_string(&async_graphql::http::GQLResponse(res));
@ -52,4 +51,4 @@ let json = serde_json::to_string(&async_graphql::http::GQLResponse(res));
## Web server integration ## Web server integration
Please refer to https://github.com/async-graphql/examples. Please refer to <https://github.com/async-graphql/examples>.

View File

@ -1,8 +1,8 @@
# Subscription # Subscription
The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a Stream, and the field parameters are usually used as data filtering conditions. The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a [Stream](https://docs.rs/futures-core/~0.3/futures_core/stream/trait.Stream.html), 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 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 ```rust
use async_graphql::*; use async_graphql::*;

View File

@ -1,3 +1,3 @@
# Type System # Type System
`Async-graphql` implemented conversion from GraphQL Object to Rust struct, and it's easy to use. `Async-graphql` implements conversions from GraphQL Objects to Rust structs, and it's easy to use.

View File

@ -12,14 +12,11 @@
</style> </style>
</head> </head>
<body> <body>
<h1>Async-graphql Book</h1>
<div> <p>This book is available in multiple languages:</p>
<a href="en/index.html">English</a> <ul>
</div> <li><a href="en/index.html">English</a></li>
<li><a href="zh-CN/index.html">简体中文</a></li>
<div> </ul>
<a href="zh-CN/index.html">简体中文</a>
</div>
</body> </body>
</html> </html>

View File

@ -8,7 +8,7 @@ use async_graphql_parser::query::Document;
use async_graphql_parser::UploadValue; use async_graphql_parser::UploadValue;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use serde::ser::SerializeSeq; use serde::ser::SerializeSeq;
use serde::Serializer; use serde::{Serialize, Serializer};
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};

View File

@ -1,13 +1,14 @@
use crate::{Pos, QueryPathNode, Value}; use crate::{Pos, QueryPathNode, Value};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use thiserror::Error;
/// Input Value Error /// An error in the format of an input value.
#[derive(Debug)] #[derive(Debug)]
pub enum InputValueError { pub enum InputValueError {
/// Custom input value parsing error. /// Custom input value parsing error.
Custom(String), Custom(String),
/// The type of input value does not match the expectation. /// The type of input value does not match the expectation. Contains the value that was found.
ExpectedType(Value), ExpectedType(Value),
} }
@ -18,7 +19,7 @@ impl<T: Display> From<T> for InputValueError {
} }
impl InputValueError { impl InputValueError {
#[allow(missing_docs)] /// Convert this error to a regular `Error` type.
pub fn into_error(self, pos: Pos, expected_type: String) -> Error { pub fn into_error(self, pos: Pos, expected_type: String) -> Error {
match self { match self {
InputValueError::Custom(reason) => Error::Query { InputValueError::Custom(reason) => Error::Query {
@ -38,10 +39,10 @@ impl InputValueError {
} }
} }
/// InputValueResult type /// An alias for `Result<T, InputValueError>`.
pub type InputValueResult<T> = std::result::Result<T, InputValueError>; pub type InputValueResult<T> = std::result::Result<T, InputValueError>;
/// FieldError type /// An error in a field resolver.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FieldError(pub String, pub Option<serde_json::Value>); pub struct FieldError(pub String, pub Option<serde_json::Value>);
@ -71,23 +72,17 @@ impl FieldError {
} }
} }
/// FieldResult type /// An alias for `Result<T, InputValueError>`.
pub type FieldResult<T> = std::result::Result<T, FieldError>; pub type FieldResult<T> = std::result::Result<T, FieldError>;
impl<E> From<E> for FieldError impl<E: Display> From<E> for FieldError {
where
E: std::fmt::Display + Send + Sync + 'static,
{
fn from(err: E) -> Self { fn from(err: E) -> Self {
FieldError(format!("{}", err), None) FieldError(format!("{}", err), None)
} }
} }
#[allow(missing_docs)] #[allow(missing_docs)]
pub trait ErrorExtensions pub trait ErrorExtensions: Sized {
where
Self: Sized,
{
fn extend(&self) -> FieldError; fn extend(&self) -> FieldError;
fn extend_with<C>(self, cb: C) -> FieldError fn extend_with<C>(self, cb: C) -> FieldError
where where
@ -125,15 +120,14 @@ impl<E: std::fmt::Display> ErrorExtensions for &E {
} }
} }
#[allow(missing_docs)] /// Extend a `Result`'s error value with [`ErrorExtensions`](trait.ErrorExtensions.html).
pub trait ResultExt<T, E> pub trait ResultExt<T, E>: Sized {
where /// Extend the error value of the result with the callback.
Self: Sized, fn extend_err<C>(self, cb: C) -> FieldResult<T>
{
fn extend_err<CB>(self, cb: CB) -> FieldResult<T>
where where
CB: FnOnce(&E) -> serde_json::Value; C: FnOnce(&E) -> serde_json::Value;
/// Extend the result to a `FieldResult`.
fn extend(self) -> FieldResult<T>; fn extend(self) -> FieldResult<T>;
} }
@ -161,25 +155,31 @@ where
} }
} }
/// Error for query /// An error processing a GraphQL query.
#[derive(Debug, Error, PartialEq)] #[derive(Debug, Error, PartialEq)]
#[allow(missing_docs)]
pub enum QueryError { pub enum QueryError {
/// The feature is not supported.
#[error("Not supported.")] #[error("Not supported.")]
NotSupported, NotSupported,
/// The actual input type did not match the expected input type.
#[error("Expected input type \"{expect}\", found {actual}.")] #[error("Expected input type \"{expect}\", found {actual}.")]
ExpectedInputType { ExpectedInputType {
/// Expect input type /// The expected input type.
expect: String, expect: String,
/// Actual input type /// The actual input type.
actual: Value, actual: Value,
}, },
/// Parsing of an input value failed.
#[error("Failed to parse input value: {reason}")] #[error("Failed to parse input value: {reason}")]
ParseInputValue { reason: String }, ParseInputValue {
/// The reason for the failure to resolve.
reason: String,
},
/// A field was not found on an object type.
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")] #[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
FieldNotFound { FieldNotFound {
/// Field name /// Field name
@ -189,27 +189,33 @@ pub enum QueryError {
object: String, object: String,
}, },
/// An operation was missing from the query.
#[error("Missing operation")] #[error("Missing operation")]
MissingOperation, MissingOperation,
/// The operation name was unknown.
#[error("Unknown operation named \"{name}\"")] #[error("Unknown operation named \"{name}\"")]
UnknownOperationNamed { UnknownOperationNamed {
/// Operation name for query /// Operation name for query.
name: String, name: String,
}, },
/// The user attempted to query an object without selecting any subfields.
#[error("Type \"{object}\" must have a selection of subfields.")] #[error("Type \"{object}\" must have a selection of subfields.")]
MustHaveSubFields { MustHaveSubFields {
/// Object name /// Object name
object: String, object: String,
}, },
/// The schema does not have mutations.
#[error("Schema is not configured for mutations.")] #[error("Schema is not configured for mutations.")]
NotConfiguredMutations, NotConfiguredMutations,
/// The schema does not have subscriptions.
#[error("Schema is not configured for subscriptions.")] #[error("Schema is not configured for subscriptions.")]
NotConfiguredSubscriptions, NotConfiguredSubscriptions,
/// The value does not exist in the enum.
#[error("Invalid value for enum \"{ty}\".")] #[error("Invalid value for enum \"{ty}\".")]
InvalidEnumValue { InvalidEnumValue {
/// Enum type name /// Enum type name
@ -219,21 +225,24 @@ pub enum QueryError {
value: String, value: String,
}, },
/// A required field in an input object was not present.
#[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")] #[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")]
RequiredField { RequiredField {
/// field name /// Field name
field_name: String, field_name: String,
/// object name /// Object name
object: &'static str, object: &'static str,
}, },
/// A variable is used but not defined.
#[error("Variable \"${var_name}\" is not defined")] #[error("Variable \"${var_name}\" is not defined")]
VarNotDefined { VarNotDefined {
/// Variable name /// Variable name
var_name: String, var_name: String,
}, },
/// A directive was required but not provided.
#[error( #[error(
"Directive \"{directive}\" argument \"{arg_name}\" of type \"{arg_type}\" is required, but it was not provided." "Directive \"{directive}\" argument \"{arg_name}\" of type \"{arg_type}\" is required, but it was not provided."
)] )]
@ -248,39 +257,50 @@ pub enum QueryError {
arg_type: &'static str, arg_type: &'static str,
}, },
/// An unknown directive name was encountered.
#[error("Unknown directive \"{name}\".")] #[error("Unknown directive \"{name}\".")]
UnknownDirective { UnknownDirective {
/// Directive name /// Directive name
name: String, name: String,
}, },
/// An unknown fragment was encountered.
#[error("Unknown fragment \"{name}\".")] #[error("Unknown fragment \"{name}\".")]
UnknownFragment { UnknownFragment {
// Fragment name /// Fragment name
name: String, name: String,
}, },
/// The query was too complex.
// TODO: Expand on this
#[error("Too complex")] #[error("Too complex")]
TooComplex, TooComplex,
/// The query was nested too deep.
#[error("Too deep")] #[error("Too deep")]
TooDeep, TooDeep,
/// A field handler errored.
#[error("Failed to resolve field: {err}")] #[error("Failed to resolve field: {err}")]
FieldError { FieldError {
/// The error description.
err: String, err: String,
/// Extensions to the error provided through the [`ErrorExtensions`](trait.ErrorExtensions)
/// or [`ResultExt`](trait.ResultExt) traits.
extended_error: Option<serde_json::Value>, extended_error: Option<serde_json::Value>,
}, },
/// Entity not found.
#[error("Entity not found")] #[error("Entity not found")]
EntityNotFound, EntityNotFound,
/// "__typename" must be an existing string.
#[error("\"__typename\" must be an existing string")] #[error("\"__typename\" must be an existing string")]
TypeNameNotExists, TypeNameNotExists,
} }
impl QueryError { impl QueryError {
#[doc(hidden)] /// Convert this error to a regular `Error` type.
pub fn into_error(self, pos: Pos) -> Error { pub fn into_error(self, pos: Pos) -> Error {
Error::Query { Error::Query {
pos, pos,
@ -290,57 +310,80 @@ impl QueryError {
} }
} }
#[allow(missing_docs)] /// An error parsing the request.
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ParseRequestError { pub enum ParseRequestError {
/// An IO error occurred.
#[error("{0}")] #[error("{0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
/// The request's syntax was invalid.
#[error("Invalid request: {0}")] #[error("Invalid request: {0}")]
InvalidRequest(serde_json::Error), InvalidRequest(serde_json::Error),
/// The request's files map was invalid.
#[error("Invalid files map: {0}")] #[error("Invalid files map: {0}")]
InvalidFilesMap(serde_json::Error), InvalidFilesMap(serde_json::Error),
/// The request's multipart data was invalid.
#[error("Invalid multipart data")] #[error("Invalid multipart data")]
InvalidMultipart(multer::Error), InvalidMultipart(multer::Error),
/// Missing "operators" part for multipart request.
#[error("Missing \"operators\" part")] #[error("Missing \"operators\" part")]
MissingOperatorsPart, MissingOperatorsPart,
/// Missing "map" part for multipart request.
#[error("Missing \"map\" part")] #[error("Missing \"map\" part")]
MissingMapPart, MissingMapPart,
/// It's not an upload operation
#[error("It's not an upload operation")] #[error("It's not an upload operation")]
NotUpload, NotUpload,
/// Files were missing the request.
#[error("Missing files")] #[error("Missing files")]
MissingFiles, MissingFiles,
/// The request's payload is too large, and this server rejected it.
#[error("Payload too large")] #[error("Payload too large")]
PayloadTooLarge, PayloadTooLarge,
} }
#[allow(missing_docs)] /// Verification error.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct RuleError { pub struct RuleError {
/// Location of this error in query string.
pub locations: Vec<Pos>, pub locations: Vec<Pos>,
/// A description of this error.
pub message: String, pub message: String,
} }
#[allow(missing_docs)] /// An error serving a GraphQL query.
#[derive(Debug, Error, PartialEq)] #[derive(Debug, Error, PartialEq)]
pub enum Error { pub enum Error {
/// Parsing the query failed.
#[error("Parse error: {0}")] #[error("Parse error: {0}")]
Parse(#[from] crate::parser::Error), Parse(#[from] crate::parser::Error),
/// Processing the query failed.
#[error("Query error: {err}")] #[error("Query error: {err}")]
Query { Query {
/// The position at which the processing failed.
pos: Pos, pos: Pos,
/// Node path.
path: Option<serde_json::Value>, path: Option<serde_json::Value>,
/// The query error.
err: QueryError, err: QueryError,
}, },
/// The query statement verification failed.
#[error("Rule error")] #[error("Rule error")]
Rule { errors: Vec<RuleError> }, Rule {
/// List of errors.
errors: Vec<RuleError>,
},
} }

View File

@ -2,6 +2,7 @@ use crate::extensions::{Extension, ResolveInfo};
use crate::{Error, Variables}; use crate::{Error, Variables};
use async_graphql_parser::query::{Definition, Document, OperationDefinition, Selection}; use async_graphql_parser::query::{Definition, Document, OperationDefinition, Selection};
use itertools::Itertools; use itertools::Itertools;
use log::{error, info, trace};
use std::borrow::Cow; use std::borrow::Cow;
use uuid::Uuid; use uuid::Uuid;

View File

@ -1,25 +1,42 @@
use crate::extensions::{Extension, ResolveInfo}; use crate::extensions::{Extension, ResolveInfo};
use crate::{QueryPathSegment, Variables}; use crate::Variables;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use tracing::{span, Id, Level}; use tracing::{event, span, Id, Level};
use uuid::Uuid;
/// Tracing extension /// Tracing extension
/// ///
/// # References /// # References
/// ///
/// https://crates.io/crates/tracing /// https://crates.io/crates/tracing
#[derive(Default)]
pub struct Tracing { pub struct Tracing {
root_id: Option<Id>, root_id: Option<Id>,
fields: BTreeMap<usize, Id>, fields: BTreeMap<usize, Id>,
} }
impl Extension for Tracing { impl Extension for Tracing {
fn parse_start(&mut self, query_source: &str, _variables: &Variables) { #[allow(clippy::deref_addrof)]
let root_span = span!(target: "async-graphql", parent:None, Level::INFO, "query", source = query_source); fn parse_start(&mut self, query_source: &str, variables: &Variables) {
let root_span: tracing::Span = span!(
target: "async_graphql::graphql",
parent:None,
Level::INFO,
"graphql",
id = %Uuid::new_v4().to_string(),
);
if let Some(id) = root_span.id() { if let Some(id) = root_span.id() {
tracing::dispatcher::get_default(|d| d.enter(&id)); tracing::dispatcher::get_default(|d| d.enter(&id));
self.root_id.replace(id); self.root_id.replace(id);
} }
event!(
target: "async_graphql::query",
Level::DEBUG,
%variables,
query = %query_source
);
} }
fn execution_end(&mut self) { fn execution_end(&mut self) {
@ -33,27 +50,15 @@ impl Extension for Tracing {
.resolve_id .resolve_id
.parent .parent
.and_then(|id| self.fields.get(&id)) .and_then(|id| self.fields.get(&id))
.or_else(|| self.root_id.as_ref())
.cloned(); .cloned();
let span = match &info.path_node.segment { let span = span!(
QueryPathSegment::Index(idx) => span!( target: "async_graphql::field",
target: "async-graphql", parent: parent_span,
parent: parent_span, Level::INFO,
Level::INFO, "field",
"field", path = %info.path_node,
index = *idx, );
parent_type = info.parent_type,
return_type = info.return_type
),
QueryPathSegment::Name(name) => span!(
target: "async-graphql",
parent: parent_span,
Level::INFO,
"field",
name = name,
parent_type = info.parent_type,
return_type = info.return_type
),
};
if let Some(id) = span.id() { if let Some(id) = span.id() {
tracing::dispatcher::get_default(|d| d.enter(&id)); tracing::dispatcher::get_default(|d| d.enter(&id));
self.fields.insert(info.resolve_id.current, id); self.fields.insert(info.resolve_id.current, id);

View File

@ -5,14 +5,16 @@ use serde::export::PhantomData;
/// Field guard /// Field guard
/// ///
/// Guard is a pre-condition for a field that is resolved if `Ok(()` is returned, otherwise an error is returned. /// Guard is a pre-condition for a field that is resolved if `Ok(())` is returned, otherwise an error is returned.
///
/// This trait is defined through the [`async-trait`](https://crates.io/crates/async-trait) macro.
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Guard { pub trait Guard {
#[allow(missing_docs)] /// Check whether the guard will allow access to the field.
async fn check(&self, ctx: &Context<'_>) -> FieldResult<()>; async fn check(&self, ctx: &Context<'_>) -> FieldResult<()>;
} }
/// An extension trait for `Guard` /// An extension trait for `Guard`.
pub trait GuardExt: Guard + Sized { pub trait GuardExt: Guard + Sized {
/// Merge the two guards. /// Merge the two guards.
fn and<R: Guard>(self, other: R) -> And<Self, R> { fn and<R: Guard>(self, other: R) -> And<Self, R> {
@ -22,7 +24,7 @@ pub trait GuardExt: Guard + Sized {
impl<T: Guard> GuardExt for T {} impl<T: Guard> GuardExt for T {}
/// Guard for `GuardExt::and` /// Guard for [`GuardExt::and`](trait.GuardExt.html#method.and).
pub struct And<A: Guard, B: Guard>(A, B); pub struct And<A: Guard, B: Guard>(A, B);
#[async_trait::async_trait] #[async_trait::async_trait]
@ -35,10 +37,12 @@ impl<A: Guard + Send + Sync, B: Guard + Send + Sync> Guard for And<A, B> {
/// Field post guard /// Field post guard
/// ///
/// Guard is a post-condition for a field that is resolved if `Ok(()` is returned, otherwise an error is returned. /// This is a post-condition for a field that is resolved if `Ok(()` is returned, otherwise an error is returned.
///
/// This trait is defined through the [`async-trait`](https://crates.io/crates/async-trait) macro.
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait PostGuard<T: Send + Sync> { pub trait PostGuard<T: Send + Sync> {
#[allow(missing_docs)] /// Check whether to allow the result of the field through.
async fn check(&self, ctx: &Context<'_>, result: &T) -> FieldResult<()>; async fn check(&self, ctx: &Context<'_>, result: &T) -> FieldResult<()>;
} }
@ -52,7 +56,7 @@ pub trait PostGuardExt<T: Send + Sync>: PostGuard<T> + Sized {
impl<T: PostGuard<R>, R: Send + Sync> PostGuardExt<R> for T {} impl<T: PostGuard<R>, R: Send + Sync> PostGuardExt<R> for T {}
/// PostGuard for `PostGuardExt<T>::and` /// PostGuard for [`PostGuardExt<T>::and`](trait.PostGuardExt.html#method.and).
pub struct PostAnd<T: Send + Sync, A: PostGuard<T>, B: PostGuard<T>>(A, B, PhantomData<T>); pub struct PostAnd<T: Send + Sync, A: PostGuard<T>, B: PostGuard<T>>(A, B, PhantomData<T>);
#[async_trait::async_trait] #[async_trait::async_trait]

View File

@ -18,7 +18,7 @@ use crate::{
Error, ParseRequestError, Pos, QueryBuilder, QueryError, QueryResponse, Result, Variables, Error, ParseRequestError, Pos, QueryBuilder, QueryError, QueryResponse, Result, Variables,
}; };
use serde::ser::{SerializeMap, SerializeSeq}; use serde::ser::{SerializeMap, SerializeSeq};
use serde::{Serialize, Serializer}; use serde::{Deserialize, Serialize, Serializer};
/// Deserializable GraphQL Request object /// Deserializable GraphQL Request object
#[derive(Deserialize, Clone, PartialEq, Debug)] #[derive(Deserialize, Clone, PartialEq, Debug)]
@ -241,7 +241,7 @@ mod tests {
}, },
}; };
let resp = GQLResponse(Err(err.into())); let resp = GQLResponse(Err(err));
assert_eq!( assert_eq!(
serde_json::to_value(resp).unwrap(), serde_json::to_value(resp).unwrap(),

View File

@ -1,3 +1,4 @@
use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;
/// Generate the page for GraphQL Playground /// Generate the page for GraphQL Playground

View File

@ -40,7 +40,7 @@
//! * Custom scalars //! * Custom scalars
//! * Minimal overhead //! * Minimal overhead
//! * Easy integration (hyper, actix_web, tide ...) //! * Easy integration (hyper, actix_web, tide ...)
//! * Upload files (Multipart request) //! * File upload (Multipart request)
//! * Subscriptions (WebSocket transport) //! * Subscriptions (WebSocket transport)
//! * Custom extensions //! * Custom extensions
//! * Apollo Tracing extension //! * Apollo Tracing extension
@ -89,7 +89,7 @@
//! cargo bench //! cargo bench
//! ``` //! ```
//! //!
//! Now HTML report is available at `benchmark/target/criterion/report` //! Now a HTML report is available at `benchmark/target/criterion/report`.
//! //!
#![warn(missing_docs)] #![warn(missing_docs)]
@ -98,13 +98,6 @@
#![recursion_limit = "256"] #![recursion_limit = "256"]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#[macro_use]
extern crate thiserror;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
mod base; mod base;
mod context; mod context;
mod error; mod error;
@ -182,7 +175,11 @@ pub use subscription::SubscriptionType;
#[doc(hidden)] #[doc(hidden)]
pub use types::{EnumItem, EnumType}; pub use types::{EnumItem, EnumType};
/// Define a GraphQL object /// Define a GraphQL object with methods
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_complex_object.html).*
///
/// All methods are converted to camelCase.
/// ///
/// # Macro parameters /// # Macro parameters
/// ///
@ -218,16 +215,17 @@ pub use types::{EnumItem, EnumType};
/// | default_with | Expression to generate default value | code string | Y | /// | default_with | Expression to generate default value | code string | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y | /// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// ///
/// # The field returns the value type /// # Valid field return types
/// ///
/// - A scalar value, such as `i32`, `bool` /// - Scalar values, such as `i32` and `bool`. `usize`, `isize`, `u128` and `i128` are not
/// - Borrowing of scalar values, such as `&i32`, `&bool` /// supported
/// - Vec<T>, such as `Vec<i32>` /// - `Vec<T>`, such as `Vec<i32>`
/// - Slice<T>, such as `&[i32]` /// - Slices, such as `&[i32]`
/// - Option<T>, such as `Option<i32>` /// - `Option<T>`, such as `Option<i32>`
/// - Object and &Object /// - GraphQL objects.
/// - Enum /// - GraphQL enums.
/// - FieldResult<T, E>, such as `FieldResult<i32, E>` /// - References to any of the above types, such as `&i32` or `&Option<String>`.
/// - `FieldResult<T, E>`, such as `FieldResult<i32, E>`
/// ///
/// # Context /// # Context
/// ///
@ -272,7 +270,7 @@ pub use types::{EnumItem, EnumType};
/// } /// }
/// ///
/// async_std::task::block_on(async move { /// async_std::task::block_on(async move {
/// let schema = Schema::new(QueryRoot{ value: 10 }, EmptyMutation, EmptySubscription); /// let schema = Schema::new(QueryRoot { value: 10 }, EmptyMutation, EmptySubscription);
/// let res = schema.execute(r#"{ /// let res = schema.execute(r#"{
/// value /// value
/// valueRef /// valueRef
@ -291,9 +289,13 @@ pub use types::{EnumItem, EnumType};
/// ``` /// ```
pub use async_graphql_derive::Object; pub use async_graphql_derive::Object;
/// Define a GraphQL object /// Define a GraphQL object with fields
/// ///
/// Similar to `Object`, but defined on a structure that automatically generates getters for all fields. /// You can also [derive this](derive.GQLSimpleObject.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html).*
///
/// Similar to `Object`, but defined on a structure that automatically generates getters for all fields. For a list of valid field types, see [`Object`](attr.Object.html). All fields are converted to camelCase.
/// ///
/// # Macro parameters /// # Macro parameters
/// ///
@ -340,6 +342,12 @@ pub use async_graphql_derive::SimpleObject;
/// Derive a GraphQL enum /// Derive a GraphQL enum
/// ///
/// You can also [use an attribute](attr.Enum.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_enum.html).*
///
/// All variants are converted to SCREAMING_SNAKE_CASE.
///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
@ -356,6 +364,10 @@ pub use async_graphql_derive::GQLEnum;
/// Derive a GraphQL input object /// Derive a GraphQL input object
/// ///
/// You can also [use an attribute](attr.InputObject.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_input_object.html).*
///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
@ -368,7 +380,11 @@ pub use async_graphql_derive::GQLEnum;
/// ``` /// ```
pub use async_graphql_derive::GQLInputObject; pub use async_graphql_derive::GQLInputObject;
/// Derive a GraphQL simple object /// Derive a GraphQL object with fields
///
/// You can also [use an attribute](attr.SimpleObject.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html).*
/// ///
/// # Examples /// # Examples
/// ///
@ -384,6 +400,10 @@ pub use async_graphql_derive::GQLSimpleObject;
/// Define a GraphQL enum /// Define a GraphQL enum
/// ///
/// You can also [derive this](derive.GQLEnum.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_enum.html).*
///
/// # Macro parameters /// # Macro parameters
/// ///
/// | Attribute | description | Type | Optional | /// | Attribute | description | Type | Optional |
@ -438,6 +458,10 @@ pub use async_graphql_derive::Enum;
/// Define a GraphQL input object /// Define a GraphQL input object
/// ///
/// You can also [derive this](derive.GQLInputObject.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_input_object.html).*
///
/// # Macro parameters /// # Macro parameters
/// ///
/// | Attribute | description | Type | Optional | /// | Attribute | description | Type | Optional |
@ -492,6 +516,10 @@ pub use async_graphql_derive::InputObject;
/// Define a GraphQL interface /// Define a GraphQL interface
/// ///
/// You can also [derive this](derive.GQLInterface.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_interface.html).*
///
/// # Macro parameters /// # Macro parameters
/// ///
/// | Attribute | description | Type | Optional | /// | Attribute | description | Type | Optional |
@ -616,10 +644,17 @@ pub use async_graphql_derive::InputObject;
pub use async_graphql_derive::Interface; pub use async_graphql_derive::Interface;
/// Derive a GraphQL interface /// Derive a GraphQL interface
///
/// You can also [use an attribute](attr.Interface.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_interface.html).*
pub use async_graphql_derive::GQLInterface; pub use async_graphql_derive::GQLInterface;
/// Define a GraphQL union /// Define a GraphQL union
/// ///
/// You can also [derive this](derive.GQLUnion.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_union.html).*
/// ///
/// # Macro parameters /// # Macro parameters
/// ///
@ -684,10 +719,16 @@ pub use async_graphql_derive::GQLInterface;
pub use async_graphql_derive::Union; pub use async_graphql_derive::Union;
/// Derive a GraphQL union /// Derive a GraphQL union
///
/// You can also [use an attribute](attr.Union.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_union.html).*
pub use async_graphql_derive::GQLUnion; pub use async_graphql_derive::GQLUnion;
/// Define a GraphQL subscription /// Define a GraphQL subscription
/// ///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/subscription.html).*
///
/// The field function is a synchronization function that performs filtering. When true is returned, the message is pushed to the client. /// The field function is a synchronization function that performs filtering. When true is returned, the message is pushed to the client.
/// The second parameter is the type of the field. /// The second parameter is the type of the field.
/// Starting with the third parameter is one or more filtering conditions, The filter condition is the parameter of the field. /// Starting with the third parameter is one or more filtering conditions, The filter condition is the parameter of the field.
@ -756,6 +797,10 @@ pub use async_graphql_derive::Scalar;
/// Define a merged object with multiple object types. /// Define a merged object with multiple object types.
/// ///
/// You can also [derive this](derive.GQLMergedObject.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*
///
/// # Macro parameters /// # Macro parameters
/// ///
/// | Attribute | description | Type | Optional | /// | Attribute | description | Type | Optional |
@ -793,10 +838,18 @@ pub use async_graphql_derive::Scalar;
pub use async_graphql_derive::MergedObject; pub use async_graphql_derive::MergedObject;
/// Derive a GraphQL Merged object /// Derive a GraphQL Merged object
///
/// You can also [use an attribute](attr.MergedObject.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*
pub use async_graphql_derive::GQLMergedObject; pub use async_graphql_derive::GQLMergedObject;
/// Define a merged subscription with multiple subscription types. /// Define a merged subscription with multiple subscription types.
/// ///
/// You can also [derive this](derive.GQLMergedSubscription.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*
///
/// # Macro parameters /// # Macro parameters
/// ///
/// | Attribute | description | Type | Optional | /// | Attribute | description | Type | Optional |
@ -835,5 +888,9 @@ pub use async_graphql_derive::GQLMergedObject;
/// ``` /// ```
pub use async_graphql_derive::MergedSubscription; pub use async_graphql_derive::MergedSubscription;
/// Derive a GraphQL Merged object /// Derive a GraphQL merged subscription with multiple subscription types.
///
/// You can also [use an attribute](attr.MergedSubscription.html).
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*
pub use async_graphql_derive::GQLMergedSubscription; pub use async_graphql_derive::GQLMergedSubscription;

View File

@ -1,15 +1,32 @@
use crate::{InputValueError, InputValueResult, ScalarType, Value}; use crate::{InputValueError, InputValueResult, ScalarType, Value};
use async_graphql_derive::Scalar; use async_graphql_derive::Scalar;
use chrono::{DateTime, FixedOffset, Utc}; use chrono::{DateTime, FixedOffset, Local, Utc};
/// Implement the DateTime<FixedOffset> scalar /// Implement the DateTime<FixedOffset> scalar
/// ///
/// The input/output is a string in RFC3339 format. /// The input/output is a string in RFC3339 format.
#[Scalar(internal, name = "DateTimeFixedOffset")] #[Scalar(internal, name = "DateTime")]
impl ScalarType for DateTime<FixedOffset> { impl ScalarType for DateTime<FixedOffset> {
fn parse(value: Value) -> InputValueResult<Self> { fn parse(value: Value) -> InputValueResult<Self> {
match &value { match &value {
Value::String(s) => Ok(DateTime::parse_from_rfc3339(s)?), Value::String(s) => Ok(s.parse::<DateTime<FixedOffset>>()?),
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn to_value(&self) -> Value {
Value::String(self.to_rfc3339())
}
}
/// Implement the DateTime<Local> scalar
///
/// The input/output is a string in RFC3339 format.
#[Scalar(internal, name = "DateTime")]
impl ScalarType for DateTime<Local> {
fn parse(value: Value) -> InputValueResult<Self> {
match &value {
Value::String(s) => Ok(s.parse::<DateTime<Local>>()?),
_ => Err(InputValueError::ExpectedType(value)), _ => Err(InputValueError::ExpectedType(value)),
} }
} }
@ -22,7 +39,7 @@ impl ScalarType for DateTime<FixedOffset> {
/// Implement the DateTime<Utc> scalar /// Implement the DateTime<Utc> scalar
/// ///
/// The input/output is a string in RFC3339 format. /// The input/output is a string in RFC3339 format.
#[Scalar(internal, name = "DateTimeUtc")] #[Scalar(internal, name = "DateTime")]
impl ScalarType for DateTime<Utc> { impl ScalarType for DateTime<Utc> {
fn parse(value: Value) -> InputValueResult<Self> { fn parse(value: Value) -> InputValueResult<Self> {
match &value { match &value {

View File

@ -1,35 +1,52 @@
use crate::{InputValueError, InputValueResult, ScalarType, Value}; use crate::{InputValueError, InputValueResult, ScalarType, Value};
use async_graphql_derive::Scalar; use async_graphql_derive::Scalar;
macro_rules! float_scalar { /// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
($($ty:ty),*) => { #[Scalar(internal, name = "Float")]
$( impl ScalarType for f32 {
/// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). fn parse(value: Value) -> InputValueResult<Self> {
#[Scalar(internal, name = "Float")] match value {
impl ScalarType for $ty { Value::Number(n) => Ok(n
fn parse(value: Value) -> InputValueResult<Self> { .as_f64()
match value { .ok_or_else(|| InputValueError::from("Invalid number"))?
Value::Number(n) => Ok(n as Self),
.as_f64() _ => Err(InputValueError::ExpectedType(value)),
.ok_or_else(|| InputValueError::from("Invalid number"))?
as Self),
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(_) => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from_f64(*self as f64).unwrap())
}
} }
)* }
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(_) => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from_f64(*self as f64).unwrap())
} }
} }
float_scalar!(f32, f64); /// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
#[Scalar(internal, name = "Float")]
impl ScalarType for f64 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => Ok(n
.as_f64()
.ok_or_else(|| InputValueError::from("Invalid number"))?
as Self),
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(_) => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from_f64(*self as f64).unwrap())
}
}

View File

@ -1,85 +1,274 @@
use crate::{InputValueError, InputValueResult, ScalarType, Value}; use crate::{InputValueError, InputValueResult, ScalarType, Value};
use async_graphql_derive::Scalar; use async_graphql_derive::Scalar;
macro_rules! int_scalar { /// The `Int` scalar type represents non-fractional whole numeric values.
($($ty:ty),*) => { #[Scalar(internal, name = "Int")]
$( impl ScalarType for i8 {
/// The `Int` scalar type represents non-fractional whole numeric values. fn parse(value: Value) -> InputValueResult<Self> {
#[Scalar(internal, name = "Int")] match value {
impl ScalarType for $ty { Value::Number(n) => {
fn parse(value: Value) -> InputValueResult<Self> { let n = n
match value { .as_i64()
Value::Number(n) => { .ok_or_else(|| InputValueError::from("Invalid number"))?;
let n = n if n < Self::MIN as i64 || n > Self::MAX as i64 {
.as_i64() return Err(InputValueError::from(format!(
.ok_or_else(|| InputValueError::from("Invalid number"))?; "Only integers from {} to {} are accepted.",
if n < Self::MIN as i64 || n > Self::MAX as i64 { Self::MIN,
return Err(InputValueError::from(format!( Self::MAX
"Only integers from {} to {} are accepted.", )));
Self::MIN,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
} }
Ok(n as Self)
} }
_ => Err(InputValueError::ExpectedType(value)),
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_i64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as i64))
}
} }
)* }
};
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_i64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as i64))
}
} }
macro_rules! uint_scalar { /// The `Int` scalar type represents non-fractional whole numeric values.
($($ty:ty),*) => { #[Scalar(internal, name = "Int")]
$( impl ScalarType for i16 {
/// The `Int` scalar type represents non-fractional whole numeric values. fn parse(value: Value) -> InputValueResult<Self> {
#[Scalar(internal, name = "Int")] match value {
impl ScalarType for $ty { Value::Number(n) => {
fn parse(value: Value) -> InputValueResult<Self> { let n = n
match value { .as_i64()
Value::Number(n) => { .ok_or_else(|| InputValueError::from("Invalid number"))?;
let n = n if n < Self::MIN as i64 || n > Self::MAX as i64 {
.as_u64() return Err(InputValueError::from(format!(
.ok_or_else(|| InputValueError::from("Invalid number"))?; "Only integers from {} to {} are accepted.",
if n > Self::MAX as u64 { Self::MIN,
return Err(InputValueError::from(format!( Self::MAX
"Only integers from {} to {} are accepted.", )));
0,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
} }
Ok(n as Self)
} }
_ => Err(InputValueError::ExpectedType(value)),
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_u64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as u64))
}
} }
)* }
};
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_i64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as i64))
}
} }
int_scalar!(i8, i16, i32, i64); /// The `Int` scalar type represents non-fractional whole numeric values.
uint_scalar!(u8, u16, u32, u64); #[Scalar(internal, name = "Int")]
impl ScalarType for i32 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => {
let n = n
.as_i64()
.ok_or_else(|| InputValueError::from("Invalid number"))?;
if n < Self::MIN as i64 || n > Self::MAX as i64 {
return Err(InputValueError::from(format!(
"Only integers from {} to {} are accepted.",
Self::MIN,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_i64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as i64))
}
}
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for i64 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => {
let n = n
.as_i64()
.ok_or_else(|| InputValueError::from("Invalid number"))?;
if n < Self::MIN as i64 || n > Self::MAX as i64 {
return Err(InputValueError::from(format!(
"Only integers from {} to {} are accepted.",
Self::MIN,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_i64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as i64))
}
}
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for u8 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => {
let n = n
.as_u64()
.ok_or_else(|| InputValueError::from("Invalid number"))?;
if n > Self::MAX as u64 {
return Err(InputValueError::from(format!(
"Only integers from {} to {} are accepted.",
0,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_u64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as u64))
}
}
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for u16 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => {
let n = n
.as_u64()
.ok_or_else(|| InputValueError::from("Invalid number"))?;
if n > Self::MAX as u64 {
return Err(InputValueError::from(format!(
"Only integers from {} to {} are accepted.",
0,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_u64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as u64))
}
}
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for u32 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => {
let n = n
.as_u64()
.ok_or_else(|| InputValueError::from("Invalid number"))?;
if n > Self::MAX as u64 {
return Err(InputValueError::from(format!(
"Only integers from {} to {} are accepted.",
0,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_u64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as u64))
}
}
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for u64 {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Number(n) => {
let n = n
.as_u64()
.ok_or_else(|| InputValueError::from("Invalid number"))?;
if n > Self::MAX as u64 {
return Err(InputValueError::from(format!(
"Only integers from {} to {} are accepted.",
0,
Self::MAX
)));
}
Ok(n as Self)
}
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Number(n) if n.is_u64() => true,
_ => false,
}
}
fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from(*self as u64))
}
}

View File

@ -6,7 +6,7 @@ use crate::{
use async_graphql_derive::Scalar; use async_graphql_derive::Scalar;
use async_graphql_parser::query::Field; use async_graphql_parser::query::Field;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -100,6 +100,7 @@ impl<T: Serialize + Send + Sync> OutputValueType for OutputJson<T> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::*; use crate::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[async_std::test] #[async_std::test]

View File

@ -25,7 +25,7 @@ mod tests {
use super::ID; use super::ID;
use crate::Type; use crate::Type;
use bson::oid::ObjectId; use bson::oid::ObjectId;
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime, Utc}; use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveTime, Utc};
use uuid::Uuid; use uuid::Uuid;
#[test] #[test]
@ -54,19 +54,22 @@ mod tests {
assert_eq!(<NaiveTime as Type>::type_name(), "NaiveTime"); assert_eq!(<NaiveTime as Type>::type_name(), "NaiveTime");
assert_eq!(<NaiveTime as Type>::qualified_type_name(), "NaiveTime!"); assert_eq!(<NaiveTime as Type>::qualified_type_name(), "NaiveTime!");
assert_eq!(<DateTime::<Utc> as Type>::type_name(), "DateTimeUtc"); assert_eq!(<DateTime::<Utc> as Type>::type_name(), "DateTime");
assert_eq!( assert_eq!(
<DateTime::<Utc> as Type>::qualified_type_name(), <DateTime::<Utc> as Type>::qualified_type_name(),
"DateTimeUtc!" "DateTime!"
); );
assert_eq!(<DateTime::<Local> as Type>::type_name(), "DateTime");
assert_eq!( assert_eq!(
<DateTime::<FixedOffset> as Type>::type_name(), <DateTime::<Local> as Type>::qualified_type_name(),
"DateTimeFixedOffset" "DateTime!"
); );
assert_eq!(<DateTime::<FixedOffset> as Type>::type_name(), "DateTime");
assert_eq!( assert_eq!(
<DateTime::<FixedOffset> as Type>::qualified_type_name(), <DateTime::<FixedOffset> as Type>::qualified_type_name(),
"DateTimeFixedOffset!" "DateTime!"
); );
assert_eq!(<Uuid as Type>::type_name(), "UUID"); assert_eq!(<Uuid as Type>::type_name(), "UUID");

View File

@ -5,6 +5,7 @@ use crate::{
QueryResponse, Result, Schema, SubscriptionStreams, SubscriptionType, Variables, QueryResponse, Result, Schema, SubscriptionStreams, SubscriptionType, Variables,
}; };
use bytes::Bytes; use bytes::Bytes;
use serde::{Deserialize, Serialize};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;

View File

@ -42,7 +42,7 @@ mod tests {
use super::*; use super::*;
use crate::{expect_fails_rule, expect_passes_rule}; use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> DefaultValuesOfCorrectType { pub fn factory() -> DefaultValuesOfCorrectType {
DefaultValuesOfCorrectType DefaultValuesOfCorrectType
} }

View File

@ -55,7 +55,7 @@ mod tests {
use super::*; use super::*;
use crate::{expect_fails_rule, expect_passes_rule}; use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> FieldsOnCorrectType { pub fn factory() -> FieldsOnCorrectType {
FieldsOnCorrectType FieldsOnCorrectType
} }

View File

@ -49,7 +49,7 @@ mod tests {
use super::*; use super::*;
use crate::{expect_fails_rule, expect_passes_rule}; use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> FragmentsOnCompositeTypes { fn factory() -> FragmentsOnCompositeTypes {
FragmentsOnCompositeTypes FragmentsOnCompositeTypes
} }

View File

@ -381,7 +381,7 @@ where
V: Visitor<'a> + 'a, V: Visitor<'a> + 'a,
F: Fn() -> V, F: Fn() -> V,
{ {
if let Ok(_) = validate(doc, factory) { if validate(doc, factory).is_ok() {
panic!("Expected rule to fail, but no errors were found"); panic!("Expected rule to fail, but no errors were found");
} }
} }

View File

@ -43,7 +43,7 @@ pub async fn test_connection_additional_fields() {
end < 10000, end < 10000,
ConnectionFields { total_count: 10000 }, ConnectionFields { total_count: 10000 },
); );
connection.append((start..end).into_iter().map(|n| { connection.append((start..end).map(|n| {
Edge::with_additional_fields( Edge::with_additional_fields(
n, n,
n as i32, n as i32,

View File

@ -33,13 +33,12 @@ pub async fn test_enum_type() {
} }
let schema = Schema::new(Root { value: MyEnum::A }, EmptyMutation, EmptySubscription); let schema = Schema::new(Root { value: MyEnum::A }, EmptyMutation, EmptySubscription);
let query = format!( let query = r#"{
r#"{{
value value
testArg(input: A) testArg(input: A)
testInput(input: {{value: B}}) }} testInput(input: {value: B})
"# }"#
); .to_owned();
assert_eq!( assert_eq!(
schema.execute(&query).await.unwrap().data, schema.execute(&query).await.unwrap().data,
serde_json::json!({ serde_json::json!({
@ -52,7 +51,7 @@ pub async fn test_enum_type() {
#[async_std::test] #[async_std::test]
pub async fn test_enum_derive_and_item_attributes() { pub async fn test_enum_derive_and_item_attributes() {
use serde_derive::Deserialize; use serde::Deserialize;
#[async_graphql::Enum] #[async_graphql::Enum]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]

View File

@ -67,13 +67,12 @@ pub async fn test_input_object_default_value() {
} }
let schema = Schema::new(Root, EmptyMutation, EmptySubscription); let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
let query = format!( let query = r#"{
r#"{{ a(input:{e:777}) {
a(input:{{e:777}}) {{
a b c d e a b c d e
}} }
}}"# }"#
); .to_owned();
assert_eq!( assert_eq!(
schema.execute(&query).await.unwrap().data, schema.execute(&query).await.unwrap().data,
serde_json::json!({ serde_json::json!({
@ -90,7 +89,7 @@ pub async fn test_input_object_default_value() {
#[async_std::test] #[async_std::test]
pub async fn test_inputobject_derive_and_item_attributes() { pub async fn test_inputobject_derive_and_item_attributes() {
use serde_derive::Deserialize; use serde::Deserialize;
#[async_graphql::InputObject] #[async_graphql::InputObject]
#[derive(Deserialize, PartialEq, Debug)] #[derive(Deserialize, PartialEq, Debug)]

View File

@ -292,9 +292,9 @@ pub async fn test_input_validator_string_email() {
case case
); );
let field_error_msg = let field_error_msg =
format!("Invalid value for argument \"email\", invalid email format"); "Invalid value for argument \"email\", invalid email format".to_owned();
let object_error_msg = let object_error_msg =
format!("Invalid value for argument \"input.email\", invalid email format"); "Invalid value for argument \"input.email\", invalid email format".to_owned();
// Testing FieldValidator // Testing FieldValidator
assert_eq!( assert_eq!(
@ -437,9 +437,9 @@ pub async fn test_input_validator_string_mac() {
"MAC validation case {} should have failed, but did not", "MAC validation case {} should have failed, but did not",
mac mac
); );
let field_error_msg = format!("Invalid value for argument \"mac\", invalid MAC format"); let field_error_msg = "Invalid value for argument \"mac\", invalid MAC format".to_owned();
let object_error_msg = let object_error_msg =
format!("Invalid value for argument \"input.mac\", invalid MAC format"); "Invalid value for argument \"input.mac\", invalid MAC format".to_owned();
assert_eq!( assert_eq!(
schema_without_colon schema_without_colon
@ -511,14 +511,14 @@ pub async fn test_input_validator_string_mac() {
for mac in valid_macs { for mac in valid_macs {
let field_query = format!("{{fieldParameter(mac: \"{}\")}}", mac); let field_query = format!("{{fieldParameter(mac: \"{}\")}}", mac);
let object_query = format!("{{inputObject(input: {{mac: \"{}\"}})}}", mac); let object_query = format!("{{inputObject(input: {{mac: \"{}\"}})}}", mac);
let contains_colon = mac.contains(":"); let contains_colon = mac.contains(':');
let should_fail_msg = format!( let should_fail_msg = format!(
"MAC validation case {} should have failed, but did not", "MAC validation case {} should have failed, but did not",
mac mac
); );
let field_error_msg = format!("Invalid value for argument \"mac\", invalid MAC format"); let field_error_msg = "Invalid value for argument \"mac\", invalid MAC format".to_owned();
let object_error_msg = let object_error_msg =
format!("Invalid value for argument \"input.mac\", invalid MAC format"); "Invalid value for argument \"input.mac\", invalid MAC format".to_owned();
let error_msg = format!("Schema returned error with test_string = {}", mac); let error_msg = format!("Schema returned error with test_string = {}", mac);
if contains_colon { if contains_colon {
@ -1413,9 +1413,10 @@ pub async fn test_input_validator_operator_or() {
case case
); );
let field_error_msg = format!("Invalid value for argument \"id\", invalid MAC format"); let field_error_msg =
"Invalid value for argument \"id\", invalid MAC format".to_owned();
let object_error_msg = let object_error_msg =
format!("Invalid value for argument \"input.id\", invalid MAC format"); "Invalid value for argument \"input.id\", invalid MAC format".to_owned();
assert_eq!( assert_eq!(
schema schema
.execute(&field_query) .execute(&field_query)
@ -1518,13 +1519,13 @@ pub async fn test_input_validator_operator_and() {
); );
let field_error_msg = if *should_be_invalid_email { let field_error_msg = if *should_be_invalid_email {
format!("Invalid value for argument \"email\", invalid email format") "Invalid value for argument \"email\", invalid email format".to_owned()
} else { } else {
format!("Invalid value for argument \"email\", the value length is {}, must be greater than or equal to {}", case_length, min_length) format!("Invalid value for argument \"email\", the value length is {}, must be greater than or equal to {}", case_length, min_length)
}; };
let object_error_msg = if *should_be_invalid_email { let object_error_msg = if *should_be_invalid_email {
format!("Invalid value for argument \"input.email\", invalid email format") "Invalid value for argument \"input.email\", invalid email format".to_owned()
} else { } else {
format!("Invalid value for argument \"input.email\", the value length is {}, must be greater than or equal to {}", case_length, min_length) format!("Invalid value for argument \"input.email\", the value length is {}, must be greater than or equal to {}", case_length, min_length)
}; };

View File

@ -52,7 +52,7 @@ enum TestEnum {
#[item(desc = "Kind 1")] #[item(desc = "Kind 1")]
Kind1, Kind1,
#[item(desc = "Kind 2", deprecation = "Kind 2 depracted")] #[item(desc = "Kind 2", deprecation = "Kind 2 deprecated")]
Kind2, Kind2,
} }
@ -269,10 +269,10 @@ impl Subscription {
// } // }
#[async_std::test] #[async_std::test]
pub async fn test_introspection_depraction() { pub async fn test_introspection_deprecation() {
let schema = Schema::new(Query, EmptyMutation, EmptySubscription); let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let get_object_query = |obj, is_depracted| { let get_object_query = |obj, is_deprecated| {
format!( format!(
r#" r#"
{{ {{
@ -285,11 +285,11 @@ pub async fn test_introspection_depraction() {
}} }}
}} }}
"#, "#,
obj, is_depracted obj, is_deprecated
) )
}; };
// SimpleObject with depracted inclusive // SimpleObject with deprecated inclusive
let mut query = get_object_query("SimpleObject", "true"); let mut query = get_object_query("SimpleObject", "true");
let mut res_json = serde_json::json!({ let mut res_json = serde_json::json!({
@ -353,7 +353,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json); assert_eq!(res, res_json);
// SimpleObject with depracted fields exclusive // SimpleObject with deprecated fields exclusive
query = get_object_query("SimpleObject", "false"); query = get_object_query("SimpleObject", "false");
res_json = serde_json::json!({ res_json = serde_json::json!({
@ -412,7 +412,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json); assert_eq!(res, res_json);
// Object with only one depracted field inclusive // Object with only one deprecated field inclusive
query = get_object_query("Square", "true"); query = get_object_query("Square", "true");
res_json = serde_json::json!({ res_json = serde_json::json!({
@ -431,7 +431,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json); assert_eq!(res, res_json);
// Object with only one depracted field exclusive // Object with only one deprecated field exclusive
query = get_object_query("Square", "false"); query = get_object_query("Square", "false");
res_json = serde_json::json!({ res_json = serde_json::json!({
@ -444,7 +444,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json); assert_eq!(res, res_json);
let get_enum_query = |obj, is_depracted| { let get_enum_query = |obj, is_deprecated| {
format!( format!(
r#" r#"
{{ {{
@ -457,11 +457,11 @@ pub async fn test_introspection_depraction() {
}} }}
}} }}
"#, "#,
obj, is_depracted obj, is_deprecated
) )
}; };
// Enum with depracted value inclusive // Enum with deprecated value inclusive
query = get_enum_query("TestEnum", "true"); query = get_enum_query("TestEnum", "true");
res_json = serde_json::json!({ res_json = serde_json::json!({
@ -475,7 +475,7 @@ pub async fn test_introspection_depraction() {
{ {
"name": "KIND_2", "name": "KIND_2",
"isDeprecated": true, "isDeprecated": true,
"deprecationReason": "Kind 2 depracted" "deprecationReason": "Kind 2 deprecated"
} }
] ]
} }
@ -485,7 +485,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json); assert_eq!(res, res_json);
// Enum with depracted value exclusive // Enum with deprecated value exclusive
query = get_enum_query("TestEnum", "false"); query = get_enum_query("TestEnum", "false");
res_json = serde_json::json!({ res_json = serde_json::json!({
@ -841,7 +841,7 @@ pub async fn test_introspection_enum() {
"name": "KIND_2", "name": "KIND_2",
"description": "Kind 2", "description": "Kind 2",
"isDeprecated": true, "isDeprecated": true,
"deprecationReason": "Kind 2 depracted" "deprecationReason": "Kind 2 deprecated"
} }
] ]
} }

View File

@ -15,7 +15,7 @@ pub async fn test_optional_type() {
#[Object] #[Object]
impl Root { impl Root {
async fn value1(&self) -> Option<i32> { async fn value1(&self) -> Option<i32> {
self.value1.clone() self.value1
} }
async fn value1_ref(&self) -> &Option<i32> { async fn value1_ref(&self) -> &Option<i32> {
@ -23,7 +23,7 @@ pub async fn test_optional_type() {
} }
async fn value2(&self) -> Option<i32> { async fn value2(&self) -> Option<i32> {
self.value2.clone() self.value2
} }
async fn value2_ref(&self) -> &Option<i32> { async fn value2_ref(&self) -> &Option<i32> {
@ -35,7 +35,7 @@ pub async fn test_optional_type() {
} }
async fn test_input<'a>(&self, input: MyInput) -> Option<i32> { async fn test_input<'a>(&self, input: MyInput) -> Option<i32> {
input.value.clone() input.value
} }
} }
@ -47,18 +47,17 @@ pub async fn test_optional_type() {
EmptyMutation, EmptyMutation,
EmptySubscription, EmptySubscription,
); );
let query = format!( let query = r#"{
r#"{{
value1 value1
value1Ref value1Ref
value2 value2
value2Ref value2Ref
testArg1: testArg(input: 10) testArg1: testArg(input: 10)
testArg2: testArg testArg2: testArg
testInput1: testInput(input: {{value: 10}}) testInput1: testInput(input: {value: 10})
testInput2: testInput(input: {{}}) testInput2: testInput(input: {})
}}"# }"#
); .to_owned();
assert_eq!( assert_eq!(
schema.execute(&query).await.unwrap().data, schema.execute(&query).await.unwrap().data,
serde_json::json!({ serde_json::json!({

View File

@ -88,7 +88,7 @@ pub async fn test_subscription_ws_transport_with_token() {
let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot); let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
let (mut sink, mut stream) = schema.subscription_connection(WebSocketTransport::new(|value| { let (mut sink, mut stream) = schema.subscription_connection(WebSocketTransport::new(|value| {
#[derive(serde_derive::Deserialize)] #[derive(serde::Deserialize)]
struct Payload { struct Payload {
token: String, token: String,
} }