milter-callback 0.1.3

Attribute macros for milter callback generation
Documentation
use proc_macro2;
use quote::{format_ident, quote};
use syn::Ident;

pub fn to_context_expr(ident_name: &str) -> (Ident, proc_macro2::TokenStream) {
    converted_ident(ident_name, to_context_expr2)
}

fn to_context_expr2(ident: &Ident) -> proc_macro2::TokenStream {
    quote! {
        ::milter::Context::new(#ident)
    }
}

pub fn to_str_expr(ident_name: &str, result_ret: bool) -> (Ident, proc_macro2::TokenStream) {
    if result_ret {
        converted_ident(ident_name, to_str_expr_result)
    } else {
        converted_ident(ident_name, to_str_expr_panic)
    }
}

/// Returns an expression that applies conversion from `*const c_char` to `&str`
/// on the given identifier. Contains `return`, so may break out of the
/// containing scope.
fn to_str_expr_result(ident: &Ident) -> proc_macro2::TokenStream {
    quote! {
        match ::std::ffi::CStr::from_ptr(#ident).to_str() {
            ::std::result::Result::Ok(s) => s,
            ::std::result::Result::Err(e) => {
                return ::std::result::Result::Err(::milter::Error::new(::milter::ErrorKind::TypeConversion, e));
            }
        }
    }
}

fn to_str_expr_panic(ident: &Ident) -> proc_macro2::TokenStream {
    quote! {
        ::std::ffi::CStr::from_ptr(#ident).to_str().expect("invalid string in char pointer")
    }
}

pub fn to_strs_expr(ident_name: &str, result_ret: bool) -> (Ident, proc_macro2::TokenStream) {
    if result_ret {
        converted_ident(ident_name, to_strs_expr_result)
    } else {
        converted_ident(ident_name, to_strs_expr_panic)
    }
}

/// Returns an expression that applies conversion from `*const *const c_char` to
/// `Vec<&str>` on the given identifier. Contains `return`, so may break out of
/// the containing scope.
fn to_strs_expr_result(ident: &Ident) -> proc_macro2::TokenStream {
    quote! {
        match (0..)
            .map(|i| *#ident.offset(i))
            .take_while(|p| !p.is_null())
            .map(|p| ::std::ffi::CStr::from_ptr(p).to_str())
            .collect::<::std::result::Result<::std::vec::Vec<_>, _>>() {
                ::std::result::Result::Ok(s) => s,
                ::std::result::Result::Err(e) => {
                    return ::std::result::Result::Err(::milter::Error::new(::milter::ErrorKind::TypeConversion, e));
                }
        }
    }
}

fn to_strs_expr_panic(ident: &Ident) -> proc_macro2::TokenStream {
    quote! {
        (0..)
            .map(|i| *#ident.offset(i))
            .take_while(|p| !p.is_null())
            .map(|p| ::std::ffi::CStr::from_ptr(p).to_str().expect("invalid string in char pointer"))
            .collect::<::std::vec::Vec<_>>()
    }
}

pub fn to_socket_addr_expr(ident_name: &str) -> (Ident, proc_macro2::TokenStream) {
    converted_ident(ident_name, to_socket_addr_expr2)
}

/// Returns a pure expression that applies conversion from `*const sockaddr` to
/// `Option<SocketAddr>` on the given identifier.
fn to_socket_addr_expr2(ident: &Ident) -> proc_macro2::TokenStream {
    quote! {
        if #ident.is_null() {
            ::std::option::Option::None
        } else {
            match (*#ident).sa_family as _ {
                ::libc::AF_INET => {
                    let addr = #ident as *const ::libc::sockaddr_in;
                    let ip = ::std::net::Ipv4Addr::from(u32::from_be((*addr).sin_addr.s_addr));
                    let port = u16::from_be((*addr).sin_port);
                    ::std::option::Option::Some(::std::net::SocketAddr::from(::std::net::SocketAddrV4::new(ip, port)))
                }
                ::libc::AF_INET6 => {
                    let addr = #ident as *const ::libc::sockaddr_in6;
                    let ip = ::std::net::Ipv6Addr::from((*addr).sin6_addr.s6_addr);
                    let port = u16::from_be((*addr).sin6_port);
                    let flowinfo = (*addr).sin6_flowinfo;
                    let scope_id = (*addr).sin6_scope_id;
                    ::std::option::Option::Some(::std::net::SocketAddr::from(::std::net::SocketAddrV6::new(ip, port, flowinfo, scope_id)))
                }
                _ => ::std::option::Option::None,
            }
        }
    }
}

fn converted_ident(
    ident_name: &str,
    convert_fn: impl Fn(&Ident) -> proc_macro2::TokenStream,
) -> (Ident, proc_macro2::TokenStream) {
    let ident = format_ident!("{}", ident_name);
    let converted_expr = convert_fn(&ident);

    (ident, converted_expr)
}

pub fn invoke_handler_expr(
    handler: proc_macro2::TokenStream,
    result_ret: bool,
) -> proc_macro2::TokenStream {
    let ok_arms = quote! {
        ::std::result::Result::Ok(status) => status as ::milter::sfsistat,
    };
    invoke_handler_expr_ext(handler, result_ret, ok_arms)
}

pub fn invoke_handler_expr_ext(
    handler: proc_macro2::TokenStream,
    result_ret: bool,
    mut ok_arms: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    if result_ret {
        ok_arms = quote! {
            ::std::result::Result::Ok(result) => match result {
                #ok_arms
                ::std::result::Result::Err(err) => {
                    eprintln!("error result in milter callback: {}", err);
                    ::milter::Status::Tempfail as ::milter::sfsistat
                }
            },
        };
    }

    quote! {
        if ::milter::is_panicked() {
            ::milter::Status::Tempfail as ::milter::sfsistat
        } else {
            match ::std::panic::catch_unwind(|| { #handler }) {
                #ok_arms
                ::std::result::Result::Err(_) => {
                    ::milter::set_panicked(true);
                    eprintln!("panic in milter callback, shutting down");
                    ::milter::shutdown();
                    ::milter::Status::Tempfail as ::milter::sfsistat
                }
            }
        }
    }
}