1use super::util::{get_struct_generics, indent, space};
2use log::error;
3use std::{env::current_dir, ffi::OsStr, fs::File, io::prelude::*};
4use syn::{parse_file, Item, ItemStruct, ItemType, Type};
5use walkdir::WalkDir;
6
7#[derive(PartialEq, Debug, Clone)]
8pub struct TypescriptType {
9 pub typescript_type: String,
10 pub is_optional: bool,
11}
12
13impl TypescriptType {
14 pub fn new(typescript_type: String, is_optional: bool) -> Self {
15 Self {
16 typescript_type,
17 is_optional,
18 }
19 }
20}
21
22impl From<String> for TypescriptType {
23 fn from(typescript_type: String) -> TypescriptType {
24 TypescriptType {
25 typescript_type,
26 is_optional: false,
27 }
28 }
29}
30
31fn convert_generic_type(generic_type: &syn::GenericArgument) -> TypescriptType {
33 match generic_type {
34 syn::GenericArgument::Type(rust_type) => convert_primitive(rust_type),
35 _ => "any".to_string().into(), }
37}
38
39pub fn convert_primitive(rust_primitive: &Type) -> TypescriptType {
41 match rust_primitive {
42 Type::Tuple(path) => {
43 let mut tuple_types: Vec<String> = Vec::new();
44 for field in path.elems.iter() {
45 let converted_primitive = convert_primitive(field);
46 tuple_types.push(converted_primitive.typescript_type);
47 }
48
49 TypescriptType {
50 typescript_type: format!("{:?}", tuple_types).replace(r#"""#, ""),
51 is_optional: false,
52 }
53 }
54 Type::Reference(path) => convert_primitive(&path.elem),
56 Type::Path(path) => {
57 let segment = path.path.segments.last().unwrap();
58 let tokens = &segment.ident;
59 let arguments = &segment.arguments;
60 let parsed_type = tokens.to_string();
61 match parsed_type.as_str() {
63 "u8" => TypescriptType::new("number".to_string(), false),
64 "u16" => TypescriptType::new("number".to_string(), false),
65 "i64" => TypescriptType::new("number".to_string(), false),
66 "u64" => TypescriptType::new("number".to_string(), false),
67 "u128" => TypescriptType::new("number".to_string(), false),
68 "i8" => TypescriptType::new("number".to_string(), false),
69 "i16" => TypescriptType::new("number".to_string(), false),
70 "i32" => TypescriptType::new("number".to_string(), false),
71 "u32" => TypescriptType::new("number".to_string(), false),
72 "i128" => TypescriptType::new("number".to_string(), false),
73 "f32" => TypescriptType::new("number".to_string(), false),
74 "f64" => TypescriptType::new("number".to_string(), false),
75 "isize" => TypescriptType::new("number".to_string(), false),
76 "usize" => TypescriptType::new("number".to_string(), false),
77 "bool" => TypescriptType::new("boolean".to_string(), false),
78 "char" => TypescriptType::new("string".to_string(), false),
79 "str" => TypescriptType::new("string".to_string(), false),
80 "String" => TypescriptType::new("string".to_string(), false),
81 "Value" => TypescriptType::new("any".to_string(), false),
82 "NaiveDateTime" => TypescriptType::new("Date".to_string(), false),
83 "DateTime" => TypescriptType::new("Date".to_string(), false),
84 "Uuid" => TypescriptType::new("string".to_string(), false),
85 "RapidPath" => TypescriptType {
86 is_optional: false,
87 typescript_type: match arguments {
88 syn::PathArguments::Parenthesized(parenthesized_argument) => {
89 format!("{:?}", parenthesized_argument)
90 }
91 syn::PathArguments::AngleBracketed(anglebracketed_argument) => {
92 convert_generic_type(anglebracketed_argument.args.first().unwrap()).typescript_type
93 }
94 _ => "unknown".to_string(),
95 },
96 },
97 "RapidJson" => TypescriptType {
98 is_optional: false,
99 typescript_type: match arguments {
100 syn::PathArguments::Parenthesized(parenthesized_argument) => {
101 format!("{:?}", parenthesized_argument)
102 }
103 syn::PathArguments::AngleBracketed(anglebracketed_argument) => {
104 convert_generic_type(anglebracketed_argument.args.first().unwrap()).typescript_type
105 }
106 _ => "unknown".to_string(),
107 },
108 },
109 "Union" => TypescriptType {
110 is_optional: false,
111 typescript_type: match arguments {
112 syn::PathArguments::AngleBracketed(anglebracketed_argument) => {
113 let mut converted_types: Vec<TypescriptType> = Vec::new();
114 for generic_type in anglebracketed_argument.args.iter() {
115 converted_types.push(convert_generic_type(generic_type));
116 }
117
118 format!("{} | {}", converted_types[0].typescript_type, converted_types[1].typescript_type)
120 }
121 _ => "unknown".to_string(),
122 },
123 },
124 "Option" => TypescriptType {
125 is_optional: true,
126 typescript_type: match arguments {
127 syn::PathArguments::Parenthesized(parenthesized_argument) => {
128 format!("{:?}", parenthesized_argument)
129 }
130 syn::PathArguments::AngleBracketed(anglebracketed_argument) => {
131 convert_generic_type(anglebracketed_argument.args.first().unwrap()).typescript_type
132 }
133 _ => "unknown".to_string(),
134 },
135 },
136 "Vec" => match arguments {
137 syn::PathArguments::Parenthesized(parenthesized_argument) => {
138 format!("{:?}", parenthesized_argument)
139 }
140 syn::PathArguments::AngleBracketed(anglebracketed_argument) => format!(
141 "Array<{}>",
142 match convert_generic_type(anglebracketed_argument.args.first().unwrap()) {
143 TypescriptType {
144 is_optional: true,
145 typescript_type,
146 } => format!("{} | undefined", typescript_type),
147 TypescriptType {
148 is_optional: false,
149 typescript_type,
150 } => typescript_type,
151 }
152 ),
153 _ => "unknown".to_string(),
154 }
155 .into(),
156 "HashMap" => match arguments {
157 syn::PathArguments::Parenthesized(parenthesized_argument) => {
158 format!("{:?}", parenthesized_argument)
159 }
160 syn::PathArguments::AngleBracketed(anglebracketed_argument) => format!(
161 "Record<{}>",
162 anglebracketed_argument
163 .args
164 .iter()
165 .map(|arg| match convert_generic_type(arg) {
166 TypescriptType {
167 is_optional: true,
168 typescript_type,
169 } => format!("{} | undefined", typescript_type),
170 TypescriptType {
171 is_optional: false,
172 typescript_type,
173 } => typescript_type,
174 })
175 .collect::<Vec<String>>()
176 .join(", ")
177 ),
178 _ => "any".to_string(),
179 }
180 .into(),
181 _ => parsed_type.to_string().into(),
183 }
184 }
185 _ => "any".to_string().into(),
186 }
187}
188
189pub fn get_rust_typename(rust_type: &Type) -> String {
191 match rust_type {
192 Type::Reference(path) => get_rust_typename(&path.elem),
193 Type::Path(path) => {
194 let segment = path.path.segments.last().unwrap();
195 let tokens = &segment.ident;
196 let parsed_type = tokens.to_string();
197 let arguments = &segment.arguments;
198 match parsed_type.as_str() {
199 "RapidPath" => match arguments {
200 syn::PathArguments::AngleBracketed(anglebracketed_argument) => match anglebracketed_argument.args.first().unwrap() {
201 syn::GenericArgument::Type(rust_type) => get_rust_typename(rust_type),
202 _ => "unknown".to_string(),
203 },
204 _ => "unknown".to_string(),
205 },
206 "RapidJson" => match arguments {
207 syn::PathArguments::AngleBracketed(anglebracketed_argument) => match anglebracketed_argument.args.first().unwrap() {
208 syn::GenericArgument::Type(rust_type) => get_rust_typename(rust_type),
209 _ => "unknown".to_string(),
210 },
211 _ => "unknown".to_string(),
212 },
213 _ => parsed_type,
214 }
215 }
216 _ => String::from("any"),
217 }
218}
219
220#[allow(dead_code)]
222pub enum ConversionType {
223 Primitive,
224 Struct,
225 Const,
226 Enum,
227 Function, TypeAlias, }
230
231pub struct TypescriptConverter {
233 pub is_interface: bool,
234 pub store: String,
235 pub should_export: bool,
236 pub indentation: u32,
237 pub file: File,
238 pub converted_types: Vec<String>,
239}
240
241impl TypescriptConverter {
242 pub fn new(is_interface: bool, initial_store_value: String, should_export: bool, indentation: u32, file: File) -> Self {
243 Self {
244 is_interface,
245 store: initial_store_value,
246 should_export,
247 indentation,
248 file,
249 converted_types: Vec::new(),
250 }
251 }
252
253 pub fn convert_struct(&mut self, rust_struct: ItemStruct) {
255 let export_str = if self.should_export { "export " } else { "" };
256
257 let keyword = if self.is_interface { "interface" } else { "type" };
258
259 let spacing = space(self.indentation);
260
261 self.store.push_str(&indent(2));
263
264 let type_scaffold = format!(
265 "{export}{key} {name}{generics} {{\n",
266 export = export_str,
267 key = keyword,
268 name = rust_struct.ident,
269 generics = get_struct_generics(rust_struct.generics.clone())
270 );
271
272 self.store.push_str(&type_scaffold);
274
275 for field in rust_struct.fields {
277 let field_name = field.ident.unwrap().to_string();
278 let field_type = convert_primitive(&field.ty);
279 let optional_marking = if field_type.is_optional { "?" } else { "" };
280
281 self.store.push_str(&format!(
283 "{space}{name}{optional}: {ts_type};\n",
284 space = spacing,
285 name = field_name,
286 ts_type = field_type.typescript_type,
287 optional = optional_marking
288 ));
289 }
290
291 self.store.push_str("}");
293
294 self.converted_types.push(rust_struct.ident.to_string());
296 }
297
298 pub fn convert_primitive(&mut self, primitive: Type) -> TypescriptType {
300 let converted = convert_primitive(&primitive);
301 if !(get_rust_typename(&primitive) == converted.typescript_type) {
303 self.converted_types.push(converted.clone().typescript_type);
304 }
305 converted
306 }
307
308 #[allow(dead_code)]
310 pub fn convert_const() {}
311
312 #[allow(dead_code)]
314 pub fn convert_enum() {}
315
316 #[allow(dead_code)]
318 pub fn convert_function() {}
319
320 pub fn convert_type_alias(&mut self, rust_type_alias: ItemType) {
322 let export_str = if self.should_export { "export " } else { "" };
323
324 let keyword = "type";
326
327 self.store.push_str(&indent(2));
329
330 let alias_name = rust_type_alias.ident.to_string();
332
333 let converted_type = convert_primitive(&rust_type_alias.ty);
334
335 let type_scaffold = format!(
337 "{export}{key} {name} = {type_value};",
338 key = keyword,
339 export = export_str,
340 name = alias_name,
341 type_value = converted_type.typescript_type
342 );
343
344 self.store.push_str(&type_scaffold);
346
347 self.converted_types.push(alias_name);
349 }
350
351 pub fn generate(&mut self, types: Option<&str>) {
352 match types {
353 Some(val) => {
354 self.file.write_all(val.as_bytes()).expect("Could not write to typescript bindings file!");
355 }
356 None => {
357 self.file
358 .write_all(self.store.as_bytes())
359 .expect("Could not write to typescript bindings file!");
360 }
361 }
362 }
363}
364
365pub fn convert_all_types_in_path(directory: &str, converter_instance: &mut TypescriptConverter) {
367 let parsing_directory = current_dir().unwrap().join(directory);
369
370 for entry in WalkDir::new(&parsing_directory).sort_by_file_name() {
371 match entry {
372 Ok(dir_entry) => {
373 let path = dir_entry.path();
374
375 if path.is_dir() {
377 continue;
378 }
379
380 if !(path.extension().unwrap_or(OsStr::new("")).to_str().unwrap_or("") == "rs") {
382 continue;
383 }
384
385 let mut file = File::open(&path).expect("Could not open file while attempting to generate Typescript types!");
387 let mut file_contents = String::new();
388 file.read_to_string(&mut file_contents)
389 .expect("Could not open file while attempting to generate Typescript types!");
390
391 let file = parse_file(&file_contents).expect("Error: Syn could not parse handler source file!");
393
394 let file_name = dir_entry.file_name();
396
397 if file_name == "_middleware.rs" || file_name == "mod.rs" {
399 continue;
400 }
401
402 for item in file.items {
405 match item {
406 Item::Struct(val) => {
407 converter_instance.convert_struct(val);
408 }
409 Item::Type(val) => {
410 if val.ident.to_string() == "RapidOutput" {
412 continue;
413 }
414 converter_instance.convert_type_alias(val)
415 }
416 _ => {
417 continue;
419 }
420 }
421 }
422 }
423 Err(_) => {
424 error!("An error occurred when attempting to parse directory with path: {:?}", parsing_directory);
426 continue;
427 }
428 }
429 }
430}
431
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn test_convert_primitive() {
439 let mut converter = TypescriptConverter::new(false, "".to_string(), false, 0, File::create("test.ts").unwrap());
440 let converted = converter.convert_primitive(syn::parse_str::<Type>("u8").unwrap());
441 assert_eq!(converted.typescript_type, "number");
442 assert_eq!(converted.is_optional, false);
443
444 let converted = converter.convert_primitive(syn::parse_str::<Type>("u16").unwrap());
445 assert_eq!(converted.typescript_type, "number");
446 assert_eq!(converted.is_optional, false);
447
448 let converted = converter.convert_primitive(syn::parse_str::<Type>("i64").unwrap());
449 assert_eq!(converted.typescript_type, "number");
450 assert_eq!(converted.is_optional, false);
451
452 let converted = converter.convert_primitive(syn::parse_str::<Type>("u64").unwrap());
453 assert_eq!(converted.typescript_type, "number");
454 assert_eq!(converted.is_optional, false);
455
456 let converted = converter.convert_primitive(syn::parse_str::<Type>("u128").unwrap());
457 assert_eq!(converted.typescript_type, "number");
458 assert_eq!(converted.is_optional, false);
459
460 let converted = converter.convert_primitive(syn::parse_str::<Type>("i8").unwrap());
461 assert_eq!(converted.typescript_type, "number");
462 assert_eq!(converted.is_optional, false);
463
464 let converted = converter.convert_primitive(syn::parse_str::<Type>("i16").unwrap());
465 assert_eq!(converted.typescript_type, "number");
466 assert_eq!(converted.is_optional, false);
467
468 let converted = converter.convert_primitive(syn::parse_str::<Type>("i32").unwrap());
469 assert_eq!(converted.typescript_type, "number");
470 assert_eq!(converted.is_optional, false);
471
472 let converted = converter.convert_primitive(syn::parse_str::<Type>("u32").unwrap());
473 assert_eq!(converted.typescript_type, "number");
474 assert_eq!(converted.is_optional, false);
475
476 let converted = converter.convert_primitive(syn::parse_str::<Type>("i128").unwrap());
477 assert_eq!(converted.typescript_type, "number");
478 assert_eq!(converted.is_optional, false);
479
480 let converted = converter.convert_primitive(syn::parse_str::<Type>("f32").unwrap());
481 assert_eq!(converted.typescript_type, "number");
482
483 let converted = converter.convert_primitive(syn::parse_str::<Type>("f64").unwrap());
484 assert_eq!(converted.typescript_type, "number");
485
486 let converted = converter.convert_primitive(syn::parse_str::<Type>("isize").unwrap());
487 assert_eq!(converted.typescript_type, "number");
488
489 let converted = converter.convert_primitive(syn::parse_str::<Type>("usize").unwrap());
490
491 assert_eq!(converted.typescript_type, "number");
492
493 let converted = converter.convert_primitive(syn::parse_str::<Type>("bool").unwrap());
494 assert_eq!(converted.typescript_type, "boolean");
495
496 let converted = converter.convert_primitive(syn::parse_str::<Type>("char").unwrap());
497 assert_eq!(converted.typescript_type, "string");
498
499 let converted = converter.convert_primitive(syn::parse_str::<Type>("str").unwrap());
500 assert_eq!(converted.typescript_type, "string");
501
502 let converted = converter.convert_primitive(syn::parse_str::<Type>("String").unwrap());
503 assert_eq!(converted.typescript_type, "string");
504
505 let converted = converter.convert_primitive(syn::parse_str::<Type>("Value").unwrap());
506 assert_eq!(converted.typescript_type, "any");
507
508 let converted = converter.convert_primitive(syn::parse_str::<Type>("NaiveDateTime").unwrap());
509 assert_eq!(converted.typescript_type, "Date");
510
511 let converted = converter.convert_primitive(syn::parse_str::<Type>("DateTime").unwrap());
512 assert_eq!(converted.typescript_type, "Date");
513
514 let converted = converter.convert_primitive(syn::parse_str::<Type>("Uuid").unwrap());
515 assert_eq!(converted.typescript_type, "string");
516
517 let converted = converter.convert_primitive(syn::parse_str::<Type>("RapidPath<String>").unwrap());
518 assert_eq!(converted.typescript_type, "string");
519
520 let converted = converter.convert_primitive(syn::parse_str::<Type>("RapidJson<String>").unwrap());
521 assert_eq!(converted.typescript_type, "string");
522
523 let converted = converter.convert_primitive(syn::parse_str::<Type>("Union<String, u8>").unwrap());
524 assert_eq!(converted.typescript_type, "string | number");
525
526 let converted = converter.convert_primitive(syn::parse_str::<Type>("Option<String>").unwrap());
527 assert_eq!(converted.typescript_type, "string");
528
529 let converted = converter.convert_primitive(syn::parse_str::<Type>("Vec<String>").unwrap());
530 assert_eq!(converted.typescript_type, "Array<string>");
531
532 let converted = converter.convert_primitive(syn::parse_str::<Type>("HashMap<String, String>").unwrap());
533 assert_eq!(converted.typescript_type, "Record<string, string>");
534 }
535}