1use super::convert::{convert_primitive, TypescriptType};
2use syn::{parse_file, Expr, File as SynFile, Generics, Item, Lit, Type};
3
4pub const GENERATED_TS_FILE_MESSAGE: &str =
5 "// @generated automatically by Rapid-web (https://rapid.cincinnati.ventures). DO NOT CHANGE OR EDIT THIS FILE!";
6
7#[derive(Debug)]
8pub enum TypeClass {
9 InputBody,
10 QueryParam,
11 Path,
12 Invalid,
13 Return,
14}
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum HandlerRequestType {
18 Get,
19 Post,
20 Delete,
21 Put,
22 Patch,
23 Query,
24 Mutation,
25}
26
27#[derive(Debug)]
28pub struct HandlerType {
29 pub type_value: Option<Type>,
30 pub class: Option<TypeClass>,
31 pub handler_type: HandlerRequestType,
32}
33
34pub fn extract_handler_types(route_source: &str) -> Option<Vec<Option<HandlerType>>> {
35 let parsed_file: SynFile = syn::parse_str(route_source).expect("Error: Syn could not parse handler source file!");
36 for item in parsed_file.items {
37 if let Item::Fn(function) = item {
40 if is_valid_handler("rapid_handler", function.attrs) {
41 let mut function_types: Vec<Option<HandlerType>> = Vec::new();
42 let arg_types = function.sig.inputs.iter();
43 let function_name = function.sig.ident;
44
45 for type_value in arg_types {
46 if let syn::FnArg::Typed(typed) = type_value {
47 let rust_type = *typed.ty.clone();
48 let type_class = get_type_class(rust_type.clone());
49 function_types.push(Some(HandlerType {
50 type_value: Some(rust_type),
51 class: type_class,
52 handler_type: match function_name.to_string().as_str() {
53 "get" => HandlerRequestType::Get,
54 "post" => HandlerRequestType::Post,
55 "delete" => HandlerRequestType::Delete,
56 "put" => HandlerRequestType::Put,
57 "patch" => HandlerRequestType::Patch,
58 "query" => HandlerRequestType::Query,
59 "mutation" => HandlerRequestType::Mutation,
60 _ => HandlerRequestType::Get,
61 },
62 }));
63 }
64 }
65
66 function_types.push(Some(HandlerType {
67 type_value: None,
68 class: Some(TypeClass::Return),
69 handler_type: match function_name.to_string().as_str() {
70 "get" => HandlerRequestType::Get,
71 "post" => HandlerRequestType::Post,
72 "delete" => HandlerRequestType::Delete,
73 "put" => HandlerRequestType::Put,
74 "patch" => HandlerRequestType::Patch,
75 "query" => HandlerRequestType::Query,
76 "mutation" => HandlerRequestType::Mutation,
77 _ => HandlerRequestType::Get,
78 },
79 }));
80
81 return Some(function_types);
82 }
83 }
84 }
85
86 None
87}
88
89pub fn get_handler_type(route_source: &str) -> Option<String> {
90 let parsed_file: SynFile = syn::parse_str(route_source).expect("Error: Syn could not parse handler source file!");
91 for item in parsed_file.items {
92 if let Item::Fn(function) = item {
95 if is_valid_handler("rapid_handler", function.attrs) {
96 let function_name = function.sig.ident.to_string();
97 return Some(function_name);
98 }
99 }
100 }
101
102 None
103}
104
105pub fn get_type_class(rust_type: Type) -> Option<TypeClass> {
106 match rust_type {
107 Type::Reference(path) => get_type_class(*path.elem),
108 Type::Path(path) => {
109 let segment = path.path.segments.last().unwrap();
110 let tokens = &segment.ident;
111
112 Some(match tokens.to_string().as_str() {
113 "RapidPath" => TypeClass::Path,
114 "RapidQuery" => TypeClass::QueryParam,
115 "RapidJson" => TypeClass::InputBody, _ => TypeClass::Invalid,
117 })
118 }
119 _ => None,
120 }
121}
122
123pub fn is_valid_handler(macro_name: &str, attributes: Vec<syn::Attribute>) -> bool {
126 attributes
127 .iter()
128 .any(|attr| attr.path().segments.iter().any(|segment| segment.ident == macro_name))
129}
130
131pub fn space(space_amount: u32) -> String {
133 let mut space_string = "".to_string();
134 for _ in 0..space_amount {
135 space_string.push(' ');
136 }
137 space_string
138}
139
140pub fn indent(amount: u32) -> String {
142 let mut new_amount = String::new();
143
144 for _ in 0..amount {
145 new_amount.push('\n');
146 }
147 new_amount
148}
149
150pub fn get_struct_generics(type_generics: Generics) -> String {
152 let mut generic_params: Vec<String> = Vec::new();
153
154 for generic_param in type_generics.params {
155 if let syn::GenericParam::Type(rust_type) = generic_param {
156 generic_params.push(rust_type.ident.to_string());
157 }
158 }
159
160 if generic_params.is_empty() {
161 "".to_string()
162 } else {
163 format!("<{}>", generic_params.join(", "))
164 }
165}
166
167pub fn get_route_key(file_path: String, handler_source: &str) -> String {
171 let file = parse_file(handler_source).expect("Error: Rapid compiler could not parse handler source file!");
173
174 let fallback_key = file_path.replacen("/", "", 1).replace("/", "_").replace(".rs", "");
176
177 for item in file.items {
179 match item {
180 Item::Const(item_const) => {
181 if item_const.ident.to_string() == "ROUTE_KEY" {
182 return match *item_const.expr {
183 Expr::Lit(item) => match item.lit {
184 Lit::Str(val) => {
185 let key = val.token().to_string();
186
187 if key == "index" {
188 panic!("Invalid route key: 'index' is a reserved route key for rapid-web");
189 }
190
191 key
192 }
193 _ => continue,
194 },
195 _ => continue,
196 };
197 }
198
199 continue;
200 }
201 _ => continue,
202 }
203 }
204
205 fallback_key
206}
207
208pub fn get_output_type_alias(handler_source: &str) -> TypescriptType {
209 let file = parse_file(handler_source).expect("Error: Rapid compiler could not parse handler source file!");
211
212 let fallback_alias = TypescriptType {
214 typescript_type: "any".to_string(),
215 is_optional: false,
216 };
217
218 for item in file.items {
219 if let Item::Type(item_type) = item {
220 if item_type.ident.to_string() == "RapidOutput" {
223 let syn_type = *item_type.ty;
224 return convert_primitive(&syn_type);
225 }
226 }
227 }
228
229 return fallback_alias;
230}
231
232pub fn is_dynamic_route(str: &str) -> bool {
234 let regex = regex::Regex::new(r"_.*?_").unwrap();
236 regex.is_match(str)
237}
238
239pub fn remove_last_occurrence(s: &str, sub: &str) -> String {
241 let mut split = s.rsplitn(2, sub);
242 let back = split.next().unwrap_or("");
243 let front = split.next().unwrap_or("").to_owned();
244 front + back
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_remove_last_occurrence() {
253 let test_string = "/src/routes/service/index";
254 let test_substring = "index";
255
256 assert_eq!(remove_last_occurrence(test_string, test_substring), "/src/routes/service/");
257 }
258
259 #[test]
260 fn test_is_dynamic_route() {
261 let test_string = "/src/routes/service/_id_";
262 let test_string_2 = "/src/routes/service/index";
263
264 assert_eq!(is_dynamic_route(test_string), true);
265 assert_eq!(is_dynamic_route(test_string_2), false);
266 }
267}