prisma_rust_schema/
transform.rs

1use crate::dmmf::Field;
2
3#[derive(Debug, Clone, PartialEq)]
4pub(crate) enum Case {
5    Snake,
6    Camel,
7    Pascal,
8    Kebab,
9    ScreamingSnake,
10    Unknown,
11}
12
13pub(crate) fn identify_case(input: &str) -> Case {
14    if input.is_empty() {
15        return Case::Snake;
16    }
17
18    let chars = input.chars().collect::<Vec<_>>();
19
20    if chars.iter().all(|c| c.is_uppercase() || c.is_numeric()) {
21        return Case::ScreamingSnake;
22    }
23
24    if chars.contains(&'_') {
25        if chars
26            .iter()
27            .all(|c| c.is_lowercase() || c.is_numeric() || *c == '_')
28        {
29            return Case::Snake;
30        }
31
32        if chars
33            .iter()
34            .all(|c| c.is_uppercase() || c.is_numeric() || *c == '_')
35        {
36            return Case::ScreamingSnake;
37        }
38
39        return Case::Unknown;
40    }
41
42    if chars.contains(&'-') {
43        if chars
44            .iter()
45            .all(|c| c.is_lowercase() || c.is_numeric() || *c == '-')
46        {
47            return Case::Kebab;
48        }
49
50        return Case::Unknown;
51    }
52
53    if chars.iter().any(|c| c.is_uppercase()) && !chars.contains(&'_') {
54        if chars[0].is_uppercase() {
55            return Case::Pascal;
56        }
57        return Case::Camel;
58    }
59
60    if chars.iter().all(|c| c.is_lowercase() || c.is_numeric()) {
61        return Case::Snake;
62    }
63
64    Case::Unknown
65}
66
67// Split by uppercase (keep), underscore (discard), hyphen (discard)
68// Lowercase all characters
69// Join with underscore
70pub fn to_snake_case(input: &str) -> String {
71    let case = identify_case(input);
72
73    match case {
74        Case::Snake => input.to_string(),
75        Case::Camel => {
76            let mut result = String::new();
77            let mut capitalize_next = false;
78
79            for c in input.chars() {
80                if c.is_uppercase() {
81                    if !result.is_empty() && !capitalize_next {
82                        result.push('_');
83                    }
84                    result.push(c.to_ascii_lowercase());
85                    capitalize_next = false;
86                } else {
87                    result.push(c);
88                }
89            }
90
91            result
92        }
93        Case::Pascal => {
94            let mut result = String::new();
95            let mut capitalize_next = true;
96
97            for c in input.chars() {
98                if c.is_uppercase() {
99                    if !result.is_empty() && !capitalize_next {
100                        result.push('_');
101                    }
102                    result.push(c.to_ascii_lowercase());
103                    capitalize_next = false;
104                } else {
105                    result.push(c);
106                }
107            }
108
109            result
110        }
111        Case::Kebab => input.replace('-', "_").to_ascii_lowercase(),
112        Case::ScreamingSnake => input.to_ascii_lowercase(),
113        Case::Unknown => {
114            let mut result = String::new();
115            let mut capitalize_next = false;
116
117            for c in input.chars() {
118                if c.is_uppercase() {
119                    if !result.is_empty() && !capitalize_next {
120                        result.push('_');
121                    }
122                    result.push(c.to_ascii_lowercase());
123                    capitalize_next = false;
124                } else {
125                    result.push(c);
126                }
127            }
128
129            result
130        }
131    }
132}
133
134pub fn to_pascal_case(input: &str) -> String {
135    let case = identify_case(input);
136
137    match case {
138        Case::Snake => {
139            let mut result = String::new();
140            let mut capitalize_next = true;
141
142            for c in input.chars() {
143                if c == '_' {
144                    capitalize_next = true;
145                } else if capitalize_next {
146                    result.push(c.to_ascii_uppercase());
147                    capitalize_next = false;
148                } else {
149                    result.push(c);
150                }
151            }
152
153            result
154        }
155        Case::Camel => {
156            let mut result = input.get(0..1).unwrap_or_default().to_ascii_uppercase();
157            let mut capitalize_next = true;
158
159            for c in input.chars().skip(1) {
160                if c.is_uppercase() {
161                    if !result.is_empty() && !capitalize_next {
162                        result.push('_');
163                    }
164                    result.push(c.to_ascii_uppercase());
165                    capitalize_next = false;
166                } else {
167                    result.push(c);
168                }
169            }
170
171            result
172        }
173        Case::Pascal => input.to_string(),
174        Case::Kebab => {
175            let mut result = String::new();
176            let mut capitalize_next = true;
177
178            for c in input.chars() {
179                if c == '-' {
180                    capitalize_next = true;
181                } else if capitalize_next {
182                    result.push(c.to_ascii_uppercase());
183                    capitalize_next = false;
184                } else {
185                    result.push(c);
186                }
187            }
188
189            result
190        }
191        Case::ScreamingSnake => {
192            // `HELLO_WORLD` -> `HelloWorld`
193            let mut result = String::new();
194            let mut capitalize_next = true;
195            for c in input.chars() {
196                if c == '_' {
197                    capitalize_next = true;
198                } else if capitalize_next {
199                    result.push(c.to_ascii_uppercase());
200                    capitalize_next = false;
201                } else {
202                    result.push(c.to_ascii_lowercase());
203                }
204            }
205            result
206        }
207        Case::Unknown => input.to_string(),
208    }
209}
210
211pub fn convert_field_to_type(field: &Field) -> String {
212    if let Some(native_type) = &field.native_type {
213        // Assume first part is the type
214        let t = native_type
215            .get(0)
216            .expect("Native type should have at least one part");
217        let t = t
218            .as_str()
219            .expect("Native type should be a stringified version");
220        // TODO: Consider automatically handling list/optional types
221        match t {
222            "ObjectId" => "bson::oid::ObjectId".to_string(),
223            _ => unimplemented!("Unsupported native type: {}", t),
224        }
225    } else {
226        let scalar = match field.field_type.as_str() {
227            "Boolean" => "bool".to_string(),
228            "Int" => "i32".to_string(),
229            "Float" => "f32".to_string(),
230            "String" => "String".to_string(),
231            "Json" => "serde_json::Value".to_string(),
232            "DateTime" => "chrono::DateTime<chrono::Utc>".to_string(),
233            _ => to_pascal_case(&field.field_type),
234        };
235
236        let maybe_list = if field.is_list {
237            format!("Vec<{}>", scalar)
238        } else {
239            scalar
240        };
241
242        let maybe_option = if field.is_required {
243            maybe_list
244        } else {
245            format!("Option<{}>", maybe_list)
246        };
247
248        maybe_option
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_identify_case() {
258        assert_eq!(identify_case("hello_world"), Case::Snake);
259        assert_eq!(identify_case("HelloWorld"), Case::Pascal);
260        assert_eq!(identify_case("helloWorld"), Case::Camel);
261        assert_eq!(identify_case("hello-world"), Case::Kebab);
262        assert_eq!(identify_case("HELLO_WORLD"), Case::ScreamingSnake);
263        assert_eq!(identify_case("helloworld"), Case::Snake);
264        assert_eq!(identify_case("hello_world_123"), Case::Snake);
265        assert_eq!(identify_case("Hello_World"), Case::Unknown);
266    }
267
268    #[test]
269    fn test_to_snake_case() {
270        assert_eq!(to_snake_case("HelloWorld"), "hello_world");
271        assert_eq!(to_snake_case("helloWorld"), "hello_world");
272        assert_eq!(to_snake_case("hello_world"), "hello_world");
273        assert_eq!(to_snake_case("HELLO_WORLD"), "hello_world");
274        assert_eq!(to_snake_case("helloworld"), "helloworld");
275    }
276
277    // #[test]
278    // fn test_to_camel_case() {
279    //     assert_eq!(to_camel_case("hello_world"), "helloWorld");
280    //     assert_eq!(to_camel_case("helloWorld"), "helloWorld");
281    //     assert_eq!(to_camel_case("hello-world"), "helloWorld");
282    //     assert_eq!(to_camel_case("HELLO_WORLD"), "helloWorld");
283    // }
284
285    #[test]
286    fn test_to_pascal_case() {
287        assert_eq!(to_pascal_case("hello_world"), "HelloWorld");
288        assert_eq!(to_pascal_case("helloWorld"), "HelloWorld");
289        assert_eq!(to_pascal_case("hello-world"), "HelloWorld");
290        assert_eq!(to_pascal_case("HELLO_WORLD"), "HelloWorld");
291        assert_eq!(to_pascal_case("helloworld"), "Helloworld");
292        assert_eq!(to_pascal_case("HelloWorld"), "HelloWorld");
293    }
294
295    // #[test]
296    // fn test_to_kebab_case() {
297    //     assert_eq!(to_kebab_case("HelloWorld"), "hello-world");
298    //     assert_eq!(to_kebab_case("helloWorld"), "hello-world");
299    //     assert_eq!(to_kebab_case("hello-world"), "hello-world");
300    //     assert_eq!(to_kebab_case("Hello-World"), "hello-world");
301    //     assert_eq!(to_kebab_case("HELLO_WORLD"), "hello-world");
302    //     assert_eq!(to_kebab_case("helloworld"), "helloworld");
303    // }
304
305    // #[test]
306    // fn test_to_screaming_snake_case() {
307    //     assert_eq!(to_screaming_snake_case("HelloWorld"), "HELLO_WORLD");
308    //     assert_eq!(to_screaming_snake_case("helloWorld"), "HELLO_WORLD");
309    //     assert_eq!(to_screaming_snake_case("hello_world"), "HELLO_WORLD");
310    //     assert_eq!(to_screaming_snake_case("Hello_World"), "HELLO_WORLD");
311    //     assert_eq!(to_screaming_snake_case("HELLO_WORLD"), "HELLO_WORLD");
312    //     assert_eq!(to_screaming_snake_case("helloworld"), "HELLOWORLD");
313    //     assert_eq!(to_screaming_snake_case("hello-world"), "HELLO_WORLD");
314    // }
315}