Implement `InputType` and `OutputType` for `[T; N]` array.
This commit is contained in:
parent
42a4ff13ed
commit
543ce408b0
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod array;
|
||||
mod btree_set;
|
||||
mod hash_set;
|
||||
mod linked_list;
|
||||
|
|
|
@ -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,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue