bulwark_sdk_macros/
lib.rs

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/// The `bulwark_plugin` attribute generates default implementations for all handler traits in a module
9/// and produces friendly errors for common mistakes.
10///
11/// All trait functions for `Handlers` are optional when used in conjunction with this macro. A no-op
12/// implementation will be automatically generated if a handler function has not been defined. Handler
13/// functions are called in sequence, in the order below. All `*_decision` handlers render an updated
14/// decision. In the case of a `restricted` outcome, no further processing will occur. Otherwise,
15/// processing will continue to the next handler.
16///
17/// # Trait Functions
18/// - `handle_init` - Rarely used. Called when the plugin is first loaded.
19/// - `handle_request_enrichment` - This handler is called for every incoming request, before any decision-making will occur.
20///   It is typically used to perform enrichment tasks.
21/// - `handle_request_decision` - This handler is called to make an initial decision.
22/// - `handle_response_decision` - This handler is called once the interior service has received the request, processed it, and
23///   returned a response, but prior to that response being sent onwards to the original exterior client. Notably, a `restricted`
24///   outcome here does not cancel any actions or side-effects from the interior service that may have taken place already.
25///   This handler is often used to process response status codes.
26/// - `handle_decision_feedback` - This handler is called once a final verdict has been reached. The combined decision
27///   of all plugins is available here, not just the decision of the currently executing plugin. This handler may be
28///   used for any form of feedback loop, counter-based detections, or to train a model. Additionally, in the case of a
29///   `restricted` outcome, this handler may be used to perform logouts or otherwise cancel or attempt to roll back undesired
30///   side-effects that could have occurred prior to the verdict being rendered.
31///
32/// # Example
33///
34#[cfg_attr(doctest, doc = " ````no_test")] // highlight, but don't run the test (rust/issues/63193)
35/// ```rust
36/// use bulwark_sdk::*;
37/// use std::collections::HashMap;
38///
39/// struct ExamplePlugin;
40///
41/// #[bulwark_plugin]
42/// impl HttpHandlers for ExamplePlugin {
43///     fn handle_request_decision(
44///         req: http::Request,
45///         labels: HashMap<String, String>,
46///     ) -> Result<HandlerOutput, Error> {
47///         println!("hello world");
48///         // implement detection logic here
49///         Ok(())
50///     }
51/// }
52/// ```
53#[proc_macro_attribute]
54pub fn bulwark_plugin(_: TokenStream, input: TokenStream) -> TokenStream {
55    // Parse the input token stream as an impl, or return an error.
56    let raw_impl = parse_macro_input!(input as ItemImpl);
57
58    // The trait must be specified by the developer even though there's only one valid value.
59    // If we inject it, that leads to a very surprising result when developers try to define helper functions
60    // in the same struct impl and can't because it's really a trait impl.
61    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            // Find and record the implemented handlers, removing any we find from the list above.
98            handlers.retain(|h| *h != iifn.sig.ident.to_string().as_str());
99            // Verify that any functions with a handler name we find have set the `handler` attribute.
100            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    // Define the missing handlers with no-op defaults
129    let noop_handlers = handlers
130        .iter()
131        .map(|handler_name| {
132            match *handler_name {
133                "handle_init" => {
134                    // handle-init: func() -> result<_, error>;
135                    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                // Builder doesn't support scheme or authority as separate functions,
288                // so we need to manually construct the URI.
289                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                // This should always be a valid URI, panics if it's not.
325                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                    // A body should be available, extract it.
347                    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                // TODO: Add support for trailers?
359
360                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                // We have no way to know the HTTP version here, so leave it as default.
373                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                    // A body should be available, extract it.
396                    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                // TODO: Add support for trailers?
408
409                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/// The `handler` attribute makes the associated function into a Bulwark event handler.
428///
429/// The `handler` attribute is normally applied automatically by the `bulwark_plugin` macro and
430/// need not be specified explicitly.
431///
432/// The associated function must have the correct signature and may only be named one of the following:
433/// - `handle_init`
434/// - `handle_request_enrichment`
435/// - `handle_request_decision`
436/// - `handle_response_decision`
437/// - `handle_decision_feedback`
438#[doc(hidden)]
439#[proc_macro_attribute]
440pub fn handler(_: TokenStream, input: TokenStream) -> TokenStream {
441    // Parse the input token stream as a free-standing function, or return an error.
442    let raw_handler = parse_macro_input!(input as ItemFn);
443
444    // Get the attributes and signature of the outer function. Then, update the
445    // attributes and visibility of the inner function that we will inline.
446    let attrs = raw_handler.attrs.clone();
447    let (name, inner_fn) = inner_fn_info(raw_handler);
448
449    let output;
450
451    // The weird variable naming for these functions is to avoid accidentally shadowing any parameter names
452    // used by the inner function, which can result in difficult-to-read compiler errors if a plugin typos
453    // a variable name.
454    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                    // Declares the inlined inner function, calls it, then performs very
460                    // basic error handling on the result
461                    #[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                        // Apparently we can exit the guest environment before IO is flushed,
469                        // causing it to never be captured? This ensures IO is flushed and captured.
470                        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                    // Declares the inlined inner function, calls it, then performs very
489                    // basic error handling on the result
490                    #[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                        // Apparently we can exit the guest environment before IO is flushed,
500                        // causing it to never be captured? This ensures IO is flushed and captured.
501                        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                    // Declares the inlined inner function, calls it, then translates the result
520                    #[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                        // Apparently we can exit the guest environment before IO is flushed,
530                        // causing it to never be captured? This ensures IO is flushed and captured.
531                        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                    // Declares the inlined inner function, calls it, then performs very
551                    // basic error handling on the result
552                    #[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                        // Apparently we can exit the guest environment before IO is flushed,
562                        // causing it to never be captured? This ensures IO is flushed and captured.
563                        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                    // Declares the inlined inner function, calls it, then performs very
581                    // basic error handling on the result
582                    #[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                        // Apparently we can exit the guest environment before IO is flushed,
590                        // causing it to never be captured? This ensures IO is flushed and captured.
591                        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
619/// Prepare our inner function to be inlined into our outer handler function.
620///
621/// This changes its visibility to [`Inherited`], and removes [`no_mangle`] from the attributes of
622/// the inner function if it is there.
623///
624/// This function returns a 2-tuple of the inner function's identifier and the function itself.
625///
626/// [`Inherited`]: syn/enum.Visibility.html#variant.Inherited
627/// [`no_mangle`]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute
628fn 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}