tracing-attributes-http 0.1.1

HTTP primitives for application-level tracing.
Documentation
use proc_macro_error::abort;
use syn::{
    parenthesized,
    parse::{Parse, ParseStream},
    spanned::Spanned,
    ItemFn,
};

use crate::server_send::parse::Args;
use crate::server_send::Ast;

pub fn analyze(ast: Ast, meta: Args) -> Model {
    let mut fields = vec![];
    let mut level: syn::ExprPath = syn::parse_quote!(Level::DEBUG);
    let mut name: syn::ExprLit = syn::parse_quote!("server_end");
    let mut skip: syn::ExprArray = syn::parse_quote!([]);

    let mut item = ast;
    let attrs = &mut item.attrs;

    for index in (0..attrs.len()).rev() {
        if let Some(ident) = attrs[index].path.get_ident() {
            let id = ident.to_string();
            if id.as_str() == "trace_field" {
                let attr = attrs.remove(index);
                let span = attr.tokens.span();
                get_field(&mut fields, span, attr.tokens);
            }
        }
    }

    for expr in meta.vars {
        //eprintln!("Meta argument: {:#?}", expr);
        #[allow(clippy::single_match)]
        match expr {
            syn::Expr::Assign(e) => {
                //eprintln!("Meta Expr::Assign: left: {:#?} right: {:#?}", *e.left, *e.right);
                if let syn::Expr::Path(ref el) = *e.left {
                        let first = el.path.segments.first().unwrap();
                        if first.arguments.is_empty() {
                            let param = first.ident.to_string();
                            //eprintln!("Meta Expr::Assign: left: {:#?}", param);
                            match param.as_str() {
                                "skip" => {
                                    if let syn::Expr::Array(er) = *e.right { skip = er;}
                                }
                                "name" => {
                                    if let syn::Expr::Lit(er) = *e.right { name = er;}
                                }
                                "level" => {
                                    if let syn::Expr::Path(er) = *e.right { level = er;}
                                }
                                &_ => {}
                            }
                        }
                    };

            }
            // syn::Expr::Path(ref expr) => {
            //     if expr.path.leading_colon.is_some() {
            //         //eprintln!("Meta Expr::Path: false");
            //     } else if expr.path.segments.len() != 1 {
            //         //eprintln!("Meta Expr::Path: {:?}",expr.path.segments);
            //     } else {
            //         //let first = expr.path.segments.first().unwrap();
            //         //self.vars.contains(&first.ident) && first.arguments.is_empty()
            //         //eprintln!("Meta Expr::Path: {:?}",first.arguments);
            //     }
            // }
            _ => {}
        }
    }

    Model {
        fields,
        item,
        level,
        name,
        skip,
    }
}

fn get_field(
    fields: &mut Vec<syn::ExprAssign>,
    span: proc_macro2::Span,
    tokens: proc_macro2::TokenStream,
) {
    if let Ok(arg) = syn::parse2::<AttributeArgument>(tokens) {
        fields.push(arg.expr);
    } else {
        // ../tests/ui/server_send/error/trace_field-is-not-an-assignment-expression.rs
        abort!(
            span,
            "expected an assigned expression as argument";
            help = "example: `#[trace_field(argument = 0)]`")
    }
}

struct AttributeArgument {
    expr: syn::ExprAssign,
}

impl Parse for AttributeArgument {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        let _parenthesis = parenthesized!(content in input);

        Ok(AttributeArgument {
            expr: content.parse()?,
        })
    }
}

#[derive(Debug)]
pub struct Model {
    pub fields: Vec<syn::ExprAssign>,
    pub item: ItemFn,
    pub level: syn::ExprPath,
    pub name: syn::ExprLit,
    pub skip: syn::ExprArray,
}

#[cfg(test)]
mod tests {
    use syn::{parse_quote, Attribute};

    use super::*;

    #[test]
    fn can_extract_trace_field() {
        let meta_args = Args { vars: vec![]} ;

        let model = analyze(
            parse_quote!(
                #[trace_field(x = 0)]
                fn f(x: bool) {}
            ),
            meta_args,
        );

        let expected: &[syn::ExprAssign] = &[parse_quote!(x = 0)];
        assert_eq!(expected, model.fields);

        assert!(model.item.attrs.is_empty());
    }

    // #[test]
    // fn can_extract_level() {
    //     let meta_args = Args { vars: vec![]} ;
    //     let model = analyze(
    //         parse_quote!(
    //             #[server_send(level=Level::DEBUG)]
    //             fn f(x: bool) {}
    //         ),
    //         parse_quote!(
    //             #[server_send(level=Level::DEBUG)]
    //         ),
    //     );

    //     let expected: syn::ExprPath = parse_quote!(Level::DEBUG);
    //     assert_eq!(expected, model.level);

    //     // assert!(model.item.attrs.is_empty());
    // }

    // #[test]
    // fn can_extract_skip() {
    //     let model = analyze(
    //         parse_quote!(
    //             #[server_send(skip=[x])]
    //             fn f(x: bool) {}
    //         ),
    //         parse_quote!(
    //             #[server_send(skip=[x])]
    //         ),
    //     );

    //     let expected: syn::ExprArray = parse_quote!([x]);
    //     assert_eq!(expected, model.skip);

    //     // assert!(model.item.attrs.is_empty());
    // }

    // Ensure attributes unrelated to this proc-macro are not removed or reordered
    #[test]
    fn non_dsl_attributes_are_preserved() {

        // let _ts = crate::utility::to_token_stream("#[server_send(level=tracing::Level::TRACE)]");
        // eprintln!("Token Stream{:#?}",ts);


        let model = analyze(
            parse_quote!(
                #[a]
                #[trace_field(x = 0)]
                #[b]
                fn f(x: bool) {}
            ),
            parse_quote!(
                // #[server_send(level=tracing::Level::TRACE)]
            ),
        );

        let expected: &[Attribute] = &[parse_quote!(#[a]), parse_quote!(#[b])];
        assert_eq!(expected, model.item.attrs);
    }
}