use proc_macro2::Ident;
use proc_macro2::TokenStream;
use quote::quote;
use std::cmp::Ordering;
use syn::{
punctuated::Punctuated, token::Comma, Attribute, Field, FieldsNamed, GenericArgument,
PathArguments, Type,
};
pub(crate) fn impl_into(attrs: &Vec<Attribute>) -> Option<Type> {
for attr in attrs {
if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "impl_into" {
return Some(
attr.parse_args().unwrap_or_else(|_| {
panic!("impl_into must be followed by the entrypoint type")
}),
);
}
}
None
}
pub(crate) fn process_impl_into(
attrs: &Vec<Attribute>,
ident: &Ident,
) -> (TokenStream, TokenStream, Punctuated<GenericArgument, Comma>) {
let impl_into = impl_into(attrs);
let mut type_generics = Punctuated::<GenericArgument, Comma>::new();
if let Some(entrypoint_msg_type) = impl_into {
if let Type::Path(e) = &entrypoint_msg_type {
let type_args = e.path.segments[0].arguments.clone();
if let PathArguments::AngleBracketed(argo) = type_args {
type_generics = argo.args
}
};
(quote!(.into()), quote!(#entrypoint_msg_type), type_generics)
} else {
(quote!(), quote!(#ident), type_generics)
}
}
#[derive(Default)]
pub(crate) struct LexiographicMatching {}
impl syn::visit_mut::VisitMut for LexiographicMatching {
fn visit_fields_named_mut(&mut self, i: &mut FieldsNamed) {
let mut fields: Vec<Field> = i.named.iter().map(Clone::clone).collect();
fields.sort_by(|a, b| {
maybe_compare_option(a, b, "Option").unwrap_or_else(|| {
a.ident
.as_ref()
.unwrap()
.to_string()
.cmp(&b.ident.as_ref().unwrap().to_string())
})
});
let sorted_fields: Punctuated<Field, Comma> = Punctuated::from_iter(fields);
*i = FieldsNamed {
named: sorted_fields,
..i.clone()
};
}
}
fn maybe_compare_option(a: &Field, b: &Field, wrapper: &str) -> Option<Ordering> {
if is_option(wrapper, &a.ty) && is_option(wrapper, &b.ty) {
return Some(
a.ident
.as_ref()
.unwrap()
.to_string()
.cmp(&b.ident.as_ref().unwrap().to_string()),
);
}
else if is_option(wrapper, &a.ty) {
return Some(Ordering::Greater);
} else if is_option(wrapper, &b.ty) {
return Some(Ordering::Less);
}
None
}
fn is_option(wrapper: &str, ty: &'_ syn::Type) -> bool {
if let syn::Type::Path(ref p) = ty {
if p.path.segments.len() != 1 || p.path.segments[0].ident != wrapper {
return false;
}
if let syn::PathArguments::AngleBracketed(ref inner_ty) = p.path.segments[0].arguments {
if inner_ty.args.len() != 1 {
return false;
}
return true;
}
}
false
}