Implement `InputType` and `OutputType` for `[T; N]` array.

This commit is contained in:
Sunli 2021-06-17 10:39:27 +08:00
parent 42a4ff13ed
commit 543ce408b0
7 changed files with 231 additions and 225 deletions

View File

@ -10,7 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `serial` attribute for `Object`, `SimpleObject` and `MergedObject` macros. [#539](https://github.com/async-graphql/async-graphql/issues/539)
- Remove the `static` constraint of the `receive_body` and `receive_batch_body` functions. [#544](https://github.com/async-graphql/async-graphql/issues/544)
- Remove the `static` constraint of the `receive_body` and `receive_batch_body` functions. [#544](https://github.com/async-graphql/async-graphql/issues/544)
- Implement `InputType` and `OutputType` for `[T; N]` array.
## [2.9.2] 2021-06-10

View File

@ -1,13 +1,13 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::{Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeReference};
use syn::{Block, Error, ImplItem, ItemImpl, ReturnType};
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,
extract_input_args, 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,
};
@ -50,16 +50,6 @@ pub fn generate(
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;
@ -72,68 +62,17 @@ pub fn generate(
};
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()),
}
let args = extract_input_args(&crate_name, method)?;
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())
}
}
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();

View File

@ -2,13 +2,13 @@ use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::ext::IdentExt;
use syn::{Block, Error, FnArg, Ident, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeReference};
use syn::{Block, Error, Ident, ImplItem, ItemImpl, ReturnType};
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,
extract_input_args, 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,
};
@ -54,6 +54,8 @@ pub fn generate(
return Err(Error::new_spanned(&method, "Must be asynchronous").into());
}
let args = extract_input_args(&crate_name, method)?;
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
@ -64,72 +66,6 @@ pub fn generate(
.into())
}
};
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());
} else {
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 entity_type = ty.value_type();
let mut key_pat = Vec::new();
@ -255,16 +191,6 @@ pub fn generate(
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;
@ -277,76 +203,20 @@ pub fn generate(
};
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 args = extract_input_args(&crate_name, method)?;
let mut schema_args = Vec::new();
let mut use_params = Vec::new();
let mut get_params = Vec::new();
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())
}
};
for (
ident,

View File

@ -6,13 +6,13 @@ use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::visit::Visit;
use syn::{
Attribute, Error, Expr, ExprPath, Ident, Lit, LitStr, Meta, NestedMeta, Type, TypeGroup,
TypeParamBound,
Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lit, LitStr, Meta, NestedMeta,
Pat, PatIdent, Type, TypeGroup, TypeParamBound, TypeReference,
};
use thiserror::Error;
use crate::args;
use crate::args::{Deprecation, Visible};
use crate::args::{Argument, Deprecation, Visible};
#[derive(Error, Debug)]
pub enum GeneratorError {
@ -446,3 +446,74 @@ pub fn gen_deprecation(deprecation: &Deprecation, crate_name: &TokenStream) -> T
}
}
}
pub fn extract_input_args(
crate_name: &proc_macro2::TokenStream,
method: &mut ImplItemMethod,
) -> GeneratorResult<Vec<(PatIdent, Type, Argument)>> {
let mut args = Vec::new();
let mut create_ctx = true;
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::Reference(TypeReference { elem, .. })) => {
if let Type::Path(path) = elem.as_ref() {
if idx != 1 || path.path.segments.last().unwrap().ident != "Context" {
args.push((
arg_ident.clone(),
pat.ty.as_ref().clone(),
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
.unwrap_or_default(),
));
} else {
create_ctx = false;
}
}
}
(Pat::Ident(arg_ident), ty) => {
args.push((
arg_ident.clone(),
ty.clone(),
parse_graphql_attrs::<args::Argument>(&pat.attrs)?.unwrap_or_default(),
));
remove_graphql_attrs(&mut pat.attrs);
}
_ => {
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);
}
Ok(args)
}

63
src/types/external/list/array.rs vendored Normal file
View File

@ -0,0 +1,63 @@
use std::borrow::Cow;
use std::convert::TryInto;
use crate::parser::types::Field;
use crate::resolver_utils::resolve_list;
use crate::{
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
Positioned, ServerResult, Type, Value,
};
impl<T: Type, const N: usize> Type for [T; N] {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
fn qualified_type_name() -> String {
format!("[{}]!", T::qualified_type_name())
}
fn create_type_info(registry: &mut registry::Registry) -> String {
T::create_type_info(registry);
Self::qualified_type_name()
}
}
impl<T: InputType, const N: usize> InputType for [T; N] {
fn parse(value: Option<Value>) -> InputValueResult<Self> {
if let Some(Value::List(values)) = value {
let items: Vec<T> = values
.into_iter()
.map(|value| InputType::parse(Some(value)))
.collect::<Result<_, _>>()
.map_err(InputValueError::propagate)?;
let len = items.len();
items.try_into().map_err(|_| {
InputValueError::custom(format!(
"Expected input type \"[{}; {}]\", found [{}; {}].",
T::type_name(),
N,
T::type_name(),
len
))
})
} else {
Err(InputValueError::expected_type(value.unwrap_or_default()))
}
}
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
}
#[async_trait::async_trait]
impl<T: OutputType, const N: usize> OutputType for [T; N] {
async fn resolve(
&self,
ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>,
) -> ServerResult<Value> {
resolve_list(ctx, field, self.iter(), Some(self.len())).await
}
}

View File

@ -1,3 +1,4 @@
mod array;
mod btree_set;
mod hash_set;
mod linked_list;

View File

@ -2,7 +2,6 @@ use async_graphql::*;
use std::cmp::Ordering;
use std::collections::{BTreeSet, HashSet, LinkedList, VecDeque};
//noinspection ALL
#[tokio::test]
pub async fn test_list_type() {
#[derive(InputObject)]
@ -116,3 +115,64 @@ pub async fn test_list_type() {
})
);
}
#[tokio::test]
pub async fn test_array_type() {
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn values(&self) -> [i32; 6] {
[1, 2, 3, 4, 5, 6]
}
async fn array_input(&self, values: [i32; 6]) -> [i32; 6] {
assert_eq!(values, [1, 2, 3, 4, 5, 6]);
values
}
}
let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
assert_eq!(
schema
.execute("{ values }")
.await
.into_result()
.unwrap()
.data,
value!({
"values": [1, 2, 3, 4, 5, 6]
})
);
assert_eq!(
schema
.execute("{ arrayInput(values: [1, 2, 3, 4, 5, 6]) }")
.await
.into_result()
.unwrap()
.data,
value!({
"arrayInput": [1, 2, 3, 4, 5, 6]
})
);
assert_eq!(
schema
.execute("{ arrayInput(values: [1, 2, 3, 4, 5]) }")
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: r#"Failed to parse "[Int!]": Expected input type "[Int; 6]", found [Int; 5]."#
.to_owned(),
locations: vec![Pos {
line: 1,
column: 22,
}],
path: vec![PathSegment::Field("arrayInput".to_owned())],
extensions: None,
}],
);
}