Skip to main content

spin_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3
4const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/wit");
5
6/// The entrypoint to a Spin Redis component.
7///
8/// The component runs in response to messages on a Redis queue.
9///
10/// # Examples
11///
12/// A handler that logs the content of each message it receives.
13///
14/// ```ignore
15/// # use anyhow::Result;
16/// # use bytes::Bytes;
17/// # use spin_sdk::redis_subscriber;
18/// # use std::str::from_utf8;
19/// #[redis_subscriber]
20/// async fn on_message(message: Bytes) -> Result<()> {
21///     println!("{}", from_utf8(&message)?);
22///     Ok(())
23/// }
24/// ```
25///
26/// See <https://spinframework.dev/redis-trigger> for more information.
27#[proc_macro_attribute]
28pub fn redis_subscriber(_attr: TokenStream, item: TokenStream) -> TokenStream {
29    let func = syn::parse_macro_input!(item as syn::ItemFn);
30    let func_name = &func.sig.ident;
31
32    if func.sig.asyncness.is_none() {
33        return syn::Error::new_spanned(
34            func.sig.fn_token,
35            "the `#[redis_subscriber]` function must be `async`",
36        )
37        .to_compile_error()
38        .into();
39    }
40
41    quote!(
42        #func
43        mod __spin_redis {
44            mod preamble {
45                #![allow(missing_docs)]
46                ::spin_sdk::wit_bindgen::generate!({
47                    world: "spin-sdk-macro-redis-trigger",
48                    path: #WIT_PATH,
49                    runtime_path: "::spin_sdk::wit_bindgen::rt",
50                    generate_all,
51                });
52                pub struct Spin;
53                export!(Spin);
54            }
55            impl self::preamble::exports::spin::redis::inbound_redis::Guest for preamble::Spin {
56                async fn handle_message(msg: self::preamble::exports::spin::redis::inbound_redis::Payload) -> Result<(), self::preamble::spin::redis::redis::Error> {
57                    match super::#func_name(msg.try_into().expect("cannot convert from Spin Redis payload")).await {
58                        Ok(()) => Ok(()),
59                        Err(e) => {
60                            eprintln!("{}", e);
61                            Err(self::preamble::spin::redis::redis::Error::Other(e.to_string()))
62                        },
63                    }
64                }
65            }
66        }
67    )
68    .into()
69}
70
71/// Marks an `async fn` as an HTTP component entrypoint for Spin.
72///
73/// The `#[http_service]` attribute designates an asynchronous function as the
74/// handler for incoming HTTP requests in a Spin component using the WASI Preview 3
75/// (`wasip3`) HTTP ABI.  
76///
77/// When applied, this macro generates the necessary boilerplate to export the
78/// function to the Spin runtime as a valid HTTP handler. The function must be
79/// declared `async` and take a single argument implementing
80/// [`FromRequest`], typically
81/// [`Request`], and must return a type that
82/// implements [`IntoResponse`].
83///
84/// # Requirements
85///
86/// - The annotated function **must** be `async`.
87/// - The function’s parameter type must implement [`FromRequest`].
88/// - The return type must implement [`IntoResponse`].
89///
90/// If the function is not asynchronous, the macro emits a compile-time error.
91///
92/// # Example
93///
94/// ```ignore
95/// use spin_sdk::http::{,Request, IntoResponse};
96/// use spin_sdk::http_service;
97///
98/// #[http_service]
99/// async fn my_handler(request: Request) -> impl IntoResponse {
100///   // Your logic goes here
101/// }
102/// ```
103///
104/// # Generated Code
105///
106/// The macro expands into a module containing a `Spin` struct that implements the
107/// WASI `http.handler/Guest` interface, wiring the annotated function as the
108/// handler’s entrypoint. This allows the function to be invoked automatically
109/// by the Spin runtime when HTTP requests are received.
110#[proc_macro_attribute]
111pub fn http_service(_attr: TokenStream, item: TokenStream) -> TokenStream {
112    let func = syn::parse_macro_input!(item as syn::ItemFn);
113
114    if func.sig.asyncness.is_none() {
115        return syn::Error::new_spanned(
116            func.sig.fn_token,
117            "the `#[http_service]` function must be `async`",
118        )
119        .to_compile_error()
120        .into();
121    }
122
123    let func_name = &func.sig.ident;
124
125    quote!(
126        #func
127        mod __spin_wasip3_http {
128            use ::spin_sdk::http::IntoResponse;
129
130            struct Spin;
131            ::spin_sdk::wasip3::http::service::export!(Spin);
132
133            impl ::spin_sdk::wasip3::exports::http::handler::Guest for self::Spin {
134                async fn handle(request: ::spin_sdk::wasip3::http::types::Request) -> Result<::spin_sdk::wasip3::http::types::Response, ::spin_sdk::wasip3::http::types::ErrorCode> {
135                    let request = <::spin_sdk::http::Request as ::spin_sdk::http::FromRequest>::from_request(request)?;
136                    ::spin_sdk::http::IntoResponse::into_response(super::#func_name(request).await)
137                }
138            }
139        }
140    )
141    .into()
142}
143
144/// This macro generates code from a Spin components dependencies using wit-bindgen. During expansion the
145/// macro will check for existence of a `spin-dependencies.wit` in the developers project directory
146/// and if it is present (used to indicate the presence of dependencies in the manifest) will invoke
147/// wit-bindgen to generate the bindings.
148///
149/// ```ignore
150/// use spin_sdk::http::{Request, IntoResponse};
151/// use spin_sdk::http_service;
152///
153/// // Optionally generate dependencies if "spin-dependencies.wit" is present.
154/// spin_sdk::dependencies!();
155///
156/// #[http_service]
157/// async fn my_handler(request: Request) -> impl IntoResponse {
158///   // Your logic goes here
159/// }
160/// ```
161#[proc_macro]
162pub fn dependencies(item: TokenStream) -> TokenStream {
163    if !item.is_empty() {
164        return syn::Error::new(
165            proc_macro2::Span::call_site(),
166            "the `dependencies!` macro does not take any arguments",
167        )
168        .to_compile_error()
169        .into();
170    }
171
172    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
173    let wit_path = std::path::Path::new(&manifest_dir).join("spin-dependencies.wit");
174
175    if !wit_path.exists() {
176        return TokenStream::new();
177    }
178
179    let wit_path_str = wit_path.to_str().expect("path is not valid UTF-8");
180
181    quote!(
182        ::spin_sdk::wit_bindgen::generate!({
183            path: #wit_path_str,
184            world: "root",
185            runtime_path: "::spin_sdk::wit_bindgen::rt",
186            generate_all,
187        });
188    )
189    .into()
190}