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}