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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use proc_macro::TokenStream;
use quote::quote;

const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/wit");

/// Generates the entrypoint to a Spin Redis component written in Rust.
#[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()
}

/// The entrypoint to a WASI HTTP component written in Rust.
///
/// Functions annotated with this attribute can be of two forms:
/// * Request/Response
/// * Input/Output Params
///
/// When in doubt prefer the Request/Response variant unless streaming response bodies is something you need.
///
/// ### Request/Response
///
/// This form takes the form of a function with one `request` param and one `response` return value.
///
/// Requests are anything that implements `spin_sdk::http::conversions::TryFromIncomingRequest` which includes
/// `spin_sdk::http::Request`, `spin_sdk::http::IncomingRequest`, and even hyperium's popular `http` crate's `Request`
/// type.
///
/// Responses are anything that implements `spin_sdk::http::IntoResponse`. This includes `Result<impl IntoResponse, impl IntoResponse`,
/// `spin_sdk::http::Response`, and even the `http` crate's `Response` type.
///
/// For example:
/// ```ignore
/// use spin_sdk::http_component;
/// use spin_sdk::http::{Request, IntoResponse};
///
/// #[http_component]
/// async fn my_handler(request: Request) -> anyhow::Result<impl IntoResponse> {
///   // Your logic goes here
/// }
/// ```
///
/// ### Input/Output Params
///
/// Input/Output functions allow for streaming HTTP bodies. This form is by its very nature harder to use than
/// the request/response form above so it should only be favored when stream response bodies is desired.
///
/// The `request` param can be anything that implements `spin_sdk::http::TryFromIncomingRequest`. And
/// the `response_out` param must be a `spin_sdk::http::ResponseOutparam`. See the docs of `ResponseOutparam`
/// for how to use this type.
///
/// For example:
///
/// ```ignore
/// use spin_sdk::http_component;
/// use spin_sdk::http::{IncomingRequest, ResponseOutparam};
///
/// #[http_component]
/// async fn my_handler(request: IncomingRequest, response_out: ResponseOutparam) {
///   // Your logic goes here
/// }
/// ```
#[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;
    }
}