1use proc_macro::TokenStream;
2use regex::Regex;
3
4#[proc_macro_attribute]
5pub fn drain_endpoint(attr: TokenStream, item: TokenStream) -> TokenStream {
6 let item_str = item.to_string();
7 let attr_str = attr.to_string();
8 let attr_str = attr_str.trim_matches('"');
9
10 let page_fn_split = item_str.split_once('(').unwrap();
11 let page_fn_body = page_fn_split.1.split_once('{').unwrap().1;
12
13 let attr_regex = Regex::new(
14 r#"^((([A-Za-z0-9\-_]*\.[[:alnum:]]+/?)+)+|([A-Za-z0-9\-_]+/?)+)+$"#
15 ).unwrap();
16
17 if !attr_regex.is_match(&attr_str) {
18 panic!("Endpoint's name must consist only of alphanumeric characters, hyphens, underscores, slashes and dots.");
19 }
20
21 let attr_str_prepared = attr_str
22 .trim_end_matches(|x| x == '/' || x == '\\')
23 .replace(|x| x == '/' || x == '\\', "::");
24
25 format!("#[unsafe(export_name = \"{attr_str_prepared}\")]{}(REQUEST_DATA: drain_common::RequestData, \
26 REQUEST_HEADERS: &std::collections::HashMap<String, String>, \
27 RESPONSE_HEADERS: &mut std::collections::HashMap<String, String>, \
28 SET_COOKIE: &mut std::collections::HashMap<String, drain_common::cookies::SetCookie>,\
29 HTTP_STATUS_CODE: &mut u16) -> Result<Option<Vec<u8>>, Box<dyn std::any::Any + Send>> {{\
30 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {{
31 tokio::runtime::Builder::new_multi_thread()\
32 .enable_all()\
33 .build()\
34 .unwrap()\
35 .block_on(async {{\
36 {page_fn_body}\
37 )\
38 }}))
39 }}",
40 page_fn_split.0)
41 .parse()
42 .unwrap()
43}
44
45#[proc_macro]
46pub fn set_header(input: TokenStream) -> TokenStream {
47 let in_str = input.to_string();
48 let args_split = in_str.split_once(',').unwrap();
49 format!("RESPONSE_HEADERS.insert({}.to_lowercase(), String::from({}))",
50 args_split.0,
51 args_split.1)
52 .parse()
53 .unwrap()
54}
55
56#[proc_macro]
57pub fn header(input: TokenStream) -> TokenStream {
58 let in_str = input.to_string();
59 format!("REQUEST_HEADERS.get({})", in_str)
60 .parse()
61 .unwrap()
62}
63
64#[proc_macro]
65pub fn cookies(_input: TokenStream) -> TokenStream {
66 "drain_common::cookies::cookies(REQUEST_HEADERS)"
67 .parse()
68 .unwrap()
69}
70
71#[proc_macro]
72pub fn start_session(_input: TokenStream) -> TokenStream {
73 "drain_common::sessions::start_session(REQUEST_HEADERS, SET_COOKIE)"
74 .parse()
75 .unwrap()
76}
77
78#[proc_macro_derive(SessionValue)]
79pub fn derive_session_value(input: TokenStream) -> TokenStream {
80 let input_str = input.to_string();
81 let impl_decl = input_str
82 .trim_start_matches("pub")
83 .trim_start_matches("(crate)")
84 .trim_start_matches("(super)")
85 .split_once(|p| p == '{' || p == ';' || p == '(')
86 .unwrap().0
87 .trim()
88 .split_once(' ')
89 .unwrap();
90
91 if !impl_decl.0.eq("struct") && !impl_decl.0.eq("enum") {
92 panic!("Implementor of SessionValue must be either a struct or an enum.");
93 }
94
95 let impl_name = impl_decl.1;
96
97 format!("\nimpl SessionValue for {impl_name} {{ fn as_any(&self) -> &dyn std::any::Any {{ self }} }}")
98 .parse()
99 .unwrap()
100}