2019-05-07 03:52:10 -05:00
|
|
|
use crate::util;
|
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use quote::quote;
|
|
|
|
|
2019-05-13 11:33:45 -05:00
|
|
|
/// Generate code for the juniper::object macro.
|
|
|
|
pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
2019-05-07 03:52:10 -05:00
|
|
|
let impl_attrs = match syn::parse::<util::ObjectAttributes>(args) {
|
|
|
|
Ok(attrs) => attrs,
|
|
|
|
Err(e) => {
|
|
|
|
panic!("Invalid attributes:\n{}", e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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::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 = match impl_attrs.name.as_ref() {
|
|
|
|
Some(type_name) => type_name.clone(),
|
|
|
|
None => match &*_impl.self_ty {
|
|
|
|
syn::Type::Path(ref type_path) => type_path
|
|
|
|
.path
|
|
|
|
.segments
|
|
|
|
.iter()
|
|
|
|
.last()
|
|
|
|
.unwrap()
|
|
|
|
.ident
|
|
|
|
.to_string(),
|
|
|
|
syn::Type::Reference(ref reference) => match &*reference.elem {
|
|
|
|
syn::Type::Path(ref type_path) => type_path
|
|
|
|
.path
|
|
|
|
.segments
|
|
|
|
.iter()
|
|
|
|
.last()
|
|
|
|
.unwrap()
|
|
|
|
.ident
|
|
|
|
.to_string(),
|
|
|
|
_ => {
|
2019-05-13 11:33:45 -05:00
|
|
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
2019-05-07 03:52:10 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {
|
2019-05-13 11:33:45 -05:00
|
|
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
2019-05-07 03:52:10 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let target_type = *_impl.self_ty.clone();
|
|
|
|
|
|
|
|
let description = impl_attrs
|
|
|
|
.description
|
|
|
|
.or(util::get_doc_comment(&_impl.attrs));
|
|
|
|
|
|
|
|
let mut definition = util::GraphQLTypeDefiniton {
|
|
|
|
name,
|
|
|
|
_type: target_type.clone(),
|
|
|
|
context: impl_attrs.context,
|
|
|
|
scalar: impl_attrs.scalar,
|
|
|
|
description,
|
|
|
|
fields: Vec::new(),
|
|
|
|
generics: _impl.generics.clone(),
|
|
|
|
interfaces: if impl_attrs.interfaces.len() > 0 {
|
|
|
|
Some(impl_attrs.interfaces)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
include_type_generics: false,
|
|
|
|
generic_scalar: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
for item in _impl.items {
|
|
|
|
match item {
|
|
|
|
syn::ImplItem::Method(method) => {
|
|
|
|
let _type = match &method.sig.decl.output {
|
|
|
|
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
|
|
|
syn::ReturnType::Default => {
|
|
|
|
panic!(
|
|
|
|
"Invalid field method {}: must return a value",
|
|
|
|
method.sig.ident
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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.decl.inputs {
|
|
|
|
match arg {
|
|
|
|
_self @ syn::FnArg::SelfRef(_) => {
|
|
|
|
// Can be ignored.
|
|
|
|
// "self" will already be in scope.
|
|
|
|
// resolve_args.push(quote!(self));
|
|
|
|
}
|
|
|
|
syn::FnArg::SelfValue(_) => {
|
|
|
|
panic!(
|
|
|
|
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
|
|
|
method.sig.ident
|
|
|
|
);
|
|
|
|
}
|
|
|
|
syn::FnArg::Captured(ref captured) => {
|
|
|
|
let arg_ident = match &captured.pat {
|
|
|
|
syn::Pat::Ident(ref pat_ident) => &pat_ident.ident,
|
|
|
|
_ => {
|
|
|
|
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;));
|
2019-05-07 04:23:46 -05:00
|
|
|
}
|
2019-05-07 03:52:10 -05:00
|
|
|
// 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 executor 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(); ));
|
2019-05-07 04:23:46 -05:00
|
|
|
}
|
2019-05-07 03:52:10 -05:00
|
|
|
// Make sure the user does not specify the Context
|
|
|
|
// without a reference. (&Context)
|
2019-05-07 04:23:46 -05:00
|
|
|
else if context_type
|
|
|
|
.clone()
|
|
|
|
.map(|ctx| ctx == &captured.ty)
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2019-05-07 03:52:10 -05:00
|
|
|
panic!(
|
|
|
|
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
|
|
|
quote!(captured.ty),
|
|
|
|
);
|
2019-05-07 04:23:46 -05:00
|
|
|
} else {
|
2019-05-07 03:52:10 -05:00
|
|
|
let ty = &captured.ty;
|
|
|
|
// TODO: respect graphql attribute overwrite.
|
|
|
|
let final_name = util::to_camel_case(&arg_name);
|
|
|
|
resolve_parts.push(quote!(
|
|
|
|
let #arg_ident = args
|
|
|
|
.get::<#ty>(#final_name)
|
|
|
|
.expect(&format!("Internal error: missing argument {} - validation must have failed", #final_name));
|
|
|
|
));
|
|
|
|
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 argument type in method {}", method.sig.ident),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let body = &method.block;
|
|
|
|
let return_ty = &method.sig.decl.output;
|
|
|
|
let resolver_code = quote!(
|
|
|
|
(|| #return_ty {
|
|
|
|
#( #resolve_parts )*
|
|
|
|
#body
|
|
|
|
})()
|
|
|
|
);
|
|
|
|
|
|
|
|
let name = attrs
|
|
|
|
.name
|
|
|
|
.unwrap_or(util::to_camel_case(&method.sig.ident.to_string()));
|
|
|
|
|
|
|
|
definition.fields.push(util::GraphQLTypeDefinitionField {
|
|
|
|
name,
|
|
|
|
_type,
|
|
|
|
args,
|
|
|
|
description: attrs.description,
|
|
|
|
deprecation: attrs.deprecation,
|
|
|
|
resolver_code,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!("Invalid item for GraphQL Object: only type declarations and methods are allowed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
|
|
|
definition.into_tokens(juniper_crate_name).into()
|
|
|
|
}
|