land_sdk_macro/
lib.rs

1#![allow(clippy::redundant_clone)]
2
3//! # Rust SDK Macro for Runtime.land.
4//!
5//! This macro is used to develop Runtime.land functions in `land-sdk`.
6//! It should not be used directly.
7//!
8//! # Hello World
9//!
10//! ```no_run
11//! use land_sdk::http::{Body, Request, Response};
12//! use land_sdk::http_main;
13//!
14//! #[http_main]
15//! pub fn handle_request(req: Request) -> Response {
16//!     // read uri and method from request
17//!     let url = req.uri().clone();
18//!     let method = req.method().to_string().to_uppercase();
19//!
20//!     // build response
21//!     http::Response::builder()
22//!         .status(200)
23//!         .header("X-Request-Url", url.to_string())
24//!         .header("X-Request-Method", method)
25//!         .body(Body::from("Hello Runtime.land!!"))
26//!         .unwrap()
27//! }
28//! ```
29//!
30
31use proc_macro::TokenStream;
32use quote::quote;
33
34/// http_main is a macro to generate a http handler function.
35#[proc_macro_attribute]
36pub fn http_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
37    let func = syn::parse_macro_input!(item as syn::ItemFn);
38    let func_name = func.sig.ident.clone();
39
40    let wit_guest_rs = include_str!("./http_handler.rs").to_string();
41    let iface: TokenStream = wit_guest_rs.parse().expect("cannot parse http_handler.rs");
42
43    let iface_impl = quote!(
44
45        use exports::land::http::http_incoming;
46
47        struct HttpImpl;
48
49        impl TryFrom<http_incoming::Request> for Request {
50            type Error = anyhow::Error;
51
52            fn try_from(wasm_req: http_incoming::Request) -> Result<Self, Self::Error> {
53                use std::str::FromStr;
54
55                let mut http_req = http::Request::builder()
56                    .method(http::Method::from_str(wasm_req.method.as_str())?)
57                    .uri(&wasm_req.uri);
58
59                for (key, value) in wasm_req.headers {
60                    http_req = http_req.header(key, value);
61                }
62                // 1 is the request body handle, which is defined in wasi host functions
63                let body = Body::from_handle(wasm_req.body.unwrap_or(1));
64                Ok(http_req.body(body)?)
65            }
66        }
67
68        impl TryFrom<Response> for http_incoming::Response {
69            type Error = anyhow::Error;
70
71            fn try_from(http_res: Response) -> Result<Self, Self::Error> {
72                let status = http_res.status().as_u16();
73                let mut headers: Vec<(String, String)> = vec![];
74                for (key, value) in http_res.headers() {
75                    headers.push((key.to_string(), value.to_str()?.to_string()));
76                }
77                let body = http_res.body();
78                Ok(http_incoming::Response {
79                    status,
80                    headers,
81                    body: Some(body.body_handle()),
82                })
83            }
84        }
85
86        impl http_incoming::Guest for HttpImpl {
87            fn handle_request(req: http_incoming::Request) -> http_incoming::Response {
88                #func
89
90                // convert wasm_request to sdk_request
91                let sdk_request: Request = req.try_into().unwrap();
92                let sdk_response = match #func_name(sdk_request){
93                    Ok(r) => r,
94                    Err(e) => {
95                        land_sdk::http::error_response(
96                            http::StatusCode::INTERNAL_SERVER_ERROR,
97                            e.to_string(),
98                        )
99                    }
100                };
101
102                let sdk_response_body_handle = sdk_response.body().body_handle();
103                // convert sdk_response to wasm_response
104                match sdk_response.try_into() {
105                    Ok(r) => r,
106                    Err(_e) => http_incoming::Response {
107                        status: 500,
108                        headers: vec![],
109                        body: Some(sdk_response_body_handle),
110                    },
111                }
112            }
113        }
114
115    );
116    let value = format!("{iface}\n{iface_impl}");
117    value.parse().unwrap()
118}