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]
name = "async-graphql"
version = "1.17.12"
version = "1.17.15"
authors = ["sunli <scott_s829@163.com>"]
edition = "2018"
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"
thiserror = "1.0.11"
async-trait = "0.1.30"
serde = "1.0.104"
serde_derive = "1.0.104"
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48"
bytes = "0.5.4"
Inflector = "0.11.4"
@ -55,8 +54,6 @@ smallvec = "1.4.2"
[dev-dependencies]
async-std = { version = "1.5.0", features = ["attributes"] }
serde = "1.0.104"
serde_derive = "1.0.104"
[workspace]
members = [
@ -65,6 +62,6 @@ members = [
"async-graphql-actix-web",
"async-graphql-warp",
"async-graphql-tide",
# "async-graphql-lambda",
# "async-graphql-lambda",
"benchmark",
]

View File

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

View File

@ -1,8 +1,8 @@
# 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.
@ -36,16 +36,16 @@ impl Query {
**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`.
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
use async_graphql::*;
use async_graphql::extensions::ApolloTracing;
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
.extension(|| ApolloTracing::default()) // Enable ApolloTracing extension
.extension(ApolloTracing::default) // Enable ApolloTracing extension
.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.
`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 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.

View File

@ -1,6 +1,6 @@
# 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`.

View File

@ -1,8 +1,8 @@
# 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
use async_graphql::*;
@ -42,4 +42,4 @@ impl Query {
}
}
```
```

View File

@ -1,15 +1,14 @@
# 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.
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
use async_graphql::*;
struct StringNumber(i64);
#[Scalar]
@ -28,5 +27,4 @@ impl ScalarType for StringNumber {
Value::String(self.0.to_string())
}
}
```

View File

@ -1,7 +1,7 @@
# Default value
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
@ -57,4 +57,4 @@ struct MyInputObject {
#[field(default = "my_default()")]
value3: i32,
}
```
```

View File

@ -1,14 +1,14 @@
# 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.
When creating `Schema`, you can use `SchemaBuilder::data` to setup `Schema` data, and `Context::data` to setup `Context`data.
The following `value_from_db` function showed how to retrieve a database connection from `Context`.
You may need access to global data in your query, for example a database connection pool.
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 shows how to retrieve a database connection from `Context`.
```rust
use async_graphql::*;

View File

@ -2,7 +2,7 @@
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
use async_graphql::*;
@ -19,4 +19,4 @@ pub enum Episode {
#[item(name="AAA", desc = "Released in 1983.")]
Jedi,
}
```
```

View File

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

View File

@ -1,16 +1,16 @@
# Interface
`Interface` is used to abstract `Object`s with common fields.
`Async-graphql` implemented it as a wrapper.
The wrapper will forward Resolve to the `Object` that implemented this `Interface`.
Therefore, the `Object`'s fields' type, arguments must match with the `Interface`'s.
`Async-graphql` implements it as a wrapper.
The wrapper will forward field resolution to the `Object` that implements this `Interface`.
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.
If you need e.g. snake_cased GraphQL field name, you can use both the `name` and `method` attribute.
Interface field names are transformed to camelCase for the schema definition.
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.
```rust

View File

@ -1,6 +1,6 @@
# Schema
After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, mutation object, and subscription object, where the mutation object and subscription object are optional.
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` 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
use async_graphql::*;

View File

@ -1,8 +1,7 @@
# Union
The definition of `Union` is similar to `Interface`'s, **but no field allowed.**.
The implemention is quite similar for `Async-graphql`.
From `Async-graphql`'s perspective, `Union` is a subset of `Interface`.
The definition of a `Union` is similar to an `Interface`, **but with no fields allowed.**.
The implementation is quite similar for `Async-graphql`; 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.

View File

@ -1,7 +1,7 @@
# Error extensions
To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18):
> 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
> 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.
## General Concept
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
field `Option<serde_json::Value>` which - if some valid `serde_json::Map` - will be exposed as the extensions key to any error.
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` also provides an additional
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
async fn parse_with_extensions(&self) -> Result<i32, FieldError> {
@ -41,7 +41,7 @@ may then return a response like this:
## 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
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.
```rust
#[macro_use]
extern crate thiserror;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MyError {
@ -102,7 +101,6 @@ async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
// OR
Err(MyError::NotFound.extend_with(|_| json!({ "on_the_fly": "some_more_info" })))
}
```
```json
@ -119,12 +117,8 @@ async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
}
]
}
```
## ResultExt
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
Rust does not provide stable trait specialization yet.
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).
The disadvantage is that the below code does **NOT** compile:

View File

@ -1,15 +1,15 @@
# Error handling
Resolve can return a `FieldResult`, following is the definition:
Resolve can return a `FieldResult`, which has the following definition:
```rust
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.
See [ErrorExtensions](error_extensions.md) sections of this book for more details.
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 the [Error Extensions](error_extensions.md) section of this book for more details.
```rust
use async_graphql::*;

View File

@ -1,11 +1,10 @@
# 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.
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:
```rust

