Add support for generic InputObject. #387

This commit is contained in:
Sunli 2021-01-14 11:27:15 +08:00
parent 94bd37c540
commit 74657c6242
8 changed files with 200 additions and 66 deletions

View File

@ -2,7 +2,9 @@ use darling::ast::{Data, Fields};
use darling::util::Ignored;
use darling::{FromDeriveInput, FromField, FromMeta, FromVariant};
use inflector::Inflector;
use syn::{Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, Type, Visibility};
use syn::{
Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, NestedMeta, Path, Type, Visibility,
};
#[derive(FromMeta)]
#[darling(default)]
@ -267,6 +269,28 @@ pub struct InputObjectField {
pub visible: Option<Visible>,
}
pub struct PathList(pub Vec<Path>);
impl FromMeta for PathList {
fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
let mut res = Vec::new();
for item in items {
if let NestedMeta::Meta(Meta::Path(p)) = item {
res.push(p.clone());
} else {
return Err(darling::Error::custom("Invalid path list"));
}
}
Ok(PathList(res))
}
}
#[derive(FromMeta)]
pub struct InputObjectConcrete {
pub name: String,
pub params: PathList,
}
#[derive(FromDeriveInput)]
#[darling(attributes(graphql), forward_attrs(doc))]
pub struct InputObject {
@ -283,6 +307,8 @@ pub struct InputObject {
pub rename_fields: Option<RenameRule>,
#[darling(default)]
pub visible: Option<Visible>,
#[darling(default, multiple, rename = "concrete")]
pub concretes: Vec<InputObjectConcrete>,
}
#[derive(FromMeta)]

View File

@ -11,6 +11,7 @@ use crate::utils::{
pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl();
let ident = &object_args.ident;
let s = match &object_args.data {
Data::Struct(s) => s,
@ -170,46 +171,118 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
}
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #ident {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
registry.create_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
description: #desc,
input_fields: {
let mut fields = #crate_name::indexmap::IndexMap::new();
#(#schema_fields)*
fields
},
visible: #visible,
})
}
}
let expanded = if object_args.concretes.is_empty() {
quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #ident {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::InputType for #ident {
fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
if let ::std::option::Option::Some(#crate_name::Value::Object(obj)) = value {
#(#get_fields)*
::std::result::Result::Ok(Self { #(#fields),* })
} else {
::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default()))
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
registry.create_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
description: #desc,
input_fields: {
let mut fields = #crate_name::indexmap::IndexMap::new();
#(#schema_fields)*
fields
},
visible: #visible,
})
}
}
fn to_value(&self) -> #crate_name::Value {
let mut map = ::std::collections::BTreeMap::new();
#(#put_fields)*
#crate_name::Value::Object(map)
}
}
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::InputType for #ident {
fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
if let ::std::option::Option::Some(#crate_name::Value::Object(obj)) = value {
#(#get_fields)*
::std::result::Result::Ok(Self { #(#fields),* })
} else {
::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default()))
}
}
impl #crate_name::InputObjectType for #ident {}
fn to_value(&self) -> #crate_name::Value {
let mut map = ::std::collections::BTreeMap::new();
#(#put_fields)*
#crate_name::Value::Object(map)
}
}
impl #crate_name::InputObjectType for #ident {}
}
} else {
let mut code = Vec::new();
code.push(quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #impl_generics #ident #ty_generics #where_clause {
fn __internal_create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String where Self: #crate_name::InputType {
registry.create_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
description: #desc,
input_fields: {
let mut fields = #crate_name::indexmap::IndexMap::new();
#(#schema_fields)*
fields
},
visible: #visible,
})
}
fn __internal_parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> where Self: #crate_name::InputType {
if let ::std::option::Option::Some(#crate_name::Value::Object(obj)) = value {
#(#get_fields)*
::std::result::Result::Ok(Self { #(#fields),* })
} else {
::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default()))
}
}
fn __internal_to_value(&self) -> #crate_name::Value where Self: #crate_name::InputType {
let mut map = ::std::collections::BTreeMap::new();
#(#put_fields)*
#crate_name::Value::Object(map)
}
}
});
for concrete in &object_args.concretes {
let gql_typename = &concrete.name;
let params = &concrete.params.0;
let concrete_type = quote! { #ident<#(#params),*> };
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #concrete_type {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
Self::__internal_create_type_info(registry)
}
}
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::InputType for #concrete_type {
fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
Self::__internal_parse(value)
}
fn to_value(&self) -> #crate_name::Value {
self.__internal_to_value()
}
}
impl #crate_name::InputObjectType for #concrete_type {}
};
code.push(expanded);
}
quote!(#(#code)*)
};
Ok(expanded.into())
}

