1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use syn::parse_macro_input;
4use syn::ItemFn;
5
6#[proc_macro_attribute]
7pub fn ssg(_: TokenStream, input: TokenStream) -> TokenStream {
8 let item = parse_macro_input!(input as ItemFn);
9
10 if !item.sig.inputs.is_empty() {
11 return syn::Error::new(
12 item.sig.ident.span(),
13 "Error: SSG routes cannot have arguments",
14 )
15 .into_compile_error()
16 .into();
17 }
18 impl_ssg(item).into()
19}
20
21fn impl_ssg(item: ItemFn) -> TokenStream2 {
22 let struct_ident = item.sig.ident.clone();
23
24 let ident_cache_str =
25 format!("{}_CACHE", item.sig.ident.to_string().to_uppercase());
26 let ident_cache = syn::Ident::new(&ident_cache_str, item.sig.ident.span());
27
28 let nice_fn = item.block;
29
30 quote::quote! {
31 titan::lazy_static! {
32 static ref #ident_cache: std::sync::RwLock<Option<Vec<u8>>> = std::sync::RwLock::new(None);
33 }
34
35 #[allow(non_camel_case_types)]
36 #[derive(Clone)]
37 pub struct #struct_ident;
38
39 impl titan::Handler<()> for #struct_ident {
40 type Output = titan::http::Response;
41 type Future =
42 std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
43 fn call(&self, _: ()) -> Self::Future {
44 let _lock = match #ident_cache.read() {
45 Ok(v) => v,
46 Err(_) => panic!("oh no"),
47 };
48
49 if let Some(cache) = _lock.as_ref() {
50 let body =
51 titan::http::body::Body::Full(cache.clone().into_boxed_slice());
52 return Box::pin(async move {
53 titan::http::ResponseBuilder::new().status(200).body(body).unwrap()
54 });
55 };
56
57 Box::pin(async move {
58 let response = titan::FutureExt::map(async #nice_fn, |x| x.respond()).await;
59
60
61 match response.body() {
62 titan::http::body::Body::Full(ref body) => {
63 let mut refs = #ident_cache.write().unwrap();
64 *refs = Some(body.clone().to_vec());
65 }
66 titan::http::body::Body::Stream(_) => {
67 panic!(
68 "Body::Stream is not available in a cached request response :("
69 )
70 }
71 };
72 response
73 })
74 }
75 }
76 }
77}