backbone_core/module.rs
1//! Backbone Module System
2//!
3//! This module provides the trait for modules to register themselves with
4//! the framework, including migration discovery for automatic database setup.
5//!
6//! Inspired by Laravel's Service Provider pattern.
7
8use async_trait::async_trait;
9use std::path::PathBuf;
10
11/// Metadata about a module's migrations
12#[derive(Debug, Clone)]
13pub struct MigrationInfo {
14 /// Module name (e.g., "bersihir")
15 pub module: String,
16 /// Path to migrations directory
17 pub path: PathBuf,
18 /// Number of migrations found (optional, for status display)
19 pub count: Option<usize>,
20}
21
22/// Metadata about a module's seeds
23#[derive(Debug, Clone)]
24pub struct SeedInfo {
25 /// Module name
26 pub module: String,
27 /// Path to seeds directory
28 pub path: PathBuf,
29}
30
31/// Trait for Backbone modules with migration support
32///
33/// Implement this trait to register a module with the Backbone framework.
34/// The framework will automatically discover and run migrations for all
35/// registered modules in dependency order.
36///
37/// # Example
38///
39/// ```ignore
40/// use backbone_core::module::BackboneModule;
41/// use std::path::PathBuf;
42///
43/// pub struct BersihirModule;
44///
45/// impl BackboneModule for BersihirModule {
46/// fn name(&self) -> &'static str { "bersihir" }
47/// fn version(&self) -> &'static str { env!("CARGO_PKG_VERSION") }
48///
49/// fn dependencies(&self) -> Vec<&'static str> {
50/// vec!["sapiens"] // Depends on user module
51/// }
52///
53/// fn migrations_path(&self) -> Option<PathBuf> {
54/// Some(PathBuf::from("libs/modules/bersihir/migrations"))
55/// }
56/// }
57/// ```
58#[async_trait]
59pub trait BackboneModule: Send + Sync {
60 /// Returns the unique identifier for this module
61 ///
62 /// This should be a short, lowercase name like "bersihir", "sapiens", etc.
63 fn name(&self) -> &'static str;
64
65 /// Returns the version of this module
66 ///
67 /// Typically uses `env!("CARGO_PKG_VERSION")` to get from Cargo.toml
68 fn version(&self) -> &'static str;
69
70 /// Returns the list of module names this module depends on
71 ///
72 /// Dependencies are used to determine migration order. A module's
73 /// migrations will only run after all its dependencies have been migrated.
74 ///
75 /// # Returns
76 /// A list of module names (e.g., `vec!["sapiens", "bucket"]`)
77 fn dependencies(&self) -> Vec<&'static str> {
78 vec![]
79 }
80
81 /// Returns the path to this module's migrations directory
82 ///
83 /// Path should be relative to the repository root.
84 /// Return `None` if the module has no migrations.
85 ///
86 /// # Example paths:
87 /// - `libs/modules/bersihir/migrations`
88 /// - `libs/modules/sapiens/migrations`
89 fn migrations_path(&self) -> Option<PathBuf>;
90
91 /// Returns the path to this module's seeds directory (optional)
92 ///
93 /// Seeds are used to populate the database with initial data.
94 fn seeds_path(&self) -> Option<PathBuf> {
95 None
96 }
97
98 /// Called after migrations are run to initialize the module
99 ///
100 /// Use this for any setup that needs to happen after the database
101 /// is ready but before the application starts serving requests.
102 async fn on_boot(&self) -> anyhow::Result<()> {
103 Ok(())
104 }
105
106 /// Called when the application is shutting down
107 ///
108 /// Use this for cleanup tasks.
109 async fn on_shutdown(&self) -> anyhow::Result<()> {
110 Ok(())
111 }
112
113 /// Perform a health check for this module
114 ///
115 /// Returns `true` if the module is healthy and ready to serve requests.
116 async fn health_check(&self) -> bool {
117 true
118 }
119
120 /// Get migration info for this module
121 fn migration_info(&self) -> Option<MigrationInfo> {
122 self.migrations_path().map(|path| MigrationInfo {
123 module: self.name().to_string(),
124 path,
125 count: None,
126 })
127 }
128
129 /// Get seed info for this module
130 fn seed_info(&self) -> Option<SeedInfo> {
131 self.seeds_path().map(|path| SeedInfo {
132 module: self.name().to_string(),
133 path,
134 })
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 struct TestModule;
143
144 impl BackboneModule for TestModule {
145 fn name(&self) -> &'static str {
146 "test"
147 }
148
149 fn version(&self) -> &'static str {
150 "0.1.0"
151 }
152
153 fn dependencies(&self) -> Vec<&'static str> {
154 vec!["core"]
155 }
156
157 fn migrations_path(&self) -> Option<PathBuf> {
158 Some(PathBuf::from("libs/modules/test/migrations"))
159 }
160
161 fn seeds_path(&self) -> Option<PathBuf> {
162 Some(PathBuf::from("libs/modules/test/migrations/seeds"))
163 }
164 }
165
166 #[test]
167 fn test_module_metadata() {
168 let module = TestModule;
169 assert_eq!(module.name(), "test");
170 assert_eq!(module.version(), "0.1.0");
171 assert_eq!(module.dependencies(), vec!["core"]);
172 }
173
174 #[test]
175 fn test_migration_info() {
176 let module = TestModule;
177 let info = module.migration_info();
178 assert!(info.is_some());
179 let info = info.unwrap();
180 assert_eq!(info.module, "test");
181 assert_eq!(info.path, PathBuf::from("libs/modules/test/migrations"));
182 }
183
184 #[test]
185 fn test_seed_info() {
186 let module = TestModule;
187 let info = module.seed_info();
188 assert!(info.is_some());
189 let info = info.unwrap();
190 assert_eq!(info.module, "test");
191 assert_eq!(info.path, PathBuf::from("libs/modules/test/migrations/seeds"));
192 }
193}