Update documents
This commit is contained in:
parent
5549c85f55
commit
3d8a8ef6af
|
@ -54,11 +54,11 @@ pub fn generate_guards(
|
|||
let expr: Expr =
|
||||
syn::parse_str(code).map_err(|err| Error::new(code.span(), err.to_string()))?;
|
||||
let code = quote! {{
|
||||
use #crate_name::guard::GuardExt;
|
||||
use #crate_name::GuardExt;
|
||||
#expr
|
||||
}};
|
||||
Ok(quote! {
|
||||
#crate_name::guard::Guard::check(&#code, &ctx).await #map_err ?;
|
||||
#crate_name::Guard::check(&#code, &ctx).await #map_err ?;
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
- [How extensions are working](extensions_inner_working.md)
|
||||
- [Available extensions](extensions_available.md)
|
||||
- [Integrations](integrations.md)
|
||||
- [Tide](integrations_to_tide.md)
|
||||
- [Poem](integrations_to_poem.md)
|
||||
- [Warp](integrations_to_warp.md)
|
||||
- [Actix-web](integrations_to_actix_web.md)
|
||||
- [Advanced topics](advanced_topics.md)
|
||||
|
|
|
@ -91,3 +91,14 @@ pub struct YetAnotherObject {
|
|||
```
|
||||
|
||||
You can pass multiple generic types to `params()`, separated by a comma.
|
||||
|
||||
## Used for both input and output
|
||||
|
||||
```rust
|
||||
#[derive(SimpleObject, InputObject)]
|
||||
#[graphql(input_name = "MyObjInput")] // Note: You must use the input_name attribute to define a new name for the input type, otherwise a runtime error will occur.
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Field Guard
|
||||
|
||||
You can define a `guard` to a field of an `Object`. This permits you to add checks before runing the code logic of the field.
|
||||
`Guard` are made of rules that you need to define before. A rule is a structure which implements the trait `Guard`.
|
||||
You can define a `guard` for the fields of `Object`, `SimpleObject`, `ComplexObject` and `Subscription`, it will be executed before calling the resolver function, and an error will be returned if it fails.
|
||||
|
||||
```rust
|
||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||
|
@ -14,6 +13,12 @@ struct RoleGuard {
|
|||
role: Role,
|
||||
}
|
||||
|
||||
impl RoleGuard {
|
||||
fn new(role: Role) -> Self {
|
||||
Self { role }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Guard for RoleGuard {
|
||||
async fn check(&self, ctx: &Context<'_>) -> Result<()> {
|
||||
|
@ -26,43 +31,54 @@ impl Guard for RoleGuard {
|
|||
}
|
||||
```
|
||||
|
||||
Once you have defined your rule you can use it in the `guard` field attribute.
|
||||
This attribute supports 4 operators to create complex rules :
|
||||
|
||||
- `and` : perform an `and` operation between two rules. (If one rule returns an error the `and` operator will return the error. If both rules return an error it's the first one that will be returned).
|
||||
|
||||
- `or` : performs an `or` operation between two rules. (If both rules return an error the error returned is the first one)
|
||||
|
||||
- `chain` : takes a set of rules and runs them until one returns an error or it returns `Ok` if all rules pass.
|
||||
|
||||
- `race` : takes a set of rules and runs them until one returns `Ok` if they all fail, it returns the last error.
|
||||
Use it with the `guard` attribute:
|
||||
|
||||
```rust
|
||||
#[derive(SimpleObject)]
|
||||
struct Query {
|
||||
#[graphql(guard(RoleGuard(role = "Role::Admin")))]
|
||||
value: i32,
|
||||
#[graphql(guard(and(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#)
|
||||
)))]
|
||||
/// Only allow Admin
|
||||
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
|
||||
value1: i32,
|
||||
/// Allow Admin or Guest
|
||||
#[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")]
|
||||
value2: i32,
|
||||
#[graphql(guard(or(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#)
|
||||
)))]
|
||||
value3: i32,
|
||||
#[graphql(guard(chain(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#),
|
||||
AgeGuard(age = r#"21"#)
|
||||
)))]
|
||||
value4: i32,
|
||||
#[graphql(guard(race(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#),
|
||||
AgeGuard(age = r#"21"#)
|
||||
)))]
|
||||
value5: i32,
|
||||
}
|
||||
```
|
||||
|
||||
## Use parameter value
|
||||
|
||||
Sometimes guards need to use field parameters, you need to pass the parameter value when creating the guard like this:
|
||||
|
||||
```rust
|
||||
struct EqGuard {
|
||||
expect: i32,
|
||||
actual: i32,
|
||||
}
|
||||
|
||||
impl EqGuard {
|
||||
fn new(expect: i32, actual: i32) -> Self {
|
||||
Self { expect, actual }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Guard for EqGuard {
|
||||
async fn check(&self, _ctx: &Context<'_>) -> Result<()> {
|
||||
if self.expect != self.actual {
|
||||
Err("Forbidden".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
#[graphql(guard = "EqGuard::new(100, value)")]
|
||||
async fn get(&self, value: i32) -> i32 {
|
||||
value
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,117 +1,78 @@
|
|||
# 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.
|
||||
`Async-graphql` has some common validators built-in, you can use them on the parameters of object fields or on the fields of `InputObject`.
|
||||
|
||||
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:
|
||||
- **maximum=N** the number cannot be greater than `N`.
|
||||
- **minimum=N** the number cannot be less than `N`.
|
||||
- **multiple_of=N** the number must be a multiple of `N`.
|
||||
- **max_items=N** the length of the list cannot be greater than `N`.
|
||||
- **min_items=N** the length of the list cannot be less than `N`.
|
||||
- **max_length=N** the length of the string cannot be greater than `N`.
|
||||
- **min_length=N** the length of the string cannot be less than `N`.
|
||||
|
||||
```rust
|
||||
use async_graphql::*;
|
||||
use async_graphql::validators::{Email, MAC};
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn input(#[graphql(validator(or(Email, MAC(colon = "false"))))] a: String) {
|
||||
/// The length of the name must be greater than or equal to 5 and less than or equal to 10.
|
||||
async fn input(#[graphql(validator(min_length = 5, max_length = 10))] name: String) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following example verifies that the `i32` parameter `a` is greater than 10 and less than 100, or else equal to 0:
|
||||
## Check every member of the list
|
||||
|
||||
You can enable the `list` attribute, and the validator will check all members in list:
|
||||
|
||||
```rust
|
||||
use async_graphql::*;
|
||||
use async_graphql::validators::{IntGreaterThan, IntLessThan, IntEqual};
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn input(#[graphql(validator(
|
||||
or(
|
||||
and(IntGreaterThan(value = "10"), IntLessThan(value = "100")),
|
||||
IntEqual(value = "0")
|
||||
)))] a: String) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validate the elements of the list.
|
||||
|
||||
You can use the `list` operator to indicate that the internal validator is used for all elements in a list:
|
||||
|
||||
```rust
|
||||
use async_graphql::*;
|
||||
use async_graphql::validators::Email;
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn input(#[graphql(validator(list(Email)))] emails: Vec<String>) {
|
||||
async fn input(#[graphql(validator(list, max_length = 10))] names: Vec<String>) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom validator
|
||||
|
||||
Here is an example of a custom validator:
|
||||
|
||||
```rust
|
||||
struct MustBeZero {}
|
||||
struct MyValidator {
|
||||
expect: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for MustBeZero {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Int(n) = value {
|
||||
if n.as_i64().unwrap() != 0 {
|
||||
// Validation failed
|
||||
Err(format!(
|
||||
"the value is {}, but must be zero",
|
||||
n.as_i64().unwrap(),
|
||||
))
|
||||
} else {
|
||||
// Validation succeeded
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
// If the type does not match we can return None and built-in validations
|
||||
// will pick up on the error
|
||||
impl MyValidator {
|
||||
pub fn new(n: i32) -> Self {
|
||||
MyValidator { expect: n }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CustomValidator<i32> for MyValidator {
|
||||
async fn check(&self, _ctx: &Context<'_>, value: &i32) -> Result<(), String> {
|
||||
if *value == self.expect {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("expect 100, actual {}", value))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example of a custom validator with extensions (return `async_graphql::Error`):
|
||||
struct Query;
|
||||
|
||||
```rust
|
||||
pub struct Email;
|
||||
|
||||
impl InputValueValidator for Email {
|
||||
fn is_valid_with_extensions(&self, value: &Value) -> Result<(), Error> {
|
||||
if let Value::String(s) = value {
|
||||
if &s.to_lowercase() != s {
|
||||
return Err(Error::new("Validation Error").extend_with(|_, e| {
|
||||
e.set("key", "email_must_lowercase")
|
||||
}));
|
||||
}
|
||||
|
||||
if !validate_non_control_character(s) {
|
||||
return Err(Error::new("Validation Error").extend_with(|_, e| {
|
||||
e.set("key", "email_must_no_non_control_character")
|
||||
}));
|
||||
}
|
||||
|
||||
if !validate_email(s) {
|
||||
return Err(Error::new("Validation Error").extend_with(|_, e| {
|
||||
e.set("key", "invalid_email_format")
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
#[Object]
|
||||
impl Query {
|
||||
/// n must be equal to 100
|
||||
async fn value(
|
||||
&self,
|
||||
#[graphql(validator(custom = "MyValidator::new(100)"))] n: i32,
|
||||
) -> i32 {
|
||||
n
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
- Poem [async-graphql-poem](https://crates.io/crates/async-graphql-poem)
|
||||
- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web)
|
||||
- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp)
|
||||
- Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide)
|
||||
- Rocket [async-graphql-rocket](https://github.com/async-graphql/async-graphql/tree/master/integrations/rocket)
|
||||
- Axum [async-graphql-axum](https://crates.io/crates/async-graphql-axum)
|
||||
- Rocket [async-graphql-rocket](https://crates.io/crates/async-graphql-rocket)
|
||||
|
||||
**Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself.**
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
# Actix-web
|
||||
|
||||
`Async-graphql-actix-web` provides an implementation of `actix_web::FromRequest` for `Request`.
|
||||
This is actually an abstraction around `async_graphql::Request` and you can call `Request::into_inner` to
|
||||
convert it into a `async_graphql::Request`.
|
||||
|
||||
`WSSubscription` is an Actor that supports WebSocket subscriptions.
|
||||
# Poem
|
||||
|
||||
## Request example
|
||||
|
||||
|
@ -14,11 +8,10 @@ When you define your `actix_web::App` you need to pass in the Schema as data.
|
|||
async fn index(
|
||||
// Schema now accessible here
|
||||
schema: web::Data<Schema>,
|
||||
request: async_graphql_actix_web::Request,
|
||||
) -> web::Json<Response> {
|
||||
web::Json(Response(schema.execute(request.into_inner()).await)
|
||||
request: GraphQLRequest,
|
||||
) -> web::Json<GraphQLResponse> {
|
||||
web::Json(schema.execute(request.into_inner()).await.into())
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Subscription example
|
||||
|
@ -29,7 +22,7 @@ async fn index_ws(
|
|||
req: HttpRequest,
|
||||
payload: web::Payload,
|
||||
) -> Result<HttpResponse> {
|
||||
WSSubscription::start(Schema::clone(&*schema), &req, payload)
|
||||
GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Poem
|
||||
|
||||
## Request example
|
||||
|
||||
```rust
|
||||
use poem::Route;
|
||||
use async_graphql_poem::GraphQL;
|
||||
|
||||
let app = Route::new()
|
||||
.at("/ws", GraphQL::new(schema));
|
||||
```
|
||||
|
||||
## Subscription example
|
||||
|
||||
```rust
|
||||
use poem::Route;
|
||||
use async_graphql_poem::GraphQLSubscription;
|
||||
|
||||
let app = Route::new()
|
||||
.at("/ws", get(GraphQLSubscription::new(schema)));
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
[https://github.com/async-graphql/examples/tree/master/poem](https://github.com/async-graphql/examples/tree/master/poem)
|
|
@ -1,73 +0,0 @@
|
|||
# Tide
|
||||
|
||||
`async_graphql_tide` provides an implementation of [tide::Endpoint](https://docs.rs/tide/0.15.0/tide/trait.Endpoint.html) trait. It also provides `receive_request` and `respond` functions to convert a Tide request to a GraphQL request and back to Tide response, if you want to handle the request manually.
|
||||
|
||||
## Request example
|
||||
|
||||
When you create your `tide` server, you will need to pass the `async_graphql_tide::endpoint` with your schema as the POST request handler. Please note that you need to enable the `attributes` feature in `async-std` for this example to work.
|
||||
|
||||
```rust
|
||||
use async_graphql::{
|
||||
http::{playground_source, GraphQLPlaygroundConfig},
|
||||
Context, EmptyMutation, EmptySubscription, Object, Schema, SimpleObject,
|
||||
};
|
||||
use tide::{http::mime, Body, Response, StatusCode};
|
||||
|
||||
#[derive(SimpleObject)]
|
||||
pub struct Demo {
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
pub struct QueryRoot;
|
||||
|
||||
#[Object]
|
||||
impl QueryRoot {
|
||||
async fn demo(&self, _ctx: &Context<'_>) -> Demo {
|
||||
Demo { id: 42 }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
let mut app = tide::new();
|
||||
|
||||
// create schema
|
||||
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish();
|
||||
|
||||
// add tide endpoint
|
||||
app.at("/graphql")
|
||||
.post(async_graphql_tide::endpoint(schema));
|
||||
|
||||
// enable graphql playground
|
||||
app.at("/").get(|_| async move {
|
||||
Ok(Response::builder(StatusCode::Ok)
|
||||
.body(Body::from_string(playground_source(
|
||||
// note that the playground needs to know
|
||||
// the path to the graphql endpoint
|
||||
GraphQLPlaygroundConfig::new("/graphql"),
|
||||
)))
|
||||
.content_type(mime::HTML)
|
||||
.build())
|
||||
});
|
||||
|
||||
Ok(app.listen("127.0.0.1:8080").await?)
|
||||
}
|
||||
```
|
||||
|
||||
## Manually handle the request
|
||||
|
||||
If you want to manually handle the request, for example to read a header, you can skip `async_graphql_tide::endpoint` and use `receive_request` and `respond` functions instead.
|
||||
|
||||
```rust
|
||||
app.at("/graphql").post(move |req: tide::Request<()>| {
|
||||
let schema = schema.clone();
|
||||
async move {
|
||||
let req = async_graphql_tide::receive_request(req).await?;
|
||||
async_graphql_tide::respond(schema.execute(req).await)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
[https://github.com/async-graphql/examples/tree/master/tide](https://github.com/async-graphql/examples/tree/master/tide)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
For `Async-graphql-warp`, two `Filter` integrations are provided: `graphql` and `graphql_subscription`.
|
||||
|
||||
The `graphql` filter is used for execution `Query` and `Mutation` requests. It always asks for the POST method and outputs a `async_graphql::Schema` and `async_graphql::Request`.
|
||||
The `graphql` filter is used for execution `Query` and `Mutation` requests. It extracts GraphQL request and outputs `async_graphql::Schema` and `async_graphql::Request`.
|
||||
You can combine other filters later, or directly call `Schema::execute` to execute the query.
|
||||
|
||||
`graphql_subscription` is used to implement WebSocket subscriptions. It outputs `warp::Reply`.
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
- [扩展如何工作](extensions_inner_working.md)
|
||||
- [可用的扩展列表](extensions_available.md)
|
||||
- [集成到WebServer](integrations.md)
|
||||
- [Poem](integrations_to_poem.md)
|
||||
- [Warp](integrations_to_warp.md)
|
||||
- [Actix-web](integrations_to_actix_web.md)
|
||||
- [Tide](integrations_to_tide.md)
|
||||
- [高级主题](advanced_topics.md)
|
||||
- [自定义标量](custom_scalars.md)
|
||||
- [优化查询(解决N+1问题)](dataloader.md)
|
||||
|
|
|
@ -85,4 +85,15 @@ impl MyObj {
|
|||
self.a + self.b
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## 同时用于输入和输出
|
||||
|
||||
```rust
|
||||
#[derive(SimpleObject, InputObject)]
|
||||
#[graphql(input_name = "MyObjInput")] // 注意: 你必须用input_name属性为输入类型定义一个新的名称,否则将产生一个运行时错误。
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# 字段守卫(Field Guard)
|
||||
|
||||
您可以在给`Object`的字段定义`守卫`。 这允许在运行字段的代码逻辑之前添加检查。
|
||||
义`守卫`由你预先定义的规则组成。 规则是一种实现`Guard`特质的结构。
|
||||
你可以为`Object`, `SimpleObject`, `ComplexObject`和`Subscription`的字段定义`守卫`,它将在调用字段的 Resolver 函数前执行,如果失败则返回一个错误。
|
||||
|
||||
```rust
|
||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||
enum Role {
|
||||
|
@ -13,6 +13,12 @@ struct RoleGuard {
|
|||
role: Role,
|
||||
}
|
||||
|
||||
impl RoleGuard {
|
||||
fn new(role: Role) -> Self {
|
||||
Self { role }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Guard for RoleGuard {
|
||||
async fn check(&self, ctx: &Context<'_>) -> Result<()> {
|
||||
|
@ -25,44 +31,54 @@ impl Guard for RoleGuard {
|
|||
}
|
||||
```
|
||||
|
||||
一旦定义了规则,就可以在`guard`字段属性中使用它。
|
||||
此属性支持4个运算符创建复杂的规则:
|
||||
|
||||
-`and`:在两个规则之间执行`与`运算。 (如果一个规则返回错误,则`and`运算符将返回错误。如果两个规则均返回错误,则将是第一个返回的错误)。
|
||||
|
||||
-`or`:在两个规则之间执行`或`运算。 (如果两个规则都返回错误,则返回的错误是第一个)
|
||||
|
||||
-`chain`:采用一组规则并运行它们,直到返回错误或如果所有规则都通过则返回`Ok`。
|
||||
|
||||
-`race`:采用一组规则并运行它们,直到其中一个返回`Ok`。
|
||||
用`guard`属性使用它:
|
||||
|
||||
```rust
|
||||
#[derive(SimpleObject)]
|
||||
struct Query {
|
||||
#[graphql(guard(RoleGuard(role = "Role::Admin")))]
|
||||
value: i32,
|
||||
#[graphql(guard(and(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#)
|
||||
)))]
|
||||
/// 只允许Admin访问
|
||||
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
|
||||
value1: i32,
|
||||
/// 允许Admin或者Guest访问
|
||||
#[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")]
|
||||
value2: i32,
|
||||
#[graphql(guard(or(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#)
|
||||
)))]
|
||||
value3: i32,
|
||||
#[graphql(guard(chain(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#),
|
||||
AgeGuard(age = r#"21"#)
|
||||
)))]
|
||||
value4: i32,
|
||||
#[graphql(guard(race(
|
||||
RoleGuard(role = "Role::Admin"),
|
||||
UserGuard(username = r#""test""#),
|
||||
AgeGuard(age = r#"21"#)
|
||||
)))]
|
||||
value5: i32,
|
||||
}
|
||||
```
|
||||
|
||||
## 从参数中获取值
|
||||
|
||||
有时候守卫需要从字段参数中获取值,你需要像下面这样在创建守卫时传递该参数值:
|
||||
|
||||
```rust
|
||||
struct EqGuard {
|
||||
expect: i32,
|
||||
actual: i32,
|
||||
}
|
||||
|
||||
impl EqGuard {
|
||||
fn new(expect: i32, actual: i32) -> Self {
|
||||
Self { expect, actual }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Guard for EqGuard {
|
||||
async fn check(&self, _ctx: &Context<'_>) -> Result<()> {
|
||||
if self.expect != self.actual {
|
||||
Err("Forbidden".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
#[graphql(guard = "EqGuard::new(100, value)")]
|
||||
async fn get(&self, value: i32) -> i32 {
|
||||
value
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,56 +1,40 @@
|
|||
# 输入值校验器
|
||||
|
||||
字段的参数,[输入对象(InputObject)](define_input_object.md)的字段在GraphQL中称之为`Input Value`,如果给出的参数值类型不匹配,查询会报类型不匹配错误。但有时候我们希望给特定类型的值更多的限制,例如我们希望一个参数总是一个格式合法的Email地址,我们可以通过[自定义标量](custom_scalars.md)来解决,但这种类型如果很多,而且我们希望能够进行自由组合,比如一个`String`类型参数,我们希望它既可以是Email地址,也可以是一个手机号码,自定义标量就特别麻烦。`Async-graphql`提供了输入值校验器来解决这个问题。
|
||||
`Async-graphql`内置了一些常用的校验器,你可以在对象字段的参数或者`InputObject`的字段上使用它们。
|
||||
|
||||
输入值校验器可以通过`and`和`or`操作符自由组合。
|
||||
|
||||
下面时一个参数校验器,它校验`String`类型的参数`a`,必须是一个合法Email或者MAC地址:
|
||||
- **maximum=N** 指定数字不能大于N
|
||||
- **minimum=N** 指定数字不能小于N
|
||||
- **multiple_of=N** 指定数字必须是N的倍数
|
||||
- **max_items=N** 指定列表的长度不能大于N
|
||||
- **min_items=N** 指定列表的长度不能小于N
|
||||
- **max_length=N** 字符串的长度不能大于N
|
||||
- **min_length=N** 字符串的长度不能小于N
|
||||
|
||||
```rust
|
||||
use async_graphql::*;
|
||||
use async_graphql::validators::{Email, MAC};
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn input(#[graphql(validator(or(Email, MAC(colon = "false"))))] a: String) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子校验`i32`类型的参数`a`必须大于10,并且小于100,或者等于0:
|
||||
|
||||
```rust
|
||||
use async_graphql::*;
|
||||
use async_graphql::validators::{IntGreaterThan, IntLessThan, IntEqual};
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn input(#[graphql(validator(
|
||||
or(
|
||||
and(IntGreaterThan(value = "10"), IntLessThan(value = "100")),
|
||||
IntEqual(value = "0")
|
||||
)))] a: String) {
|
||||
/// name参数的长度必须大于等于5,小于等于10
|
||||
async fn input(#[graphql(validator(min_length = 5, max_length = 10))] name: String) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 校验列表成员
|
||||
|
||||
你可以用`list`操作符表示内部的校验器作用于一个列表内的所有成员:
|
||||
你可以打开`list`属性表示校验器作用于一个列表内的所有成员:
|
||||
|
||||
```rust
|
||||
use async_graphql::*;
|
||||
use async_graphql::validators::{Email};
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn input(#[graphql(validator(list(Email)))] emails: Vec<String>) {
|
||||
async fn input(#[graphql(validator(list, max_length = 10))] names: Vec<String>) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -58,25 +42,37 @@ impl Query {
|
|||
## 自定义校验器
|
||||
|
||||
```rust
|
||||
struct MustBeZero {}
|
||||
struct MyValidator {
|
||||
expect: i32,
|
||||
}
|
||||
|
||||
impl InputValueValidator for MustBeZero {
|
||||
fn is_valid(&self, value: &Value) -> Result<(), String> {
|
||||
if let Value::Int(n) = value {
|
||||
if n.as_i64().unwrap() != 0 {
|
||||
// 校验失败
|
||||
Err(format!(
|
||||
"the value is {}, but must be zero",
|
||||
n.as_i64().unwrap(),
|
||||
))
|
||||
} else {
|
||||
// 校验通过
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
// 类型不匹配,直接返回None,内置校验器会发现这个错误
|
||||
impl MyValidator {
|
||||
pub fn new(n: i32) -> Self {
|
||||
MyValidator { expect: n }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CustomValidator<i32> for MyValidator {
|
||||
async fn check(&self, _ctx: &Context<'_>, value: &i32) -> Result<(), String> {
|
||||
if *value == self.expect {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("expect 100, actual {}", value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
/// n的值必须等于100
|
||||
async fn value(
|
||||
&self,
|
||||
#[graphql(validator(custom = "MyValidator::new(100)"))] n: i32,
|
||||
) -> i32 {
|
||||
n
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
- Poem [async-graphql-poem](https://crates.io/crates/async-graphql-poem)
|
||||
- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web)
|
||||
- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp)
|
||||
- Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide)
|
||||
- Axum [async-graphql-axum](https://crates.io/crates/async-graphql-axum)
|
||||
- Rocket [async-graphql-rocket](https://crates.io/crates/async-graphql-rocket)
|
||||
|
||||
**即使你目前使用的Web Server不在上面的列表中,自己实现类似的功能也相当的简单。**
|
||||
**即使你目前使用的Web Server不在上面的列表中,自己实现类似的功能也相当的简单。**
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Actix-web
|
||||
|
||||
`Async-graphql-actix-web`提供实现了`actix_web::FromRequest`的`Request`,它其实是`async_graphql::Request`的包装,你可以调用`Request::into_inner`把它转换成一个`async_graphql::Request`。
|
||||
`Async-graphql-actix-web`提供了`GraphQLRequest`提取器用于提取`GraphQL`请求,和`GraphQLResponse`用于输出`GraphQL`响应。
|
||||
|
||||
`WSSubscription`是一个支持Web Socket订阅的Actor。
|
||||
`GraphQLSubscription`用于创建一个支持Web Socket订阅的Actor。
|
||||
|
||||
## 请求例子
|
||||
|
||||
|
@ -11,11 +11,10 @@
|
|||
```rust
|
||||
async fn index(
|
||||
schema: web::Data<Schema>,
|
||||
request: async_graphql_actix_web::Request,
|
||||
) -> web::Json<Response> {
|
||||
web::Json(Response(schema.execute(request.into_inner()).await)
|
||||
request: GraphQLRequest,
|
||||
) -> web::Json<GraphQLResponse> {
|
||||
web::Json(schema.execute(request.into_inner()).await.into())
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 订阅例子
|
||||
|
@ -26,7 +25,7 @@ async fn index_ws(
|
|||
req: HttpRequest,
|
||||
payload: web::Payload,
|
||||
) -> Result<HttpResponse> {
|
||||
WSSubscription::start(Schema::clone(&*schema), &req, payload)
|
||||
GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Poem
|
||||
|
||||
## 请求例子
|
||||
|
||||
```rust
|
||||
use poem::Route;
|
||||
use async_graphql_poem::GraphQL;
|
||||
|
||||
let app = Route::new()
|
||||
.at("/ws", GraphQL::new(schema));
|
||||
```
|
||||
|
||||
## 订阅例子
|
||||
|
||||
```rust
|
||||
use poem::Route;
|
||||
use async_graphql_poem::GraphQLSubscription;
|
||||
|
||||
let app = Route::new()
|
||||
.at("/ws", get(GraphQLSubscription::new(schema)));
|
||||
```
|
||||
|
||||
## 更多例子
|
||||
|
||||
[https://github.com/async-graphql/examples/tree/master/poem](https://github.com/async-graphql/examples/tree/master/poem)
|
|
@ -1,73 +0,0 @@
|
|||
# Tide
|
||||
|
||||
`async_graphql_tide` 提供一个 trait [tide::Endpoint](https://docs.rs/tide/0.15.0/tide/trait.Endpoint.html) 的实现。如果您想手动处理请求的话,它还提供了 `receive_request` 和 `respond` 函数来将一个 Tide 请求转换为一个 GraphQL 请求,并返回 `tide::Response`。
|
||||
|
||||
## Request example
|
||||
|
||||
当创建你的 `tide` 服务器时,你需要将 `Schema` 传递给 `async_graphql_tide::endpoint` 。请注意,你需要启用 `async-std` 中的 feature `attributes` 才能使这个例子正常工作。
|
||||
|
||||
```rust
|
||||
use async_graphql::{
|
||||
http::{playground_source, GraphQLPlaygroundConfig},
|
||||
Context, EmptyMutation, EmptySubscription, Object, Schema, SimpleObject,
|
||||
};
|
||||
use tide::{http::mime, Body, Response, StatusCode};
|
||||
|
||||
#[derive(SimpleObject)]
|
||||
pub struct Demo {
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
pub struct QueryRoot;
|
||||
|
||||
#[Object]
|
||||
impl QueryRoot {
|
||||
async fn demo(&self, _ctx: &Context<'_>) -> Demo {
|
||||
Demo { id: 42 }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
let mut app = tide::new();
|
||||
|
||||
// create schema
|
||||
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish();
|
||||
|
||||
// add tide endpoint
|
||||
app.at("/graphql")
|
||||
.post(async_graphql_tide::endpoint(schema));
|
||||
|
||||
// enable graphql playground
|
||||
app.at("/").get(|_| async move {
|
||||
Ok(Response::builder(StatusCode::Ok)
|
||||
.body(Body::from_string(playground_source(
|
||||
// note that the playground needs to know
|
||||
// the path to the graphql endpoint
|
||||
GraphQLPlaygroundConfig::new("/graphql"),
|
||||
)))
|
||||
.content_type(mime::HTML)
|
||||
.build())
|
||||
});
|
||||
|
||||
Ok(app.listen("127.0.0.1:8080").await?)
|
||||
}
|
||||
```
|
||||
|
||||
## Manually handle the request
|
||||
|
||||
如果你想手动处理请求,例如读取头,你可以跳过 `async_graphql_tide::endpoint` 而使用 `receive_request` 和 `respond` 函数。
|
||||
|
||||
```rust
|
||||
app.at("/graphql").post(move |req: tide::Request<()>| {
|
||||
let schema = schema.clone();
|
||||
async move {
|
||||
let req = async_graphql_tide::receive_request(req).await?;
|
||||
async_graphql_tide::respond(schema.execute(req).await)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
[https://github.com/async-graphql/examples/tree/master/tide](https://github.com/async-graphql/examples/tree/master/tide)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
`Async-graphql-warp`提供了两个`Filter`,`graphql`和`graphql_subscription`。
|
||||
|
||||
`graphql`用于执行`Query`和`Mutation`请求,他总是要求POST方法,输出一个包含`async_graphql::Schema`和`async_graphql::Request的元组`,你可以在之后组合其它Filter,或者直接调用`Schema::execute`执行查询。
|
||||
`graphql`用于执行`Query`和`Mutation`请求,它提取GraphQL请求,然后输出一个包含`async_graphql::Schema`和`async_graphql::Request`元组,你可以在之后组合其它Filter,或者直接调用`Schema::execute`执行查询。
|
||||
|
||||
`graphql_subscription`用于实现基于Web Socket的订阅,它输出`warp::Reply`。
|
||||
|
||||
|
|
|
@ -162,6 +162,7 @@
|
|||
|
||||
mod base;
|
||||
mod error;
|
||||
mod guard;
|
||||
mod look_ahead;
|
||||
mod model;
|
||||
mod request;
|
||||
|
@ -175,7 +176,6 @@ pub mod context;
|
|||
#[cfg_attr(docsrs, doc(cfg(feature = "dataloader")))]
|
||||
pub mod dataloader;
|
||||
pub mod extensions;
|
||||
pub mod guard;
|
||||
pub mod http;
|
||||
pub mod resolver_utils;
|
||||
pub mod types;
|
||||
|
@ -213,6 +213,7 @@ pub use error::{
|
|||
Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult,
|
||||
ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult,
|
||||
};
|
||||
pub use guard::{Guard, GuardExt};
|
||||
pub use look_ahead::Lookahead;
|
||||
pub use registry::CacheControl;
|
||||
pub use request::{BatchRequest, Request};
|
||||
|
|
Loading…
Reference in New Issue