pub(crate) mod arg;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned as _,
token,
};
use crate::common::{
Description, SpanContainer, deprecation, filter_attrs,
parse::{
ParseBufferExt as _,
attr::{OptionExt as _, err},
},
scalar,
};
pub(crate) use self::arg::OnMethod as MethodArgument;
#[derive(Debug, Default)]
pub(crate) struct Attr {
pub(crate) name: Option<SpanContainer<syn::LitStr>>,
pub(crate) description: Option<SpanContainer<Description>>,
pub(crate) deprecated: Option<SpanContainer<deprecation::Directive>>,
pub(crate) ignore: Option<SpanContainer<syn::Ident>>,
}
impl Parse for Attr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut out = Self::default();
while !input.is_empty() {
let ident = input.parse::<syn::Ident>()?;
match ident.to_string().as_str() {
"name" => {
input.parse::<token::Eq>()?;
let name = input.parse::<syn::LitStr>()?;
out.name
.replace(SpanContainer::new(ident.span(), Some(name.span()), name))
.none_or_else(|_| err::dup_arg(&ident))?
}
"desc" | "description" => {
input.parse::<token::Eq>()?;
let desc = input.parse::<Description>()?;
out.description
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
.none_or_else(|_| err::dup_arg(&ident))?
}
"deprecated" => {
let directive = input.parse::<deprecation::Directive>()?;
out.deprecated
.replace(SpanContainer::new(
ident.span(),
directive.reason.as_ref().map(|r| r.span()),
directive,
))
.none_or_else(|_| err::dup_arg(&ident))?
}
"ignore" | "skip" => out
.ignore
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
.none_or_else(|_| err::dup_arg(&ident))?,
name => {
return Err(err::unknown_arg(&ident, name));
}
}
input.try_parse::<token::Comma>()?;
}
Ok(out)
}
}
impl Attr {
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
Ok(Self {
name: try_merge_opt!(name: self, another),
description: try_merge_opt!(description: self, another),
deprecated: try_merge_opt!(deprecated: self, another),
ignore: try_merge_opt!(ignore: self, another),
})
}
pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
let mut attr = filter_attrs(name, attrs)
.map(|attr| attr.parse_args())
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
if let Some(ignore) = &attr.ignore {
if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() {
return Err(syn::Error::new(
ignore.span(),
"`ignore` attribute argument is not composable with any other arguments",
));
}
}
if attr.description.is_none() {
attr.description = Description::parse_from_doc_attrs(attrs)?;
}
if attr.deprecated.is_none() {
attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?;
}
Ok(attr)
}
}
#[derive(Debug)]
pub(crate) struct Definition {
pub(crate) ty: syn::Type,
pub(crate) name: String,
pub(crate) description: Option<Description>,
pub(crate) deprecated: Option<deprecation::Directive>,
pub(crate) ident: syn::Ident,
pub(crate) arguments: Option<Vec<MethodArgument>>,
pub(crate) has_receiver: bool,
pub(crate) is_async: bool,
}
impl Definition {
#[must_use]
pub(crate) fn is_method(&self) -> bool {
self.arguments.is_some()
}
#[must_use]
pub(crate) fn method_resolve_field_err_no_field_tokens(
scalar: &scalar::Type,
ty_name: &str,
) -> TokenStream {
quote! {
return ::core::result::Result::Err(::juniper::FieldError::from(::std::format!(
"Field `{}` not found on type `{}`",
field,
<Self as ::juniper::GraphQLType<#scalar>>::name(info)
.ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?,
)))
}
}
#[must_use]
pub(crate) fn method_mark_tokens(
&self,
infer_result: bool,
scalar: &scalar::Type,
) -> TokenStream {
let args_marks = self
.arguments
.iter()
.flat_map(|args| args.iter().filter_map(|a| a.method_mark_tokens(scalar)));
let ty = &self.ty;
let mut ty = quote! { #ty };
if infer_result {
ty = quote! {
<#ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item
};
}
let resolved_ty = quote! {
<#ty as ::juniper::IntoResolvable<
'_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
>>::Type
};
quote_spanned! { self.ty.span() =>
#( #args_marks )*
<#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
}
}
#[must_use]
pub(crate) fn method_meta_tokens(
&self,
extract_stream_type: Option<&scalar::Type>,
) -> TokenStream {
let (name, ty) = (&self.name, &self.ty);
let mut ty = quote! { #ty };
if let Some(scalar) = extract_stream_type {
ty = quote! {
<#ty as ::juniper::ExtractTypeFromStream<_, #scalar>>::Item
};
}
let description = &self.description;
let deprecated = &self.deprecated;
let args = self
.arguments
.iter()
.flat_map(|args| args.iter().filter_map(MethodArgument::method_meta_tokens));
quote! {
registry
.field_convert::<#ty, _, Self::Context>(::juniper::arcstr::literal!(#name), info)
#( #args )*
#description
#deprecated
}
}
#[must_use]
pub(crate) fn method_resolve_field_into_stream_tokens(
&self,
scalar: &scalar::Type,
) -> TokenStream {
let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident);
let mut fut = if self.is_method() {
let args = self
.arguments
.as_ref()
.unwrap()
.iter()
.map(|arg| arg.method_resolve_field_tokens(scalar, false));
let rcv = self.has_receiver.then(|| {
quote! { self, }
});
quote! { Self::#ident(#rcv #( #args ),*) }
} else {
ty = parse_quote! { _ };
quote! { &self.#ident }
};
if !self.is_async {
fut = quote! { ::juniper::futures::future::ready(#fut) };
}
quote! {
#name => {
::juniper::futures::FutureExt::boxed(async move {
let res: #ty = #fut.await;
let res = ::juniper::IntoFieldResult::<_, #scalar>::into_result(res)?;
let executor = executor.as_owned_executor();
let stream = ::juniper::futures::StreamExt::then(res, move |res| {
let executor = executor.clone();
let res2: ::juniper::FieldResult<_, #scalar> =
::juniper::IntoResolvable::into_resolvable(res, executor.context());
async move {
let ex = executor.as_executor();
match res2 {
::core::result::Result::Ok(
::core::option::Option::Some((ctx, r)),
) => {
let sub = ex.replaced_context(ctx);
sub.resolve_with_ctx_async(&(), &r)
.await
.map_err(|e| ex.new_error(e))
}
::core::result::Result::Ok(::core::option::Option::None) => {
::core::result::Result::Ok(::juniper::Value::null())
}
::core::result::Result::Err(e) => {
::core::result::Result::Err(ex.new_error(e))
}
}
}
});
::core::result::Result::Ok(::juniper::Value::Scalar::<
::juniper::ValuesStream::<#scalar>
>(::juniper::futures::StreamExt::boxed(stream)))
})
}
}
}
}
#[must_use]
pub(crate) fn all_different(fields: &[Definition]) -> bool {
let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect();
names.dedup();
names.len() == fields.len()
}