1use proc_macro::TokenStream;
2use quote::quote;
3
4const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/wit");
5
6#[proc_macro_attribute]
8pub fn redis_component(_attr: TokenStream, item: TokenStream) -> TokenStream {
9 let func = syn::parse_macro_input!(item as syn::ItemFn);
10 let func_name = &func.sig.ident;
11 let await_postfix = func.sig.asyncness.map(|_| quote!(.await));
12 let preamble = preamble(Export::Redis);
13
14 quote!(
15 #func
16 mod __spin_redis {
17 mod preamble {
18 #preamble
19 }
20 impl self::preamble::exports::fermyon::spin::inbound_redis::Guest for preamble::Spin {
21 fn handle_message(msg: self::preamble::exports::fermyon::spin::inbound_redis::Payload) -> Result<(), self::preamble::fermyon::spin::redis_types::Error> {
22 ::spin_sdk::http::run(async move {
23 match super::#func_name(msg.try_into().expect("cannot convert from Spin Redis payload"))#await_postfix {
24 Ok(()) => Ok(()),
25 Err(e) => {
26 eprintln!("{}", e);
27 Err(self::preamble::fermyon::spin::redis_types::Error::Error)
28 },
29 }
30 })
31 }
32 }
33 }
34 )
35 .into()
36}
37
38#[proc_macro_attribute]
89pub fn http_component(_attr: TokenStream, item: TokenStream) -> TokenStream {
90 let func = syn::parse_macro_input!(item as syn::ItemFn);
91 let func_name = &func.sig.ident;
92 let preamble = preamble(Export::WasiHttp);
93 let is_native_wasi_http_handler = func.sig.inputs.len() == 2;
94 let await_postfix = func.sig.asyncness.map(|_| quote!(.await));
95 let handler = if is_native_wasi_http_handler {
96 quote! { super::#func_name(req, response_out)#await_postfix }
97 } else {
98 quote! { handle_response(response_out, super::#func_name(req)#await_postfix).await }
99 };
100
101 quote!(
102 #func
103 mod __spin_wasi_http {
104 mod preamble {
105 #preamble
106 }
107 impl self::preamble::exports::wasi::http::incoming_handler::Guest for self::preamble::Spin {
108 fn handle(request: self::preamble::wasi::http::types::IncomingRequest, response_out: self::preamble::wasi::http::types::ResponseOutparam) {
109 let request: ::spin_sdk::http::IncomingRequest = ::std::convert::Into::into(request);
110 let response_out: ::spin_sdk::http::ResponseOutparam = ::std::convert::Into::into(response_out);
111 ::spin_sdk::http::run(async move {
112 match ::spin_sdk::http::conversions::TryFromIncomingRequest::try_from_incoming_request(request).await {
113 ::std::result::Result::Ok(req) => #handler,
114 ::std::result::Result::Err(e) => handle_response(response_out, e).await,
115 }
116 });
117 }
118 }
119
120 async fn handle_response<R: ::spin_sdk::http::IntoResponse>(response_out: ::spin_sdk::http::ResponseOutparam, resp: R) {
121 let mut response = ::spin_sdk::http::IntoResponse::into_response(resp);
122 let body = ::std::mem::take(response.body_mut());
123 match ::std::convert::TryInto::try_into(response) {
124 ::std::result::Result::Ok(response) => {
125 if let Err(e) = ::spin_sdk::http::ResponseOutparam::set_with_body(response_out, response, body).await {
126 ::std::eprintln!("Could not set `ResponseOutparam`: {e}");
127 }
128 }
129 ::std::result::Result::Err(e) => {
130 ::std::eprintln!("Could not convert response: {e}");
131 }
132 }
133 }
134
135 impl From<self::preamble::wasi::http::types::IncomingRequest> for ::spin_sdk::http::IncomingRequest {
136 fn from(req: self::preamble::wasi::http::types::IncomingRequest) -> Self {
137 unsafe { Self::from_handle(req.into_handle()) }
138 }
139 }
140
141 impl From<::spin_sdk::http::OutgoingResponse> for self::preamble::wasi::http::types::OutgoingResponse {
142 fn from(resp: ::spin_sdk::http::OutgoingResponse) -> Self {
143 unsafe { Self::from_handle(resp.into_handle()) }
144 }
145 }
146
147 impl From<self::preamble::wasi::http::types::ResponseOutparam> for ::spin_sdk::http::ResponseOutparam {
148 fn from(resp: self::preamble::wasi::http::types::ResponseOutparam) -> Self {
149 unsafe { Self::from_handle(resp.into_handle()) }
150 }
151 }
152 }
153
154 )
155 .into()
156}
157
158#[derive(Copy, Clone)]
159enum Export {
160 WasiHttp,
161 Redis,
162}
163
164fn preamble(export: Export) -> proc_macro2::TokenStream {
165 let export_decl = match export {
166 Export::WasiHttp => quote!("wasi:http/incoming-handler": Spin),
167 Export::Redis => quote!("fermyon:spin/inbound-redis": Spin),
168 };
169 let world = match export {
170 Export::WasiHttp => quote!("wasi-http-trigger"),
171 Export::Redis => quote!("redis-trigger"),
172 };
173 quote! {
174 #![allow(missing_docs)]
175 ::spin_sdk::wit_bindgen::generate!({
176 world: #world,
177 path: #WIT_PATH,
178 runtime_path: "::spin_sdk::wit_bindgen::rt",
179 exports: {
180 #export_decl
181 }
182 });
183 pub struct Spin;
184 }
185}