View File

@ -1,9 +1,9 @@
# 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)
- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp)
- 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
`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

View File

@ -35,4 +35,4 @@ cd benchmark
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.
@ -51,7 +51,7 @@ let schema = Schema::new(
# 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.

View File

@ -1,6 +1,6 @@
# 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:
@ -14,7 +14,7 @@ The following is the corresponding table:
|Merged Object|MergedObject|GQLMergedObject|
|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
#[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
#[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
#[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
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
use async_graphql::*;

View File

@ -4,10 +4,10 @@
```toml
[dependencies]
async-graphql = "1.11.0"
async-graphql-actix-web = "1.3.0" # If you need to integrate into actix-web
async-graphql-warp = "1.3.0" # If you need to integrate into warp
async-graphql-tide = "1.2.0" # If you need to integrate into tide
async-graphql = "1.17.15"
async-graphql-actix-web = "1.17.3" # If you need to integrate into actix-web
async-graphql-warp = "1.17.3" # If you need to integrate into warp
async-graphql-tide = "1.17.9" # If you need to integrate into tide
```
## Write a Schema
@ -30,12 +30,11 @@ impl Query {
a + b
}
}
```
## 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
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
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
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
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
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
use async_graphql::*;

View File

@ -1,3 +1,3 @@
# 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>
</head>
<body>
<div>
<a href="en/index.html">English</a>
</div>
<div>
<a href="zh-CN/index.html">简体中文</a>
</div>
<h1>Async-graphql Book</h1>
<p>This book is available in multiple languages:</p>
<ul>
<li><a href="en/index.html">English</a></li>
<li><a href="zh-CN/index.html">简体中文</a></li>
</ul>
</body>
</html>

View File

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

View File

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

View File

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

View File

