2020-12-19 10:39:33 +00:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
2022-04-19 04:25:11 +00:00
|
|
|
use darling::{util::SpannedValue, FromMeta};
|
2020-09-06 05:38:31 +00:00
|
|
|
use proc_macro2::{Span, TokenStream, TokenTree};
|
2021-03-24 04:25:03 +00:00
|
|
|
use proc_macro_crate::{crate_name, FoundCrate};
|
2020-06-06 07:48:21 +00:00
|
|
|
use quote::quote;
|
2020-11-22 01:53:18 +00:00
|
|
|
use syn::{
|
2022-04-19 04:25:11 +00:00
|
|
|
visit::Visit, visit_mut, visit_mut::VisitMut, Attribute, Error, Expr, ExprPath, FnArg, Ident,
|
|
|
|
ImplItemMethod, Lifetime, Lit, LitStr, Meta, Pat, PatIdent, Type, TypeGroup, TypeParamBound,
|
|
|
|
TypeReference,
|
2020-11-22 01:53:18 +00:00
|
|
|
};
|
2020-09-28 09:44:00 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
2022-02-28 07:26:33 +00:00
|
|
|
use crate::args::{self, Deprecation, Visible};
|
2020-10-16 02:45:27 +00:00
|
|
|
|
2020-09-28 09:44:00 +00:00
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum GeneratorError {
|
|
|
|
#[error("{0}")]
|
|
|
|
Syn(#[from] syn::Error),
|
|
|
|
|
|
|
|
#[error("{0}")]
|
|
|
|
Darling(#[from] darling::Error),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GeneratorError {
|
|
|
|
pub fn write_errors(self) -> TokenStream {
|
|
|
|
match self {
|
|
|
|
GeneratorError::Syn(err) => err.to_compile_error(),
|
|
|
|
GeneratorError::Darling(err) => err.write_errors(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type GeneratorResult<T> = std::result::Result<T, GeneratorError>;
|
2020-06-11 03:23:23 +00:00
|
|
|
|
2020-06-06 07:48:21 +00:00
|
|
|
pub fn get_crate_name(internal: bool) -> TokenStream {
|
|
|
|
if internal {
|
|
|
|
quote! { crate }
|
|
|
|
} else {
|
2021-03-24 04:25:03 +00:00
|
|
|
let name = match crate_name("async-graphql") {
|
|
|
|
Ok(FoundCrate::Name(name)) => name,
|
|
|
|
Ok(FoundCrate::Itself) | Err(_) => "async_graphql".to_string(),
|
|
|
|
};
|
2020-09-06 05:38:31 +00:00
|
|
|
TokenTree::from(Ident::new(&name, Span::call_site())).into()
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-11 03:23:23 +00:00
|
|
|
|
2020-09-28 09:44:00 +00:00
|
|
|
pub fn generate_guards(
|
|
|
|
crate_name: &TokenStream,
|
2021-11-16 02:22:39 +00:00
|
|
|
code: &SpannedValue<String>,
|
|
|
|
map_err: TokenStream,
|
|
|
|
) -> GeneratorResult<TokenStream> {
|
|
|
|
let expr: Expr =
|
|
|
|
syn::parse_str(code).map_err(|err| Error::new(code.span(), err.to_string()))?;
|
|
|
|
let code = quote! {{
|
2021-11-16 06:51:20 +00:00
|
|
|
use #crate_name::GuardExt;
|
2021-11-16 02:22:39 +00:00
|
|
|
#expr
|
|
|
|
}};
|
|
|
|
Ok(quote! {
|
2021-11-16 06:51:20 +00:00
|
|
|
#crate_name::Guard::check(&#code, &ctx).await #map_err ?;
|
2021-11-16 02:22:39 +00:00
|
|
|
})
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
2020-06-11 03:23:23 +00:00
|
|
|
|
2020-09-28 09:44:00 +00:00
|
|
|
pub fn get_rustdoc(attrs: &[Attribute]) -> GeneratorResult<Option<String>> {
|
2020-06-06 07:48:21 +00:00
|
|
|
let mut full_docs = String::new();
|
|
|
|
for attr in attrs {
|
|
|
|
match attr.parse_meta()? {
|
|
|
|
Meta::NameValue(nv) if nv.path.is_ident("doc") => {
|
|
|
|
if let Lit::Str(doc) = nv.lit {
|
|
|
|
let doc = doc.value();
|
|
|
|
let doc_str = doc.trim();
|
|
|
|
if !full_docs.is_empty() {
|
|
|
|
full_docs += "\n";
|
|
|
|
}
|
|
|
|
full_docs += doc_str;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(if full_docs.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(full_docs)
|
|
|
|
})
|
|
|
|
}
|
2020-06-11 03:23:23 +00:00
|
|
|
|
2020-09-28 09:44:00 +00:00
|
|
|
fn generate_default_value(lit: &Lit) -> GeneratorResult<TokenStream> {
|
2020-06-06 07:48:21 +00:00
|
|
|
match lit {
|
|
|
|
Lit::Str(value) =>{
|
|
|
|
let value = value.value();
|
2020-10-16 10:37:59 +00:00
|
|
|
Ok(quote!({ ::std::borrow::ToOwned::to_owned(#value) }))
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
|
|
|
Lit::Int(value) => {
|
|
|
|
let value = value.base10_parse::<i32>()?;
|
2021-11-02 12:34:41 +00:00
|
|
|
Ok(quote!({ ::std::convert::TryInto::try_into(#value).unwrap() }))
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
|
|
|
Lit::Float(value) => {
|
|
|
|
let value = value.base10_parse::<f64>()?;
|
2021-11-02 12:34:41 +00:00
|
|
|
Ok(quote!({ ::std::convert::TryInto::try_into(#value) }))
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
|
|
|
Lit::Bool(value) => {
|
|
|
|
let value = value.value;
|
|
|
|
Ok(quote!({ #value }))
|
|
|
|
}
|
|
|
|
_ => Err(Error::new_spanned(
|
|
|
|
lit,
|
|
|
|
"The default value type only be string, integer, float and boolean, other types should use default_with",
|
2020-09-28 09:44:00 +00:00
|
|
|
).into()),
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-11 03:23:23 +00:00
|
|
|
|
2020-09-28 09:44:00 +00:00
|
|
|
fn generate_default_with(lit: &LitStr) -> GeneratorResult<TokenStream> {
|
|
|
|
let str = lit.value();
|
|
|
|
let tokens: TokenStream = str
|
|
|
|
.parse()
|
|
|
|
.map_err(|err| GeneratorError::Syn(syn::Error::from(err)))?;
|
|
|
|
Ok(quote! { (#tokens) })
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn generate_default(
|
|
|
|
default: &Option<args::DefaultValue>,
|
|
|
|
default_with: &Option<LitStr>,
|
|
|
|
) -> GeneratorResult<Option<TokenStream>> {
|
|
|
|
match (default, default_with) {
|
2020-10-16 10:37:59 +00:00
|
|
|
(Some(args::DefaultValue::Default), _) => {
|
|
|
|
Ok(Some(quote! { ::std::default::Default::default() }))
|
|
|
|
}
|
2020-09-28 09:44:00 +00:00
|
|
|
(Some(args::DefaultValue::Value(lit)), _) => Ok(Some(generate_default_value(lit)?)),
|
|
|
|
(None, Some(lit)) => Ok(Some(generate_default_with(lit)?)),
|
|
|
|
(None, None) => Ok(None),
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-11 03:23:23 +00:00
|
|
|
|
2020-09-27 02:20:20 +00:00
|
|
|
pub fn get_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
|
|
|
|
attrs
|
|
|
|
.iter()
|
|
|
|
.filter(|attr| !attr.path.segments.is_empty() && attr.path.segments[0].ident == "cfg")
|
|
|
|
.cloned()
|
|
|
|
.collect()
|
2020-06-06 07:48:21 +00:00
|
|
|
}
|
2020-09-28 09:44:00 +00:00
|
|
|
|
2022-02-28 07:26:33 +00:00
|
|
|
pub fn parse_graphql_attrs<T: FromMeta + Default>(
|
|
|
|
attrs: &[Attribute],
|
|
|
|
) -> GeneratorResult<Option<T>> {
|
2020-09-28 09:44:00 +00:00
|
|
|
for attr in attrs {
|
|
|
|
if attr.path.is_ident("graphql") {
|
|
|
|
let meta = attr.parse_meta()?;
|
|
|
|
return Ok(Some(T::from_meta(&meta)?));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_graphql_attrs(attrs: &mut Vec<Attribute>) {
|
|
|
|
if let Some((idx, _)) = attrs
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.find(|(_, a)| a.path.is_ident("graphql"))
|
|
|
|
{
|
|
|
|
attrs.remove(idx);
|
|
|
|
}
|
|
|
|
}
|
2020-11-22 01:53:18 +00:00
|
|
|
|
2021-01-10 03:21:47 +00:00
|
|
|
pub fn get_type_path_and_name(ty: &Type) -> GeneratorResult<(&Type, String)> {
|
2020-11-22 01:53:18 +00:00
|
|
|
match ty {
|
|
|
|
Type::Path(path) => Ok((
|
2021-01-10 03:21:47 +00:00
|
|
|
ty,
|
2020-11-22 01:53:18 +00:00
|
|
|
path.path
|
|
|
|
.segments
|
|
|
|
.last()
|
|
|
|
.map(|s| s.ident.to_string())
|
|
|
|
.unwrap(),
|
|
|
|
)),
|
2021-07-31 15:54:16 +00:00
|
|
|
Type::Group(TypeGroup { elem, .. }) => get_type_path_and_name(elem),
|
2021-01-10 03:21:47 +00:00
|
|
|
Type::TraitObject(trait_object) => Ok((
|
|
|
|
ty,
|
|
|
|
trait_object
|
|
|
|
.bounds
|
|
|
|
.iter()
|
|
|
|
.find_map(|bound| match bound {
|
|
|
|
TypeParamBound::Trait(t) => {
|
|
|
|
Some(t.path.segments.last().map(|s| s.ident.to_string()).unwrap())
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.unwrap(),
|
|
|
|
)),
|
2020-11-23 04:50:35 +00:00
|
|
|
_ => Err(Error::new_spanned(ty, "Invalid type").into()),
|
2020-11-22 01:53:18 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-11 08:03:28 +00:00
|
|
|
|
|
|
|
pub fn visible_fn(visible: &Option<Visible>) -> TokenStream {
|
|
|
|
match visible {
|
|
|
|
None | Some(Visible::None) => quote! { ::std::option::Option::None },
|
|
|
|
Some(Visible::HiddenAlways) => quote! { ::std::option::Option::Some(|_| false) },
|
|
|
|
Some(Visible::FnName(name)) => {
|
2021-07-18 12:14:22 +00:00
|
|
|
quote! { ::std::option::Option::Some(#name) }
|
2020-12-11 08:03:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-19 10:39:33 +00:00
|
|
|
|
|
|
|
pub fn parse_complexity_expr(s: &str) -> GeneratorResult<(HashSet<String>, Expr)> {
|
|
|
|
#[derive(Default)]
|
|
|
|
struct VisitComplexityExpr {
|
|
|
|
variables: HashSet<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Visit<'a> for VisitComplexityExpr {
|
|
|
|
fn visit_expr_path(&mut self, i: &'a ExprPath) {
|
|
|
|
if let Some(ident) = i.path.get_ident() {
|
|
|
|
if ident != "child_complexity" {
|
|
|
|
self.variables.insert(ident.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let expr: Expr = syn::parse_str(s)?;
|
|
|
|
let mut visit = VisitComplexityExpr::default();
|
|
|
|
visit.visit_expr(&expr);
|
|
|
|
Ok((visit.variables, expr))
|
|
|
|
}
|
2021-02-27 03:59:58 +00:00
|
|
|
|
|
|
|
pub fn gen_deprecation(deprecation: &Deprecation, crate_name: &TokenStream) -> TokenStream {
|
|
|
|
match deprecation {
|
|
|
|
Deprecation::NoDeprecated => {
|
|
|
|
quote! { #crate_name::registry::Deprecation::NoDeprecated }
|
|
|
|
}
|
|
|
|
Deprecation::Deprecated {
|
|
|
|
reason: Some(reason),
|
|
|
|
} => {
|
|
|
|
quote! { #crate_name::registry::Deprecation::Deprecated { reason: ::std::option::Option::Some(#reason) } }
|
|
|
|
}
|
|
|
|
Deprecation::Deprecated { reason: None } => {
|
|
|
|
quote! { #crate_name::registry::Deprecation::Deprecated { reason: ::std::option::Option::None } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-17 02:39:27 +00:00
|
|
|
|
2022-02-28 07:26:33 +00:00
|
|
|
pub fn extract_input_args<T: FromMeta + Default>(
|
2021-06-17 02:39:27 +00:00
|
|
|
crate_name: &proc_macro2::TokenStream,
|
|
|
|
method: &mut ImplItemMethod,
|
2022-02-28 07:26:33 +00:00
|
|
|
) -> GeneratorResult<Vec<(PatIdent, Type, T)>> {
|
2021-06-17 02:39:27 +00:00
|
|
|
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(),
|
2022-02-28 07:26:33 +00:00
|
|
|
parse_graphql_attrs::<T>(&pat.attrs)?.unwrap_or_default(),
|
2021-06-17 02:39:27 +00:00
|
|
|
));
|
|
|
|
} else {
|
|
|
|
create_ctx = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(Pat::Ident(arg_ident), ty) => {
|
|
|
|
args.push((
|
|
|
|
arg_ident.clone(),
|
|
|
|
ty.clone(),
|
2022-02-28 07:26:33 +00:00
|
|
|
parse_graphql_attrs::<T>(&pat.attrs)?.unwrap_or_default(),
|
2021-06-17 02:39:27 +00:00
|
|
|
));
|
|
|
|
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)
|
|
|
|
}
|
2022-02-17 08:55:32 +00:00
|
|
|
|
|
|
|
pub struct RemoveLifetime;
|
|
|
|
|
|
|
|
impl VisitMut for RemoveLifetime {
|
|
|
|
fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
|
|
|
|
i.ident = Ident::new("_", Span::call_site());
|
|
|
|
visit_mut::visit_lifetime_mut(self, i);
|
|
|
|
}
|
|
|
|
}
|