Add support flatten attribute for SimpleObject, ComplexObject and Object macros. #533

This commit is contained in:
Sunli 2022-01-18 10:33:07 +08:00
parent c6bb4c026c
commit 3217f7aecd
22 changed files with 646 additions and 288 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.22] 2022-1-11
- Add support `flatten` attribute for `SimpleObject`, `ComplexObject` and `Object` macros. [#533](https://github.com/async-graphql/async-graphql/issues/533)
## [3.0.21] 2022-1-11
- Add `Union` and `Interface` support for trait objects. [#780](https://github.com/async-graphql/async-graphql/issues/780)

View File

@ -265,6 +265,7 @@ pub struct ObjectField {
pub complexity: Option<ComplexityType>,
#[darling(default, multiple)]
pub derived: Vec<DerivedField>,
pub flatten: bool,
}
#[derive(FromMeta, Default, Clone)]
@ -698,6 +699,9 @@ pub struct ComplexObjectField {
pub guard: Option<SpannedValue<String>>,
pub visible: Option<Visible>,
pub complexity: Option<ComplexityType>,
#[darling(multiple)]
pub derived: Vec<DerivedField>,
pub flatten: bool,
}
#[derive(FromMeta, Default)]

View File

@ -33,7 +33,7 @@ pub fn generate(
let mut derived_impls = vec![];
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =
let method_args: args::ComplexObjectField =
parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
for derived in method_args.derived {
@ -116,12 +116,47 @@ pub fn generate(
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =
let method_args: args::ComplexObjectField =
parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
if method_args.skip {
remove_graphql_attrs(&mut method.attrs);
continue;
}
let cfg_attrs = get_cfg_attrs(&method.attrs);
if method_args.flatten {
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
return Err(Error::new_spanned(
&method.sig.output,
"Flatten resolver must have a return type",
)
.into())
}
};
let ty = ty.value_type();
let ident = &method.sig.ident;
schema_fields.push(quote! {
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::ObjectType);
<#ty>::create_type_info(registry);
if let #crate_name::registry::MetaType::Object { fields: obj_fields, .. } =
registry.create_fake_output_type::<#ty>() {
fields.extend(obj_fields);
}
});
resolvers.push(quote! {
#(#cfg_attrs)*
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident().await, ctx).await? {
return ::std::result::Result::Ok(std::option::Option::Some(value));
}
});
remove_graphql_attrs(&mut method.attrs);
continue;
}
let field_name = method_args.name.clone().unwrap_or_else(|| {
object_args
@ -151,7 +186,6 @@ pub fn generate(
}
}
};
let cfg_attrs = get_cfg_attrs(&method.attrs);
let args = extract_input_args(&crate_name, method)?;
let ty = match &method.sig.output {

View File

@ -258,6 +258,41 @@ pub fn generate(
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(&method, "Must be asynchronous").into());
}
let cfg_attrs = get_cfg_attrs(&method.attrs);
if method_args.flatten {
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
return Err(Error::new_spanned(
&method.sig.output,
"Flatten resolver must have a return type",
)
.into())
}
};
let ty = ty.value_type();
let ident = &method.sig.ident;
schema_fields.push(quote! {
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::ObjectType);
<#ty>::create_type_info(registry);
if let #crate_name::registry::MetaType::Object { fields: obj_fields, .. } =
registry.create_fake_output_type::<#ty>() {
fields.extend(obj_fields);
}
});
resolvers.push(quote! {
#(#cfg_attrs)*
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident().await, ctx).await? {
return ::std::result::Result::Ok(std::option::Option::Some(value));
}
});
remove_graphql_attrs(&mut method.attrs);
continue;
}
let field_name = method_args.name.clone().unwrap_or_else(|| {
object_args
@ -287,7 +322,6 @@ pub fn generate(
}
}
};
let cfg_attrs = get_cfg_attrs(&method.attrs);
let args = extract_input_args(&crate_name, method)?;
let mut schema_args = Vec::new();

View File