@ -5,14 +5,16 @@ use serde::export::PhantomData;
/// 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]
pub trait Guard {
#[allow(missing_docs)]
/// Check whether the guard will allow access to the field.
async fn check(&self, ctx: &Context<'_>) -> FieldResult<()>;
}
/// An extension trait for `Guard`
/// An extension trait for `Guard`.
pub trait GuardExt: Guard + Sized {
/// Merge the two guards.
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 {}
/// Guard for `GuardExt::and`
/// Guard for [`GuardExt::and`](trait.GuardExt.html#method.and).
pub struct And<A: Guard, B: Guard>(A, B);
#[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
///
/// 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]
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<()>;
}
@ -52,7 +56,7 @@ pub trait PostGuardExt<T: Send + Sync>: PostGuard<T> + Sized {
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>);
#[async_trait::async_trait]

View File

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

View File

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

View File

@ -40,7 +40,7 @@
//! * Custom scalars
//! * Minimal overhead
//! * Easy integration (hyper, actix_web, tide ...)
//! * Upload files (Multipart request)
//! * File upload (Multipart request)
//! * Subscriptions (WebSocket transport)
//! * Custom extensions
//! * Apollo Tracing extension
@ -89,7 +89,7 @@
//! 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)]
@ -98,13 +98,6 @@
#![recursion_limit = "256"]
#![forbid(unsafe_code)]
#[macro_use]
extern crate thiserror;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
mod base;
mod context;
mod error;
@ -182,7 +175,11 @@ pub use subscription::SubscriptionType;
#[doc(hidden)]
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
///
@ -218,16 +215,17 @@ pub use types::{EnumItem, EnumType};
/// | default_with | Expression to generate default value | code string | 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`
/// - Borrowing of scalar values, such as `&i32`, `&bool`
/// - Vec<T>, such as `Vec<i32>`
/// - Slice<T>, such as `&[i32]`
/// - Option<T>, such as `Option<i32>`
/// - Object and &Object
/// - Enum
/// - FieldResult<T, E>, such as `FieldResult<i32, E>`
/// - Scalar values, such as `i32` and `bool`. `usize`, `isize`, `u128` and `i128` are not
/// supported
/// - `Vec<T>`, such as `Vec<i32>`
/// - Slices, such as `&[i32]`
/// - `Option<T>`, such as `Option<i32>`
/// - GraphQL objects.
/// - GraphQL enums.
/// - References to any of the above types, such as `&i32` or `&Option<String>`.
/// - `FieldResult<T, E>`, such as `FieldResult<i32, E>`
///
/// # Context
///
@ -272,7 +270,7 @@ pub use types::{EnumItem, EnumType};
/// }
///
/// 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#"{
/// value
/// valueRef
@ -291,9 +289,13 @@ pub use types::{EnumItem, EnumType};
/// ```
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
///
@ -340,6 +342,12 @@ pub use async_graphql_derive::SimpleObject;
/// 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
///
/// ```rust
@ -356,6 +364,10 @@ pub use async_graphql_derive::GQLEnum;
/// 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
///
/// ```rust
@ -368,7 +380,11 @@ pub use async_graphql_derive::GQLEnum;
/// ```
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
///
@ -384,6 +400,10 @@ pub use async_graphql_derive::GQLSimpleObject;
/// 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
///
/// | Attribute | description | Type | Optional |
@ -438,6 +458,10 @@ pub use async_graphql_derive::Enum;
/// 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
///
/// | Attribute | description | Type | Optional |
@ -492,6 +516,10 @@ pub use async_graphql_derive::InputObject;
/// 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
///
/// | Attribute | description | Type | Optional |
@ -616,10 +644,17 @@ pub use async_graphql_derive::InputObject;
pub use async_graphql_derive::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;
/// 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
///
@ -684,10 +719,16 @@ pub use async_graphql_derive::GQLInterface;
pub use async_graphql_derive::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;
/// 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 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.
@ -756,6 +797,10 @@ pub use async_graphql_derive::Scalar;
/// 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
///
/// | Attribute | description | Type | Optional |
@ -793,10 +838,18 @@ pub use async_graphql_derive::Scalar;
pub use async_graphql_derive::MergedObject;
/// 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;
/// 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
///
/// | Attribute | description | Type | Optional |
@ -835,5 +888,9 @@ pub use async_graphql_derive::GQLMergedObject;
/// ```
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;

View File

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

View File

@ -1,35 +1,52 @@
use crate::{InputValueError, InputValueResult, ScalarType, Value};
use async_graphql_derive::Scalar;
macro_rules! float_scalar {
($($ty:ty),*) => {
$(
/// 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 $ty {
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())
}
/// 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 f32 {
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())
}
}
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 async_graphql_derive::Scalar;
macro_rules! int_scalar {
($($ty:ty),*) => {
$(
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for $ty {
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)),
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for i8 {
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)
}
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))
}
_ => 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))
}
}
macro_rules! uint_scalar {
($($ty:ty),*) => {
$(
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for $ty {
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)),
/// The `Int` scalar type represents non-fractional whole numeric values.
#[Scalar(internal, name = "Int")]
impl ScalarType for i16 {
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)
}
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))
}
_ => 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))
}
}
int_scalar!(i8, i16, i32, i64);
uint_scalar!(u8, u16, u32, u64);
/// The `Int` scalar type represents non-fractional whole numeric values.
#[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_parser::query::Field;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::ops::{Deref, DerefMut};
@ -100,6 +100,7 @@ impl<T: Serialize + Send + Sync> OutputValueType for OutputJson<T> {
#[cfg(test)]
mod test {
use crate::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[async_std::test]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -381,7 +381,7 @@ where
V: Visitor<'a> + 'a,
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");
}
}

View File

