use proc_macro::TokenStream;
use quote::quote;
const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/wit");
#[proc_macro_attribute]
pub fn redis_component(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = syn::parse_macro_input!(item as syn::ItemFn);
let func_name = &func.sig.ident;
let preamble = preamble(Export::Redis);
quote!(
#func
mod __spin_redis {
mod preamble {
#preamble
}
impl self::preamble::exports::fermyon::spin::inbound_redis::Guest for preamble::Spin {
fn handle_message(msg: self::preamble::exports::fermyon::spin::inbound_redis::Payload) -> Result<(), self::preamble::fermyon::spin::redis_types::Error> {
match super::#func_name(msg.try_into().expect("cannot convert from Spin Redis payload")) {
Ok(()) => Ok(()),
Err(e) => {
eprintln!("{}", e);
Err(self::preamble::fermyon::spin::redis_types::Error::Error)
},
}
}
}
}
)
.into()
}
#[proc_macro_attribute]
pub fn http_component(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = syn::parse_macro_input!(item as syn::ItemFn);
let func_name = &func.sig.ident;
let preamble = preamble(Export::WasiHttp);
let is_native_wasi_http_handler = func.sig.inputs.len() == 2;
let await_postfix = func.sig.asyncness.map(|_| quote!(.await));
let handler = if is_native_wasi_http_handler {
quote! { super::#func_name(req, response_out)#await_postfix }
} else {
quote! { handle_response(response_out, super::#func_name(req)#await_postfix).await }
};
quote!(
#func
mod __spin_wasi_http {
mod preamble {
#preamble
}
impl self::preamble::exports::wasi::http::incoming_handler::Guest for self::preamble::Spin {
fn handle(request: self::preamble::wasi::http::types::IncomingRequest, response_out: self::preamble::wasi::http::types::ResponseOutparam) {
let request: ::spin_sdk::http::IncomingRequest = ::std::convert::Into::into(request);
let response_out: ::spin_sdk::http::ResponseOutparam = ::std::convert::Into::into(response_out);
::spin_sdk::http::run(async move {
match ::spin_sdk::http::conversions::TryFromIncomingRequest::try_from_incoming_request(request).await {
::std::result::Result::Ok(req) => #handler,
::std::result::Result::Err(e) => handle_response(response_out, e).await,
}
});
}
}
async fn handle_response<R: ::spin_sdk::http::IntoResponse>(response_out: ::spin_sdk::http::ResponseOutparam, resp: R) {
let mut response = ::spin_sdk::http::IntoResponse::into_response(resp);
let body = std::mem::take(response.body_mut());
let response = ::std::convert::Into::into(response);
if let Err(e) = ::spin_sdk::http::ResponseOutparam::set_with_body(response_out, response, body).await {
eprintln!("Could not set `ResponseOutparam`: {e}");
}
}
impl From<self::preamble::wasi::http::types::IncomingRequest> for ::spin_sdk::http::IncomingRequest {
fn from(req: self::preamble::wasi::http::types::IncomingRequest) -> Self {
unsafe { Self::from_handle(req.into_handle()) }
}
}
impl From<::spin_sdk::http::OutgoingResponse> for self::preamble::wasi::http::types::OutgoingResponse {
fn from(resp: ::spin_sdk::http::OutgoingResponse) -> Self {
unsafe { Self::from_handle(resp.into_handle()) }
}
}
impl From<self::preamble::wasi::http::types::ResponseOutparam> for ::spin_sdk::http::ResponseOutparam {
fn from(resp: self::preamble::wasi::http::types::ResponseOutparam) -> Self {
unsafe { Self::from_handle(resp.into_handle()) }
}
}
}
)
.into()
}
#[derive(Copy, Clone)]
enum Export {
WasiHttp,
Redis,
}
fn preamble(export: Export) -> proc_macro2::TokenStream {
let export_decl = match export {
Export::WasiHttp => quote!("wasi:http/incoming-handler": Spin),
Export::Redis => quote!("fermyon:spin/inbound-redis": Spin),
};
let world = match export {
Export::WasiHttp => quote!("wasi-http-trigger"),
Export::Redis => quote!("redis-trigger"),
};
quote! {
#![allow(missing_docs)]
::spin_sdk::wit_bindgen::generate!({
world: #world,
path: #WIT_PATH,
runtime_path: "::spin_sdk::wit_bindgen::rt",
exports: {
#export_decl
}
});
pub struct Spin;
}
}