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