feat: add derived for simple object & complex object

This commit is contained in:
Miaxos 2021-10-25 12:23:12 +00:00
parent e9e73f489d
commit b7232cea65
3 changed files with 157 additions and 1 deletions

View File

@ -1,7 +1,13 @@
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use std::iter::FromIterator;
use std::str::FromStr;
use syn::ext::IdentExt;
use syn::{Block, Error, ImplItem, ItemImpl, ReturnType};
use syn::{
punctuated::Punctuated, Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Token, Type,
TypeReference,
};
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget};
use crate::output_type::OutputType;
@ -23,6 +29,86 @@ pub fn generate(
let mut resolvers = Vec::new();
let mut schema_fields = Vec::new();
// Computation of the derivated fields
let mut derived_impls = vec![];
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();
for derived in method_args.derived {
if derived.name.is_some() && derived.into.is_some() {
let base_function_name = &method.sig.ident;
let name = derived.name.unwrap();
let into = Type::Verbatim(
proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(),
);
let mut new_impl = method.clone();
new_impl.sig.ident = name;
new_impl.sig.output =
syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#into> })
.expect("invalid result type");
let should_create_context = new_impl
.sig
.inputs
.iter()
.nth(1)
.map(|x| {
if let FnArg::Typed(pat) = x {
if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty {
if let Type::Path(path) = elem.as_ref() {
return path.path.segments.last().unwrap().ident
!= "Context";
}
}
};
true
})
.unwrap_or(true);
if should_create_context {
let arg_ctx = syn::parse2::<FnArg>(quote! { ctx: &Context<'_> })
.expect("invalid arg type");
new_impl.sig.inputs.insert(1, arg_ctx);
}
let other_atts: Punctuated<Ident, Token![,]> = Punctuated::from_iter(
new_impl
.sig
.inputs
.iter()
.filter_map(|x| match x {
FnArg::Typed(pat) => match &*pat.pat {
Pat::Ident(ident) => Some(Ok(ident.ident.clone())),
_ => Some(Err(Error::new_spanned(
&pat,
"Must be a simple argument",
)
.into())),
},
FnArg::Receiver(_) => None,
})
.collect::<Result<Vec<Ident>, Error>>()?
.into_iter(),
);
let new_block = quote!({
{
::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into())
}
});
new_impl.block = syn::parse2::<Block>(new_block).expect("invalid block");
derived_impls.push(ImplItem::Method(new_impl));
}
}
}
}
item_impl.items.append(&mut derived_impls);
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =

View File

@ -515,6 +515,7 @@ pub use async_graphql_derive::SimpleObject;
/// | name | Field name | string | Y |
/// | deprecation | Field deprecated | bool | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | 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 |
@ -524,6 +525,13 @@ pub use async_graphql_derive::SimpleObject;
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
///
/// # Derived argument parameters
///
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|------------ |----------|
/// | name | Generated derived field name | string | N |
/// | into | Type to derived an into | string | Y |
///
/// # Examples
///
/// ```rust

View File

@ -101,3 +101,65 @@ pub async fn test_derived_field_simple_object() {
})
);
}
#[tokio::test]
pub async fn test_derived_field_complex_object() {
use serde::{Deserialize, Serialize};
#[derive(SimpleObject)]
#[graphql(complex)]
struct MyObj {
a: i32,
#[graphql(owned, derived(name = "f", into = "ValueDerived"))]
b: i32,
}
#[derive(Serialize, Deserialize)]
struct ValueDerived(String);
scalar!(ValueDerived);
impl From<i32> for ValueDerived {
fn from(value: i32) -> Self {
ValueDerived(format!("{}", value))
}
}
#[ComplexObject]
impl MyObj {
async fn c(&self) -> i32 {
self.a + self.b
}
#[graphql(derived(name = "e", into = "ValueDerived"))]
async fn d(&self, v: i32) -> i32 {
self.a + self.b + v
}
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> MyObj {
MyObj { a: 10, b: 20 }
}
}
let query = "{ obj { a b c d(v:100) e(v: 200) f } }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
dbg!(schema.execute(query).await);
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 10,
"b": 20,
"c": 30,
"d": 130,
"e": "230",
"f": "20",
},
})
);
}