schema_bridge_core/
lib.rs

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>), // Simple string enum
17    Union(Vec<Schema>),
18    Tuple(Vec<Schema>),
19    Ref(String), // Reference to another type
20    Record {
21        key: Box<Schema>,
22        value: Box<Schema>,
23    }, // For HashMap/Record types
24                 // For complex enums, we might need more structure, but let's start simple
25                 // or maybe just use a custom "Type" definition
26}
27
28pub trait SchemaBridge {
29    fn to_ts() -> String;
30    fn to_schema() -> Schema;
31}
32
33// Implement for basic types
34impl 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
70// Implement for all integer types
71impl 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
179// Implement for char
180impl SchemaBridge for char {
181    fn to_ts() -> String {
182        "string".to_string()
183    }
184    fn to_schema() -> Schema {
185        Schema::String
186    }
187}
188
189// Implement for unit type
190impl 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
312// Tuple implementations
313impl<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
418// Helper to generate the full TS file content
419pub 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
430/// Export types to a TypeScript file
431pub 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 to easily export types to a file
437#[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 numeric types
559    #[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 BTreeMap
588    #[test]
589    fn test_btreemap_to_ts() {
590        assert_eq!(BTreeMap::<String, i32>::to_ts(), "Record<string, number>");
591    }
592
593    // Test HashSet and BTreeSet
594    #[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 smart pointers
607    #[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 Result
627    #[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 tuples
645    #[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 complex combinations
683    #[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}