Merge branch 'master' of https://github.com/async-graphql/async-graphql
This commit is contained in:
commit
c3a4bf6e11
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>.
|
||||||
|
|
|
@ -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();
|
||||||
|
```
|
||||||
```
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -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 {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.**
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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._
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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>.
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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};
|
||||||
|
|
113
src/error.rs
113
src/error.rs
|
@ -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>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
18
src/guard.rs
18
src/guard.rs
|
@ -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]
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
105
src/lib.rs
105
src/lib.rs
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
||||||
};
|
};
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!({
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user