@ -153,6 +153,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
let visible = visible_fn(&field.visible);
if !field.flatten {
schema_fields.push(quote! {
fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField {
name: ::std::borrow::ToOwned::to_owned(#field_name),
@ -168,6 +169,16 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
compute_complexity: ::std::option::Option::None,
});
});
} else {
schema_fields.push(quote! {
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::ObjectType);
#ty::create_type_info(registry);
if let #crate_name::registry::MetaType::Object { fields: obj_fields, .. } =
registry.create_fake_output_type::<#ty>() {
fields.extend(obj_fields);
}
});
}
let guard_map_err = quote! {
.map_err(|err| err.into_server_error(ctx.item.pos))
@ -203,15 +214,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
false => quote! { #ty },
};
getters.push(
quote! {
if !field.flatten {
getters.push(quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#ty> {
::std::result::Result::Ok(#block)
}
}
);
});
resolvers.push(quote! {
if ctx.item.node.name.node == #field_name {
@ -224,6 +234,13 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
}
});
} else {
resolvers.push(quote! {
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident, ctx).await? {
return ::std::result::Result::Ok(std::option::Option::Some(value));
}
});
}
}
if !object_args.fake && resolvers.is_empty() {

View File

@ -11,7 +11,7 @@ some simple fields, and use the `ComplexObject` macro to define some other field
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| 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 |
@ -19,7 +19,7 @@ some simple fields, and use the `ComplexObject` macro to define some other field
# Field attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|
| skip | Skip this field | bool | Y |
| name | Field name | string | Y |
| desc | Field description | string | Y |
@ -35,11 +35,12 @@ some simple fields, and use the `ComplexObject` macro to define some other field
| complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
| complexity | Custom field complexity. | string | Y |
| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
| flatten | Similar to serde (flatten) | boolean | Y |
# Field argument attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|------------ |----------|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
| name | Argument name | string | Y |
| desc | Argument description | string | Y |
| default | Use `Default::default` for default value | none | Y |

View File

@ -5,7 +5,7 @@ Define a directive for query.
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Object name | string | Y |
| 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 |
@ -16,7 +16,7 @@ Define a directive for query.
# Directive attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|------------ |----------|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
| name | Argument name | string | Y |
| desc | Argument description | string | Y |
| default | Use `Default::default` for default value | none | Y |

View File

@ -5,7 +5,7 @@ Define a GraphQL enum
# Macro attributes
| Attribute | description | Type | Optional |
|--------------|---------------------------|----------|----------|
|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Enum name | string | Y |
| rename_items | 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 |
| remote | Derive a remote enum | string | Y |
@ -15,7 +15,7 @@ Define a GraphQL enum
# Item attributes
| Attribute | description | Type | Optional |
|-------------|---------------------------|----------|----------|
|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Item name | string | Y |
| deprecation | Item deprecated | bool | Y |
| deprecation | Item deprecation reason | string | Y |

View File

@ -5,7 +5,7 @@ Define a GraphQL input object
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| 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 |
| 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 |
@ -14,7 +14,7 @@ Define a GraphQL input object
# Field attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|-------------|----------|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
| name | Field name | string | Y |
| default | Use `Default::default` for default value | none | Y |
| default | Argument default value | literal | Y |

View File

@ -5,7 +5,7 @@ Define a GraphQL interface
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|----------|
| 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 |
@ -17,7 +17,7 @@ Define a GraphQL interface
# Field attributes
| Attribute | description | Type | Optional |
|-------------|---------------------------|----------|----------|
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|----------|
| name | Field name | string | N |
| type | Field type | string | N |
| method | Rust resolver method name. If specified, `name` will not be camelCased in schema definition | string | Y |
@ -34,7 +34,7 @@ Define a GraphQL interface
# Field argument attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|-------------|----------|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
| name | Argument name | string | N |
| type | Argument type | string | N |
| desc | Argument description | string | Y |

View File

@ -5,7 +5,7 @@ Define a merged object with multiple object types.
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|
| name | Object name | string | Y |
| cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
| extends | Add fields to an entity that's defined in another service | bool | Y |

View File

@ -5,7 +5,7 @@ Define a merged subscription with multiple subscription types.
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Object name | string | Y |
| extends | Add fields to an entity that's defined in another service | bool | Y |
| 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 |

View File

@ -5,7 +5,7 @@ It also implements `From<InnerType>` and `Into<InnerType>`.
# Macro attributes
| Attribute | description | Type | Optional |
|-------------|---------------------------|----------|----------|
|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | string | Y |
| name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | bool | Y |
| visible(Only valid for new scalars) | 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 |

View File

@ -7,7 +7,7 @@ All methods are converted to camelCase.
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|
| 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 |
@ -21,7 +21,7 @@ All methods are converted to camelCase.
# Field attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|
| skip | Skip this field | bool | Y |
| name | Field name | string | Y |
| desc | Field description | string | Y |
@ -37,11 +37,12 @@ All methods are converted to camelCase.
| complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
| complexity | Custom field complexity. | string | Y |
| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
| flatten | Similar to serde (flatten) | boolean | Y |
# Field argument attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|------------ |----------|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
| name | Argument name | string | Y |
| desc | Argument description | string | Y |
| default | Use `Default::default` for default value | none | Y |
@ -56,7 +57,7 @@ All methods are converted to camelCase.
# Derived argument attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|------------ |----------|
|-----------|------------------------------------------------|--------|----------|
| name | Generated derived field name | string | N |
| into | Type to derived an into | string | Y |
| with | Function to apply to manage advanced use cases | string | Y |

View File

@ -3,6 +3,6 @@ Define a Scalar
# Macro attributes
| Attribute | description | Type | Optional |
|-------------|---------------------------|----------|----------|
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Scalar name | string | Y |
| specified_by_url | Provide a specification URL for this scalar type, it must link to a human-readable specification of the data format, serialization and coercion rules for this scalar. | string | Y |

View File

@ -7,7 +7,7 @@ Similar to `Object`, but defined on a structure that automatically generates get
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|
| 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 |
| cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
@ -20,7 +20,7 @@ Similar to `Object`, but defined on a structure that automatically generates get
# Field attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|
| skip | Skip this field | bool | Y |
| name | Field name | string | Y |
| deprecation | Field deprecated | bool | Y |
@ -34,11 +34,12 @@ Similar to `Object`, but defined on a structure that automatically generates get
| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y |
| 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 |
| flatten | Similar to serde (flatten) | boolean | Y |
# Derived attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|------------ |----------|
|-----------|------------------------------------------------|--------|----------|
| name | Generated derived field name | string | N |
| into | Type to derived an into | string | Y |
| owned | Field resolver return a ownedship value | bool | Y |

View File

@ -10,7 +10,7 @@ The filter function should be synchronous.
# Macro attributes
| Attribute | description | Type | Optional |
|---------------|---------------------------|----------|----------|
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| 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 |
@ -22,7 +22,7 @@ The filter function should be synchronous.
# Field attributes
| Attribute | description | Type | Optional |
|-------------|---------------------------|----------|----------|
|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Field name | string | Y |
| deprecation | Field deprecated | bool | Y |
| deprecation | Field deprecation reason | string | Y |
@ -36,7 +36,7 @@ The filter function should be synchronous.
# Field argument attributes
| Attribute | description | Type | Optional |
|--------------|------------------------------------------|------------ |----------|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
| name | Argument name | string | Y |
| desc | Argument description | string | Y |
| default | Use `Default::default` for default value | none | Y |

View File

@ -5,7 +5,7 @@ Define a GraphQL union
# Macro attributes
| Attribute | description | Type | Optional |
|-------------|---------------------------|----------|----------|
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
| name | Object name | string | Y |
| 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 |

View File

@ -8,7 +8,8 @@ use indexmap::IndexMap;
use crate::extensions::ResolveInfo;
use crate::parser::types::Selection;
use crate::{
Context, ContextBase, ContextSelectionSet, Name, OutputType, ServerError, ServerResult, Value,
Context, ContextBase, ContextSelectionSet, Error, Name, OutputType, ServerError, ServerResult,
Value,
};
/// Represents a GraphQL container object.
@ -84,6 +85,23 @@ impl<T: ContainerType + ?Sized> ContainerType for Box<T> {
}
}
#[async_trait::async_trait]
impl<T: ContainerType, E: Into<Error> + Send + Sync + Clone> ContainerType for Result<T, E> {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
match self {
Ok(value) => T::resolve_field(value, ctx).await,
Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))),
}
}
async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
match self {
Ok(value) => T::find_entity(value, ctx, params).await,
Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))),
}
}
}
/// Resolve an container by executing each of the fields concurrently.
pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
ctx: &ContextSelectionSet<'a>,

