prax_cli/commands/
import.rs1use std::fs;
4use std::path::Path;
5
6use prax_import::prelude::*;
7
8use crate::cli::{ImportArgs, ImportSource};
9use crate::error::{CliError, CliResult};
10use crate::output;
11
12pub async fn run(args: ImportArgs) -> CliResult<()> {
14 output::info(&format!(
15 "Importing schema from {} → {}",
16 match args.from {
17 ImportSource::Prisma => "Prisma",
18 ImportSource::Diesel => "Diesel",
19 ImportSource::SeaOrm => "SeaORM",
20 },
21 args.output
22 .as_ref()
23 .map(|p| p.display().to_string())
24 .unwrap_or_else(|| "stdout".to_string())
25 ));
26
27 if !args.input.exists() {
29 return Err(CliError::Config(format!(
30 "Input file not found: {}",
31 args.input.display()
32 )));
33 }
34
35 let prax_schema = match args.from {
37 ImportSource::Prisma => import_from_prisma(&args.input)?,
38 ImportSource::Diesel => import_from_diesel(&args.input)?,
39 ImportSource::SeaOrm => import_from_seaorm(&args.input)?,
40 };
41
42 output::success(&format!(
43 "✓ Successfully imported {} models, {} enums",
44 prax_schema.models.len(),
45 prax_schema.enums.len()
46 ));
47
48 let schema_text = format_schema(&prax_schema);
50
51 if args.print {
53 println!("{}", schema_text);
55 } else {
56 let output_path = args.output.unwrap_or_else(|| {
58 std::path::PathBuf::from("schema.prax")
60 });
61
62 if output_path.exists() && !args.force {
64 return Err(CliError::Config(format!(
65 "Output file already exists: {}. Use --force to overwrite.",
66 output_path.display()
67 )));
68 }
69
70 fs::write(&output_path, schema_text).map_err(|e| {
72 CliError::Config(format!(
73 "Failed to write schema to {}: {}",
74 output_path.display(),
75 e
76 ))
77 })?;
78
79 output::success(&format!(
80 "✓ Schema written to {}",
81 output_path.display()
82 ));
83 }
84
85 output::newline();
87 output::info("Next steps:");
88 output::info(" 1. Review the generated schema file");
89 output::info(" 2. Run `prax validate` to check for any issues");
90 output::info(" 3. Run `prax generate` to generate Rust client code");
91 output::info(" 4. Run `prax migrate dev` to apply migrations");
92
93 Ok(())
94}
95
96fn import_from_prisma(input: &Path) -> CliResult<prax_schema::Schema> {
98 output::info(&format!("Reading Prisma schema from {}", input.display()));
99
100 import_prisma_schema_file(input).map_err(|e| {
101 CliError::Schema(format!("Failed to import Prisma schema: {}", e))
102 })
103}
104
105fn import_from_diesel(input: &Path) -> CliResult<prax_schema::Schema> {
107 output::info(&format!("Reading Diesel schema from {}", input.display()));
108
109 import_diesel_schema_file(input).map_err(|e| {
110 CliError::Schema(format!("Failed to import Diesel schema: {}", e))
111 })
112}
113
114fn import_from_seaorm(input: &Path) -> CliResult<prax_schema::Schema> {
116 output::info(&format!("Reading SeaORM entity from {}", input.display()));
117
118 #[cfg(feature = "seaorm")]
119 {
120 use prax_import::seaorm::import_seaorm_entity_file;
121 import_seaorm_entity_file(input).map_err(|e| {
122 CliError::Schema(format!("Failed to import SeaORM entity: {}", e))
123 })
124 }
125
126 #[cfg(not(feature = "seaorm"))]
127 {
128 Err(CliError::Config(
129 "SeaORM import support not enabled. Rebuild with --features seaorm".to_string()
130 ))
131 }
132}
133
134fn format_schema(schema: &prax_schema::Schema) -> String {
139 let mut output = String::new();
140
141 if let Some(datasource) = &schema.datasource {
143 output.push_str(&format!(
144 "datasource db {{\n provider = \"{}\"\n",
145 datasource.provider.as_str()
146 ));
147
148 if let Some(url) = &datasource.url {
149 output.push_str(&format!(" url = \"{}\"\n", url));
150 }
151
152 output.push_str("}\n\n");
153 }
154
155 for (_, enum_def) in &schema.enums {
157 output.push_str(&format!("enum {} {{\n", enum_def.name()));
158
159 for variant in &enum_def.variants {
160 output.push_str(&format!(" {}\n", variant.name()));
161 }
162
163 output.push_str("}\n\n");
164 }
165
166 for (_, model) in &schema.models {
168 if let Some(doc) = &model.documentation {
169 for line in doc.text.lines() {
170 output.push_str(&format!("/// {}\n", line));
171 }
172 }
173
174 output.push_str(&format!("model {} {{\n", model.name()));
175
176 for (_, field) in &model.fields {
178 if let Some(doc) = &field.documentation {
179 for line in doc.text.lines() {
180 output.push_str(&format!(" /// {}\n", line));
181 }
182 }
183
184 let field_name = field.name();
185 let field_type = format_field_type(&field.field_type, &field.modifier);
186
187 output.push_str(&format!(" {} {}", field_name, field_type));
188
189 for attr in &field.attributes {
191 output.push_str(&format!(" @{}", attr.name.as_str()));
192
193 if !attr.args.is_empty() {
195 output.push('(');
196 for (i, arg) in attr.args.iter().enumerate() {
197 if i > 0 {
198 output.push_str(", ");
199 }
200 if let Some(name) = &arg.name {
201 output.push_str(&format!("{}: ", name.as_str()));
202 }
203 output.push_str(&format_attribute_value(&arg.value));
204 }
205 output.push(')');
206 }
207 }
208
209 output.push('\n');
210 }
211
212 for attr in &model.attributes {
214 output.push_str(&format!(" @@{}", attr.name.as_str()));
215
216 if !attr.args.is_empty() {
217 output.push('(');
218 for (i, arg) in attr.args.iter().enumerate() {
219 if i > 0 {
220 output.push_str(", ");
221 }
222 if let Some(name) = &arg.name {
223 output.push_str(&format!("{}: ", name.as_str()));
224 }
225 output.push_str(&format_attribute_value(&arg.value));
226 }
227 output.push(')');
228 }
229
230 output.push('\n');
231 }
232
233 output.push_str("}\n\n");
234 }
235
236 output
237}
238
239fn format_field_type(field_type: &prax_schema::FieldType, modifier: &prax_schema::TypeModifier) -> String {
241 let base = field_type.type_name().to_string();
242
243 match modifier {
244 prax_schema::TypeModifier::Required => base,
245 prax_schema::TypeModifier::Optional => format!("{}?", base),
246 prax_schema::TypeModifier::List => format!("{}[]", base),
247 prax_schema::TypeModifier::OptionalList => format!("{}[]?", base),
248 }
249}
250
251fn format_attribute_value(value: &prax_schema::AttributeValue) -> String {
253 use prax_schema::AttributeValue;
254
255 match value {
256 AttributeValue::String(s) => format!("\"{}\"", s),
257 AttributeValue::Int(i) => i.to_string(),
258 AttributeValue::Float(f) => f.to_string(),
259 AttributeValue::Boolean(b) => b.to_string(),
260 AttributeValue::Ident(id) => id.to_string(),
261 AttributeValue::Function(name, args) => {
262 if args.is_empty() {
263 format!("{}()", name)
264 } else {
265 let args_str = args
266 .iter()
267 .map(format_attribute_value)
268 .collect::<Vec<_>>()
269 .join(", ");
270 format!("{}({})", name, args_str)
271 }
272 }
273 AttributeValue::Array(items) => {
274 let items_str = items
275 .iter()
276 .map(format_attribute_value)
277 .collect::<Vec<_>>()
278 .join(", ");
279 format!("[{}]", items_str)
280 }
281 AttributeValue::FieldRef(name) => name.to_string(),
282 AttributeValue::FieldRefList(names) => {
283 let names_str = names
284 .iter()
285 .map(|n| n.to_string())
286 .collect::<Vec<_>>()
287 .join(", ");
288 format!("[{}]", names_str)
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_format_field_type() {
299 use prax_schema::{FieldType, ScalarType, TypeModifier};
300
301 assert_eq!(
302 format_field_type(&FieldType::Scalar(ScalarType::String), &TypeModifier::Required),
303 "String"
304 );
305
306 assert_eq!(
307 format_field_type(&FieldType::Scalar(ScalarType::Int), &TypeModifier::Optional),
308 "Int?"
309 );
310
311 assert_eq!(
312 format_field_type(&FieldType::Scalar(ScalarType::String), &TypeModifier::List),
313 "String[]"
314 );
315 }
316}