@ -43,7 +43,7 @@ pub async fn test_connection_additional_fields() {
end < 10000,
ConnectionFields { total_count: 10000 },
);
connection.append((start..end).into_iter().map(|n| {
connection.append((start..end).map(|n| {
Edge::with_additional_fields(
n,
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 query = format!(
r#"{{
let query = r#"{
value
testArg(input: A)
testInput(input: {{value: B}}) }}
"#
);
testInput(input: {value: B})
}"#
.to_owned();
assert_eq!(
schema.execute(&query).await.unwrap().data,
serde_json::json!({
@ -52,7 +51,7 @@ pub async fn test_enum_type() {
#[async_std::test]
pub async fn test_enum_derive_and_item_attributes() {
use serde_derive::Deserialize;
use serde::Deserialize;
#[async_graphql::Enum]
#[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 query = format!(
r#"{{
a(input:{{e:777}}) {{
let query = r#"{
a(input:{e:777}) {
a b c d e
}}
}}"#
);
}
}"#
.to_owned();
assert_eq!(
schema.execute(&query).await.unwrap().data,
serde_json::json!({
@ -90,7 +89,7 @@ pub async fn test_input_object_default_value() {
#[async_std::test]
pub async fn test_inputobject_derive_and_item_attributes() {
use serde_derive::Deserialize;
use serde::Deserialize;
#[async_graphql::InputObject]
#[derive(Deserialize, PartialEq, Debug)]

View File

@ -292,9 +292,9 @@ pub async fn test_input_validator_string_email() {
case
);
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 =
format!("Invalid value for argument \"input.email\", invalid email format");
"Invalid value for argument \"input.email\", invalid email format".to_owned();
// Testing FieldValidator
assert_eq!(
@ -437,9 +437,9 @@ pub async fn test_input_validator_string_mac() {
"MAC validation case {} should have failed, but did not",
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 =
format!("Invalid value for argument \"input.mac\", invalid MAC format");
"Invalid value for argument \"input.mac\", invalid MAC format".to_owned();
assert_eq!(
schema_without_colon
@ -511,14 +511,14 @@ pub async fn test_input_validator_string_mac() {
for mac in valid_macs {
let field_query = format!("{{fieldParameter(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!(
"MAC validation case {} should have failed, but did not",
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 =
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);
if contains_colon {
@ -1413,9 +1413,10 @@ pub async fn test_input_validator_operator_or() {
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 =
format!("Invalid value for argument \"input.id\", invalid MAC format");
"Invalid value for argument \"input.id\", invalid MAC format".to_owned();
assert_eq!(
schema
.execute(&field_query)
@ -1518,13 +1519,13 @@ pub async fn test_input_validator_operator_and() {
);
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 {
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 {
format!("Invalid value for argument \"input.email\", invalid email format")
"Invalid value for argument \"input.email\", invalid email format".to_owned()
} else {
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")]
Kind1,
#[item(desc = "Kind 2", deprecation = "Kind 2 depracted")]
#[item(desc = "Kind 2", deprecation = "Kind 2 deprecated")]
Kind2,
}
@ -269,10 +269,10 @@ impl Subscription {
// }
#[async_std::test]
pub async fn test_introspection_depraction() {
pub async fn test_introspection_deprecation() {
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let get_object_query = |obj, is_depracted| {
let get_object_query = |obj, is_deprecated| {
format!(
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 res_json = serde_json::json!({
@ -353,7 +353,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json);
// SimpleObject with depracted fields exclusive
// SimpleObject with deprecated fields exclusive
query = get_object_query("SimpleObject", "false");
res_json = serde_json::json!({
@ -412,7 +412,7 @@ pub async fn test_introspection_depraction() {
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");
res_json = serde_json::json!({
@ -431,7 +431,7 @@ pub async fn test_introspection_depraction() {
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");
res_json = serde_json::json!({
@ -444,7 +444,7 @@ pub async fn test_introspection_depraction() {
assert_eq!(res, res_json);
let get_enum_query = |obj, is_depracted| {
let get_enum_query = |obj, is_deprecated| {
format!(
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");
res_json = serde_json::json!({
@ -475,7 +475,7 @@ pub async fn test_introspection_depraction() {
{
"name": "KIND_2",
"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);
// Enum with depracted value exclusive
// Enum with deprecated value exclusive
query = get_enum_query("TestEnum", "false");
res_json = serde_json::json!({
@ -841,7 +841,7 @@ pub async fn test_introspection_enum() {
"name": "KIND_2",
"description": "Kind 2",
"isDeprecated": true,
"deprecationReason": "Kind 2 depracted"
"deprecationReason": "Kind 2 deprecated"
}
]
}

View File

@ -15,7 +15,7 @@ pub async fn test_optional_type() {
#[Object]
impl Root {
async fn value1(&self) -> Option<i32> {
self.value1.clone()
self.value1
}
async fn value1_ref(&self) -> &Option<i32> {
@ -23,7 +23,7 @@ pub async fn test_optional_type() {
}
async fn value2(&self) -> Option<i32> {
self.value2.clone()
self.value2
}
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> {
input.value.clone()
input.value
}
}
@ -47,18 +47,17 @@ pub async fn test_optional_type() {
EmptyMutation,
EmptySubscription,
);
let query = format!(
r#"{{
let query = r#"{
value1
value1Ref
value2
value2Ref
testArg1: testArg(input: 10)
testArg2: testArg
testInput1: testInput(input: {{value: 10}})
testInput2: testInput(input: {{}})
}}"#
);
testInput1: testInput(input: {value: 10})
testInput2: testInput(input: {})
}"#
.to_owned();
assert_eq!(
schema.execute(&query).await.unwrap().data,
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 (mut sink, mut stream) = schema.subscription_connection(WebSocketTransport::new(|value| {
#[derive(serde_derive::Deserialize)]
#[derive(serde::Deserialize)]
struct Payload {
token: String,
}