Skip to main content

doido_generators/generators/
storage_install.rs

1//! `doido generate storage:install` — the `active_storage:install` analogue.
2//!
3//! Emits a migration creating the `storage_blobs`, `storage_attachments` and
4//! `storage_variant_records` tables (via the `doido_model::migration` builders),
5//! registers it in `db/migration/src/lib.rs`, and appends a `storage:` section to
6//! `config/development.yml` and `config/test.yml` when those files exist.
7
8use crate::generator::{GeneratedFile, Generator};
9use crate::generators::bootstrap_migrations::{
10    apply_bootstrap_migrations, storage_config_section, storage_migration_installed,
11};
12use crate::generators::migration_support::{MIGRATION_LIB_BASE, MIGRATION_SRC_DIR};
13use doido_core::Result;
14
15pub struct StorageInstallGenerator;
16
17/// Append the storage section to `path` if the file exists and has none yet.
18fn config_file(path: &str, active: &str) -> Option<GeneratedFile> {
19    let existing = std::fs::read_to_string(path).ok()?;
20    if existing.contains("storage:") {
21        return None;
22    }
23    Some(GeneratedFile {
24        path: path.to_string(),
25        content: format!(
26            "{}\n{}",
27            existing.trim_end(),
28            storage_config_section(active)
29        ),
30    })
31}
32
33impl Generator for StorageInstallGenerator {
34    fn name(&self) -> &str {
35        "storage:install"
36    }
37
38    fn generate(&self, _args: &[&str]) -> Result<Vec<GeneratedFile>> {
39        let lib_path = format!("{MIGRATION_SRC_DIR}/lib.rs");
40        let existing =
41            std::fs::read_to_string(&lib_path).unwrap_or_else(|_| MIGRATION_LIB_BASE.to_string());
42
43        let mut files = Vec::new();
44        if storage_migration_installed(&existing) {
45            files.push(GeneratedFile {
46                path: lib_path,
47                content: existing,
48            });
49        } else {
50            let (lib, migrations) = apply_bootstrap_migrations(&existing, false);
51            files.push(GeneratedFile {
52                path: lib_path,
53                content: lib,
54            });
55            for (module, content) in migrations {
56                files.push(GeneratedFile {
57                    path: format!("{MIGRATION_SRC_DIR}/{module}.rs"),
58                    content,
59                });
60            }
61        }
62
63        // Best-effort: wire the `storage` config section into existing env files.
64        if let Some(f) = config_file("config/development.yml", "local") {
65            files.push(f);
66        }
67        if let Some(f) = config_file("config/test.yml", "test") {
68            files.push(f);
69        }
70
71        Ok(files)
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::generators::bootstrap_migrations::STORAGE_MIGRATION_MODULE;
79
80    #[test]
81    fn emits_migration_and_registers_it() {
82        let files = StorageInstallGenerator.generate(&[]).unwrap();
83        // A migration file plus the updated lib.rs (config files depend on cwd).
84        let migration = files
85            .iter()
86            .find(|f| f.path.contains(STORAGE_MIGRATION_MODULE) && f.path.ends_with(".rs"))
87            .expect("migration file emitted");
88        assert!(migration.content.contains("storage_blobs"));
89        assert!(migration.content.contains("storage_attachments"));
90        assert!(migration.content.contains("storage_variant_records"));
91        assert!(migration
92            .content
93            .contains("impl MigrationName for Migration"));
94        assert!(!migration.content.contains("DeriveMigrationName"));
95        let module = migration
96            .path
97            .strip_prefix("db/migration/src/")
98            .unwrap()
99            .strip_suffix(".rs")
100            .unwrap();
101        assert!(migration.content.contains(&format!("\"{module}\"")));
102
103        let lib = files
104            .iter()
105            .find(|f| f.path.ends_with("lib.rs"))
106            .expect("lib.rs emitted");
107        assert!(lib.content.contains(STORAGE_MIGRATION_MODULE));
108    }
109}