Skip to main content

doido_generators/generators/
migration_support.rs

1//! Shared helpers for emitting SeaORM migration files that call the
2//! `doido_model::migration` builders (`create_table`, `alter_table`,
3//! `drop_table`, `add_index`, …) instead of raw sea-orm boilerplate.
4//!
5//! Used by both the model generator (which creates a table for a new model) and
6//! the migration generator.
7
8use crate::generators::field::Field;
9
10/// Directory holding the SeaORM migration crate's sources.
11pub const MIGRATION_SRC_DIR: &str = "db/migration/src";
12
13/// Fallback migration `lib.rs` used when the app doesn't have one on disk yet;
14/// kept in sync with the generated-app template so injection markers line up.
15pub const MIGRATION_LIB_BASE: &str = include_str!("../../templates/new/db/migration/src/lib.rs");
16
17/// Renders a full migration file from the imports line and the `up`/`down`
18/// bodies (each already indented as an `async fn` body ending in a newline).
19pub fn render_migration_file(
20    migration_name: &str,
21    imports: &str,
22    up_body: &str,
23    down_body: &str,
24) -> String {
25    crate::templates::get("migration/migration.rs.template")
26        .replace("{migration_name}", migration_name)
27        .replace("{migration_imports}", imports)
28        .replace("{up_body}", up_body)
29        .replace("{down_body}", down_body)
30}
31
32/// The `doido_model::migration` import line for a `create_table` migration —
33/// pulls in `add_index` only when a field requested an index, so generated code
34/// carries no unused imports.
35pub fn create_table_imports(fields: &[Field]) -> String {
36    if fields.iter().any(Field::wants_index) {
37        "use doido::model::migration::{add_index, create_table, drop_table};".to_string()
38    } else {
39        "use doido::model::migration::{create_table, drop_table};".to_string()
40    }
41}
42
43/// Builds the `up()` body for a `create_table` migration — a `create_table`
44/// call carrying the declared columns, followed by any `add_index` calls for
45/// `:index` fields.
46pub fn create_table_up(table_name: &str, fields: &[Field]) -> String {
47    // No columns: keep the hint and an unused-arg-safe closure.
48    if fields.is_empty() {
49        return format!(
50            "        // `create_table` adds an auto-incrementing `id` primary key for you.\n\
51             \x20       // Add columns with the builder, e.g. `t.string(\"name\").not_null();`.\n\
52             \x20       create_table(manager, \"{table_name}\", |_t| {{}}).await\n"
53        );
54    }
55
56    let columns: String = fields
57        .iter()
58        .map(|f| format!("            {}\n", f.migration_line()))
59        .collect();
60
61    let indexes: Vec<&Field> = fields.iter().filter(|f| f.wants_index()).collect();
62
63    let mut body = String::new();
64    body.push_str(
65        "        // `create_table` adds an auto-incrementing `id` primary key for you.\n",
66    );
67    body.push_str(&format!(
68        "        create_table(manager, \"{table_name}\", |t| {{\n{columns}        }})\n"
69    ));
70
71    if indexes.is_empty() {
72        // Return the `create_table` result directly.
73        body.push_str("        .await\n");
74    } else {
75        body.push_str("        .await?;\n");
76        for f in indexes {
77            body.push_str(&format!(
78                "        add_index(manager, \"{table_name}\", &[\"{}\"]).await?;\n",
79                f.column_name()
80            ));
81        }
82        body.push_str("        Ok(())\n");
83    }
84
85    body
86}
87
88/// The `down()` body that drops a table: `drop_table(manager, "<table>").await`.
89pub fn drop_table_down(table_name: &str) -> String {
90    format!("        drop_table(manager, \"{table_name}\").await\n")
91}
92
93/// Inserts a `mod <module>;` declaration and a `Box::new(<module>::Migration)`
94/// registration into the migration crate's `lib.rs`, just above the generator
95/// markers. Indentation of the list entry mirrors the marker line.
96pub fn register_migration(lib: &str, module: &str) -> String {
97    let mut lines: Vec<String> = lib.lines().map(String::from).collect();
98
99    if let Some(i) = lines
100        .iter()
101        .position(|l| l.contains("@generated-migrations-mod"))
102    {
103        lines.insert(i, format!("mod {module};"));
104    }
105
106    if let Some(i) = lines
107        .iter()
108        .position(|l| l.contains("@generated-migrations-list"))
109    {
110        let indent: String = lines[i].chars().take_while(|c| c.is_whitespace()).collect();
111        lines.insert(i, format!("{indent}Box::new({module}::Migration),"));
112    }
113
114    let mut out = lines.join("\n");
115    out.push('\n');
116    out
117}