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 for attr in &field.attributes {
186 output.push_str(&format!(" @{}", attr.name.as_str()));
187
188 if !attr.args.is_empty() {
190 output.push('(');
191 for (i, arg) in attr.args.iter().enumerate() {
192 if i > 0 {
193 output.push_str(", ");
194 }
195 if let Some(name) = &arg.name {
196 output.push_str(&format!("{}: ", name.as_str()));
197 }
198 output.push_str(&format_attribute_value(&arg.value));
199 }
200 output.push(')');
201 }
202 }
203
204 output.push('\n');
205 }
206
207 for attr in &model.attributes {
209 output.push_str(&format!(" @@{}", attr.name.as_str()));
210
211 if !attr.args.is_empty() {
212 output.push('(');
213 for (i, arg) in attr.args.iter().enumerate() {
214 if i > 0 {
215 output.push_str(", ");
216 }
217 if let Some(name) = &arg.name {
218 output.push_str(&format!("{}: ", name.as_str()));
219 }
220 output.push_str(&format_attribute_value(&arg.value));
221 }
222 output.push(')');
223 }
224
225 output.push('\n');
226 }
227
228 output.push_str("}\n\n");
229 }
230
231 output
232}
233
234fn format_field_type(
236 field_type: &prax_schema::FieldType,
237 modifier: &prax_schema::TypeModifier,
238) -> String {
239 let base = field_type.type_name().to_string();
240
241 match modifier {
242 prax_schema::TypeModifier::Required => base,
243 prax_schema::TypeModifier::Optional => format!("{}?", base),
244 prax_schema::TypeModifier::List => format!("{}[]", base),
245 prax_schema::TypeModifier::OptionalList => format!("{}[]?", base),
246 }
247}
248
249fn format_attribute_value(value: &prax_schema::AttributeValue) -> String {
251 use prax_schema::AttributeValue;
252
253 match value {
254 AttributeValue::String(s) => format!("\"{}\"", s),
255 AttributeValue::Int(i) => i.to_string(),
256 AttributeValue::Float(f) => f.to_string(),
257 AttributeValue::Boolean(b) => b.to_string(),
258 AttributeValue::Ident(id) => id.to_string(),
259 AttributeValue::Function(name, args) => {
260 if args.is_empty() {
261 format!("{}()", name)
262 } else {
263 let args_str = args
264 .iter()
265 .map(format_attribute_value)
266 .collect::<Vec<_>>()
267 .join(", ");
268 format!("{}({})", name, args_str)
269 }
270 }
271 AttributeValue::Array(items) => {
272 let items_str = items
273 .iter()
274 .map(format_attribute_value)
275 .collect::<Vec<_>>()
276 .join(", ");
277 format!("[{}]", items_str)
278 }
279 AttributeValue::FieldRef(name) => name.to_string(),
280 AttributeValue::FieldRefList(names) => {
281 let names_str = names
282 .iter()
283 .map(|n| n.to_string())
284 .collect::<Vec<_>>()
285 .join(", ");
286 format!("[{}]", names_str)
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_format_field_type() {
297 use prax_schema::{FieldType, ScalarType, TypeModifier};
298
299 assert_eq!(
300 format_field_type(
301 &FieldType::Scalar(ScalarType::String),
302 &TypeModifier::Required
303 ),
304 "String"
305 );
306
307 assert_eq!(
308 format_field_type(&FieldType::Scalar(ScalarType::Int), &TypeModifier::Optional),
309 "Int?"
310 );
311
312 assert_eq!(
313 format_field_type(&FieldType::Scalar(ScalarType::String), &TypeModifier::List),
314 "String[]"
315 );
316 }
317}