View File

@ -300,3 +300,131 @@ pub async fn test_complex_object_with_generic_concrete_type() {
})
);
}
#[tokio::test]
async fn test_flatten() {
#[derive(SimpleObject)]
struct A {
a: i32,
b: i32,
}
#[derive(SimpleObject)]
#[graphql(complex)]
struct B {
#[graphql(skip)]
a: A,
c: i32,
}
#[ComplexObject]
impl B {
#[graphql(flatten)]
async fn a(&self) -> &A {
&self.a
}
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> B {
B {
a: A { a: 100, b: 200 },
c: 300,
}
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = "{ __type(name: \"B\") { fields { name } } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"__type": {
"fields": [
{"name": "c"},
{"name": "a"},
{"name": "b"}
]
}
})
);
let query = "{ obj { a b c } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 100,
"b": 200,
"c": 300,
}
})
);
}
#[tokio::test]
async fn test_flatten_with_result() {
#[derive(SimpleObject)]
struct A {
a: i32,
b: i32,
}
#[derive(SimpleObject)]
#[graphql(complex)]
struct B {
#[graphql(skip)]
a: A,
c: i32,
}
#[ComplexObject]
impl B {
#[graphql(flatten)]
async fn a(&self) -> FieldResult<&A> {
Ok(&self.a)
}
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> B {
B {
a: A { a: 100, b: 200 },
c: 300,
}
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = "{ __type(name: \"B\") { fields { name } } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"__type": {
"fields": [
{"name": "c"},
{"name": "a"},
{"name": "b"}
]
}
})
);
let query = "{ obj { a b c } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 100,
"b": 200,
"c": 300,
}
})
);
}

