kiwi_macro/
lib.rs

1//! This crate exports macros that are  are used to generate the necessary code
2//! to turn a source file into a `Guest` module, suitable for compilation and
3//! execution within Kiwi's embedded WASM component runtime ([wasmtime](https://github.com/bytecodealliance/wasmtime)).
4//!
5//! ### NOTE
6//! This crate is intended for use only via the Kiwi SDK and should not be used directly.
7
8use proc_macro::TokenStream;
9use quote::quote;
10
11/// Wit sources are packaged as part of the release process, so we can reference them
12/// from the crate root. We need to hoist the path here to ensure it references the correct
13/// location.
14const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/wit");
15
16/// Macro necessary for creating an intercept hook.
17#[proc_macro_attribute]
18pub fn intercept(_attr: TokenStream, item: TokenStream) -> TokenStream {
19    let func = syn::parse_macro_input!(item as syn::ItemFn);
20    let func_name = &func.sig.ident;
21
22    // Note that intercept modules don't link in WASI, so there's no need
23    // to remap any WASI imports to their counterparts located in the Kiwi SDK.
24    // In fact, because Kiwi does not link in WASI for intercept modules, attempting
25    // to use WASI imports should result in an error.
26    quote!(
27        #func
28        mod __kiwi_intercept {
29            mod bindings {
30                #![allow(missing_docs)]
31                ::kiwi_sdk::wit_bindgen::generate!({
32                    path: #WIT_PATH,
33                    world: "intercept-hook",
34                    runtime_path: "::kiwi_sdk::wit_bindgen::rt",
35                });
36            }
37
38            struct Kiwi;
39
40            impl bindings::Guest for Kiwi {
41                fn intercept(ctx: self::bindings::kiwi::kiwi::intercept_types::Context) -> self::bindings::kiwi::kiwi::intercept_types::Action {
42                    super::#func_name(ctx.into()).into()
43                }
44            }
45
46            impl From<self::bindings::kiwi::kiwi::intercept_types::Context> for ::kiwi_sdk::hook::intercept::Context {
47                fn from(value: self::bindings::kiwi::kiwi::intercept_types::Context) -> Self {
48                    Self {
49                        auth: value.auth.map(|raw| {
50                            ::kiwi_sdk::hook::intercept::AuthCtx {
51                                raw,
52                            }
53                        }),
54                        connection: value.connection.into(),
55                        event: value.event.into(),
56                    }
57                }
58            }
59
60            impl From<self::bindings::kiwi::kiwi::intercept_types::EventCtx> for ::kiwi_sdk::hook::intercept::EventCtx {
61                fn from(value: self::bindings::kiwi::kiwi::intercept_types::EventCtx) -> Self {
62                    match value {
63                        self::bindings::kiwi::kiwi::intercept_types::EventCtx::Kafka(ctx) => Self::Kafka(ctx.into()),
64                        self::bindings::kiwi::kiwi::intercept_types::EventCtx::Counter(ctx) => Self::Counter(ctx.into()),
65                    }
66                }
67            }
68
69            impl From<self::bindings::kiwi::kiwi::intercept_types::CounterEventCtx> for ::kiwi_sdk::hook::intercept::CounterEventCtx {
70                fn from(value: self::bindings::kiwi::kiwi::intercept_types::CounterEventCtx) -> Self {
71                    Self {
72                        source_id: value.source_id,
73                        count: value.count,
74                    }
75                }
76            }
77
78            impl From<self::bindings::kiwi::kiwi::intercept_types::KafkaEventCtx> for ::kiwi_sdk::hook::intercept::KafkaEventCtx {
79                fn from(value: self::bindings::kiwi::kiwi::intercept_types::KafkaEventCtx) -> Self {
80                    let timestamp: Option<i64> = value.timestamp.map(|t| t.try_into().expect("timestamp conversion must not fail"));
81                    let partition: i32 = value.partition.try_into().expect("partition conversion must not fail");
82                    let offset: i64 = value.offset.try_into().expect("offset conversion must not fail");
83
84                    Self {
85                        payload: value.payload,
86                        topic: value.topic,
87                        timestamp,
88                        partition,
89                        offset,
90                    }
91                }
92            }
93
94            impl From<self::bindings::kiwi::kiwi::intercept_types::ConnectionCtx> for ::kiwi_sdk::hook::intercept::ConnectionCtx {
95                fn from(value: self::bindings::kiwi::kiwi::intercept_types::ConnectionCtx) -> Self {
96                    match value {
97                        self::bindings::kiwi::kiwi::intercept_types::ConnectionCtx::Websocket(ctx) => Self::WebSocket(ctx.into()),
98                    }
99                }
100            }
101
102            impl From<self::bindings::kiwi::kiwi::intercept_types::Websocket> for ::kiwi_sdk::hook::intercept::WebSocketConnectionCtx {
103                fn from(value: self::bindings::kiwi::kiwi::intercept_types::Websocket) -> Self {
104                    Self {
105                        addr: value.addr,
106                    }
107                }
108            }
109
110            impl From<::kiwi_sdk::hook::intercept::Action> for self::bindings::kiwi::kiwi::intercept_types::Action {
111                fn from(value: ::kiwi_sdk::hook::intercept::Action) -> Self {
112                    match value {
113                        ::kiwi_sdk::hook::intercept::Action::Forward => Self::Forward,
114                        ::kiwi_sdk::hook::intercept::Action::Discard => Self::Discard,
115                        ::kiwi_sdk::hook::intercept::Action::Transform(payload) => Self::Transform(payload.into()),
116                    }
117                }
118            }
119
120            impl From<::kiwi_sdk::hook::intercept::TransformedPayload> for self::bindings::kiwi::kiwi::intercept_types::TransformedPayload {
121                fn from(value: ::kiwi_sdk::hook::intercept::TransformedPayload) -> Self {
122                    match value {
123                        ::kiwi_sdk::hook::intercept::TransformedPayload::Kafka(payload) => Self::Kafka(payload),
124                        ::kiwi_sdk::hook::intercept::TransformedPayload::Counter(count) => Self::Counter(count),
125                    }
126                }
127            }
128
129            bindings::export!(Kiwi with_types_in bindings);
130        }
131    )
132        .into()
133}
134
135/// Macro necessary for creating an authenticate hook.
136#[proc_macro_attribute]
137pub fn authenticate(_attr: TokenStream, item: TokenStream) -> TokenStream {
138    let func = syn::parse_macro_input!(item as syn::ItemFn);
139    let func_name = &func.sig.ident;
140
141    // Kiwi does link in WASI for authenticate modules, so we need to remap the
142    // WASI imports to their counterparts located in the Kiwi SDK.
143    quote!(
144        #func
145        mod __kiwi_authenticate {
146            mod bindings {
147                #![allow(missing_docs)]
148                ::kiwi_sdk::wit_bindgen::generate!({
149                    path: #WIT_PATH,
150                    world: "authenticate-hook",
151                    runtime_path: "::kiwi_sdk::wit_bindgen::rt",
152                    with: {
153                        "wasi:http/outgoing-handler@0.2.0": ::kiwi_sdk::wit::wasi::http::outgoing_handler,
154                        "wasi:http/types@0.2.0": ::kiwi_sdk::wit::wasi::http::types,
155                        "wasi:clocks/monotonic-clock@0.2.0": ::kiwi_sdk::wit::wasi::clocks::monotonic_clock,
156                        "wasi:io/poll@0.2.0": ::kiwi_sdk::wit::wasi::io::poll,
157                        "wasi:io/streams@0.2.0": ::kiwi_sdk::wit::wasi::io::streams,
158                        "wasi:io/error@0.2.0": ::kiwi_sdk::wit::wasi::io::error,
159                    },
160                });
161            }
162
163            struct Kiwi;
164
165            impl bindings::Guest for Kiwi {
166                fn authenticate(incoming: self::bindings::kiwi::kiwi::authenticate_types::HttpRequest) -> self::bindings::kiwi::kiwi::authenticate_types::Outcome {
167                    super::#func_name(incoming.into()).into()
168                }
169            }
170
171            impl From<::kiwi_sdk::hook::authenticate::Outcome> for self::bindings::kiwi::kiwi::authenticate_types::Outcome {
172                fn from(value: ::kiwi_sdk::hook::authenticate::Outcome) -> Self {
173                    match value {
174                        ::kiwi_sdk::hook::authenticate::Outcome::Authenticate => Self::Authenticate,
175                        ::kiwi_sdk::hook::authenticate::Outcome::Reject => Self::Reject,
176                        ::kiwi_sdk::hook::authenticate::Outcome::WithContext(payload) => Self::WithContext(payload),
177                    }
178                }
179            }
180
181            impl From<self::bindings::kiwi::kiwi::authenticate_types::HttpRequest> for ::kiwi_sdk::http::Request<()> {
182                fn from(value: self::bindings::kiwi::kiwi::authenticate_types::HttpRequest) -> Self {
183                    let mut uri_builder = ::kiwi_sdk::http::Uri::builder();
184
185                    if let Some(scheme) = value.scheme {
186                        let scheme = match scheme {
187                            ::kiwi_sdk::wit::wasi::http::types::Scheme::Http => ::kiwi_sdk::http::Scheme::HTTP,
188                            ::kiwi_sdk::wit::wasi::http::types::Scheme::Https => ::kiwi_sdk::http::Scheme::HTTPS,
189                            ::kiwi_sdk::wit::wasi::http::types::Scheme::Other(scheme) => scheme.as_str().parse().expect("failed to parse scheme"),
190                        };
191
192                        uri_builder = uri_builder.scheme(scheme);
193                    }
194
195                    if let Some(authority) = value.authority {
196                        uri_builder = uri_builder.authority(authority.as_str());
197                    }
198
199                    if let Some(path_with_query) = value.path_with_query {
200                        uri_builder = uri_builder.path_and_query(path_with_query.as_str());
201                    }
202
203                    let uri = uri_builder.build().expect("failed to build uri");
204
205                    let method = match value.method {
206                        ::kiwi_sdk::wit::wasi::http::types::Method::Get => ::kiwi_sdk::http::Method::GET,
207                        ::kiwi_sdk::wit::wasi::http::types::Method::Head => ::kiwi_sdk::http::Method::HEAD,
208                        ::kiwi_sdk::wit::wasi::http::types::Method::Post => ::kiwi_sdk::http::Method::POST,
209                        ::kiwi_sdk::wit::wasi::http::types::Method::Put => ::kiwi_sdk::http::Method::PUT,
210                        ::kiwi_sdk::wit::wasi::http::types::Method::Delete => ::kiwi_sdk::http::Method::DELETE,
211                        ::kiwi_sdk::wit::wasi::http::types::Method::Connect => ::kiwi_sdk::http::Method::CONNECT,
212                        ::kiwi_sdk::wit::wasi::http::types::Method::Options => ::kiwi_sdk::http::Method::OPTIONS,
213                        ::kiwi_sdk::wit::wasi::http::types::Method::Trace => ::kiwi_sdk::http::Method::TRACE,
214                        ::kiwi_sdk::wit::wasi::http::types::Method::Patch => ::kiwi_sdk::http::Method::PATCH,
215                        ::kiwi_sdk::wit::wasi::http::types::Method::Other(_) => panic!("Unknown method"),
216                    };
217
218                    let mut request_builder = ::kiwi_sdk::http::Request::builder()
219                        .method(method)
220                        .uri(uri);
221
222                    for (key, value) in value.headers {
223                        request_builder = request_builder.header(key, value);
224                    }
225
226                    request_builder.body(()).expect("failed to build request")
227                }
228            }
229
230            bindings::export!(Kiwi with_types_in bindings);
231        }
232    )
233        .into()
234}