mold_cli/generators/
prisma.rs1use crate::generators::{Generator, GeneratorConfig};
2use crate::types::{NestedType, ObjectType, Schema, SchemaType};
3use crate::utils::{is_prisma_reserved, sanitize_identifier, to_pascal_case};
4use anyhow::Result;
5
6pub struct PrismaGenerator;
7
8impl PrismaGenerator {
9 pub fn new() -> Self {
10 Self
11 }
12
13 fn generate_prisma_type(&self, schema_type: &SchemaType) -> Option<String> {
14 match schema_type {
15 SchemaType::String => Some("String".to_string()),
16 SchemaType::Number => Some("Float".to_string()),
17 SchemaType::Integer => Some("Int".to_string()),
18 SchemaType::Boolean => Some("Boolean".to_string()),
19 SchemaType::Array(inner) => {
20 match inner.as_ref() {
22 SchemaType::String => Some("String[]".to_string()),
23 SchemaType::Integer => Some("Int[]".to_string()),
24 SchemaType::Number => Some("Float[]".to_string()),
25 SchemaType::Boolean => Some("Boolean[]".to_string()),
26 _ => None, }
28 }
29 SchemaType::Null => None, SchemaType::Optional(inner) => {
31 self.generate_prisma_type(inner).map(|t| format!("{}?", t))
32 }
33 SchemaType::Any | SchemaType::Union(_) => Some("Json".to_string()), SchemaType::Object(_) => None, }
36 }
37
38 fn generate_model(&self, name: &str, obj: &ObjectType, indent: &str) -> String {
39 let model_name = self.format_model_name(name);
40 let mut lines = vec![format!("model {} {{", model_name)];
41
42 lines.push(format!("{}id Int @id @default(autoincrement())", indent));
44
45 for field in &obj.fields {
47 if let Some(field_str) = self.generate_field(field, indent) {
48 lines.push(field_str);
49 }
50 }
51
52 lines.push("}".to_string());
53 lines.join("\n")
54 }
55
56 fn generate_field(&self, field: &crate::types::Field, indent: &str) -> Option<String> {
57 let field_name = self.format_field_name(&field.name);
58
59 if let SchemaType::Object(_) = &field.field_type {
61 return None;
63 }
64
65 let prisma_type = self.generate_prisma_type(&field.field_type)?;
66 let optional = if field.optional { "?" } else { "" };
67
68 Some(format!(
69 "{}{} {}{}",
70 indent,
71 field_name,
72 prisma_type,
73 if optional.is_empty() { "" } else { optional }
74 ))
75 }
76
77 fn format_field_name(&self, name: &str) -> String {
78 let sanitized = sanitize_identifier(name);
79 if is_prisma_reserved(&sanitized) {
81 format!("{}_", sanitized)
82 } else {
83 sanitized
84 }
85 }
86
87 fn format_model_name(&self, name: &str) -> String {
88 let pascal = to_pascal_case(name);
89 if is_prisma_reserved(&pascal) {
90 format!("{}Model", pascal)
91 } else {
92 pascal
93 }
94 }
95
96 fn generate_nested_models(&self, nested_types: &[NestedType], indent: &str) -> Vec<String> {
97 nested_types
99 .iter()
100 .rev()
101 .map(|nt| self.generate_model(&nt.name, &nt.object, indent))
102 .collect()
103 }
104}
105
106impl Default for PrismaGenerator {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl Generator for PrismaGenerator {
113 fn generate(&self, schema: &Schema, config: &GeneratorConfig) -> Result<String> {
114 let mut output = vec!["// Generated by mold".to_string(), String::new()];
115
116 if !config.flat_mode && !schema.nested_types.is_empty() {
118 let nested = self.generate_nested_models(&schema.nested_types, &config.indent);
119 for model in nested {
120 output.push(model);
121 output.push(String::new());
122 }
123 }
124
125 if let SchemaType::Object(obj) = &schema.root_type {
127 output.push(self.generate_model(&schema.name, obj, &config.indent));
128 }
129
130 Ok(output.join("\n"))
131 }
132
133 fn file_extension(&self) -> &'static str {
134 "prisma"
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::types::Field;
142
143 #[test]
144 fn test_generate_simple_model() {
145 let gen = PrismaGenerator::new();
146 let obj = ObjectType::new(vec![
147 Field::new("name", SchemaType::String),
148 Field::new("age", SchemaType::Integer),
149 Field::new("active", SchemaType::Boolean),
150 ]);
151 let schema = Schema::new("User", SchemaType::Object(obj));
152 let config = GeneratorConfig::default();
153
154 let output = gen.generate(&schema, &config).unwrap();
155
156 assert!(output.contains("model User {"));
157 assert!(output.contains("id Int @id @default(autoincrement())"));
158 assert!(output.contains("name String"));
159 assert!(output.contains("age Int"));
160 assert!(output.contains("active Boolean"));
161 }
162
163 #[test]
164 fn test_generate_array_field() {
165 let gen = PrismaGenerator::new();
166 let obj = ObjectType::new(vec![Field::new(
167 "tags",
168 SchemaType::Array(Box::new(SchemaType::String)),
169 )]);
170 let schema = Schema::new("Post", SchemaType::Object(obj));
171 let config = GeneratorConfig::default();
172
173 let output = gen.generate(&schema, &config).unwrap();
174
175 assert!(output.contains("tags String[]"));
176 }
177
178 #[test]
179 fn test_generate_float_field() {
180 let gen = PrismaGenerator::new();
181 let obj = ObjectType::new(vec![Field::new("price", SchemaType::Number)]);
182 let schema = Schema::new("Product", SchemaType::Object(obj));
183 let config = GeneratorConfig::default();
184
185 let output = gen.generate(&schema, &config).unwrap();
186
187 assert!(output.contains("price Float"));
188 }
189
190 #[test]
191 fn test_reserved_word_handling() {
192 let gen = PrismaGenerator::new();
193 let obj = ObjectType::new(vec![Field::new("model", SchemaType::String)]);
194 let schema = Schema::new("Test", SchemaType::Object(obj));
195 let config = GeneratorConfig::default();
196
197 let output = gen.generate(&schema, &config).unwrap();
198
199 assert!(output.contains("model_ String"));
201 }
202}