View File

@ -14,7 +14,7 @@ use crate::utils::{generate_default, get_crate_name, get_rustdoc, visible_fn, Ge
pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(interface_args.internal);
let ident = &interface_args.ident;
let generics = &interface_args.generics;
let (impl_generics, ty_generics, where_clause) = interface_args.generics.split_for_impl();
let s = match &interface_args.data {
Data::Enum(s) => s,
_ => {
@ -89,7 +89,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
#crate_name::static_assertions::assert_impl_any!(#assert_ty: #crate_name::ObjectType, #crate_name::InterfaceType);
#[allow(clippy::all, clippy::pedantic)]
impl #generics ::std::convert::From<#p> for #ident #generics {
impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause {
fn from(obj: #p) -> Self {
#ident::#enum_name(obj)
}
@ -313,12 +313,12 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
#(#type_into_impls)*
#[allow(clippy::all, clippy::pedantic)]
impl #generics #ident #generics {
impl #impl_generics #ident #ty_generics #where_clause {
#(#methods)*
}
#[allow(clippy::all, clippy::pedantic)]
impl #generics #crate_name::Type for #ident #generics {
impl #impl_generics #crate_name::Type for #ident #ty_generics #where_clause {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -354,7 +354,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics {
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)*
::std::result::Result::Ok(::std::option::Option::None)
@ -369,13 +369,13 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputType for #ident #generics {
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await
}
}
impl #generics #crate_name::InterfaceType for #ident #generics {}
impl #impl_generics #crate_name::InterfaceType for #ident #ty_generics #where_clause {}
};
Ok(expanded.into())
}

View File

@ -10,9 +10,8 @@ use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &object_args.ident;
let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl();
let extends = object_args.extends;
let generics = &object_args.generics;
let where_clause = &generics.where_clause;
let gql_typename = object_args
.name
.clone()
@ -58,7 +57,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #generics #crate_name::Type for #ident #generics #where_clause {
impl #impl_generics #crate_name::Type for #ident #ty_generics #where_clause {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -92,7 +91,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics #where_clause {
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>> {
#create_merged_obj.resolve_field(ctx).await
}
@ -104,13 +103,13 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputType for #ident #generics #where_clause {
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await
}
}
impl #generics #crate_name::ObjectType for #ident #generics #where_clause {}
impl #impl_generics #crate_name::ObjectType for #ident #ty_generics #where_clause {}
};
Ok(expanded.into())
}

View File

@ -10,8 +10,7 @@ use crate::utils::{generate_guards, get_crate_name, get_rustdoc, visible_fn, Gen
pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &object_args.ident;
let generics = &object_args.generics;
let where_clause = &generics.where_clause;
let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl();
let extends = object_args.extends;
let gql_typename = object_args
.name
@ -156,12 +155,12 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #generics #ident #generics #where_clause {
impl #impl_generics #ident #ty_generics #where_clause {
#(#getters)*
}
#[allow(clippy::all, clippy::pedantic)]
impl #generics #crate_name::Type for #ident #generics #where_clause {
impl #impl_generics #crate_name::Type for #ident #ty_generics #where_clause {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -186,7 +185,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics #where_clause {
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)*
::std::result::Result::Ok(::std::option::Option::None)
@ -195,13 +194,13 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputType for #ident #generics #where_clause {
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await
}
}
impl #generics #crate_name::ObjectType for #ident #generics #where_clause {}
impl #impl_generics #crate_name::ObjectType for #ident #ty_generics #where_clause {}
};
Ok(expanded.into())
}

