faasta_macros/lib.rs
1use proc_macro::TokenStream;
2use quote::quote;
3use std::env;
4use syn::{parse_macro_input, ItemFn};
5
6/// Transforms an `async fn` into an exported function suitable for use in FaaS-based runtimes.
7///
8/// # Overview
9///
10/// The `#[faasta]` attribute takes an async function with certain parameters and generates a
11/// new exported function named `dy_<package_name>`, where `<package_name>` is the crate's package name,
12/// obtained from the `CARGO_PKG_NAME` environment variable (or defaults to `default_package` if not set).
13///
14/// The generated function has an ABI of `extern "Rust"` and returns a
15/// `Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>`. This allows it to be
16/// consumed by the faasta function-as-a-service (FaaS) runtimes while preserving the async execution model.
17///
18/// # Usage
19///
20/// 1. Mark your async function with the `#[faasta]` attribute.
21/// 2. Ensure your function arguments are only among the supported types:
22/// - [`Method`](https://docs.rs/http/latest/http/struct.Method.html)
23/// - [`Uri`](https://docs.rs/http/latest/http/uri/struct.Uri.html)
24/// - [`HeaderMap`](https://docs.rs/http/latest/http/header/struct.HeaderMap.html)
25/// - [`Bytes`](https://docs.rs/bytes/latest/bytes/struct.Bytes.html)
26/// - `Dir` (a custom type provided by your application)
27/// 3. Return a [`Response<Body>`](https://docs.rs/http/latest/http/response/struct.Response.html).
28///
29/// ```rust
30/// # use axum::Bytes;
31/// # use axum::{Method, StatusCode};
32/// # use axum::response::Response;
33/// # use axum::Body;
34/// # use faasta_macro::faasta;
35/// # use CapStd::fs::Dir;
36///
37/// #[faasta]
38/// async fn handler(method: Method, body: Bytes, dir: Dir) -> Response<Body> {
39/// Response::builder()
40/// .status(StatusCode::OK)
41/// .body(Body::from("HELLO WORLD"))
42/// .unwrap()
43/// }
44/// ```
45///
46/// The macro will generate code similar to:
47///
48/// ```ignore
49/// #[no_mangle]
50/// pub extern "Rust" fn dy_<HMAC>(
51/// method: Method,
52/// uri: Uri,
53/// headers: HeaderMap,
54/// body: Bytes,
55/// dir: Dir
56/// ) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>> {
57/// Box::pin(async move {
58/// // Original function body
59/// })
60/// }
61/// ```
62///
63/// # Compile Errors
64///
65/// A compile error will occur if:
66///
67/// - The function is not `async`.
68/// - Any function parameter type is not among the supported types (listed above).
69///
70/// # Environment Variables
71///
72/// - `CARGO_PKG_NAME`: Used to determine the crate name. Defaults to `default_package` if not set.
73///
74/// # Example
75///
76/// ```rust,ignore
77/// // In Cargo.toml, name might be "my-awesome-service"
78/// // $ export CARGO_PKG_NAME="my-awesome-service"
79///
80/// #[faasta]
81/// async fn my_handler(method: Method, body: Bytes, dir: Dir) -> Response<Body> {
82/// Response::builder()
83/// .status(StatusCode::OK)
84/// .body(Body::from("Hello from my-awesome-service!"))
85/// .unwrap()
86/// }
87/// // This compiles into a function named `dy_my-awesome-service(...)`.
88/// ```
89#[proc_macro_attribute]
90pub fn faasta(_attr: TokenStream, item: TokenStream) -> TokenStream {
91 let input = parse_macro_input!(item as ItemFn);
92 let vis = input.vis;
93 let sig = input.sig;
94 let fn_args = &sig.inputs;
95 let block = input.block;
96
97 let supported_args = ["Method", "Uri", "HeaderMap", "Bytes", "Dir"];
98
99 // Validate function arguments
100 for arg in fn_args.iter() {
101 if let syn::FnArg::Typed(pat_type) = arg {
102 if let syn::Type::Path(type_path) = &*pat_type.ty {
103 let arg_type = type_path.path.segments.last().unwrap().ident.to_string();
104 if !supported_args.contains(&arg_type.as_str()) {
105 return syn::Error::new_spanned(
106 pat_type,
107 format!(
108 "Unsupported argument type: {}. Supported types are: {:?}",
109 arg_type, supported_args
110 ),
111 )
112 .to_compile_error()
113 .into();
114 }
115 }
116 }
117 }
118
119 // Get the package name from the environment
120 let package_name = env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "default_package".to_string());
121
122 // Generate the new function name
123 let new_fn_name = quote::format_ident!("dy_{}", package_name);
124 println!("Generated function name: {}", new_fn_name);
125
126 // Generate the wrapper function
127 let output = quote! {
128 #[unsafe(no_mangle)]
129 #vis extern "Rust" fn #new_fn_name(
130 method: Method,
131 uri: Uri,
132 headers: HeaderMap,
133 body: Bytes,
134 dir: Dir
135 ) -> std::pin::Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>> {
136 Box::pin(async move {
137 #block
138 })
139 }
140 };
141
142 output.into()
143}