schema_bridge_core/
lib.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4pub enum Schema {
5    String,
6    Number,
7    Boolean,
8    Null,
9    Any,
10    Array(Box<Schema>),
11    Object(Vec<(String, Schema)>),
12    Enum(Vec<String>), // Simple string enum
13    Union(Vec<Schema>),
14    Tuple(Vec<Schema>),
15    Ref(String), // Reference to another type
16                 // For complex enums, we might need more structure, but let's start simple
17                 // or maybe just use a custom "Type" definition
18}
19
20pub trait SchemaBridge {
21    fn to_ts() -> String;
22    fn to_schema() -> Schema;
23}
24
25// Implement for basic types
26impl SchemaBridge for String {
27    fn to_ts() -> String {
28        "string".to_string()
29    }
30    fn to_schema() -> Schema {
31        Schema::String
32    }
33}
34
35impl SchemaBridge for i32 {
36    fn to_ts() -> String {
37        "number".to_string()
38    }
39    fn to_schema() -> Schema {
40        Schema::Number
41    }
42}
43
44impl SchemaBridge for f64 {
45    fn to_ts() -> String {
46        "number".to_string()
47    }
48    fn to_schema() -> Schema {
49        Schema::Number
50    }
51}
52
53impl SchemaBridge for bool {
54    fn to_ts() -> String {
55        "boolean".to_string()
56    }
57    fn to_schema() -> Schema {
58        Schema::Boolean
59    }
60}
61
62impl<T: SchemaBridge> SchemaBridge for Option<T> {
63    fn to_ts() -> String {
64        format!("{} | null", T::to_ts())
65    }
66    fn to_schema() -> Schema {
67        Schema::Union(vec![T::to_schema(), Schema::Null])
68    }
69}
70
71impl<T: SchemaBridge> SchemaBridge for Vec<T> {
72    fn to_ts() -> String {
73        format!("{}[]", T::to_ts())
74    }
75    fn to_schema() -> Schema {
76        Schema::Array(Box::new(T::to_schema()))
77    }
78}
79
80// Helper to generate the full TS file content
81pub fn generate_ts_file(types: Vec<(&str, String)>) -> String {
82    let mut content = String::new();
83    content.push_str("// This file is auto-generated by schema-bridge\n\n");
84
85    for (name, ts_def) in types {
86        content.push_str(&format!("export type {} = {};\n\n", name, ts_def));
87    }
88
89    content
90}
91
92/// Export types to a TypeScript file
93pub fn export_to_file(types: Vec<(&str, String)>, path: &str) -> std::io::Result<()> {
94    let content = generate_ts_file(types);
95    std::fs::write(path, content)
96}
97
98/// Macro to easily export types to a file
99#[macro_export]
100macro_rules! export_types {
101    ($path:expr, $($name:ident),+ $(,)?) => {{
102        let types = vec![
103            $((stringify!($name), $name::to_ts()),)+
104        ];
105        $crate::export_to_file(types, $path)
106    }};
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_string_to_ts() {
115        assert_eq!(String::to_ts(), "string");
116    }
117
118    #[test]
119    fn test_i32_to_ts() {
120        assert_eq!(i32::to_ts(), "number");
121    }
122
123    #[test]
124    fn test_f64_to_ts() {
125        assert_eq!(f64::to_ts(), "number");
126    }
127
128    #[test]
129    fn test_bool_to_ts() {
130        assert_eq!(bool::to_ts(), "boolean");
131    }
132
133    #[test]
134    fn test_option_to_ts() {
135        assert_eq!(Option::<String>::to_ts(), "string | null");
136        assert_eq!(Option::<i32>::to_ts(), "number | null");
137    }
138
139    #[test]
140    fn test_vec_to_ts() {
141        assert_eq!(Vec::<String>::to_ts(), "string[]");
142        assert_eq!(Vec::<i32>::to_ts(), "number[]");
143    }
144
145    #[test]
146    fn test_nested_vec() {
147        assert_eq!(Vec::<Vec::<String>>::to_ts(), "string[][]");
148    }
149
150    #[test]
151    fn test_optional_vec() {
152        assert_eq!(Option::<Vec::<String>>::to_ts(), "string[] | null");
153    }
154
155    #[test]
156    fn test_generate_ts_file() {
157        let types = vec![
158            ("User", "{ name: string; age: number; }".to_string()),
159            ("Status", "'Active' | 'Inactive'".to_string()),
160        ];
161
162        let result = generate_ts_file(types);
163
164        assert!(result.contains("// This file is auto-generated by schema-bridge"));
165        assert!(result.contains("export type User = { name: string; age: number; };"));
166        assert!(result.contains("export type Status = 'Active' | 'Inactive';"));
167    }
168
169    #[test]
170    fn test_schema_enum() {
171        let schema = Schema::String;
172        assert_eq!(schema, Schema::String);
173
174        let schema = Schema::Array(Box::new(Schema::Number));
175        assert!(matches!(schema, Schema::Array(_)));
176    }
177}