mold_cli/generators/prisma/
mod.rs

1mod relations;
2mod types;
3
4use crate::generators::{Generator, GeneratorConfig};
5use crate::types::{NestedType, ObjectType, Schema, SchemaType};
6use anyhow::Result;
7use std::collections::HashMap;
8
9use relations::{format_model_name, generate_field};
10
11pub struct PrismaGenerator;
12
13impl PrismaGenerator {
14    pub fn new() -> Self {
15        Self
16    }
17
18    fn generate_model(
19        &self,
20        name: &str,
21        obj: &ObjectType,
22        indent: &str,
23        type_refs: &HashMap<String, String>,
24        generate_relations: bool,
25    ) -> String {
26        let model_name = format_model_name(name);
27        let mut lines = vec![format!("model {} {{", model_name)];
28
29        lines.push(format!("{}id Int @id @default(autoincrement())", indent));
30
31        for field in &obj.fields {
32            if let Some(field_lines) = generate_field(field, indent, type_refs, generate_relations) {
33                for line in field_lines {
34                    lines.push(line);
35                }
36            }
37        }
38
39        lines.push("}".to_string());
40        lines.join("\n")
41    }
42
43    fn build_type_refs(&self, nested_types: &[NestedType]) -> HashMap<String, String> {
44        let mut refs = HashMap::new();
45        for nt in nested_types {
46            let key = format!("{:?}", nt.object);
47            refs.insert(key, nt.name.clone());
48        }
49        refs
50    }
51
52    fn generate_nested_models(
53        &self,
54        nested_types: &[NestedType],
55        indent: &str,
56        type_refs: &HashMap<String, String>,
57        generate_relations: bool,
58    ) -> Vec<String> {
59        nested_types
60            .iter()
61            .rev()
62            .map(|nt| self.generate_model(&nt.name, &nt.object, indent, type_refs, generate_relations))
63            .collect()
64    }
65}
66
67impl Default for PrismaGenerator {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73impl Generator for PrismaGenerator {
74    fn generate(&self, schema: &Schema, config: &GeneratorConfig) -> Result<String> {
75        let mut output = vec!["// Generated by mold".to_string(), String::new()];
76
77        let type_refs = if config.flat_mode {
78            HashMap::new()
79        } else {
80            self.build_type_refs(&schema.nested_types)
81        };
82
83        if !config.flat_mode && !schema.nested_types.is_empty() {
84            let nested = self.generate_nested_models(
85                &schema.nested_types,
86                &config.indent,
87                &type_refs,
88                config.prisma_generate_relations,
89            );
90            for model in nested {
91                output.push(model);
92                output.push(String::new());
93            }
94        }
95
96        if let SchemaType::Object(obj) = &schema.root_type {
97            output.push(self.generate_model(
98                &schema.name,
99                obj,
100                &config.indent,
101                &type_refs,
102                config.prisma_generate_relations,
103            ));
104        }
105
106        Ok(output.join("\n"))
107    }
108
109    fn file_extension(&self) -> &'static str {
110        "prisma"
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::types::Field;
118
119    #[test]
120    fn test_generate_simple_model() {
121        let gen = PrismaGenerator::new();
122        let obj = ObjectType::new(vec![
123            Field::new("name", SchemaType::String),
124            Field::new("age", SchemaType::Integer),
125            Field::new("active", SchemaType::Boolean),
126        ]);
127        let schema = Schema::new("User", SchemaType::Object(obj));
128        let config = GeneratorConfig::default();
129
130        let output = gen.generate(&schema, &config).unwrap();
131
132        assert!(output.contains("model User {"));
133        assert!(output.contains("id Int @id @default(autoincrement())"));
134        assert!(output.contains("name String"));
135        assert!(output.contains("age Int"));
136        assert!(output.contains("active Boolean"));
137    }
138
139    #[test]
140    fn test_generate_array_field() {
141        let gen = PrismaGenerator::new();
142        let obj = ObjectType::new(vec![Field::new(
143            "tags",
144            SchemaType::Array(Box::new(SchemaType::String)),
145        )]);
146        let schema = Schema::new("Post", SchemaType::Object(obj));
147        let config = GeneratorConfig::default();
148
149        let output = gen.generate(&schema, &config).unwrap();
150
151        assert!(output.contains("tags String[]"));
152    }
153
154    #[test]
155    fn test_generate_float_field() {
156        let gen = PrismaGenerator::new();
157        let obj = ObjectType::new(vec![Field::new("price", SchemaType::Number)]);
158        let schema = Schema::new("Product", SchemaType::Object(obj));
159        let config = GeneratorConfig::default();
160
161        let output = gen.generate(&schema, &config).unwrap();
162
163        assert!(output.contains("price Float"));
164    }
165
166    #[test]
167    fn test_reserved_word_handling() {
168        let gen = PrismaGenerator::new();
169        let obj = ObjectType::new(vec![Field::new("model", SchemaType::String)]);
170        let schema = Schema::new("Test", SchemaType::Object(obj));
171        let config = GeneratorConfig::default();
172
173        let output = gen.generate(&schema, &config).unwrap();
174
175        assert!(output.contains("model_ String"));
176    }
177}