refactor(macros): use syn2.0 (#2890)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
Narayan Bhat
2023-11-22 15:46:33 +05:30
committed by GitHub
parent 7d223ee0d1
commit 46e13d5475
26 changed files with 501 additions and 365 deletions

View File

@ -1,3 +1,5 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
parse::Parse, spanned::Spanned, DeriveInput, Field, Fields, LitStr, Token, TypePath, Variant,
};
@ -38,10 +40,10 @@ impl Parse for EnumMeta {
}
}
impl Spanned for EnumMeta {
fn span(&self) -> proc_macro2::Span {
impl ToTokens for EnumMeta {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::ErrorTypeEnum { keyword, .. } => keyword.span(),
Self::ErrorTypeEnum { keyword, .. } => keyword.to_tokens(tokens),
}
}
}
@ -143,13 +145,13 @@ impl Parse for VariantMeta {
}
}
impl Spanned for VariantMeta {
fn span(&self) -> proc_macro2::Span {
impl ToTokens for VariantMeta {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::ErrorType { keyword, .. } => keyword.span,
Self::Code { keyword, .. } => keyword.span,
Self::Message { keyword, .. } => keyword.span,
Self::Ignore { keyword, .. } => keyword.span,
Self::ErrorType { keyword, .. } => keyword.to_tokens(tokens),
Self::Code { keyword, .. } => keyword.to_tokens(tokens),
Self::Message { keyword, .. } => keyword.to_tokens(tokens),
Self::Ignore { keyword, .. } => keyword.to_tokens(tokens),
}
}
}

View File

