use crate::util::{remove_parens, CollectToResult, PathEndsWith}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse::Parse, spanned::Spanned, Attribute, AttributeArgs, Error, ItemTrait, LitStr, Meta, NestedMeta, PredicateType, Result, TraitItem, WherePredicate }; struct TraitItemAttrs { openapi_only: bool, openapi_bound: Vec, non_openapi_bound: Vec, other_attrs: Vec } impl TraitItemAttrs { fn parse(attrs: Vec) -> Result { let mut openapi_only = false; let mut openapi_bound = Vec::new(); let mut non_openapi_bound = Vec::new(); let mut other = Vec::new(); for attr in attrs { if attr.path.ends_with("openapi_only") { openapi_only = true; } else if attr.path.ends_with("openapi_bound") { let attr_arg: LitStr = syn::parse2(remove_parens(attr.tokens))?; let predicate = attr_arg.parse_with(WherePredicate::parse)?; openapi_bound.push(match predicate { WherePredicate::Type(ty) => ty, _ => return Err(Error::new(predicate.span(), "Expected type bound")) }); } else if attr.path.ends_with("non_openapi_bound") { let attr_arg: LitStr = syn::parse2(remove_parens(attr.tokens))?; let predicate = attr_arg.parse_with(WherePredicate::parse)?; non_openapi_bound.push(match predicate { WherePredicate::Type(ty) => ty, _ => return Err(Error::new(predicate.span(), "Expected type bound")) }); } else { other.push(attr); } } Ok(Self { openapi_only, openapi_bound, non_openapi_bound, other_attrs: other }) } } pub(crate) fn expand_private_openapi_trait(mut attrs: AttributeArgs, tr8: ItemTrait) -> Result { let tr8_attrs = &tr8.attrs; let vis = &tr8.vis; let ident = &tr8.ident; let generics = &tr8.generics; let colon_token = &tr8.colon_token; let supertraits = &tr8.supertraits; if attrs.len() != 1 { return Err(Error::new( Span::call_site(), "Expected one argument. Example: #[_private_openapi_trait(OpenapiTraitName)]" )); } let openapi_ident = match attrs.remove(0) { NestedMeta::Meta(Meta::Path(path)) => path, p => { return Err(Error::new( p.span(), "Expected name of the Resource struct this method belongs to" )) }, }; let orig_trait = { let items = tr8 .items .clone() .into_iter() .map(|item| { Ok(match item { TraitItem::Method(mut method) => { let attrs = TraitItemAttrs::parse(method.attrs)?; method.attrs = attrs.other_attrs; for bound in attrs.non_openapi_bound { method .sig .generics .type_params_mut() .filter(|param| param.ident.to_string() == bound.bounded_ty.to_token_stream().to_string()) .for_each(|param| param.bounds.extend(bound.bounds.clone())); } if attrs.openapi_only { None } else { Some(TraitItem::Method(method)) } }, TraitItem::Type(mut ty) => { let attrs = TraitItemAttrs::parse(ty.attrs)?; ty.attrs = attrs.other_attrs; Some(TraitItem::Type(ty)) }, item => Some(item) }) }) .collect_to_result()?; quote! { #(#tr8_attrs)* #vis trait #ident #generics #colon_token #supertraits { #(#items)* } } }; let openapi_trait = if !cfg!(feature = "openapi") { None } else { let items = tr8 .items .clone() .into_iter() .map(|item| { Ok(match item { TraitItem::Method(mut method) => { let attrs = TraitItemAttrs::parse(method.attrs)?; method.attrs = attrs.other_attrs; for bound in attrs.openapi_bound { method .sig .generics .type_params_mut() .filter(|param| param.ident.to_string() == bound.bounded_ty.to_token_stream().to_string()) .for_each(|param| param.bounds.extend(bound.bounds.clone())); } TraitItem::Method(method) }, TraitItem::Type(mut ty) => { let attrs = TraitItemAttrs::parse(ty.attrs)?; ty.attrs = attrs.other_attrs; for bound in attrs.openapi_bound { ty.bounds.extend(bound.bounds.clone()); } TraitItem::Type(ty) }, item => item }) }) .collect_to_result()?; Some(quote! { #(#tr8_attrs)* #vis trait #openapi_ident #generics #colon_token #supertraits { #(#items)* } }) }; Ok(quote! { #orig_trait #openapi_trait }) }