View File

@ -20,8 +20,6 @@ pub fn generate(
) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(subscription_args.internal);
let (self_ty, self_name) = get_type_path_and_name(item_impl.self_ty.as_ref())?;
let generics = &item_impl.generics;
let where_clause = &generics.where_clause;
let gql_typename = subscription_args
.name
@ -427,7 +425,7 @@ pub fn generate(
#item_impl
#[allow(clippy::all, clippy::pedantic)]
impl #generics #crate_name::Type for #self_ty #where_clause {
impl #crate_name::Type for #self_ty {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -452,7 +450,7 @@ pub fn generate(
#[allow(clippy::all, clippy::pedantic)]
#[allow(unused_braces, unused_variables)]
impl #crate_name::SubscriptionType for #self_ty #where_clause {
impl #crate_name::SubscriptionType for #self_ty {
fn create_field_stream<'__life>(
&'__life self,
ctx: &'__life #crate_name::Context<'__life>,

View File

@ -12,7 +12,7 @@ use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(union_args.internal);
let ident = &union_args.ident;
let generics = &union_args.generics;
let (impl_generics, ty_generics, where_clause) = union_args.generics.split_for_impl();
let s = match &union_args.data {
Data::Enum(s) => s,
_ => {
@ -87,7 +87,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#crate_name::static_assertions::assert_impl_one!(#assert_ty: #crate_name::ObjectType);
#[allow(clippy::all, clippy::pedantic)]
impl #generics ::std::convert::From<#p> for #ident #generics {
impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause {
fn from(obj: #p) -> Self {
#ident::#enum_name(obj)
}
@ -98,7 +98,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#crate_name::static_assertions::assert_impl_one!(#assert_ty: #crate_name::UnionType);
#[allow(clippy::all, clippy::pedantic)]
impl #generics ::std::convert::From<#p> for #ident #generics {
impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause {
fn from(obj: #p) -> Self {
#ident::#enum_name(obj)
}
@ -153,7 +153,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#(#type_into_impls)*
#[allow(clippy::all, clippy::pedantic)]
impl #generics #crate_name::Type for #ident #generics {
impl #impl_generics #crate_name::Type for #ident #ty_generics #where_clause {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -185,7 +185,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics {
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>> {
::std::result::Result::Ok(::std::option::Option::None)
}
@ -199,13 +199,13 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputType for #ident #generics {
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await
}
}
impl #generics #crate_name::UnionType for #ident #generics {}
impl #impl_generics #crate_name::UnionType for #ident #ty_generics #where_clause {}
};
Ok(expanded.into())
}

View File

@ -349,3 +349,42 @@ pub async fn test_box_input_object() {
})
);
}
#[async_std::test]
pub async fn test_input_object_generic() {
#[derive(InputObject)]
#[graphql(
concrete(name = "IntEqualityFilter", params(i32)),
concrete(name = "StringEqualityFilter", params(String))
)]
struct EqualityFilter<T: InputType> {
equals: Option<T>,
not_equals: Option<T>,
}
assert_eq!(EqualityFilter::<i32>::type_name(), "IntEqualityFilter");
assert_eq!(
EqualityFilter::<String>::type_name(),
"StringEqualityFilter"
);
struct Root;
#[Object]
impl Root {
async fn q(&self, input: EqualityFilter<i32>) -> i32 {
input.equals.unwrap_or_default() + input.not_equals.unwrap_or_default()
}
}
let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
let query = r#"{
q(input: { equals: 7, notEquals: 8 } )
}"#;
assert_eq!(
schema.execute(query).await.into_result().unwrap().data,
value!({
"q": 15
})
);
}