fastly_macros/
lib.rs

1// Warnings (other than unused variables) in doctests are promoted to errors.
2#![doc(test(attr(deny(warnings))))]
3#![doc(test(attr(allow(dead_code))))]
4#![doc(test(attr(allow(unused_variables))))]
5#![warn(missing_docs)]
6#![deny(rustdoc::broken_intra_doc_links)]
7#![deny(rustdoc::invalid_codeblock_attributes)]
8
9//! Implementation detail of the `fastly` crate.
10
11extern crate proc_macro;
12use {
13    proc_macro::TokenStream,
14    proc_macro2::Span,
15    quote::quote_spanned,
16    syn::{
17        parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Ident,
18        ItemFn, ReturnType, Signature, Visibility,
19    },
20};
21
22/// Main function attribute for a Compute program.
23///
24/// ## Usage
25///
26/// This attribute should be applied to a `main` function that takes a request and returns a
27/// response or an error. For example:
28///
29/// ```rust,no_run
30/// use fastly::{Error, Request, Response};
31///
32/// #[fastly::main]
33/// fn main(ds_req: Request) -> Result<Response, Error> {
34///     Ok(ds_req.send("example_backend")?)
35/// }
36/// ```
37///
38/// You can apply `#[fastly::main]` to any function that takes `Request` as its sole argument, and
39/// returns a `Result<Response, Error>`.
40///
41/// ## More Information
42///
43/// This is a convenience to abstract over the common usage of `Request::from_client()` and
44/// `Response::send_to_client()` at the beginning and end of a program's `main()` function. The
45/// macro use above is equivalent to the following code:
46///
47/// ```rust,no_run
48/// use fastly::{Error, Request};
49///
50/// fn main() -> Result<(), Error> {
51///     let ds_req = Request::from_client();
52///     let us_resp = ds_req.send("example_backend")?;
53///     us_resp.send_to_client();
54///     Ok(())
55/// }
56/// ```
57#[proc_macro_attribute]
58pub fn main(_: TokenStream, input: TokenStream) -> TokenStream {
59    // Parse the input token stream as a free-standing function, or return an error.
60    let raw_main = parse_macro_input!(input as ItemFn);
61
62    // Check that the function signature looks okay-ish. If we have the wrong number of arguments,
63    // or no return type is specified , print a friendly spanned error with the expected signature.
64    if !check_impl_signature(&raw_main.sig) {
65        return syn::Error::new(
66            raw_main.sig.span(),
67            "`fastly::main` expects a function such as:
68
69#[fastly::main]
70fn main (request: Request) -> Result<Response, Error> {
71    ...
72}
73",
74        )
75        .to_compile_error()
76        .into();
77    }
78
79    // Get the attributes, visibility, and signature of our outer function. Then, update the
80    // attributes and visibility of the inner function that we will inline.
81    let (attrs, vis, sig) = outer_main_info(&raw_main);
82    let (name, inner_fn) = inner_fn_info(raw_main);
83
84    // Define our raw main function, which will provide the downstream request to our main function
85    // implementation as its argument, and then send the `ResponseExt` result downstream.
86    let output = quote_spanned! {inner_fn.span() =>
87        #(#attrs)*
88        #vis
89        #sig {
90            #[inline(always)]
91            #inner_fn
92            fastly::init();
93            let ds_req = fastly::Request::from_client();
94            match #name(ds_req) {
95                Ok(ds_resp) => ds_resp.send_to_client(),
96                Err(e) => {
97                    fastly::Response::from_body(e.to_string())
98                        .with_status(fastly::http::StatusCode::INTERNAL_SERVER_ERROR)
99                        .send_to_client()
100                }
101            };
102            Ok(())
103        }
104    };
105
106    output.into()
107}
108
109/// Check if the signature of the `#[main]` function seems correct.
110///
111/// Unfortunately, we cannot precisely typecheck in a procedural macro attribute, because we are
112/// dealing with [`TokenStream`]s. This checks that our signature takes one input, and has a return
113/// type. Specific type errors are caught later, after the [`fastly_main`] macro has been expanded.
114///
115/// This is used by the [`fastly_main`] procedural macro attribute to help provide friendly errors
116/// when given a function with the incorrect signature.
117///
118/// [`fastly_main`]: attr.fastly_main.html
119/// [`TokenStream`]: proc_macro/struct.TokenStream.html
120fn check_impl_signature(sig: &Signature) -> bool {
121    if sig.inputs.iter().len() != 1 {
122        false // Return false if the signature takes no inputs, or more than one input.
123    } else if let ReturnType::Default = sig.output {
124        false // Return false if the signature's output type is empty.
125    } else {
126        true
127    }
128}
129
130/// Returns a 3-tuple containing the attributes, visibility, and signature of our outer `main`.
131///
132/// The outer main function will use the same attributes and visibility as our raw main function.
133///
134/// The signature of the outer function will be changed to have inputs and outputs of the form
135/// `fn main() -> Result<(), fastly::Error>`. The name of the outer main will always be just that,
136/// `main`.
137fn outer_main_info(inner_main: &ItemFn) -> (Vec<Attribute>, Visibility, Signature) {
138    let attrs = inner_main.attrs.clone();
139    let vis = Visibility::Inherited;
140    let sig = {
141        let mut sig = inner_main.sig.clone();
142        sig.ident = Ident::new("main", Span::call_site());
143        sig.inputs = Punctuated::new();
144        sig.output = parse_quote!(-> ::std::result::Result<(), fastly::Error>);
145        sig
146    };
147
148    (attrs, vis, sig)
149}
150
151/// Prepare our inner function to be inlined into our main function.
152///
153/// This changes its visibility to [`Inherited`], and removes [`no_mangle`] from the attributes of
154/// the inner function if it is there.
155///
156/// This function returns a 2-tuple of the inner function's identifier, and the function itself.
157/// This identifier is used to emit code calling this function in our `main`.
158///
159/// [`Inherited`]: syn/enum.Visibility.html#variant.Inherited
160/// [`no_mangle`]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute
161fn inner_fn_info(mut inner_main: ItemFn) -> (Ident, ItemFn) {
162    let name = inner_main.sig.ident.clone();
163    inner_main.vis = Visibility::Inherited;
164    inner_main
165        .attrs
166        .retain(|attr| !attr.path.is_ident("no_mangle"));
167    (name, inner_main)
168}