@ -1,10 +1,8 @@
#![allow(clippy::use_self)]
use darling::FromMeta;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{AttributeArgs, Data, DeriveInput, ItemEnum};
use quote::{format_ident, quote, ToTokens};
use syn::{parse::Parse, Data, DeriveInput, ItemEnum};
use crate::macros::helpers::non_enum_error;
use crate::macros::helpers;
pub(crate) fn diesel_enum_text_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
@ -12,10 +10,11 @@ pub(crate) fn diesel_enum_text_derive_inner(ast: &DeriveInput) -> syn::Result<To
match &ast.data {
Data::Enum(_) => (),
_ => return Err(non_enum_error()),
}
_ => return Err(helpers::non_enum_error()),
};
Ok(quote! {
#[automatically_derived]
impl #impl_generics ::diesel::serialize::ToSql<::diesel::sql_types::Text, ::diesel::pg::Pg> for #name #ty_generics
#where_clause
@ -42,18 +41,20 @@ pub(crate) fn diesel_enum_text_derive_inner(ast: &DeriveInput) -> syn::Result<To
})
}
pub(crate) fn diesel_enum_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
pub(crate) fn diesel_enum_db_enum_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
match &ast.data {
Data::Enum(_) => (),
_ => return Err(non_enum_error()),
}
_ => return Err(helpers::non_enum_error()),
};
let struct_name = format_ident!("Db{name}");
let type_name = format!("{name}");
Ok(quote! {
#[derive(::core::clone::Clone, ::core::marker::Copy, ::core::fmt::Debug, ::diesel::QueryId, ::diesel::SqlType)]
#[diesel(postgres_type(name = #type_name))]
pub struct #struct_name;
@ -84,45 +85,138 @@ pub(crate) fn diesel_enum_derive_inner(ast: &DeriveInput) -> syn::Result<TokenSt
})
}
pub(crate) fn diesel_enum_attribute_inner(
args: &AttributeArgs,
item: &ItemEnum,
) -> syn::Result<TokenStream> {
#[derive(FromMeta, Debug)]
enum StorageType {
PgEnum,
Text,
}
mod diesel_keyword {
use syn::custom_keyword;
#[derive(FromMeta, Debug)]
struct StorageTypeArgs {
storage_type: StorageType,
}
custom_keyword!(storage_type);
custom_keyword!(db_enum);
custom_keyword!(text);
}
let storage_type_args = match StorageTypeArgs::from_list(args) {
Ok(v) => v,
Err(_) => {
return Err(syn::Error::new(
Span::call_site(),
"Expected storage_type of text or pg_enum",
));
}
};
#[derive(Debug, strum::EnumString, strum::EnumIter, strum::Display)]
#[strum(serialize_all = "snake_case")]
pub enum StorageType {
/// Store the Enum as Text value in the database
Text,
/// Store the Enum as Enum in the database. This requires a corresponding enum to be created
/// in the database with the same name
DbEnum,
}
match storage_type_args.storage_type {
StorageType::PgEnum => {
let name = &item.ident;
let type_name = format_ident!("Db{name}");
Ok(quote! {
#[derive(diesel::AsExpression, diesel::FromSqlRow, router_derive::DieselEnum) ]
#[diesel(sql_type = #type_name)]
#item
})
}
StorageType::Text => Ok(quote! {
#[derive(diesel::AsExpression, diesel::FromSqlRow, router_derive::DieselEnumText) ]
#[diesel(sql_type = ::diesel::sql_types::Text)]
#item
}),
#[derive(Debug)]
pub enum DieselEnumMeta {
StorageTypeEnum {
keyword: diesel_keyword::storage_type,
value: StorageType,
},
}
impl Parse for StorageType {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let text = input.parse::<syn::LitStr>()?;
let value = text.value();
value.as_str().parse().map_err(|_| {
syn::Error::new_spanned(
&text,
format!(
"Unexpected value for storage_type: `{value}`. Possible values are `{}`",
helpers::get_possible_values_for_enum::<Self>()
),
)
})
}
}
impl DieselEnumMeta {
pub fn get_storage_type(&self) -> &StorageType {
match self {
Self::StorageTypeEnum { value, .. } => value,
}
}
}
impl Parse for DieselEnumMeta {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(diesel_keyword::storage_type) {
let keyword = input.parse()?;
input.parse::<syn::Token![=]>()?;
let value = input.parse()?;
Ok(Self::StorageTypeEnum { keyword, value })
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for DieselEnumMeta {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::StorageTypeEnum { keyword, .. } => keyword.to_tokens(tokens),
}
}
}
trait DieselDeriveInputExt {
/// Get all the error metadata associated with an enum.
fn get_metadata(&self) -> syn::Result<Vec<DieselEnumMeta>>;
}
impl DieselDeriveInputExt for DeriveInput {
fn get_metadata(&self) -> syn::Result<Vec<DieselEnumMeta>> {
helpers::get_metadata_inner("storage_type", &self.attrs)
}
}
pub(crate) fn diesel_enum_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let storage_type = ast.get_metadata()?;
match storage_type
.first()
.ok_or(syn::Error::new(
Span::call_site(),
"Storage type must be specified",
))?
.get_storage_type()
{
StorageType::Text => diesel_enum_text_derive_inner(ast),
StorageType::DbEnum => diesel_enum_db_enum_derive_inner(ast),
}
}
/// Based on the storage type, derive appropriate diesel traits
/// This will add the appropriate #[diesel(sql_type)]
/// Since the `FromSql` and `ToSql` have to be derived for all the enums, this will add the
/// `DieselEnum` derive trait.
pub(crate) fn diesel_enum_attribute_macro(
diesel_enum_meta: DieselEnumMeta,
item: &ItemEnum,
) -> syn::Result<TokenStream> {
let diesel_derives =
quote!(#[derive(diesel::AsExpression, diesel::FromSqlRow, router_derive::DieselEnum) ]);
match diesel_enum_meta {
DieselEnumMeta::StorageTypeEnum {
value: storage_type,
..
} => match storage_type {
StorageType::Text => Ok(quote! {
#diesel_derives
#[diesel(sql_type = ::diesel::sql_types::Text)]
#[storage_type(storage_type = "text")]
#item
}),
StorageType::DbEnum => {
let name = &item.ident;
let type_name = format_ident!("Db{name}");
Ok(quote! {
#diesel_derives
#[diesel(sql_type = #type_name)]
#[storage_type(storage_type= "db_enum")]
#item
})
}
},
}
}

View File

@ -42,12 +42,14 @@ pub fn polymorphic_macro_derive_inner(
let (mandatory_attribute, other_attributes) = field
.attrs
.iter()
.partition::<Vec<_>, _>(|attribute| attribute.path.is_ident("mandatory_in"));
.partition::<Vec<_>, _>(|attribute| attribute.path().is_ident("mandatory_in"));
// Other attributes ( schema ) are to be printed as is
other_attributes
.iter()
.filter(|attribute| attribute.path.is_ident("schema") || attribute.path.is_ident("doc"))
.filter(|attribute| {
attribute.path().is_ident("schema") || attribute.path().is_ident("doc")
})
.for_each(|attribute| {
// Since attributes will be modified, the field should not contain any attributes
// So create a field, with previous attributes removed

View File

@ -23,13 +23,24 @@ pub(super) fn syn_error(span: Span, message: &str) -> syn::Error {
syn::Error::new(span, message)
}
/// Get all the variants of a enum in the form of a string
pub fn get_possible_values_for_enum<T>() -> String
where
T: strum::IntoEnumIterator + ToString,
{
T::iter()
.map(|variants| variants.to_string())
.collect::<Vec<_>>()
.join(", ")
}
pub(super) fn get_metadata_inner<'a, T: Parse + Spanned>(
ident: &str,
attrs: impl IntoIterator<Item = &'a Attribute>,
) -> syn::Result<Vec<T>> {
attrs
.into_iter()
.filter(|attr| attr.path.is_ident(ident))
.filter(|attr| attr.path().is_ident(ident))
.try_fold(Vec::new(), |mut vec, attr| {
vec.extend(attr.parse_args_with(Punctuated::<T, Token![,]>::parse_terminated)?);
Ok(vec)

View File

@ -1,25 +1,27 @@
use std::collections::HashMap;
use std::str::FromStr;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{self, spanned::Spanned, DeriveInput, Lit, Meta, MetaNameValue, NestedMeta};
use quote::{quote, ToTokens};
use strum::IntoEnumIterator;
use syn::{self, parse::Parse, DeriveInput};
use crate::macros::helpers;
use crate::macros::helpers::{self};
#[derive(Debug, Clone, Copy)]
enum Derives {
#[derive(Debug, Clone, Copy, strum::EnumString, strum::EnumIter, strum::Display)]
#[strum(serialize_all = "snake_case")]
pub enum Derives {
Sync,
Cancel,
Reject,
Capture,
Approvedata,
ApproveData,
Authorize,
Authorizedata,
Syncdata,
Canceldata,
Capturedata,
AuthorizeData,
SyncData,
CancelData,
CaptureData,
CompleteAuthorizeData,
Rejectdata,
RejectData,
SetupMandateData,
Start,
Verify,
@ -27,31 +29,6 @@ enum Derives {
SessionData,
}
impl From<String> for Derives {
fn from(s: String) -> Self {
match s.as_str() {
"sync" => Self::Sync,
"cancel" => Self::Cancel,
"reject" => Self::Reject,
"syncdata" => Self::Syncdata,
"authorize" => Self::Authorize,
"approvedata" => Self::Approvedata,
"authorizedata" => Self::Authorizedata,
"canceldata" => Self::Canceldata,
"capture" => Self::Capture,
"capturedata" => Self::Capturedata,
"completeauthorizedata" => Self::CompleteAuthorizeData,
"rejectdata" => Self::Rejectdata,
"start" => Self::Start,
"verify" => Self::Verify,
"setupmandatedata" => Self::SetupMandateData,
"session" => Self::Session,
"sessiondata" => Self::SessionData,
_ => Self::Authorize,
}
}
}
impl Derives {
fn to_operation(
self,
@ -82,8 +59,9 @@ impl Derives {
}
}
#[derive(PartialEq, Eq, Hash)]
enum Conversion {
#[derive(Debug, Clone, strum::EnumString, strum::EnumIter, strum::Display)]
#[strum(serialize_all = "snake_case")]
pub enum Conversion {
ValidateRequest,
GetTracker,
Domain,
@ -93,34 +71,20 @@ enum Conversion {
Invalid(String),
}
impl From<String> for Conversion {
fn from(s: String) -> Self {
match s.as_str() {
"validate_request" => Self::ValidateRequest,
"get_tracker" => Self::GetTracker,
"domain" => Self::Domain,
"update_tracker" => Self::UpdateTracker,
"post_tracker" => Self::PostUpdateTracker,
"all" => Self::All,
s => Self::Invalid(s.to_string()),
}
}
}
impl Conversion {
fn get_req_type(ident: Derives) -> syn::Ident {
match ident {
Derives::Authorize => syn::Ident::new("PaymentsRequest", Span::call_site()),
Derives::Authorizedata => syn::Ident::new("PaymentsAuthorizeData", Span::call_site()),
Derives::AuthorizeData => syn::Ident::new("PaymentsAuthorizeData", Span::call_site()),
Derives::Sync => syn::Ident::new("PaymentsRetrieveRequest", Span::call_site()),
Derives::Syncdata => syn::Ident::new("PaymentsSyncData", Span::call_site()),
Derives::SyncData => syn::Ident::new("PaymentsSyncData", Span::call_site()),
Derives::Cancel => syn::Ident::new("PaymentsCancelRequest", Span::call_site()),
Derives::Canceldata => syn::Ident::new("PaymentsCancelData", Span::call_site()),
Derives::Approvedata => syn::Ident::new("PaymentsApproveData", Span::call_site()),
Derives::CancelData => syn::Ident::new("PaymentsCancelData", Span::call_site()),
Derives::ApproveData => syn::Ident::new("PaymentsApproveData", Span::call_site()),
Derives::Reject => syn::Ident::new("PaymentsRejectRequest", Span::call_site()),
Derives::Rejectdata => syn::Ident::new("PaymentsRejectData", Span::call_site()),
Derives::RejectData => syn::Ident::new("PaymentsRejectData", Span::call_site()),
Derives::Capture => syn::Ident::new("PaymentsCaptureRequest", Span::call_site()),
Derives::Capturedata => syn::Ident::new("PaymentsCaptureData", Span::call_site()),
Derives::CaptureData => syn::Ident::new("PaymentsCaptureData", Span::call_site()),
Derives::CompleteAuthorizeData => {
syn::Ident::new("CompleteAuthorizeData", Span::call_site())
}
@ -231,103 +195,206 @@ impl Conversion {
}
}
fn find_operation_attr(a: &[syn::Attribute]) -> syn::Result<syn::Attribute> {
a.iter()
.find(|a| {
a.path
.get_ident()
.map(|ident| *ident == "operation")
.unwrap_or(false)
})
.cloned()
.ok_or_else(|| {
helpers::syn_error(
Span::call_site(),
"Cannot find attribute 'operation' in the macro",
)
})
mod operations_keyword {
use syn::custom_keyword;
custom_keyword!(operations);
custom_keyword!(flow);
}
fn find_value(v: &NestedMeta) -> Option<(String, Vec<String>)> {
match v {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
ref path,
eq_token: _,
lit: Lit::Str(ref litstr),
})) => {
let key = path.get_ident()?.to_string();
Some((
key,
litstr.value().split(',').map(ToString::to_string).collect(),
))
#[derive(Debug)]
pub enum OperationsEnumMeta {
Operations {
keyword: operations_keyword::operations,
value: Vec<Conversion>,
},
Flow {
keyword: operations_keyword::flow,
value: Vec<Derives>,
},
}
#[derive(Clone)]
pub struct OperationProperties {
operations: Vec<Conversion>,
flows: Vec<Derives>,
}
fn get_operation_properties(
operation_enums: Vec<OperationsEnumMeta>,
) -> syn::Result<OperationProperties> {
let mut operations = vec![];
let mut flows = vec![];
for operation in operation_enums {
match operation {
OperationsEnumMeta::Operations { value, .. } => {
operations = value;
}
OperationsEnumMeta::Flow { value, .. } => {
flows = value;
}
}
_ => None,
}
if operations.is_empty() {
Err(syn::Error::new(
Span::call_site(),
"atleast one operation must be specitied",
))?;
}
if flows.is_empty() {
Err(syn::Error::new(
Span::call_site(),
"atleast one flow must be specitied",
))?;
}
Ok(OperationProperties { operations, flows })
}
impl Parse for Derives {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let text = input.parse::<syn::LitStr>()?;
let value = text.value();
value.as_str().parse().map_err(|_| {
syn::Error::new_spanned(
&text,
format!(
"Unexpected value for flow: `{value}`. Possible values are `{}`",
helpers::get_possible_values_for_enum::<Self>()
),
)
})
}
}
fn find_properties(attr: &syn::Attribute) -> syn::Result<HashMap<String, Vec<String>>> {
let meta = attr.parse_meta();
match meta {
Ok(syn::Meta::List(syn::MetaList {
ref path,
paren_token: _,
nested,
})) => {
path.get_ident().map(|i| i == "operation").ok_or_else(|| {
helpers::syn_error(path.span(), "Attribute 'operation' was not found")
})?;
Ok(HashMap::from_iter(nested.iter().filter_map(find_value)))
impl Parse for Conversion {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let text = input.parse::<syn::LitStr>()?;
let value = text.value();
value.as_str().parse().map_err(|_| {
syn::Error::new_spanned(
&text,
format!(
"Unexpected value for operation: `{value}`. Possible values are `{}`",
helpers::get_possible_values_for_enum::<Self>()
),
)
})
}
}
fn parse_list_string<T>(list_string: String, keyword: &str) -> syn::Result<Vec<T>>
where
T: FromStr + IntoEnumIterator + ToString,
{
list_string
.split(',')
.map(str::trim)
.map(T::from_str)
.map(|result| {
result.map_err(|_| {
syn::Error::new(
Span::call_site(),
format!(
"Unexpected {keyword}, possible values are {}",
helpers::get_possible_values_for_enum::<T>()
),
)
})
})
.collect()
}
fn get_conversions(input: syn::parse::ParseStream<'_>) -> syn::Result<Vec<Conversion>> {
let lit_str_list = input.parse::<syn::LitStr>()?;
parse_list_string(lit_str_list.value(), "operation")
}
fn get_derives(input: syn::parse::ParseStream<'_>) -> syn::Result<Vec<Derives>> {
let lit_str_list = input.parse::<syn::LitStr>()?;
parse_list_string(lit_str_list.value(), "flow")
}
impl Parse for OperationsEnumMeta {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(operations_keyword::operations) {
let keyword = input.parse()?;
input.parse::<syn::Token![=]>()?;
let value = get_conversions(input)?;
Ok(Self::Operations { keyword, value })
} else if lookahead.peek(operations_keyword::flow) {
let keyword = input.parse()?;
input.parse::<syn::Token![=]>()?;
let value = get_derives(input)?;
Ok(Self::Flow { keyword, value })
} else {
Err(lookahead.error())
}
}
}
trait OperationsDeriveInputExt {
/// Get all the error metadata associated with an enum.
fn get_metadata(&self) -> syn::Result<Vec<OperationsEnumMeta>>;
}
impl OperationsDeriveInputExt for DeriveInput {
fn get_metadata(&self) -> syn::Result<Vec<OperationsEnumMeta>> {
helpers::get_metadata_inner("operation", &self.attrs)
}
}
impl ToTokens for OperationsEnumMeta {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Operations { keyword, .. } => keyword.to_tokens(tokens),
Self::Flow { keyword, .. } => keyword.to_tokens(tokens),
}
_ => Err(helpers::syn_error(
attr.span(),
"No attributes were found. Expected format is ops=..,flow=..",
)),
}
}
pub fn operation_derive_inner(input: DeriveInput) -> syn::Result<proc_macro::TokenStream> {
let struct_name = &input.ident;
let op = find_operation_attr(&input.attrs)?;
let prop = find_properties(&op)?;
let ops = prop.get("ops").ok_or_else(|| {
helpers::syn_error(
op.span(),
"Invalid properties. Property 'ops' was not found",
)
})?;
let flow = prop.get("flow").ok_or_else(|| {
helpers::syn_error(
op.span(),
"Invalid properties. Property 'flow' was not found",
)
})?;
let current_crate = syn::Ident::new(
&prop
.get("crate")
.map(|v| v.join(""))
.unwrap_or_else(|| String::from("crate")),
Span::call_site(),
);
let operations_meta = input.get_metadata()?;
let operation_properties = get_operation_properties(operations_meta)?;
let current_crate = syn::Ident::new("crate", Span::call_site());
let trait_derive = operation_properties
.clone()
.flows
.into_iter()
.map(|derive| {
let fns = operation_properties
.operations
.iter()
.map(|conversion| conversion.to_function(derive));
derive.to_operation(fns, struct_name)
})
.collect::<Vec<_>>();
let ref_trait_derive = operation_properties
.flows
.into_iter()
.map(|derive| {
let fns = operation_properties
.operations
.iter()
.map(|conversion| conversion.to_ref_function(derive));
derive.to_ref_operation(fns, struct_name)
})
.collect::<Vec<_>>();
let trait_derive = flow.iter().map(|derive| {
let derive: Derives = derive.to_owned().into();
let fns = ops.iter().map(|t| {
let con: Conversion = t.to_owned().into();
con.to_function(derive)
});
derive.to_operation(fns, struct_name)
});
let ref_trait_derive = flow.iter().map(|derive| {
let derive: Derives = derive.to_owned().into();
let fns = ops.iter().map(|t| {
let con: Conversion = t.to_owned().into();
con.to_ref_function(derive)
});
derive.to_ref_operation(fns, struct_name)
});
let trait_derive = quote! {
#(#ref_trait_derive)* #(#trait_derive)*
};
let output = quote! {
const _: () = {
use #current_crate::core::errors::RouterResult;

View File

@ -1,21 +1,62 @@
use proc_macro2::Span;
use syn::punctuated::Punctuated;
use quote::ToTokens;
use syn::{parse::Parse, punctuated::Punctuated};
mod try_get_keyword {
use syn::custom_keyword;
custom_keyword!(error_type);
}
#[derive(Debug)]
pub struct TryGetEnumMeta {
error_type: syn::Ident,
variant: syn::Ident,
}
impl Parse for TryGetEnumMeta {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let error_type = input.parse()?;
_ = input.parse::<syn::Token![::]>()?;
let variant = input.parse()?;
Ok(Self {
error_type,
variant,
})
}
}
trait TryGetDeriveInputExt {
/// Get all the error metadata associated with an enum.
fn get_metadata(&self) -> syn::Result<Vec<TryGetEnumMeta>>;
}
impl TryGetDeriveInputExt for syn::DeriveInput {
fn get_metadata(&self) -> syn::Result<Vec<TryGetEnumMeta>> {
super::helpers::get_metadata_inner("error", &self.attrs)
}
}
impl ToTokens for TryGetEnumMeta {
fn to_tokens(&self, _: &mut proc_macro2::TokenStream) {}
}
/// Try and get the variants for an enum
pub fn try_get_enum_variant(
input: syn::DeriveInput,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let name = &input.ident;
let parsed_error_type = input.get_metadata()?;
let (error_type, error_variant) = parsed_error_type
.first()
.ok_or(syn::Error::new(
Span::call_site(),
"One error should be specified",
))
.map(|error_struct| (&error_struct.error_type, &error_struct.variant))?;
let error_attr = input
.attrs
.iter()
.find(|attr| attr.path.is_ident("error"))
.ok_or(super::helpers::syn_error(
proc_macro2::Span::call_site(),
"Unable to find attribute error. Expected #[error(..)]",
))?;
let (error_type, error_variant) = get_error_type_and_variant(error_attr)?;
let (impl_generics, generics, where_clause) = input.generics.split_for_impl();
let variants = get_enum_variants(&input.data)?;
@ -49,52 +90,6 @@ pub fn try_get_enum_variant(
Ok(expanded)
}
/// Parses the attribute #[error(ErrorType(ErrorVariant))]
fn get_error_type_and_variant(attr: &syn::Attribute) -> syn::Result<(syn::Ident, syn::Path)> {
let meta = attr.parse_meta()?;
let metalist = match meta {
syn::Meta::List(list) => list,
_ => {
return Err(super::helpers::syn_error(
proc_macro2::Span::call_site(),
"Invalid attribute format #[error(ErrorType(ErrorVariant)]",
))
}
};
for meta in metalist.nested.iter() {
if let syn::NestedMeta::Meta(syn::Meta::List(meta)) = meta {
let error_type = meta
.path
.get_ident()
.ok_or(super::helpers::syn_error(
proc_macro2::Span::call_site(),
"Invalid attribute format #[error(ErrorType(ErrorVariant))]",
))
.cloned()?;
let error_variant = get_error_variant(meta)?;
return Ok((error_type, error_variant));
};
}
Err(super::helpers::syn_error(
proc_macro2::Span::call_site(),
"Invalid attribute format #[error(ErrorType(ErrorVariant))]",
))
}
fn get_error_variant(meta: &syn::MetaList) -> syn::Result<syn::Path> {
for meta in meta.nested.iter() {
if let syn::NestedMeta::Meta(syn::Meta::Path(meta)) = meta {
return Ok(meta.clone());
}
}
Err(super::helpers::syn_error(
proc_macro2::Span::call_site(),
"Invalid attribute format expected #[error(ErrorType(ErrorVariant))]",
))
}
/// Get variants from Enum
fn get_enum_variants(data: &syn::Data) -> syn::Result<Punctuated<syn::Variant, syn::token::Comma>> {
if let syn::Data::Enum(syn::DataEnum { variants, .. }) = data {