Convert graphql_object to central util parsing (#526)
This commit is contained in:
parent
df2740393a
commit
7ee67ed6e8
3 changed files with 200 additions and 197 deletions
juniper_codegen/src
|
@ -4,210 +4,161 @@ use quote::quote;
|
||||||
|
|
||||||
/// Generate code for the juniper::graphql_object macro.
|
/// Generate code for the juniper::graphql_object macro.
|
||||||
pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
||||||
let impl_attrs = match syn::parse::<util::ObjectAttributes>(args) {
|
let _impl = util::parse_impl::ImplBlock::parse(args, body);
|
||||||
Ok(attrs) => attrs,
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Invalid attributes:\n{}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let item = match syn::parse::<syn::Item>(body) {
|
let name = _impl
|
||||||
Ok(item) => item,
|
.attrs
|
||||||
Err(err) => {
|
.name
|
||||||
panic!("Parsing error:\n{}", err);
|
.clone()
|
||||||
}
|
.unwrap_or_else(|| _impl.type_ident.to_string());
|
||||||
};
|
|
||||||
let mut _impl = match item {
|
|
||||||
syn::Item::Impl(_impl) => _impl,
|
|
||||||
_ => {
|
|
||||||
panic!("#[juniper::graphql_object] can only be applied to impl blocks");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match _impl.trait_ {
|
|
||||||
Some((_, ref path, _)) => {
|
|
||||||
let name = path
|
|
||||||
.segments
|
|
||||||
.iter()
|
|
||||||
.map(|segment| segment.ident.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(".");
|
|
||||||
if !(name == "GraphQLObject" || name == "juniper.GraphQLObject") {
|
|
||||||
panic!("The impl block must implement the 'GraphQLObject' trait");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// panic!("The impl block must implement the 'GraphQLObject' trait");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = if let Some(name) = impl_attrs.name.as_ref() {
|
|
||||||
name.to_string()
|
|
||||||
} else {
|
|
||||||
if let Some(ident) = util::name_of_type(&*_impl.self_ty) {
|
|
||||||
ident.to_string()
|
|
||||||
} else {
|
|
||||||
panic!("Could not determine a name for the object type: specify one with #[juniper::graphql_object(name = \"SomeName\")");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let target_type = *_impl.self_ty.clone();
|
|
||||||
|
|
||||||
let description = impl_attrs
|
|
||||||
.description
|
|
||||||
.or(util::get_doc_comment(&_impl.attrs));
|
|
||||||
|
|
||||||
let mut definition = util::GraphQLTypeDefiniton {
|
let mut definition = util::GraphQLTypeDefiniton {
|
||||||
name,
|
name,
|
||||||
_type: target_type.clone(),
|
_type: *_impl.target_type.clone(),
|
||||||
context: impl_attrs.context,
|
context: _impl.attrs.context,
|
||||||
scalar: impl_attrs.scalar,
|
scalar: _impl.attrs.scalar,
|
||||||
description,
|
description: _impl.description,
|
||||||
fields: Vec::new(),
|
fields: Vec::new(),
|
||||||
generics: _impl.generics.clone(),
|
generics: _impl.generics.clone(),
|
||||||
interfaces: if impl_attrs.interfaces.len() > 0 {
|
interfaces: if _impl.attrs.interfaces.len() > 0 {
|
||||||
Some(impl_attrs.interfaces)
|
Some(_impl.attrs.interfaces)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
include_type_generics: false,
|
include_type_generics: false,
|
||||||
generic_scalar: false,
|
generic_scalar: false,
|
||||||
no_async: impl_attrs.no_async,
|
no_async: _impl.attrs.no_async,
|
||||||
};
|
};
|
||||||
|
|
||||||
for item in _impl.items {
|
for method in _impl.methods {
|
||||||
match item {
|
let _type = match &method.sig.output {
|
||||||
syn::ImplItem::Method(method) => {
|
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
||||||
let _type = match &method.sig.output {
|
syn::ReturnType::Default => {
|
||||||
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
panic!(
|
||||||
syn::ReturnType::Default => {
|
"Invalid field method {}: must return a value",
|
||||||
|
method.sig.ident
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_async = method.sig.asyncness.is_some();
|
||||||
|
|
||||||
|
let attrs = match util::FieldAttributes::from_attrs(
|
||||||
|
method.attrs,
|
||||||
|
util::FieldAttributeParseMode::Impl,
|
||||||
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(err) => panic!(
|
||||||
|
"Invalid #[graphql(...)] attribute on field {}:\n{}",
|
||||||
|
method.sig.ident, err
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
let mut resolve_parts = Vec::new();
|
||||||
|
|
||||||
|
for arg in method.sig.inputs {
|
||||||
|
match arg {
|
||||||
|
syn::FnArg::Receiver(rec) => {
|
||||||
|
if rec.reference.is_none() || rec.mutability.is_some() {
|
||||||
panic!(
|
panic!(
|
||||||
"Invalid field method {}: must return a value",
|
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
||||||
method.sig.ident
|
method.sig.ident
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
syn::FnArg::Typed(ref captured) => {
|
||||||
let is_async = method.sig.asyncness.is_some();
|
let (arg_ident, is_mut) = match &*captured.pat {
|
||||||
|
syn::Pat::Ident(ref pat_ident) => {
|
||||||
let attrs = match util::FieldAttributes::from_attrs(
|
(&pat_ident.ident, pat_ident.mutability.is_some())
|
||||||
method.attrs,
|
|
||||||
util::FieldAttributeParseMode::Impl,
|
|
||||||
) {
|
|
||||||
Ok(attrs) => attrs,
|
|
||||||
Err(err) => panic!(
|
|
||||||
"Invalid #[graphql(...)] attribute on field {}:\n{}",
|
|
||||||
method.sig.ident, err
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut args = Vec::new();
|
|
||||||
let mut resolve_parts = Vec::new();
|
|
||||||
|
|
||||||
for arg in method.sig.inputs {
|
|
||||||
match arg {
|
|
||||||
syn::FnArg::Receiver(rec) => {
|
|
||||||
if rec.reference.is_none() || rec.mutability.is_some() {
|
|
||||||
panic!(
|
|
||||||
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
|
||||||
method.sig.ident
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
syn::FnArg::Typed(ref captured) => {
|
_ => {
|
||||||
let (arg_ident, is_mut) = match &*captured.pat {
|
panic!("Invalid token for function argument");
|
||||||
syn::Pat::Ident(ref pat_ident) => {
|
|
||||||
(&pat_ident.ident, pat_ident.mutability.is_some())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Invalid token for function argument");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let arg_name = arg_ident.to_string();
|
|
||||||
|
|
||||||
let context_type = definition.context.as_ref();
|
|
||||||
|
|
||||||
// Check for executor arguments.
|
|
||||||
if util::type_is_identifier_ref(&captured.ty, "Executor") {
|
|
||||||
resolve_parts.push(quote!(let #arg_ident = executor;));
|
|
||||||
}
|
|
||||||
// Make sure executor is specified as a reference.
|
|
||||||
else if util::type_is_identifier(&captured.ty, "Executor") {
|
|
||||||
panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?");
|
|
||||||
}
|
|
||||||
// Check for context arg.
|
|
||||||
else if context_type
|
|
||||||
.clone()
|
|
||||||
.map(|ctx| util::type_is_ref_of(&captured.ty, ctx))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
resolve_parts.push(quote!( let #arg_ident = executor.context(); ));
|
|
||||||
}
|
|
||||||
// Make sure the user does not specify the Context
|
|
||||||
// without a reference. (&Context)
|
|
||||||
else if context_type
|
|
||||||
.clone()
|
|
||||||
.map(|ctx| ctx == &*captured.ty)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
panic!(
|
|
||||||
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
|
||||||
quote!(captured.ty),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Regular argument.
|
|
||||||
|
|
||||||
let ty = &captured.ty;
|
|
||||||
// TODO: respect graphql attribute overwrite.
|
|
||||||
let final_name = util::to_camel_case(&arg_name);
|
|
||||||
let expect_text = format!("Internal error: missing argument {} - validation must have failed", &final_name);
|
|
||||||
let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
|
|
||||||
resolve_parts.push(quote!(
|
|
||||||
let #mut_modifier #arg_ident = args
|
|
||||||
.get::<#ty>(#final_name)
|
|
||||||
.expect(#expect_text);
|
|
||||||
));
|
|
||||||
args.push(util::GraphQLTypeDefinitionFieldArg {
|
|
||||||
description: attrs.argument(&arg_name).and_then(|arg| {
|
|
||||||
arg.description.as_ref().map(|d| d.value())
|
|
||||||
}),
|
|
||||||
default: attrs
|
|
||||||
.argument(&arg_name)
|
|
||||||
.and_then(|arg| arg.default.clone()),
|
|
||||||
_type: ty.clone(),
|
|
||||||
name: final_name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let arg_name = arg_ident.to_string();
|
||||||
|
|
||||||
|
let context_type = definition.context.as_ref();
|
||||||
|
|
||||||
|
// Check for executor arguments.
|
||||||
|
if util::type_is_identifier_ref(&captured.ty, "Executor") {
|
||||||
|
resolve_parts.push(quote!(let #arg_ident = executor;));
|
||||||
|
}
|
||||||
|
// Make sure executor is specified as a reference.
|
||||||
|
else if util::type_is_identifier(&captured.ty, "Executor") {
|
||||||
|
panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?");
|
||||||
|
}
|
||||||
|
// Check for context arg.
|
||||||
|
else if context_type
|
||||||
|
.clone()
|
||||||
|
.map(|ctx| util::type_is_ref_of(&captured.ty, ctx))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
resolve_parts.push(quote!( let #arg_ident = executor.context(); ));
|
||||||
|
}
|
||||||
|
// Make sure the user does not specify the Context
|
||||||
|
// without a reference. (&Context)
|
||||||
|
else if context_type
|
||||||
|
.clone()
|
||||||
|
.map(|ctx| ctx == &*captured.ty)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
||||||
|
quote!(captured.ty),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Regular argument.
|
||||||
|
|
||||||
|
let ty = &captured.ty;
|
||||||
|
// TODO: respect graphql attribute overwrite.
|
||||||
|
let final_name = util::to_camel_case(&arg_name);
|
||||||
|
let expect_text = format!(
|
||||||
|
"Internal error: missing argument {} - validation must have failed",
|
||||||
|
&final_name
|
||||||
|
);
|
||||||
|
let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
|
||||||
|
resolve_parts.push(quote!(
|
||||||
|
let #mut_modifier #arg_ident = args
|
||||||
|
.get::<#ty>(#final_name)
|
||||||
|
.expect(#expect_text);
|
||||||
|
));
|
||||||
|
args.push(util::GraphQLTypeDefinitionFieldArg {
|
||||||
|
description: attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|arg| arg.description.as_ref().map(|d| d.value())),
|
||||||
|
default: attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|arg| arg.default.clone()),
|
||||||
|
_type: ty.clone(),
|
||||||
|
name: final_name,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = &method.block;
|
|
||||||
let resolver_code = quote!(
|
|
||||||
#( #resolve_parts )*
|
|
||||||
#body
|
|
||||||
);
|
|
||||||
|
|
||||||
let ident = &method.sig.ident;
|
|
||||||
let name = attrs
|
|
||||||
.name
|
|
||||||
.unwrap_or_else(|| util::to_camel_case(&ident.to_string()));
|
|
||||||
|
|
||||||
definition.fields.push(util::GraphQLTypeDefinitionField {
|
|
||||||
name,
|
|
||||||
_type,
|
|
||||||
args,
|
|
||||||
description: attrs.description,
|
|
||||||
deprecation: attrs.deprecation,
|
|
||||||
resolver_code,
|
|
||||||
is_type_inferred: false,
|
|
||||||
is_async,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Invalid item for GraphQL Object: only type declarations and methods are allowed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body = &method.block;
|
||||||
|
let resolver_code = quote!(
|
||||||
|
#( #resolve_parts )*
|
||||||
|
#body
|
||||||
|
);
|
||||||
|
|
||||||
|
let ident = &method.sig.ident;
|
||||||
|
let name = attrs
|
||||||
|
.name
|
||||||
|
.unwrap_or_else(|| util::to_camel_case(&ident.to_string()));
|
||||||
|
|
||||||
|
definition.fields.push(util::GraphQLTypeDefinitionField {
|
||||||
|
name,
|
||||||
|
_type,
|
||||||
|
args,
|
||||||
|
description: attrs.description,
|
||||||
|
deprecation: attrs.deprecation,
|
||||||
|
resolver_code,
|
||||||
|
is_type_inferred: false,
|
||||||
|
is_async,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
definition.into_tokens(juniper_crate_name).into()
|
definition.into_tokens(juniper_crate_name).into()
|
||||||
|
|
|
@ -77,16 +77,31 @@ pub fn impl_union(
|
||||||
quote! { #crate_name::DefaultScalarValue }
|
quote! { #crate_name::DefaultScalarValue }
|
||||||
});
|
});
|
||||||
|
|
||||||
if _impl.methods.len() != 1 {
|
if !_impl.has_resolve_method() {
|
||||||
return Err(MacroError::new(
|
return Err(MacroError::new(
|
||||||
_impl.target_type.span(),
|
_impl.target_type.span(),
|
||||||
"Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"
|
"Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let method = _impl.methods.first().unwrap();
|
|
||||||
|
|
||||||
let resolve_args = _impl.parse_resolve_method(method);
|
let method = _impl
|
||||||
|
.methods
|
||||||
|
.iter()
|
||||||
|
.find(|&m| _impl.parse_resolve_method(&m).is_ok());
|
||||||
|
|
||||||
|
if _impl.methods.is_empty() || !method.is_some() {
|
||||||
|
return Err(MacroError::new(
|
||||||
|
_impl.target_type.span(),
|
||||||
|
"Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = method.expect("checked above");
|
||||||
|
let resolve_args = _impl
|
||||||
|
.parse_resolve_method(method)
|
||||||
|
.expect("Invalid impl body: expected one method with signature: fn resolve(&self) { ... }");
|
||||||
|
|
||||||
let stmts = &method.block.stmts;
|
let stmts = &method.block.stmts;
|
||||||
let body_raw = quote!( #( #stmts )* );
|
let body_raw = quote!( #( #stmts )* );
|
||||||
|
|
|
@ -2,9 +2,37 @@
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use std::{convert::From, fmt};
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResolveFnError(String);
|
||||||
|
|
||||||
|
impl From<&str> for ResolveFnError {
|
||||||
|
fn from(item: &str) -> Self {
|
||||||
|
ResolveFnError(item.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for ResolveFnError {
|
||||||
|
fn from(item: String) -> Self {
|
||||||
|
ResolveFnError(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ResolveFnError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ResolveFnError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ImplBlock {
|
pub struct ImplBlock {
|
||||||
pub attrs: util::ObjectAttributes,
|
pub attrs: util::ObjectAttributes,
|
||||||
pub target_trait: Option<(String, syn::Path)>,
|
pub target_trait: Option<(String, syn::Path)>,
|
||||||
|
@ -17,20 +45,28 @@ pub struct ImplBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImplBlock {
|
impl ImplBlock {
|
||||||
/// Parse a 'fn resolve()' metho declaration found in union or interface
|
/// Check if the block has the special `resolve()` method.
|
||||||
/// impl blocks.
|
pub fn has_resolve_method(&self) -> bool {
|
||||||
|
self.methods
|
||||||
|
.iter()
|
||||||
|
.position(|m| m.sig.ident == "resolve")
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a 'fn resolve()' method declaration found in union or interface
|
||||||
|
/// `impl` blocks.
|
||||||
/// Returns the variable definitions needed for the resolve body.
|
/// Returns the variable definitions needed for the resolve body.
|
||||||
pub fn parse_resolve_method(
|
pub fn parse_resolve_method(
|
||||||
&self,
|
&self,
|
||||||
method: &syn::ImplItemMethod,
|
method: &syn::ImplItemMethod,
|
||||||
) -> Vec<proc_macro2::TokenStream> {
|
) -> Result<Vec<proc_macro2::TokenStream>, ResolveFnError> {
|
||||||
if method.sig.ident != "resolve" {
|
if method.sig.ident != "resolve" {
|
||||||
panic!("Expect a method named 'fn resolve(...)");
|
return Err("Expect a method named 'fn resolve(...)".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let _type = match &method.sig.output {
|
let _type = match &method.sig.output {
|
||||||
syn::ReturnType::Type(_, _) => {
|
syn::ReturnType::Type(_, _) => {
|
||||||
panic!("resolve() method must not have a declared return type");
|
return Err("resolve() method must not have a declared return type".into());
|
||||||
}
|
}
|
||||||
syn::ReturnType::Default => {}
|
syn::ReturnType::Default => {}
|
||||||
};
|
};
|
||||||
|
@ -48,7 +84,7 @@ impl ImplBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Expected a '&self' argument");
|
return Err("Expected a '&self' argument".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,10 +93,11 @@ impl ImplBlock {
|
||||||
for arg in arguments {
|
for arg in arguments {
|
||||||
match arg {
|
match arg {
|
||||||
syn::FnArg::Receiver(_) => {
|
syn::FnArg::Receiver(_) => {
|
||||||
panic!(
|
return Err(format!(
|
||||||
"Malformed method signature {}: self receiver must be the first argument",
|
"Malformed method signature {}: self receiver must be the first argument",
|
||||||
method.sig.ident
|
method.sig.ident
|
||||||
);
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
syn::FnArg::Typed(captured) => {
|
syn::FnArg::Typed(captured) => {
|
||||||
let (arg_ident, _is_mut) = match &*captured.pat {
|
let (arg_ident, _is_mut) = match &*captured.pat {
|
||||||
|
@ -96,18 +133,18 @@ impl ImplBlock {
|
||||||
.map(|ctx| ctx == &*captured.ty)
|
.map(|ctx| ctx == &*captured.ty)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
panic!(
|
return Err(format!(
|
||||||
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
||||||
quote!(captured.ty),
|
quote!(captured.ty),
|
||||||
);
|
).into());
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid argument for 'resolve' method: only executor or context are allowed");
|
return Err("Invalid argument for 'resolve' method: only executor or context are allowed".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_parts
|
Ok(resolve_parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(attr_tokens: TokenStream, body: TokenStream) -> ImplBlock {
|
pub fn parse(attr_tokens: TokenStream, body: TokenStream) -> ImplBlock {
|
||||||
|
@ -159,7 +196,7 @@ impl ImplBlock {
|
||||||
methods.push(method);
|
methods.push(method);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Invalid item for GraphQL Object: only type declarations and methods are allowed");
|
panic!("Invalid item: only type declarations and methods are allowed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue