Add support `group` attribute to Object/SimpleObject/ComplexObject/Subscription macros. #838
This commit is contained in:
parent
954664bb72
commit
8909752107
|
@ -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`.
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
195
tests/guard.rs
195
tests/guard.rs
|
@ -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)")]
|
||||
|
|
Loading…
Reference in New Issue