60
tests/object.rs Normal file
View File

@ -0,0 +1,60 @@
use async_graphql::*;
#[tokio::test]
async fn test_flatten() {
#[derive(SimpleObject)]
struct A {
a: i32,
b: i32,
}
struct B;
#[Object]
impl B {
#[graphql(flatten)]
async fn a(&self) -> A {
A { a: 100, b: 200 }
}
async fn c(&self) -> i32 {
300
}
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> B {
B
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = "{ __type(name: \"B\") { fields { name } } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"__type": {
"fields": [
{"name": "a"},
{"name": "b"},
{"name": "c"}
]
}
})
);
let query = "{ obj { a b c } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 100,
"b": 200,
"c": 300,
}
})
);
}

56
tests/simple_object.rs Normal file
View File

@ -0,0 +1,56 @@
use async_graphql::*;
#[tokio::test]
async fn test_flatten() {
#[derive(SimpleObject)]
struct A {
a: i32,
b: i32,
}
#[derive(SimpleObject)]
struct B {
#[graphql(flatten)]
a: A,
c: i32,
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> B {
B {
a: A { a: 100, b: 200 },
c: 300,
}
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let query = "{ __type(name: \"B\") { fields { name } } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"__type": {
"fields": [
{"name": "a"},
{"name": "b"},
{"name": "c"}
]
}
})
);
let query = "{ obj { a b c } }";
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 100,
"b": 200,
"c": 300,
}
})
);
}