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}