1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#![deny(missing_docs)]

//! The official Rust runtime for AWS Lambda.
//!
//! This package contains macro definitions to work with lambda.
//!
//! An asynchronous function annotated with the `#[lambda]` attribute must
//! accept an argument of type `A` which implements [`serde::Deserialize`], a [`lambda::Context`] and
//! return a `Result<B, E>`, where `B` implements [`serde::Serializable`]. `E` is
//! any type that implements `Into<Box<dyn std::error::Error + Send + Sync + 'static>>`.
//! ```

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{spanned::Spanned, AttributeArgs, FnArg, ItemFn, Meta, NestedMeta};

/// Return true if attribute macro args declares http flavor in the form `#[lambda(http)]`
fn is_http(args: &AttributeArgs) -> bool {
    args.iter().any(|arg| match arg {
        NestedMeta::Meta(Meta::Path(path)) => path.is_ident("http"),
        _ => false,
    })
}

/// Return true if attribute macro args declares http flavor in the form `#[lambda(http::invoke)]`
fn is_http_invoke(args: &AttributeArgs) -> bool {
    args.iter().any(|arg| match arg {
        NestedMeta::Meta(Meta::Path(path)) => path.is_ident("http::invoke"),
        _ => false,
    })
}

#[proc_macro_attribute]
/// Wrap an async function into the lambda constructs
pub fn lambda(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(item as ItemFn);
    let args = syn::parse_macro_input!(attr as AttributeArgs);
    let ret = &input.sig.output;
    let name = &input.sig.ident;
    let body = &input.block;
    let attrs = &input.attrs;
    let asyncness = &input.sig.asyncness;
    let inputs = &input.sig.inputs;

    if name != "main" {
        let tokens = quote_spanned! { name.span() =>
            compile_error!("only the main function can be tagged with #[lambda]");
        };
        return TokenStream::from(tokens);
    }

    if asyncness.is_none() {
        let tokens = quote_spanned! { input.span() =>
          compile_error!("the async keyword is missing from the function declaration");
        };
        return TokenStream::from(tokens);
    }

    let result = match inputs.len() {
        2 => {
            let event = match inputs.first().expect("expected event argument") {
                FnArg::Typed(arg) => arg,
                _ => {
                    let tokens = quote_spanned! { inputs.span() =>
                        compile_error!("fn main's first argument must be fully formed");
                    };
                    return TokenStream::from(tokens);
                }
            };
            let event_name = &event.pat;
            let event_type = &event.ty;
            let context = match inputs.iter().nth(1).expect("expected context argument") {
                FnArg::Typed(arg) => arg,
                _ => {
                    let tokens = quote_spanned! { inputs.span() =>
                        compile_error!("fn main's second argument must be fully formed");
                    };
                    return TokenStream::from(tokens);
                }
            };
            let context_name = &context.pat;
            let context_type = &context.ty;

            if is_http(&args) {
                quote_spanned! { input.span() =>

                    #(#attrs)*
                    #asyncness fn main() {
                        async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body

                        let f = lamedh_http::handler(actual);
                        lamedh_http::lambda::run(f).await.unwrap();
                    }
                }
            } else if is_http_invoke(&args) {
                quote_spanned! { input.span() =>

                    #(#attrs)*
                    #asyncness fn main() {
                        async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body

                        let f = lamedh_http::proxy_handler(actual);
                        lamedh_http::lambda::run(f).await.unwrap();
                    }
                }
            } else {
                quote_spanned! { input.span() =>

                    #(#attrs)*
                    #asyncness fn main() {
                        async fn actual(#event_name: #event_type, #context_name: #context_type) #ret #body

                        let f = lamedh_runtime::handler_fn(actual);
                        lamedh_runtime::run(f).await.unwrap();
                    }
                }
            }
        }
        _ => {
            let tokens = quote_spanned! { inputs.span() =>
                compile_error!("The #[lambda] macro can expects two arguments: a triggered event and lambda context.");
            };
            return TokenStream::from(tokens);
        }
    };

    result.into()
}