1use serde::{Deserialize, Serialize};
2use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
3use std::path::PathBuf;
4use std::rc::Rc;
5use std::sync::Arc;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub enum Schema {
9 String,
10 Number,
11 Boolean,
12 Null,
13 Any,
14 Array(Box<Schema>),
15 Object(Vec<(String, Schema)>),
16 Enum(Vec<String>), Union(Vec<Schema>),
18 Tuple(Vec<Schema>),
19 Ref(String), Record {
21 key: Box<Schema>,
22 value: Box<Schema>,
23 }, }
27
28pub trait SchemaBridge {
29 fn to_ts() -> String;
30 fn to_schema() -> Schema;
31}
32
33impl SchemaBridge for String {
35 fn to_ts() -> String {
36 "string".to_string()
37 }
38 fn to_schema() -> Schema {
39 Schema::String
40 }
41}
42
43impl SchemaBridge for i32 {
44 fn to_ts() -> String {
45 "number".to_string()
46 }
47 fn to_schema() -> Schema {
48 Schema::Number
49 }
50}
51
52impl SchemaBridge for f64 {
53 fn to_ts() -> String {
54 "number".to_string()
55 }
56 fn to_schema() -> Schema {
57 Schema::Number
58 }
59}
60
61impl SchemaBridge for bool {
62 fn to_ts() -> String {
63 "boolean".to_string()
64 }
65 fn to_schema() -> Schema {
66 Schema::Boolean
67 }
68}
69
70impl SchemaBridge for i8 {
72 fn to_ts() -> String {
73 "number".to_string()
74 }
75 fn to_schema() -> Schema {
76 Schema::Number
77 }
78}
79
80impl SchemaBridge for i16 {
81 fn to_ts() -> String {
82 "number".to_string()
83 }
84 fn to_schema() -> Schema {
85 Schema::Number
86 }
87}
88
89impl SchemaBridge for i64 {
90 fn to_ts() -> String {
91 "number".to_string()
92 }
93 fn to_schema() -> Schema {
94 Schema::Number
95 }
96}
97
98impl SchemaBridge for i128 {
99 fn to_ts() -> String {
100 "number".to_string()
101 }
102 fn to_schema() -> Schema {
103 Schema::Number
104 }
105}
106
107impl SchemaBridge for isize {
108 fn to_ts() -> String {
109 "number".to_string()
110 }
111 fn to_schema() -> Schema {
112 Schema::Number
113 }
114}
115
116impl SchemaBridge for u8 {
117 fn to_ts() -> String {
118 "number".to_string()
119 }
120 fn to_schema() -> Schema {
121 Schema::Number
122 }
123}
124
125impl SchemaBridge for u16 {
126 fn to_ts() -> String {
127 "number".to_string()
128 }
129 fn to_schema() -> Schema {
130 Schema::Number
131 }
132}
133
134impl SchemaBridge for u32 {
135 fn to_ts() -> String {
136 "number".to_string()
137 }
138 fn to_schema() -> Schema {
139 Schema::Number
140 }
141}
142
143impl SchemaBridge for u64 {
144 fn to_ts() -> String {
145 "number".to_string()
146 }
147 fn to_schema() -> Schema {
148 Schema::Number
149 }
150}
151
152impl SchemaBridge for u128 {
153 fn to_ts() -> String {
154 "number".to_string()
155 }
156 fn to_schema() -> Schema {
157 Schema::Number
158 }
159}
160
161impl SchemaBridge for usize {
162 fn to_ts() -> String {
163 "number".to_string()
164 }
165 fn to_schema() -> Schema {
166 Schema::Number
167 }
168}
169
170impl SchemaBridge for f32 {
171 fn to_ts() -> String {
172 "number".to_string()
173 }
174 fn to_schema() -> Schema {
175 Schema::Number
176 }
177}
178
179impl SchemaBridge for char {
181 fn to_ts() -> String {
182 "string".to_string()
183 }
184 fn to_schema() -> Schema {
185 Schema::String
186 }
187}
188
189impl SchemaBridge for () {
191 fn to_ts() -> String {
192 "null".to_string()
193 }
194 fn to_schema() -> Schema {
195 Schema::Null
196 }
197}
198
199impl<T: SchemaBridge> SchemaBridge for Option<T> {
200 fn to_ts() -> String {
201 format!("{} | null", T::to_ts())
202 }
203 fn to_schema() -> Schema {
204 Schema::Union(vec![T::to_schema(), Schema::Null])
205 }
206}
207
208impl<T: SchemaBridge> SchemaBridge for Vec<T> {
209 fn to_ts() -> String {
210 format!("{}[]", T::to_ts())
211 }
212 fn to_schema() -> Schema {
213 Schema::Array(Box::new(T::to_schema()))
214 }
215}
216
217impl SchemaBridge for PathBuf {
218 fn to_ts() -> String {
219 "string".to_string()
220 }
221 fn to_schema() -> Schema {
222 Schema::String
223 }
224}
225
226impl<K, V> SchemaBridge for HashMap<K, V>
227where
228 K: SchemaBridge,
229 V: SchemaBridge,
230{
231 fn to_ts() -> String {
232 format!("Record<{}, {}>", K::to_ts(), V::to_ts())
233 }
234 fn to_schema() -> Schema {
235 Schema::Record {
236 key: Box::new(K::to_schema()),
237 value: Box::new(V::to_schema()),
238 }
239 }
240}
241
242impl<K, V> SchemaBridge for BTreeMap<K, V>
243where
244 K: SchemaBridge,
245 V: SchemaBridge,
246{
247 fn to_ts() -> String {
248 format!("Record<{}, {}>", K::to_ts(), V::to_ts())
249 }
250 fn to_schema() -> Schema {
251 Schema::Record {
252 key: Box::new(K::to_schema()),
253 value: Box::new(V::to_schema()),
254 }
255 }
256}
257
258impl<T: SchemaBridge> SchemaBridge for HashSet<T> {
259 fn to_ts() -> String {
260 format!("{}[]", T::to_ts())
261 }
262 fn to_schema() -> Schema {
263 Schema::Array(Box::new(T::to_schema()))
264 }
265}
266
267impl<T: SchemaBridge> SchemaBridge for BTreeSet<T> {
268 fn to_ts() -> String {
269 format!("{}[]", T::to_ts())
270 }
271 fn to_schema() -> Schema {
272 Schema::Array(Box::new(T::to_schema()))
273 }
274}
275
276impl<T: SchemaBridge> SchemaBridge for Box<T> {
277 fn to_ts() -> String {
278 T::to_ts()
279 }
280 fn to_schema() -> Schema {
281 T::to_schema()
282 }
283}
284
285impl<T: SchemaBridge> SchemaBridge for Rc<T> {
286 fn to_ts() -> String {
287 T::to_ts()
288 }
289 fn to_schema() -> Schema {
290 T::to_schema()
291 }
292}
293
294impl<T: SchemaBridge> SchemaBridge for Arc<T> {
295 fn to_ts() -> String {
296 T::to_ts()
297 }
298 fn to_schema() -> Schema {
299 T::to_schema()
300 }
301}
302
303impl<T: SchemaBridge, E: SchemaBridge> SchemaBridge for Result<T, E> {
304 fn to_ts() -> String {
305 format!("{} | {}", T::to_ts(), E::to_ts())
306 }
307 fn to_schema() -> Schema {
308 Schema::Union(vec![T::to_schema(), E::to_schema()])
309 }
310}
311
312impl<T: SchemaBridge> SchemaBridge for (T,) {
314 fn to_ts() -> String {
315 format!("[{}]", T::to_ts())
316 }
317 fn to_schema() -> Schema {
318 Schema::Tuple(vec![T::to_schema()])
319 }
320}
321
322impl<T1: SchemaBridge, T2: SchemaBridge> SchemaBridge for (T1, T2) {
323 fn to_ts() -> String {
324 format!("[{}, {}]", T1::to_ts(), T2::to_ts())
325 }
326 fn to_schema() -> Schema {
327 Schema::Tuple(vec![T1::to_schema(), T2::to_schema()])
328 }
329}
330
331impl<T1: SchemaBridge, T2: SchemaBridge, T3: SchemaBridge> SchemaBridge for (T1, T2, T3) {
332 fn to_ts() -> String {
333 format!("[{}, {}, {}]", T1::to_ts(), T2::to_ts(), T3::to_ts())
334 }
335 fn to_schema() -> Schema {
336 Schema::Tuple(vec![T1::to_schema(), T2::to_schema(), T3::to_schema()])
337 }
338}
339
340impl<T1: SchemaBridge, T2: SchemaBridge, T3: SchemaBridge, T4: SchemaBridge> SchemaBridge
341 for (T1, T2, T3, T4)
342{
343 fn to_ts() -> String {
344 format!(
345 "[{}, {}, {}, {}]",
346 T1::to_ts(),
347 T2::to_ts(),
348 T3::to_ts(),
349 T4::to_ts()
350 )
351 }
352 fn to_schema() -> Schema {
353 Schema::Tuple(vec![
354 T1::to_schema(),
355 T2::to_schema(),
356 T3::to_schema(),
357 T4::to_schema(),
358 ])
359 }
360}
361
362impl<T1: SchemaBridge, T2: SchemaBridge, T3: SchemaBridge, T4: SchemaBridge, T5: SchemaBridge>
363 SchemaBridge for (T1, T2, T3, T4, T5)
364{
365 fn to_ts() -> String {
366 format!(
367 "[{}, {}, {}, {}, {}]",
368 T1::to_ts(),
369 T2::to_ts(),
370 T3::to_ts(),
371 T4::to_ts(),
372 T5::to_ts()
373 )
374 }
375 fn to_schema() -> Schema {
376 Schema::Tuple(vec![
377 T1::to_schema(),
378 T2::to_schema(),
379 T3::to_schema(),
380 T4::to_schema(),
381 T5::to_schema(),
382 ])
383 }
384}
385
386impl<
387 T1: SchemaBridge,
388 T2: SchemaBridge,
389 T3: SchemaBridge,
390 T4: SchemaBridge,
391 T5: SchemaBridge,
392 T6: SchemaBridge,
393 > SchemaBridge for (T1, T2, T3, T4, T5, T6)
394{
395 fn to_ts() -> String {
396 format!(
397 "[{}, {}, {}, {}, {}, {}]",
398 T1::to_ts(),
399 T2::to_ts(),
400 T3::to_ts(),
401 T4::to_ts(),
402 T5::to_ts(),
403 T6::to_ts()
404 )
405 }
406 fn to_schema() -> Schema {
407 Schema::Tuple(vec![
408 T1::to_schema(),
409 T2::to_schema(),
410 T3::to_schema(),
411 T4::to_schema(),
412 T5::to_schema(),
413 T6::to_schema(),
414 ])
415 }
416}
417
418pub fn generate_ts_file(types: Vec<(&str, String)>) -> String {
420 let mut content = String::new();
421 content.push_str("// This file is auto-generated by schema-bridge\n\n");
422
423 for (name, ts_def) in types {
424 content.push_str(&format!("export type {} = {};\n\n", name, ts_def));
425 }
426
427 content
428}
429
430pub fn export_to_file(types: Vec<(&str, String)>, path: &str) -> std::io::Result<()> {
432 let content = generate_ts_file(types);
433 std::fs::write(path, content)
434}
435
436#[macro_export]
438macro_rules! export_types {
439 ($path:expr, $($name:ident),+ $(,)?) => {{
440 let types = vec![
441 $((stringify!($name), $name::to_ts()),)+
442 ];
443 $crate::export_to_file(types, $path)
444 }};
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_string_to_ts() {
453 assert_eq!(String::to_ts(), "string");
454 }
455
456 #[test]
457 fn test_i32_to_ts() {
458 assert_eq!(i32::to_ts(), "number");
459 }
460
461 #[test]
462 fn test_f64_to_ts() {
463 assert_eq!(f64::to_ts(), "number");
464 }
465
466 #[test]
467 fn test_bool_to_ts() {
468 assert_eq!(bool::to_ts(), "boolean");
469 }
470
471 #[test]
472 fn test_option_to_ts() {
473 assert_eq!(Option::<String>::to_ts(), "string | null");
474 assert_eq!(Option::<i32>::to_ts(), "number | null");
475 }
476
477 #[test]
478 fn test_vec_to_ts() {
479 assert_eq!(Vec::<String>::to_ts(), "string[]");
480 assert_eq!(Vec::<i32>::to_ts(), "number[]");
481 }
482
483 #[test]
484 fn test_nested_vec() {
485 assert_eq!(Vec::<Vec::<String>>::to_ts(), "string[][]");
486 }
487
488 #[test]
489 fn test_optional_vec() {
490 assert_eq!(Option::<Vec::<String>>::to_ts(), "string[] | null");
491 }
492
493 #[test]
494 fn test_generate_ts_file() {
495 let types = vec![
496 ("User", "{ name: string; age: number; }".to_string()),
497 ("Status", "'Active' | 'Inactive'".to_string()),
498 ];
499
500 let result = generate_ts_file(types);
501
502 assert!(result.contains("// This file is auto-generated by schema-bridge"));
503 assert!(result.contains("export type User = { name: string; age: number; };"));
504 assert!(result.contains("export type Status = 'Active' | 'Inactive';"));
505 }
506
507 #[test]
508 fn test_schema_enum() {
509 let schema = Schema::String;
510 assert_eq!(schema, Schema::String);
511
512 let schema = Schema::Array(Box::new(Schema::Number));
513 assert!(matches!(schema, Schema::Array(_)));
514 }
515
516 #[test]
517 fn test_pathbuf_to_ts() {
518 assert_eq!(PathBuf::to_ts(), "string");
519 }
520
521 #[test]
522 fn test_pathbuf_to_schema() {
523 assert_eq!(PathBuf::to_schema(), Schema::String);
524 }
525
526 #[test]
527 fn test_hashmap_to_ts() {
528 assert_eq!(HashMap::<String, i32>::to_ts(), "Record<string, number>");
529 assert_eq!(HashMap::<String, String>::to_ts(), "Record<string, string>");
530 }
531
532 #[test]
533 fn test_hashmap_to_schema() {
534 let schema = HashMap::<String, i32>::to_schema();
535 assert!(matches!(schema, Schema::Record { .. }));
536 if let Schema::Record { key, value } = schema {
537 assert_eq!(*key, Schema::String);
538 assert_eq!(*value, Schema::Number);
539 }
540 }
541
542 #[test]
543 fn test_nested_hashmap() {
544 assert_eq!(
545 HashMap::<String, Vec::<String>>::to_ts(),
546 "Record<string, string[]>"
547 );
548 }
549
550 #[test]
551 fn test_optional_hashmap() {
552 assert_eq!(
553 Option::<HashMap::<String, i32>>::to_ts(),
554 "Record<string, number> | null"
555 );
556 }
557
558 #[test]
560 fn test_numeric_types() {
561 assert_eq!(i8::to_ts(), "number");
562 assert_eq!(i16::to_ts(), "number");
563 assert_eq!(i64::to_ts(), "number");
564 assert_eq!(i128::to_ts(), "number");
565 assert_eq!(isize::to_ts(), "number");
566 assert_eq!(u8::to_ts(), "number");
567 assert_eq!(u16::to_ts(), "number");
568 assert_eq!(u32::to_ts(), "number");
569 assert_eq!(u64::to_ts(), "number");
570 assert_eq!(u128::to_ts(), "number");
571 assert_eq!(usize::to_ts(), "number");
572 assert_eq!(f32::to_ts(), "number");
573 }
574
575 #[test]
576 fn test_char_to_ts() {
577 assert_eq!(char::to_ts(), "string");
578 assert_eq!(char::to_schema(), Schema::String);
579 }
580
581 #[test]
582 fn test_unit_to_ts() {
583 assert_eq!(<()>::to_ts(), "null");
584 assert_eq!(<()>::to_schema(), Schema::Null);
585 }
586
587 #[test]
589 fn test_btreemap_to_ts() {
590 assert_eq!(BTreeMap::<String, i32>::to_ts(), "Record<string, number>");
591 }
592
593 #[test]
595 fn test_hashset_to_ts() {
596 assert_eq!(HashSet::<String>::to_ts(), "string[]");
597 assert_eq!(HashSet::<i32>::to_ts(), "number[]");
598 }
599
600 #[test]
601 fn test_btreeset_to_ts() {
602 assert_eq!(BTreeSet::<String>::to_ts(), "string[]");
603 assert_eq!(BTreeSet::<i32>::to_ts(), "number[]");
604 }
605
606 #[test]
608 fn test_box_to_ts() {
609 assert_eq!(Box::<String>::to_ts(), "string");
610 assert_eq!(Box::<i32>::to_ts(), "number");
611 assert_eq!(Box::<String>::to_schema(), Schema::String);
612 }
613
614 #[test]
615 fn test_rc_to_ts() {
616 assert_eq!(Rc::<String>::to_ts(), "string");
617 assert_eq!(Rc::<i32>::to_ts(), "number");
618 }
619
620 #[test]
621 fn test_arc_to_ts() {
622 assert_eq!(Arc::<String>::to_ts(), "string");
623 assert_eq!(Arc::<i32>::to_ts(), "number");
624 }
625
626 #[test]
628 fn test_result_to_ts() {
629 assert_eq!(Result::<String, String>::to_ts(), "string | string");
630 assert_eq!(Result::<i32, String>::to_ts(), "number | string");
631 }
632
633 #[test]
634 fn test_result_to_schema() {
635 let schema = Result::<String, i32>::to_schema();
636 assert!(matches!(schema, Schema::Union(_)));
637 if let Schema::Union(types) = schema {
638 assert_eq!(types.len(), 2);
639 assert_eq!(types[0], Schema::String);
640 assert_eq!(types[1], Schema::Number);
641 }
642 }
643
644 #[test]
646 fn test_tuple_1() {
647 assert_eq!(<(String,)>::to_ts(), "[string]");
648 let schema = <(String,)>::to_schema();
649 assert!(matches!(schema, Schema::Tuple(_)));
650 }
651
652 #[test]
653 fn test_tuple_2() {
654 assert_eq!(<(String, i32)>::to_ts(), "[string, number]");
655 }
656
657 #[test]
658 fn test_tuple_3() {
659 assert_eq!(<(String, i32, bool)>::to_ts(), "[string, number, boolean]");
660 }
661
662 #[test]
663 fn test_tuple_4() {
664 assert_eq!(
665 <(String, i32, bool, f64)>::to_ts(),
666 "[string, number, boolean, number]"
667 );
668 }
669
670 #[test]
671 fn test_tuple_schema() {
672 let schema = <(String, i32)>::to_schema();
673 if let Schema::Tuple(types) = schema {
674 assert_eq!(types.len(), 2);
675 assert_eq!(types[0], Schema::String);
676 assert_eq!(types[1], Schema::Number);
677 } else {
678 panic!("Expected Tuple schema");
679 }
680 }
681
682 #[test]
684 fn test_complex_types() {
685 assert_eq!(Option::<Box::<String>>::to_ts(), "string | null");
686 assert_eq!(Vec::<Arc::<String>>::to_ts(), "string[]");
687 assert_eq!(
688 HashMap::<String, Vec::<i32>>::to_ts(),
689 "Record<string, number[]>"
690 );
691 }
692}