Convert graphql_object to central util parsing (#526)

This commit is contained in:
Christian Legnitto 2020-02-12 23:46:59 -07:00 committed by GitHub
parent df2740393a
commit 7ee67ed6e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 200 additions and 197 deletions

View file

@ -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()

View file

@ -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 )* );

View file

@ -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");
}
}
}