use std::collections::{HashSet, VecDeque};
use proc_macro2::Ident;
use syn::{parenthesized, spanned::Spanned, token, Field, FieldsNamed, Meta};
pub const C_CHAR_ATTR: &str = "c_char";
pub const FIXED_PRICE_ATTR: &str = "fixed_price";
pub const FMT_BINARY: &str = "fmt_binary";
pub const FMT_METHOD: &str = "fmt_method";
pub const INDEX_TS_ATTR: &str = "index_ts";
pub const SKIP_ATTR: &str = "skip";
pub const UNIX_NANOS_ATTR: &str = "unix_nanos";
pub fn get_sorted_fields(fields: FieldsNamed) -> syn::Result<VecDeque<Field>> {
let mut fields: VecDeque<_> = fields.named.into_iter().collect();
let mut encode_orders = HashSet::new();
let mut encode_order_fields = Vec::new();
for field in fields.iter() {
if let Some(encode_order) = find_encode_order_attr(field)? {
if !encode_orders.insert(encode_order) {
return Err(syn::Error::new_spanned(
field,
format!("Specified duplicate encode order `{encode_order}` for field"),
));
}
encode_order_fields.push((encode_order, field.clone()));
}
}
encode_order_fields.sort_by_key(|field| field.0);
for (encode_order, prioritized_field) in encode_order_fields {
let idx = fields
.iter()
.position(|f| f.ident == prioritized_field.ident)
.expect("to find field");
fields.remove(idx).expect("to find field");
if encode_order >= fields.len() {
fields.push_back(prioritized_field);
} else {
fields.insert(encode_order, prioritized_field);
}
}
Ok(fields)
}
pub fn find_dbn_attr_args(field: &Field) -> syn::Result<Vec<Ident>> {
for attr in field.attrs.iter() {
if let Meta::List(ref meta_list) = attr.meta {
if meta_list.path.is_ident("dbn") {
let mut args = Vec::new();
meta_list.parse_nested_meta(|meta| {
if meta.path.is_ident("encode_order") {
if meta.input.peek(token::Paren) {
let content;
parenthesized!(content in meta.input);
let _lit: syn::LitInt = content.parse()?;
}
Ok(())
} else if let Some(i) = meta.path.get_ident() {
if i == C_CHAR_ATTR
|| i == FIXED_PRICE_ATTR
|| i == FMT_BINARY
|| i == FMT_METHOD
|| i == INDEX_TS_ATTR
|| i == SKIP_ATTR
|| i == UNIX_NANOS_ATTR
{
args.push(i.clone());
Ok(())
} else {
Err(meta.error(format!("unrecognized dbn attr argument {i}")))
}
} else {
Err(meta.error("unrecognized dbn attr"))
}
})?;
return Ok(args);
}
}
}
Ok(vec![])
}
fn find_encode_order_attr(field: &Field) -> syn::Result<Option<usize>> {
for attr in field.attrs.iter() {
if let Meta::List(ref meta_list) = attr.meta {
if meta_list.path.is_ident("dbn") {
let mut encode_order = None;
meta_list.parse_nested_meta(|meta| {
if meta.path.is_ident("encode_order") {
if meta.input.peek(token::Paren) {
let content;
parenthesized!(content in meta.input);
let lit: syn::LitInt = content.parse()?;
let n: usize = lit.base10_parse()?;
encode_order = Some(n);
} else {
encode_order = Some(0)
}
}
Ok(())
})?;
return Ok(encode_order);
}
}
}
Ok(None)
}
pub fn is_hidden(field: &Field) -> bool {
let ident = field.ident.as_ref().unwrap();
ident.to_string().starts_with('_')
|| find_dbn_attr_args(field)
.unwrap_or_default()
.iter()
.any(|id| id == SKIP_ATTR)
}
pub fn find_dbn_debug_attr(field: &Field) -> syn::Result<Option<Ident>> {
let mut args: Vec<_> = find_dbn_attr_args(field)?
.into_iter()
.filter(|id| {
id == C_CHAR_ATTR
|| id == FIXED_PRICE_ATTR
|| id == FMT_BINARY
|| id == FMT_METHOD
|| id == UNIX_NANOS_ATTR
})
.collect();
match args.len() {
0 => Ok(None),
1 => Ok(Some(args.pop().unwrap())),
_ => Err(syn::Error::new(
field.span(),
"Passed incompatible format arguments to dbn attr",
)),
}
}
pub fn find_dbn_serialize_attr(field: &Field) -> syn::Result<Option<Ident>> {
let mut args: Vec<_> = find_dbn_attr_args(field)?
.into_iter()
.filter(|id| id == C_CHAR_ATTR || id == FIXED_PRICE_ATTR || id == UNIX_NANOS_ATTR)
.collect();
match args.len() {
0 => Ok(None),
1 => Ok(Some(args.pop().unwrap())),
_ => Err(syn::Error::new(
field.span(),
"Passed incompatible serialization arguments to dbn attr",
)),
}
}
#[cfg(test)]
mod tests {
use quote::quote;
use syn::FieldsNamed;
use super::*;
#[test]
fn find_encode_order_attr_blank() {
let input = quote!({
pub b: bool,
});
let fields = syn::parse2::<FieldsNamed>(input).unwrap();
assert_eq!(fields.named.len(), 1);
assert!(find_encode_order_attr(fields.named.first().unwrap())
.unwrap()
.is_none());
}
#[test]
fn find_encode_order_attr_none() {
let input = quote!({
#[dbn(c_char)]
pub f: c_char,
});
let fields = syn::parse2::<FieldsNamed>(input).unwrap();
assert_eq!(fields.named.len(), 1);
assert!(find_encode_order_attr(fields.named.first().unwrap())
.unwrap()
.is_none());
}
#[test]
fn find_encode_order_attr_only() {
let input = quote!({
#[dbn(encode_order(3))]
pub b: bool,
});
let fields = syn::parse2::<FieldsNamed>(input).unwrap();
assert_eq!(fields.named.len(), 1);
assert_eq!(
find_encode_order_attr(fields.named.first().unwrap())
.unwrap()
.unwrap(),
3
);
}
#[test]
fn find_encode_order_attr_first() {
let input = quote!({
#[dbn(encode_order(2), unix_nanos)]
pub ts: u64,
});
let fields = syn::parse2::<FieldsNamed>(input).unwrap();
assert_eq!(fields.named.len(), 1);
assert_eq!(
find_encode_order_attr(fields.named.first().unwrap())
.unwrap()
.unwrap(),
2
);
}
#[test]
fn find_encode_order_attr_last() {
let input = quote!({
#[dbn(unix_nanos, encode_order(4))]
pub ts: u64,
});
let fields = syn::parse2::<FieldsNamed>(input).unwrap();
assert_eq!(fields.named.len(), 1);
assert_eq!(
find_encode_order_attr(fields.named.first().unwrap())
.unwrap()
.unwrap(),
4
);
}
#[test]
fn find_encode_order_attr_default() {
let input = quote!({
#[dbn(unix_nanos, encode_order)]
pub ts: u64,
});
let fields = syn::parse2::<FieldsNamed>(input).unwrap();
assert_eq!(fields.named.len(), 1);
assert_eq!(
find_encode_order_attr(fields.named.first().unwrap())
.unwrap()
.unwrap(),
0
);
}
}