Add ComplexObject macro. #355
This commit is contained in:
parent
6099e14562
commit
15036be792
|
@ -158,6 +158,8 @@ pub struct SimpleObject {
|
|||
#[darling(default)]
|
||||
pub dummy: bool,
|
||||
#[darling(default)]
|
||||
pub complex: bool,
|
||||
#[darling(default)]
|
||||
pub name: Option<String>,
|
||||
#[darling(default)]
|
||||
pub rename_fields: Option<RenameRule>,
|
||||
|
@ -584,3 +586,27 @@ pub struct NewType {
|
|||
#[darling(default)]
|
||||
pub internal: bool,
|
||||
}
|
||||
|
||||
#[derive(FromMeta, Default)]
|
||||
#[darling(default)]
|
||||
pub struct ComplexObject {
|
||||
pub internal: bool,
|
||||
pub name: Option<String>,
|
||||
pub rename_fields: Option<RenameRule>,
|
||||
pub rename_args: Option<RenameRule>,
|
||||
}
|
||||
|
||||
#[derive(FromMeta, Default)]
|
||||
#[darling(default)]
|
||||
pub struct ComplexObjectField {
|
||||
pub skip: bool,
|
||||
pub name: Option<String>,
|
||||
pub deprecation: Deprecation,
|
||||
pub cache_control: CacheControl,
|
||||
pub external: bool,
|
||||
pub provides: Option<String>,
|
||||
pub requires: Option<String>,
|
||||
pub guard: Option<Meta>,
|
||||
pub visible: Option<Visible>,
|
||||
pub complexity: Option<ComplexityType>,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::{Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeReference};
|
||||
|
||||
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget};
|
||||
use crate::output_type::OutputType;
|
||||
use crate::utils::{
|
||||
gen_deprecation, generate_default, generate_guards, generate_validator, get_cfg_attrs,
|
||||
get_crate_name, get_param_getter_ident, get_rustdoc, get_type_path_and_name,
|
||||
parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, visible_fn, GeneratorResult,
|
||||
};
|
||||
|
||||
pub fn generate(
|
||||
object_args: &args::ComplexObject,
|
||||
item_impl: &mut ItemImpl,
|
||||
) -> GeneratorResult<TokenStream> {
|
||||
let crate_name = get_crate_name(object_args.internal);
|
||||
let (self_ty, _) = get_type_path_and_name(item_impl.self_ty.as_ref())?;
|
||||
let generics = &item_impl.generics;
|
||||
let generics_params = &generics.params;
|
||||
let where_clause = &item_impl.generics.where_clause;
|
||||
|
||||
let mut resolvers = Vec::new();
|
||||
let mut schema_fields = Vec::new();
|
||||
|
||||
for item in &mut item_impl.items {
|
||||
if let ImplItem::Method(method) = item {
|
||||
let method_args: args::ObjectField =
|
||||
parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
|
||||
if method_args.skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
let field_name = method_args.name.clone().unwrap_or_else(|| {
|
||||
object_args
|
||||
.rename_fields
|
||||
.rename(method.sig.ident.unraw().to_string(), RenameTarget::Field)
|
||||
});
|
||||
let field_desc = get_rustdoc(&method.attrs)?
|
||||
.map(|s| quote! { ::std::option::Option::Some(#s) })
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
let field_deprecation = gen_deprecation(&method_args.deprecation, &crate_name);
|
||||
let external = method_args.external;
|
||||
let requires = match &method_args.requires {
|
||||
Some(requires) => quote! { ::std::option::Option::Some(#requires) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
let provides = match &method_args.provides {
|
||||
Some(provides) => quote! { ::std::option::Option::Some(#provides) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
let ty = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||
ReturnType::Default => {
|
||||
return Err(Error::new_spanned(
|
||||
&method.sig.output,
|
||||
"Resolver must have a return type",
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let cache_control = {
|
||||
let public = method_args.cache_control.is_public();
|
||||
let max_age = method_args.cache_control.max_age;
|
||||
quote! {
|
||||
#crate_name::CacheControl {
|
||||
public: #public,
|
||||
max_age: #max_age,
|
||||
}
|
||||
}
|
||||
};
|
||||
let cfg_attrs = get_cfg_attrs(&method.attrs);
|
||||
|
||||
let mut create_ctx = true;
|
||||
let mut args = Vec::new();
|
||||
|
||||
if method.sig.inputs.is_empty() {
|
||||
return Err(Error::new_spanned(
|
||||
&method.sig,
|
||||
"The self receiver must be the first parameter.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
for (idx, arg) in method.sig.inputs.iter_mut().enumerate() {
|
||||
if let FnArg::Receiver(receiver) = arg {
|
||||
if idx != 0 {
|
||||
return Err(Error::new_spanned(
|
||||
receiver,
|
||||
"The self receiver must be the first parameter.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
} else if let FnArg::Typed(pat) = arg {
|
||||
if idx == 0 {
|
||||
return Err(Error::new_spanned(
|
||||
pat,
|
||||
"The self receiver must be the first parameter.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
match (&*pat.pat, &*pat.ty) {
|
||||
(Pat::Ident(arg_ident), Type::Path(arg_ty)) => {
|
||||
args.push((
|
||||
arg_ident.clone(),
|
||||
arg_ty.clone(),
|
||||
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
|
||||
.unwrap_or_default(),
|
||||
));
|
||||
remove_graphql_attrs(&mut pat.attrs);
|
||||
}
|
||||
(arg, Type::Reference(TypeReference { elem, .. })) => {
|
||||
if let Type::Path(path) = elem.as_ref() {
|
||||
if idx != 1 || path.path.segments.last().unwrap().ident != "Context"
|
||||
{
|
||||
return Err(Error::new_spanned(
|
||||
arg,
|
||||
"Only types that implement `InputType` can be used as input arguments.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
create_ctx = false;
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::new_spanned(arg, "Invalid argument type.").into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if create_ctx {
|
||||
let arg = syn::parse2::<FnArg>(quote! { _: &#crate_name::Context<'_> }).unwrap();
|
||||
method.sig.inputs.insert(1, arg);
|
||||
}
|
||||
|
||||
let mut schema_args = Vec::new();
|
||||
let mut use_params = Vec::new();
|
||||
let mut get_params = Vec::new();
|
||||
|
||||
for (
|
||||
ident,
|
||||
ty,
|
||||
args::Argument {
|
||||
name,
|
||||
desc,
|
||||
default,
|
||||
default_with,
|
||||
validator,
|
||||
visible,
|
||||
..
|
||||
},
|
||||
) in &args
|
||||
{
|
||||
let name = name.clone().unwrap_or_else(|| {
|
||||
object_args
|
||||
.rename_args
|
||||
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
||||
});
|
||||
let desc = desc
|
||||
.as_ref()
|
||||
.map(|s| quote! {::std::option::Option::Some(#s)})
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
let default = generate_default(&default, &default_with)?;
|
||||
let schema_default = default
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
quote! {
|
||||
::std::option::Option::Some(::std::string::ToString::to_string(
|
||||
&<#ty as #crate_name::InputType>::to_value(&#value)
|
||||
))
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
|
||||
let validator = match &validator {
|
||||
Some(meta) => {
|
||||
let stream = generate_validator(&crate_name, meta)?;
|
||||
quote!(::std::option::Option::Some(#stream))
|
||||
}
|
||||
None => quote!(::std::option::Option::None),
|
||||
};
|
||||
|
||||
let visible = visible_fn(&visible);
|
||||
schema_args.push(quote! {
|
||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
||||
name: #name,
|
||||
description: #desc,
|
||||
ty: <#ty as #crate_name::Type>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
validator: #validator,
|
||||
visible: #visible,
|
||||
});
|
||||
});
|
||||
|
||||
let param_ident = &ident.ident;
|
||||
use_params.push(quote! { #param_ident });
|
||||
|
||||
let default = match default {
|
||||
Some(default) => {
|
||||
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
||||
}
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
// We're generating a new identifier,
|
||||
// so remove the 'r#` prefix if present
|
||||
let param_getter_name = get_param_getter_ident(&ident.ident.unraw().to_string());
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let #param_getter_name = || -> #crate_name::ServerResult<#ty> { ctx.param_value(#name, #default) };
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = #param_getter_name()?;
|
||||
});
|
||||
}
|
||||
|
||||
let schema_ty = ty.value_type();
|
||||
let visible = visible_fn(&method_args.visible);
|
||||
|
||||
let complexity = if let Some(complexity) = &method_args.complexity {
|
||||
match complexity {
|
||||
ComplexityType::Const(n) => {
|
||||
quote! { ::std::option::Option::Some(#crate_name::registry::ComplexityType::Const(#n)) }
|
||||
}
|
||||
ComplexityType::Fn(s) => {
|
||||
let (variables, expr) = parse_complexity_expr(s)?;
|
||||
let mut parse_args = Vec::new();
|
||||
for variable in variables {
|
||||
if let Some((
|
||||
ident,
|
||||
ty,
|
||||
args::Argument {
|
||||
name,
|
||||
default,
|
||||
default_with,
|
||||
..
|
||||
},
|
||||
)) = args
|
||||
.iter()
|
||||
.find(|(pat_ident, _, _)| pat_ident.ident == variable)
|
||||
{
|
||||
let default = match generate_default(&default, &default_with)? {
|
||||
Some(default) => {
|
||||
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
||||
}
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
let name = name.clone().unwrap_or_else(|| {
|
||||
object_args.rename_args.rename(
|
||||
ident.ident.unraw().to_string(),
|
||||
RenameTarget::Argument,
|
||||
)
|
||||
});
|
||||
parse_args.push(quote! {
|
||||
let #ident: #ty = __ctx.param_value(__variables_definition, __field, #name, #default)?;
|
||||
});
|
||||
}
|
||||
}
|
||||
quote! {
|
||||
Some(#crate_name::registry::ComplexityType::Fn(|__ctx, __variables_definition, __field, child_complexity| {
|
||||
#(#parse_args)*
|
||||
Ok(#expr)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { ::std::option::Option::None }
|
||||
};
|
||||
|
||||
schema_fields.push(quote! {
|
||||
#(#cfg_attrs)*
|
||||
fields.push((#field_name.to_string(), #crate_name::registry::MetaField {
|
||||
name: ::std::borrow::ToOwned::to_owned(#field_name),
|
||||
description: #field_desc,
|
||||
args: {
|
||||
let mut args = #crate_name::indexmap::IndexMap::new();
|
||||
#(#schema_args)*
|
||||
args
|
||||
},
|
||||
ty: <#schema_ty as #crate_name::Type>::create_type_info(registry),
|
||||
deprecation: #field_deprecation,
|
||||
cache_control: #cache_control,
|
||||
external: #external,
|
||||
provides: #provides,
|
||||
requires: #requires,
|
||||
visible: #visible,
|
||||
compute_complexity: #complexity,
|
||||
}));
|
||||
});
|
||||
|
||||
let field_ident = &method.sig.ident;
|
||||
if let OutputType::Value(inner_ty) = &ty {
|
||||
let block = &method.block;
|
||||
let new_block = quote!({
|
||||
{
|
||||
::std::result::Result::Ok(async move {
|
||||
let value:#inner_ty = #block;
|
||||
value
|
||||
}.await)
|
||||
}
|
||||
});
|
||||
method.block = syn::parse2::<Block>(new_block).expect("invalid block");
|
||||
method.sig.output =
|
||||
syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> })
|
||||
.expect("invalid result type");
|
||||
}
|
||||
|
||||
let resolve_obj = quote! {
|
||||
{
|
||||
let res = self.#field_ident(ctx, #(#use_params),*).await;
|
||||
res.map_err(|err| err.into_server_error().at(ctx.item.pos))?
|
||||
}
|
||||
};
|
||||
|
||||
let guard = match &method_args.guard {
|
||||
Some(meta_list) => generate_guards(&crate_name, meta_list)?,
|
||||
None => None,
|
||||
};
|
||||
|
||||
let guard = guard.map(|guard| {
|
||||
quote! {
|
||||
#guard.check(ctx).await
|
||||
.map_err(|err| err.into_server_error().at(ctx.item.pos))?;
|
||||
}
|
||||
});
|
||||
|
||||
resolvers.push(quote! {
|
||||
#(#cfg_attrs)*
|
||||
if ctx.item.node.name.node == #field_name {
|
||||
#(#get_params)*
|
||||
#guard
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
let res = #resolve_obj;
|
||||
return #crate_name::OutputType::resolve(&res, &ctx_obj, ctx.item).await.map(::std::option::Option::Some);
|
||||
}
|
||||
});
|
||||
|
||||
remove_graphql_attrs(&mut method.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#item_impl
|
||||
|
||||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::ComplexObject for #self_ty#generics_params #where_clause {
|
||||
fn fields(registry: &mut #crate_name::registry::Registry) -> ::std::vec::Vec<(::std::string::String, #crate_name::registry::MetaField)> {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
#(#schema_fields)*
|
||||
fields
|
||||
}
|
||||
|
||||
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
|
||||
#(#resolvers)*
|
||||
::std::result::Result::Ok(::std::option::Option::None)
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(expanded.into())
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
mod args;
|
||||
mod complex_object;
|
||||
mod description;
|
||||
mod r#enum;
|
||||
mod input_object;
|
||||
|
@ -51,6 +52,21 @@ pub fn derive_simple_object(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ComplexObject(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let object_args =
|
||||
match args::ComplexObject::from_list(&parse_macro_input!(args as AttributeArgs)) {
|
||||
Ok(object_args) => object_args,
|
||||
Err(err) => return TokenStream::from(err.write_errors()),
|
||||
};
|
||||
let mut item_impl = parse_macro_input!(input as ItemImpl);
|
||||
match complex_object::generate(&object_args, &mut item_impl) {
|
||||
Ok(expanded) => expanded,
|
||||
Err(err) => err.write_errors().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Enum, attributes(graphql))]
|
||||
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||
let enum_args = match args::Enum::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
|
||||
|
|
|
@ -151,6 +151,20 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
|
||||
let visible = visible_fn(&object_args.visible);
|
||||
|
||||
let mut concat_complex_fields = quote!();
|
||||
let mut complex_resolver = quote!();
|
||||
|
||||
if object_args.complex {
|
||||
concat_complex_fields = quote! {
|
||||
fields.extend(<Self as #crate_name::ComplexObject>::fields(registry));
|
||||
};
|
||||
complex_resolver = quote! {
|
||||
if let Some(value) = <Self as #crate_name::ComplexObject>::resolve_field(self, ctx).await? {
|
||||
return Ok(Some(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let expanded = if object_args.concretes.is_empty() {
|
||||
quote! {
|
||||
#[allow(clippy::all, clippy::pedantic)]
|
||||
|
@ -171,6 +185,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
fields: {
|
||||
let mut fields = #crate_name::indexmap::IndexMap::new();
|
||||
#(#schema_fields)*
|
||||
#concat_complex_fields
|
||||
fields
|
||||
},
|
||||
cache_control: #cache_control,
|
||||
|
@ -187,6 +202,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
impl #impl_generics #crate_name::resolver_utils::ContainerType for #ident #ty_generics #where_clause {
|
||||
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
|
||||
#(#resolvers)*
|
||||
#complex_resolver
|
||||
::std::result::Result::Ok(::std::option::Option::None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,3 +64,27 @@ pub struct YetAnotherObject {
|
|||
```
|
||||
|
||||
You can pass multiple generic types to `params()`, separated by a comma.
|
||||
|
||||
## Complex resolvers
|
||||
|
||||
Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few
|
||||
fields are calculated. Usually we use the `Object` macro to define such a GraphQL object.
|
||||
|
||||
But this can be done more beautifully with the `ComplexObject` macro. We can use the `SimpleObject` macro to define
|
||||
some simple fields, and use the `ComplexObject` macro to define some other fields that need to be calculated.
|
||||
|
||||
```rust
|
||||
#[derive(SimpleObject)]
|
||||
#[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required.
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
#[ComplexObject]
|
||||
impl MyObj {
|
||||
async fn c(&self) -> i32 {
|
||||
self.a + self.b
|
||||
}
|
||||
}
|
||||
```
|
|
@ -63,3 +63,26 @@ pub struct YetAnotherObject {
|
|||
```
|
||||
|
||||
你可以将多个通用类型传递给`params()`,并用逗号分隔。
|
||||
|
||||
## 复杂字段
|
||||
|
||||
有时GraphQL对象的大多数字段仅返回结构成员的值,但是少数字段需要计算。 通常我们使用`Object`宏来定义这样一个GraphQL对象。
|
||||
|
||||
用`ComplexObject`宏可以更漂亮的完成这件事,我们可以使用`SimpleObject`宏来定义
|
||||
一些简单的字段,并使用`ComplexObject`宏来定义其他一些需要计算的字段。
|
||||
|
||||
```rust
|
||||
#[derive(SimpleObject)]
|
||||
#[graphql(complex)] // 注意: 如果你希望ComplexObject宏生效,complex属性是必须的
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
#[ComplexObject]
|
||||
impl MyObj {
|
||||
async fn c(&self) -> i32 {
|
||||
self.a + self.b
|
||||
}
|
||||
}
|
||||
```
|
13
src/base.rs
13
src/base.rs
|
@ -4,9 +4,9 @@ use std::sync::Arc;
|
|||
use async_graphql_value::ConstValue;
|
||||
|
||||
use crate::parser::types::Field;
|
||||
use crate::registry::Registry;
|
||||
use crate::registry::{self, Registry};
|
||||
use crate::{
|
||||
registry, ContainerType, ContextSelectionSet, InputValueError, InputValueResult, Positioned,
|
||||
ContainerType, Context, ContextSelectionSet, InputValueError, InputValueResult, Positioned,
|
||||
Result, ServerResult, Value,
|
||||
};
|
||||
|
||||
|
@ -186,7 +186,6 @@ impl<T: OutputType + ?Sized> OutputType for Arc<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: InputType> InputType for Arc<T> {
|
||||
fn parse(value: Option<ConstValue>) -> InputValueResult<Self> {
|
||||
T::parse(value)
|
||||
|
@ -198,3 +197,11 @@ impl<T: InputType> InputType for Arc<T> {
|
|||
T::to_value(&self)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[async_trait::async_trait]
|
||||
pub trait ComplexObject {
|
||||
fn fields(registry: &mut registry::Registry) -> Vec<(String, registry::MetaField)>;
|
||||
|
||||
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>>;
|
||||
}
|
||||
|
|
79
src/lib.rs
79
src/lib.rs
|
@ -211,7 +211,8 @@ pub use async_graphql_value::{
|
|||
SerializerError, Variables,
|
||||
};
|
||||
pub use base::{
|
||||
Description, InputObjectType, InputType, InterfaceType, ObjectType, OutputType, Type, UnionType,
|
||||
ComplexObject, Description, InputObjectType, InputType, InterfaceType, ObjectType, OutputType,
|
||||
Type, UnionType,
|
||||
};
|
||||
pub use error::{
|
||||
Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult,
|
||||
|
@ -473,6 +474,82 @@ pub use async_graphql_derive::Object;
|
|||
/// ```
|
||||
pub use async_graphql_derive::SimpleObject;
|
||||
|
||||
/// Define a complex GraphQL object for SimpleObject's complex field resolver.
|
||||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html).*
|
||||
///
|
||||
/// Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few
|
||||
/// fields are calculated. Usually we use the `Object` macro to define such a GraphQL object.
|
||||
///
|
||||
/// But this can be done more beautifully with the `ComplexObject` macro. We can use the `SimpleObject` macro to define
|
||||
/// some simple fields, and use the `ComplexObject` macro to define some other fields that need to be calculated.
|
||||
///
|
||||
/// # Macro parameters
|
||||
///
|
||||
/// | 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 |
|
||||
///
|
||||
/// # Field parameters
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
/// | skip | Skip this field | bool | Y |
|
||||
/// | name | Field name | string | Y |
|
||||
/// | deprecation | Field deprecated | bool | Y |
|
||||
/// | deprecation | Field deprecation reason | string | Y |
|
||||
/// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y |
|
||||
/// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y |
|
||||
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
|
||||
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
|
||||
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | 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 |
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_graphql::*;
|
||||
///
|
||||
/// #[derive(SimpleObject)]
|
||||
/// #[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required.
|
||||
/// struct MyObj {
|
||||
/// a: i32,
|
||||
/// b: i32,
|
||||
/// }
|
||||
///
|
||||
/// #[ComplexObject]
|
||||
/// impl MyObj {
|
||||
/// async fn c(&self) -> i32 {
|
||||
/// self.a + self.b
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// async fn obj(&self) -> MyObj {
|
||||
/// MyObj { a: 10, b: 20 }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute("{ obj { a b c } }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
/// "obj": {
|
||||
/// "a": 10,
|
||||
/// "b": 20,
|
||||
/// "c": 30,
|
||||
/// },
|
||||
/// }));
|
||||
/// });
|
||||
/// ```
|
||||
pub use async_graphql_derive::ComplexObject;
|
||||
|
||||
/// Define a GraphQL enum
|
||||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_enum.html).*
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
use async_graphql::*;
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_complex_object() {
|
||||
#[derive(SimpleObject)]
|
||||
#[graphql(complex)]
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
#[ComplexObject]
|
||||
impl MyObj {
|
||||
async fn c(&self) -> i32 {
|
||||
self.a + self.b
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Interface)]
|
||||
#[graphql(
|
||||
field(name = "a", type = "&i32"),
|
||||
field(name = "b", type = "&i32"),
|
||||
field(name = "c", type = "i32")
|
||||
)]
|
||||
enum ObjInterface {
|
||||
MyObj(MyObj),
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn obj(&self) -> MyObj {
|
||||
MyObj { a: 10, b: 20 }
|
||||
}
|
||||
|
||||
async fn obj2(&self) -> ObjInterface {
|
||||
MyObj { a: 10, b: 20 }.into()
|
||||
}
|
||||
}
|
||||
|
||||
let query = "{ obj { a b c } obj2 { a b c } }";
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
assert_eq!(
|
||||
schema.execute(query).await.data,
|
||||
value!({
|
||||
"obj": {
|
||||
"a": 10,
|
||||
"b": 20,
|
||||
"c": 30,
|
||||
},
|
||||
"obj2": {
|
||||
"a": 10,
|
||||
"b": 20,
|
||||
"c": 30,
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue