Add support `group` attribute to Object/SimpleObject/ComplexObject/Subscription macros. #838

This commit is contained in:
Sunli 2022-03-14 09:19:27 +08:00
parent 954664bb72
commit 8909752107
11 changed files with 214 additions and 6 deletions

View File

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# [3.0.36] 2022-3-11
- Add support `group` attribute to Object/SimpleObject/ComplexObject/Subscription macros. [#838](https://github.com/async-graphql/async-graphql/issues/838)
# [3.0.35] 2022-3-11
- Make `HashMap` more generics for `InputOutput` and `OutputType`.

View File

@ -200,6 +200,8 @@ pub struct SimpleObject {
// for InputObject
#[darling(default)]
pub input_name: Option<String>,
#[darling(default)]
pub guard: Option<SpannedValue<String>>,
}
#[derive(FromMeta, Default)]
@ -229,6 +231,8 @@ pub struct Object {
pub serial: bool,
#[darling(multiple, rename = "concrete")]
pub concretes: Vec<ConcreteType>,
#[darling(default)]
pub guard: Option<SpannedValue<String>>,
}
pub enum ComplexityType {
@ -531,6 +535,8 @@ pub struct Subscription {
pub use_type_description: bool,
pub extends: bool,
pub visible: Option<Visible>,
#[darling(default)]
pub guard: Option<SpannedValue<String>>,
}
#[derive(FromMeta, Default)]
@ -559,7 +565,6 @@ pub struct SubscriptionField {
#[derive(FromField)]
pub struct MergedObjectField {
pub ident: Option<Ident>,
pub ty: Type,
}
@ -587,7 +592,6 @@ pub struct MergedObject {
#[derive(FromField)]
pub struct MergedSubscriptionField {
pub ident: Option<Ident>,
pub ty: Type,
}
@ -738,6 +742,7 @@ pub struct ComplexObject {
pub name: Option<String>,
pub rename_fields: Option<RenameRule>,
pub rename_args: Option<RenameRule>,
pub guard: Option<SpannedValue<String>>,
}
#[derive(FromMeta, Default)]

View File

@ -417,7 +417,7 @@ pub fn generate(
let guard_map_err = quote! {
.map_err(|err| err.into_server_error(ctx.item.pos))
};
let guard = match &method_args.guard {
let guard = match method_args.guard.as_ref().or(object_args.guard.as_ref()) {
Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?),
None => None,
};

View File

@ -562,7 +562,7 @@ pub fn generate(
let guard_map_err = quote! {
.map_err(|err| err.into_server_error(ctx.item.pos))
};
let guard = match &method_args.guard {
let guard = match method_args.guard.as_ref().or(object_args.guard.as_ref()) {
Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?),
None => None,
};

View File

@ -184,7 +184,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
let guard_map_err = quote! {
.map_err(|err| err.into_server_error(ctx.item.pos))
};
let guard = match &field.guard {
let guard = match field.guard.as_ref().or(object_args.guard.as_ref()) {
Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?),
None => None,
};

View File

@ -305,7 +305,7 @@ pub fn generate(
.with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))])
})
};
let guard = match &field.guard {
let guard = match field.guard.as_ref().or(subscription_args.guard.as_ref()) {
Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?),
None => None,
};

View File

@ -15,6 +15,7 @@ some simple fields, and use the `ComplexObject` macro to define some other field
| name | Object name | string | Y |
| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y |
| rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y |
| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y |
# Field attributes

View File

@ -18,6 +18,7 @@ All methods are converted to camelCase.
| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
| serial | Resolve each field sequentially. | bool | Y |
| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y |
| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y |
# Field attributes

View File

@ -16,6 +16,7 @@ Similar to `Object`, but defined on a structure that automatically generates get
| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html#generic-simpleobjects) | ConcreteType | Y |
| serial | Resolve each field sequentially. | bool | Y |
| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y |
# Field attributes

View File

@ -18,6 +18,7 @@ The filter function should be synchronous.
| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y |
| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
| use_type_description | Specifies that the description of the type is on the type declaration. [`Description`]()(derive.Description.html) | bool | Y |
| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y |
# Field attributes

View File

@ -346,6 +346,158 @@ pub async fn test_guard_use_params() {
);
}
#[tokio::test]
pub async fn test_guard_on_simple_object() {
#[derive(SimpleObject)]
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
struct Query {
value: i32,
}
let schema = Schema::new(Query { value: 100 }, EmptyMutation, EmptySubscription);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Admin))
.await
.data,
value!({"value": 100})
);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Guest))
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: "Forbidden".to_string(),
source: None,
locations: vec![Pos { line: 1, column: 3 }],
path: vec![PathSegment::Field("value".to_owned())],
extensions: None,
}]
);
}
#[tokio::test]
pub async fn test_guard_on_simple_object_field() {
#[derive(SimpleObject)]
#[graphql]
struct Query {
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
value: i32,
}
let schema = Schema::new(Query { value: 100 }, EmptyMutation, EmptySubscription);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Admin))
.await
.data,
value!({"value": 100})
);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Guest))
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: "Forbidden".to_string(),
source: None,
locations: vec![Pos { line: 1, column: 3 }],
path: vec![PathSegment::Field("value".to_owned())],
extensions: None,
}]
);
}
#[tokio::test]
pub async fn test_guard_on_object() {
struct Query;
#[Object(guard = "RoleGuard::new(Role::Admin)")]
impl Query {
async fn value(&self) -> i32 {
100
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Admin))
.await
.data,
value!({"value": 100})
);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Guest))
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: "Forbidden".to_string(),
source: None,
locations: vec![Pos { line: 1, column: 3 }],
path: vec![PathSegment::Field("value".to_owned())],
extensions: None,
}]
);
}
#[tokio::test]
pub async fn test_guard_on_object_field() {
struct Query;
#[Object]
impl Query {
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
async fn value(&self) -> i32 {
100
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Admin))
.await
.data,
value!({"value": 100})
);
let query = "{ value }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Guest))
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: "Forbidden".to_string(),
source: None,
locations: vec![Pos { line: 1, column: 3 }],
path: vec![PathSegment::Field("value".to_owned())],
extensions: None,
}]
);
}
#[tokio::test]
pub async fn test_guard_on_complex_object() {
#[derive(SimpleObject)]
@ -354,6 +506,49 @@ pub async fn test_guard_on_complex_object() {
value1: i32,
}
#[ComplexObject(guard = "RoleGuard::new(Role::Admin)")]
impl Query {
async fn value2(&self) -> i32 {
100
}
}
let schema = Schema::new(Query { value1: 10 }, EmptyMutation, EmptySubscription);
let query = "{ value2 }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Admin))
.await
.data,
value!({"value2": 100})
);
let query = "{ value2 }";
assert_eq!(
schema
.execute(Request::new(query).data(Role::Guest))
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: "Forbidden".to_string(),
source: None,
locations: vec![Pos { line: 1, column: 3 }],
path: vec![PathSegment::Field("value2".to_owned())],
extensions: None,
}]
);
}
#[tokio::test]
pub async fn test_guard_on_complex_object_field() {
#[derive(SimpleObject)]
#[graphql(complex)]
struct Query {
value1: i32,
}
#[ComplexObject]
impl Query {
#[graphql(guard = "RoleGuard::new(Role::Admin)")]