1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::{parse_macro_input, parse_quote, spanned::Spanned, Ident, ItemFn, ItemImpl, Visibility};
4extern crate proc_macro;
5
6const WIT_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/wit");
7
8#[cfg_attr(doctest, doc = " ````no_test")] #[proc_macro_attribute]
54pub fn bulwark_plugin(_: TokenStream, input: TokenStream) -> TokenStream {
55 let raw_impl = parse_macro_input!(input as ItemImpl);
57
58 if let Some((_, path, _)) = raw_impl.trait_ {
62 let trait_name = path.get_ident().map_or(String::new(), |id| id.to_string());
63 if &trait_name != "HttpHandlers" {
64 return syn::Error::new(
65 path.span(),
66 format!(
67 "`bulwark_plugin` expected `HttpHandlers` trait, encountered unexpected trait `{}` for the impl",
68 trait_name
69 ),
70 )
71 .to_compile_error()
72 .into();
73 }
74 } else {
75 return syn::Error::new(
76 raw_impl.self_ty.span(),
77 "`bulwark_plugin` requires an impl for the guest `HttpHandlers` trait",
78 )
79 .to_compile_error()
80 .into();
81 }
82
83 let struct_type = &raw_impl.self_ty;
84
85 let mut handlers = vec![
86 "handle_init",
87 "handle_request_enrichment",
88 "handle_request_decision",
89 "handle_response_decision",
90 "handle_decision_feedback",
91 ];
92
93 let mut new_items = Vec::with_capacity(raw_impl.items.len());
94 for item in &raw_impl.items {
95 if let syn::ImplItem::Fn(iifn) = item {
96 let initial_len = handlers.len();
97 handlers.retain(|h| *h != iifn.sig.ident.to_string().as_str());
99 let mut use_original_item = true;
101 if handlers.len() < initial_len {
102 let mut handler_attr_found = false;
103 for attr in &iifn.attrs {
104 if let Some(ident) = attr.meta.path().get_ident() {
105 if ident.to_string().as_str() == "handler" {
106 handler_attr_found = true;
107 break;
108 }
109 }
110 }
111 if !handler_attr_found {
112 use_original_item = false;
113 let mut new_iifn = iifn.clone();
114 new_iifn.attrs.push(parse_quote! {
115 #[handler]
116 });
117 new_items.push(syn::ImplItem::Fn(new_iifn));
118 }
119 }
120 if use_original_item {
121 new_items.push(item.clone());
122 }
123 } else {
124 new_items.push(item.clone());
125 }
126 }
127
128 let noop_handlers = handlers
130 .iter()
131 .map(|handler_name| {
132 match *handler_name {
133 "handle_init" => {
134 quote! {
136 #[handler]
137 fn handle_init() -> Result<(), ::bulwark_sdk::Error> {
138 Ok(())
139 }
140 }
141 }
142 "handle_request_enrichment" => {
143 quote! {
144 #[handler]
145 fn handle_request_enrichment(
146 _: ::bulwark_sdk::http::Request,
147 _: ::std::collections::HashMap<String, String>
148 ) -> Result<::std::collections::HashMap<String, String>, ::bulwark_sdk::Error> {
149 Ok(::std::collections::HashMap::new())
150 }
151 }
152 }
153 "handle_request_decision" => {
154 quote! {
155 #[handler]
156 fn handle_request_decision(
157 _: ::bulwark_sdk::http::Request,
158 _: ::std::collections::HashMap<String, String>
159 ) -> Result<::bulwark_sdk::HandlerOutput, ::bulwark_sdk::Error> {
160 Ok(::bulwark_sdk::HandlerOutput {
161 labels: ::std::collections::HashMap::new(),
162 decision: ::bulwark_sdk::Decision::default(),
163 tags: vec![],
164 })
165 }
166 }
167 }
168 "handle_response_decision" => {
169 quote! {
170 #[handler]
171 fn handle_response_decision(
172 _: ::bulwark_sdk::http::Request,
173 _: ::bulwark_sdk::http::Response,
174 _: ::std::collections::HashMap<String, String>
175 ) -> Result<::bulwark_sdk::HandlerOutput, ::bulwark_sdk::Error> {
176 Ok(::bulwark_sdk::HandlerOutput {
177 labels: ::std::collections::HashMap::new(),
178 decision: ::bulwark_sdk::Decision::default(),
179 tags: vec![],
180 })
181 }
182 }
183 }
184 "handle_decision_feedback" => {
185 quote! {
186 #[handler]
187 fn handle_decision_feedback(
188 _: ::bulwark_sdk::http::Request,
189 _: ::bulwark_sdk::http::Response,
190 _: ::std::collections::HashMap<String, String>,
191 _: ::bulwark_sdk::Verdict,
192 ) -> Result<(), ::bulwark_sdk::Error> {
193 Ok(())
194 }
195 }
196 }
197 _ => {
198 syn::Error::new(
199 raw_impl.self_ty.span(),
200 "Could not generate no-op handler for the guest `HttpHandlers` trait",
201 )
202 .to_compile_error()
203 }
204 }
205 })
206 .collect::<Vec<proc_macro2::TokenStream>>();
207
208 let output = quote! {
209 mod handlers {
210 use super::#struct_type;
211
212 ::bulwark_sdk::wit_bindgen::generate!({
213 world: "bulwark:plugin/http-detection",
214 path: #WIT_PATH,
215 runtime_path: "::bulwark_sdk::wit_bindgen::rt",
216 });
217 }
218
219 impl From<crate::handlers::bulwark::plugin::types::Decision> for ::bulwark_sdk::Decision {
220 fn from(decision: crate::handlers::bulwark::plugin::types::Decision) -> Self {
221 Self {
222 accept: decision.accepted,
223 restrict: decision.restricted,
224 unknown: decision.unknown,
225 }
226 }
227 }
228
229 impl From<::bulwark_sdk::Decision> for crate::handlers::bulwark::plugin::types::Decision {
230 fn from(decision: ::bulwark_sdk::Decision) -> Self {
231 Self {
232 accepted: decision.accept,
233 restricted: decision.restrict,
234 unknown: decision.unknown,
235 }
236 }
237 }
238
239 impl From<crate::handlers::exports::bulwark::plugin::http_handlers::HandlerOutput> for ::bulwark_sdk::HandlerOutput {
240 fn from(handler_output: crate::handlers::exports::bulwark::plugin::http_handlers::HandlerOutput) -> Self {
241 Self {
242 labels: handler_output.labels.iter().cloned().collect(),
243 decision: handler_output.decision.into(),
244 tags: handler_output.tags.clone(),
245 }
246 }
247 }
248
249 impl From<bulwark_sdk::HandlerOutput> for crate::handlers::exports::bulwark::plugin::http_handlers::HandlerOutput {
250 fn from(handler_output: ::bulwark_sdk::HandlerOutput) -> Self {
251 Self {
252 labels: handler_output.labels.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
253 decision: handler_output.decision.into(),
254 tags: handler_output.tags.clone(),
255 }
256 }
257 }
258
259 impl From<crate::handlers::bulwark::plugin::types::Outcome> for ::bulwark_sdk::Outcome {
260 fn from(outcome: crate::handlers::bulwark::plugin::types::Outcome) -> Self {
261 match outcome {
262 crate::handlers::bulwark::plugin::types::Outcome::Trusted => Self::Trusted,
263 crate::handlers::bulwark::plugin::types::Outcome::Accepted => Self::Accepted,
264 crate::handlers::bulwark::plugin::types::Outcome::Suspected => Self::Suspected,
265 crate::handlers::bulwark::plugin::types::Outcome::Restricted => Self::Restricted,
266 }
267 }
268 }
269
270 impl From<crate::handlers::bulwark::plugin::types::Verdict> for ::bulwark_sdk::Verdict {
271 fn from(verdict: crate::handlers::bulwark::plugin::types::Verdict) -> Self {
272 Self {
273 decision: verdict.decision.into(),
274 outcome: verdict.outcome.into(),
275 count: verdict.count,
276 tags: verdict.tags.clone(),
277 }
278 }
279 }
280
281 impl TryFrom<crate::handlers::wasi::http::types::IncomingRequest> for ::bulwark_sdk::http::Request {
282 type Error = crate::handlers::exports::bulwark::plugin::http_handlers::Error;
283
284 fn try_from(request: crate::handlers::wasi::http::types::IncomingRequest) -> Result<Self, Self::Error> {
285 const MAX_SIZE: u64 = 1048576;
286 let mut builder = ::bulwark_sdk::http::request::Builder::new();
287 let mut uri = ::bulwark_sdk::http::uri::Builder::new();
290 if let Some(scheme) = request.scheme() {
291 let other;
292 uri = uri.scheme(match scheme {
293 crate::handlers::wasi::http::types::Scheme::Http => "http",
294 crate::handlers::wasi::http::types::Scheme::Https => "https",
295 crate::handlers::wasi::http::types::Scheme::Other(o) => {
296 other = o;
297 other.as_str()
298 },
299 });
300 }
301 if let Some(authority) = request.authority() {
302 uri = uri.authority(authority);
303 }
304 let other;
305 let method = match request.method() {
306 crate::handlers::wasi::http::types::Method::Get => "GET",
307 crate::handlers::wasi::http::types::Method::Head => "HEAD",
308 crate::handlers::wasi::http::types::Method::Post => "POST",
309 crate::handlers::wasi::http::types::Method::Put => "PUT",
310 crate::handlers::wasi::http::types::Method::Delete => "DELETE",
311 crate::handlers::wasi::http::types::Method::Connect => "CONNECT",
312 crate::handlers::wasi::http::types::Method::Options => "OPTIONS",
313 crate::handlers::wasi::http::types::Method::Trace => "TRACE",
314 crate::handlers::wasi::http::types::Method::Patch => "PATCH",
315 crate::handlers::wasi::http::types::Method::Other(o) => {
316 other = o;
317 other.as_str()
318 },
319 };
320 builder = builder.method(method);
321 if let Some(request_uri) = request.path_with_query() {
322 uri = uri.path_and_query(request_uri);
323 }
324 builder = builder.uri(uri.build().expect("invalid uri"));
326 let mut end_of_stream = true;
327 let headers = request.headers().entries();
328 for (name, value) in headers {
329 if name.eq_ignore_ascii_case("content-length") {
330 if let Ok(value) = std::str::from_utf8(&value) {
331 if let Ok(value) = value.parse::<u64>() {
332 if value > 0 {
333 end_of_stream = false;
334 }
335 }
336 }
337 }
338 if name.eq_ignore_ascii_case("transfer-encoding") {
339 end_of_stream = false;
340 }
341 builder = builder.header(name, value);
342 }
343
344 let mut buffer = Vec::new();
345 if !end_of_stream {
346 let body = request.consume().map_err(|_| {
348 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other("body cannot be consumed".to_string())
349 })?;
350 let mut stream = body.stream().map_err(|_| {
351 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other("could not get body stream".to_string())
352 })?;
353 buffer.extend(stream.read(MAX_SIZE).map_err(|e| {
354 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
355 })?);
356 }
357
358 builder.body(bulwark_sdk::Bytes::from(buffer)).map_err(|e| {
361 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
362 })
363 }
364 }
365
366 impl TryFrom<crate::handlers::wasi::http::types::IncomingResponse> for ::bulwark_sdk::http::Response {
367 type Error = crate::handlers::exports::bulwark::plugin::http_handlers::Error;
368
369 fn try_from(response: crate::handlers::wasi::http::types::IncomingResponse) -> Result<Self, Self::Error> {
370 const MAX_SIZE: u64 = 1048576;
371 let mut builder = ::bulwark_sdk::http::response::Builder::new();
372 builder = builder.status(response.status());
374
375 let mut end_of_stream = true;
376 let headers = response.headers().entries();
377 for (name, value) in headers {
378 if name.eq_ignore_ascii_case("content-length") {
379 if let Ok(value) = std::str::from_utf8(&value) {
380 if let Ok(value) = value.parse::<u64>() {
381 if value > 0 {
382 end_of_stream = false;
383 }
384 }
385 }
386 }
387 if name.eq_ignore_ascii_case("transfer-encoding") {
388 end_of_stream = false;
389 }
390 builder = builder.header(name, value);
391 }
392
393 let mut buffer = Vec::new();
394 if !end_of_stream {
395 let body = response.consume().map_err(|_| {
397 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other("body cannot be consumed".to_string())
398 })?;
399 let mut stream = body.stream().map_err(|_| {
400 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other("could not get body stream".to_string())
401 })?;
402 buffer.extend(stream.read(MAX_SIZE).map_err(|e| {
403 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
404 })?);
405 }
406
407 builder.body(bulwark_sdk::Bytes::from(buffer)).map_err(|e| {
410 crate::handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
411 })
412 }
413 }
414
415 use crate::handlers::exports::bulwark::plugin::http_handlers::Guest as HttpHandlers;
416 impl HttpHandlers for #struct_type {
417 #(#new_items)*
418 #(#noop_handlers)*
419 }
420
421 handlers::export!(#struct_type with_types_in handlers);
422 };
423
424 output.into()
425}
426
427#[doc(hidden)]
439#[proc_macro_attribute]
440pub fn handler(_: TokenStream, input: TokenStream) -> TokenStream {
441 let raw_handler = parse_macro_input!(input as ItemFn);
443
444 let attrs = raw_handler.attrs.clone();
447 let (name, inner_fn) = inner_fn_info(raw_handler);
448
449 let output;
450
451 match name.to_string().as_str() {
455 "handle_init" => {
456 output = quote_spanned! {inner_fn.span() =>
457 #(#attrs)*
458 fn handle_init() -> Result<(), handlers::exports::bulwark::plugin::http_handlers::Error> {
459 #[inline(always)]
462 #inner_fn
463 let result = #name().map_err(|e| {
464 handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
465 });
466 #[allow(unused_must_use)]
467 {
468 use std::io::Write;
471 std::io::stdout().flush();
472 std::io::stderr().flush();
473 }
474 result
475 }
476 }
477 }
478 "handle_request_enrichment" => {
479 output = quote_spanned! {inner_fn.span() =>
480 #(#attrs)*
481 fn handle_request_enrichment(
482 in_reqst: handlers::wasi::http::types::IncomingRequest,
483 lbls: wit_bindgen::rt::vec::Vec<handlers::bulwark::plugin::types::Label>,
484 ) -> Result<
485 wit_bindgen::rt::vec::Vec<handlers::bulwark::plugin::types::Label>,
486 handlers::exports::bulwark::plugin::http_handlers::Error,
487 > {
488 #[inline(always)]
491 #inner_fn
492 let result = #name(in_reqst.try_into()?, lbls.iter().cloned().collect()).map(|t| {
493 t.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
494 }).map_err(|e| {
495 handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
496 });
497 #[allow(unused_must_use)]
498 {
499 use std::io::Write;
502 std::io::stdout().flush();
503 std::io::stderr().flush();
504 }
505 result
506 }
507 }
508 }
509 "handle_request_decision" => {
510 output = quote_spanned! {inner_fn.span() =>
511 #(#attrs)*
512 fn handle_request_decision(
513 in_reqst: handlers::wasi::http::types::IncomingRequest,
514 lbls: wit_bindgen::rt::vec::Vec<handlers::bulwark::plugin::types::Label>,
515 ) -> Result<
516 handlers::exports::bulwark::plugin::http_handlers::HandlerOutput,
517 handlers::exports::bulwark::plugin::http_handlers::Error,
518 > {
519 #[inline(always)]
521 #inner_fn
522 let result = #name(in_reqst.try_into()?, lbls.iter().cloned().collect()).map(|t| {
523 t.into()
524 }).map_err(|e| {
525 handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
526 });
527 #[allow(unused_must_use)]
528 {
529 use std::io::Write;
532 std::io::stdout().flush();
533 std::io::stderr().flush();
534 }
535 result
536 }
537 }
538 }
539 "handle_response_decision" => {
540 output = quote_spanned! {inner_fn.span() =>
541 #(#attrs)*
542 fn handle_response_decision(
543 in_reqst: handlers::wasi::http::types::IncomingRequest,
544 in_respn: handlers::wasi::http::types::IncomingResponse,
545 lbls: wit_bindgen::rt::vec::Vec<handlers::bulwark::plugin::types::Label>,
546 ) -> Result<
547 handlers::exports::bulwark::plugin::http_handlers::HandlerOutput,
548 handlers::exports::bulwark::plugin::http_handlers::Error,
549 > {
550 #[inline(always)]
553 #inner_fn
554 let result = #name(in_reqst.try_into()?, in_respn.try_into()?, lbls.iter().cloned().collect()).map(|t| {
555 t.into()
556 }).map_err(|e| {
557 handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
558 });
559 #[allow(unused_must_use)]
560 {
561 use std::io::Write;
564 std::io::stdout().flush();
565 std::io::stderr().flush();
566 }
567 result
568 }
569 }
570 }
571 "handle_decision_feedback" => {
572 output = quote_spanned! {inner_fn.span() =>
573 #(#attrs)*
574 fn handle_decision_feedback(
575 in_reqst: handlers::wasi::http::types::IncomingRequest,
576 in_respn: handlers::wasi::http::types::IncomingResponse,
577 lbls: wit_bindgen::rt::vec::Vec<handlers::bulwark::plugin::types::Label>,
578 vrdct: handlers::bulwark::plugin::types::Verdict,
579 ) -> Result<(), handlers::exports::bulwark::plugin::http_handlers::Error> {
580 #[inline(always)]
583 #inner_fn
584 let result = #name(in_reqst.try_into()?, in_respn.try_into()?, lbls.iter().cloned().collect(), vrdct.into()).map_err(|e| {
585 handlers::exports::bulwark::plugin::http_handlers::Error::Other(e.to_string())
586 });
587 #[allow(unused_must_use)]
588 {
589 use std::io::Write;
592 std::io::stdout().flush();
593 std::io::stderr().flush();
594 }
595 result
596 }
597 }
598 }
599 _ => {
600 return syn::Error::new(
601 inner_fn.sig.span(),
602 "`handler` expects a function named one of:
603
604- `handle_init`
605- `handle_request_enrichment`
606- `handle_request_decision`
607- `handle_response_decision`
608- `handle_decision_feedback`
609",
610 )
611 .to_compile_error()
612 .into()
613 }
614 }
615
616 output.into()
617}
618
619fn inner_fn_info(mut inner_handler: ItemFn) -> (Ident, ItemFn) {
629 let name = inner_handler.sig.ident.clone();
630 inner_handler.vis = Visibility::Inherited;
631 inner_handler
632 .attrs
633 .retain(|attr| !attr.path().is_ident("no_mangle"));
634 (name, inner_handler)
635}