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
|
@ -4,210 +4,161 @@ use quote::quote;
|
|||
|
||||
/// Generate code for the juniper::graphql_object macro.
|
||||
pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
||||
let impl_attrs = match syn::parse::<util::ObjectAttributes>(args) {
|
||||
Ok(attrs) => attrs,
|
||||
Err(e) => {
|
||||
panic!("Invalid attributes:\n{}", e);
|
||||
}
|
||||
};
|
||||
let _impl = util::parse_impl::ImplBlock::parse(args, body);
|
||||
|
||||
let item = match syn::parse::<syn::Item>(body) {
|
||||
Ok(item) => item,
|
||||
Err(err) => {
|
||||
panic!("Parsing error:\n{}", err);
|
||||
}
|
||||
};
|
||||
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 name = _impl
|
||||
.attrs
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| _impl.type_ident.to_string());
|
||||
|
||||
let mut definition = util::GraphQLTypeDefiniton {
|
||||
name,
|
||||
_type: target_type.clone(),
|
||||
context: impl_attrs.context,
|
||||
scalar: impl_attrs.scalar,
|
||||
description,
|
||||
_type: *_impl.target_type.clone(),
|
||||
context: _impl.attrs.context,
|
||||
scalar: _impl.attrs.scalar,
|
||||
description: _impl.description,
|
||||
fields: Vec::new(),
|
||||
generics: _impl.generics.clone(),
|
||||
interfaces: if impl_attrs.interfaces.len() > 0 {
|
||||
Some(impl_attrs.interfaces)
|
||||
interfaces: if _impl.attrs.interfaces.len() > 0 {
|
||||
Some(_impl.attrs.interfaces)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
include_type_generics: false,
|
||||
generic_scalar: false,
|
||||
no_async: impl_attrs.no_async,
|
||||
no_async: _impl.attrs.no_async,
|
||||
};
|
||||
|
||||
for item in _impl.items {
|
||||
match item {
|
||||
syn::ImplItem::Method(method) => {
|
||||
let _type = match &method.sig.output {
|
||||
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
||||
syn::ReturnType::Default => {
|
||||
for method in _impl.methods {
|
||||
let _type = match &method.sig.output {
|
||||
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
||||
syn::ReturnType::Default => {
|
||||
panic!(
|
||||
"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!(
|
||||
"Invalid field method {}: must return a value",
|
||||
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
||||
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!(
|
||||
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
||||
method.sig.ident
|
||||
);
|
||||
}
|
||||
}
|
||||
syn::FnArg::Typed(ref captured) => {
|
||||
let (arg_ident, is_mut) = match &*captured.pat {
|
||||
syn::Pat::Ident(ref pat_ident) => {
|
||||
(&pat_ident.ident, pat_ident.mutability.is_some())
|
||||
}
|
||||
syn::FnArg::Typed(ref captured) => {
|
||||
let (arg_ident, is_mut) = match &*captured.pat {
|
||||
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,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
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 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" };
|
||||
definition.into_tokens(juniper_crate_name).into()
|
||||
|
|
|
@ -77,16 +77,31 @@ pub fn impl_union(
|
|||
quote! { #crate_name::DefaultScalarValue }
|
||||
});
|
||||
|
||||
if _impl.methods.len() != 1 {
|
||||
if !_impl.has_resolve_method() {
|
||||
return Err(MacroError::new(
|
||||
_impl.target_type.span(),
|
||||
"Invalid impl body: expected one method with signature: fn resolve(&self) { ... }"
|
||||
.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 body_raw = quote!( #( #stmts )* );
|
||||
|
|
|
@ -2,9 +2,37 @@
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::{convert::From, fmt};
|
||||
|
||||
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 attrs: util::ObjectAttributes,
|
||||
pub target_trait: Option<(String, syn::Path)>,
|
||||
|
@ -17,20 +45,28 @@ pub struct ImplBlock {
|
|||
}
|
||||
|
||||
impl ImplBlock {
|
||||
/// Parse a 'fn resolve()' metho declaration found in union or interface
|
||||
/// impl blocks.
|
||||
/// Check if the block has the special `resolve()` method.
|
||||
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.
|
||||
pub fn parse_resolve_method(
|
||||
&self,
|
||||
method: &syn::ImplItemMethod,
|
||||
) -> Vec<proc_macro2::TokenStream> {
|
||||
) -> Result<Vec<proc_macro2::TokenStream>, ResolveFnError> {
|
||||
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 {
|
||||
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 => {}
|
||||
};
|
||||
|
@ -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 {
|
||||
match arg {
|
||||
syn::FnArg::Receiver(_) => {
|
||||
panic!(
|
||||
return Err(format!(
|
||||
"Malformed method signature {}: self receiver must be the first argument",
|
||||
method.sig.ident
|
||||
);
|
||||
)
|
||||
.into());
|
||||
}
|
||||
syn::FnArg::Typed(captured) => {
|
||||
let (arg_ident, _is_mut) = match &*captured.pat {
|
||||
|
@ -96,18 +133,18 @@ impl ImplBlock {
|
|||
.map(|ctx| ctx == &*captured.ty)
|
||||
.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 &{}?",
|
||||
quote!(captured.ty),
|
||||
);
|
||||
).into());
|
||||
} 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 {
|
||||
|
@ -159,7 +196,7 @@ impl ImplBlock {
|
|||
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…
Reference in a new issue