1use std::collections::HashMap;
10
11use crate::db::Db;
12use crate::error::{CoreError, CoreResult};
13use crate::migration::MigrationSet;
14use crate::registry::Registry;
15use crate::strata::async_trait;
16
17pub struct BootCx<'a> {
21 pub db: &'a Db,
22 pub registry: &'a Registry,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct ModuleId(&'static str);
30
31impl ModuleId {
32 pub const fn new(code: &'static str) -> Self {
33 Self(code)
34 }
35 pub const fn as_str(&self) -> &'static str {
36 self.0
37 }
38}
39
40impl std::fmt::Display for ModuleId {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.write_str(self.0)
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub struct Capability(&'static str);
51
52impl Capability {
53 pub const fn new(name: &'static str) -> Self {
54 Self(name)
55 }
56 pub const fn as_str(&self) -> &'static str {
57 self.0
58 }
59}
60
61#[async_trait]
63pub trait Module: Send + Sync + 'static {
64 fn id(&self) -> ModuleId;
69
70 fn migrations(&self) -> MigrationSet {
73 MigrationSet::new(self.id().as_str(), Vec::new())
74 }
75
76 fn requires(&self) -> &'static [ModuleId] {
80 &[]
81 }
82
83 fn requires_db_capabilities(&self) -> &'static [Capability] {
87 &[]
88 }
89
90 fn optional_db_capabilities(&self) -> &'static [Capability] {
94 &[]
95 }
96
97 fn register(&self, registry: &mut Registry) {
102 let _ = registry;
103 }
104 async fn boot(&self, cx: &BootCx<'_>) -> CoreResult<()> {
109 let _ = cx;
110 Ok(())
111 }
112
113 fn admin_base(&self) -> Option<&'static str> {
121 None
122 }
123
124 fn catalogs(&self) -> &'static [(&'static str, &'static str)] {
129 &[]
130 }
131}
132
133#[derive(Default)]
135pub struct ModuleRegistry {
136 modules: Vec<Box<dyn Module>>,
137}
138
139impl ModuleRegistry {
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn register(&mut self, module: impl Module) -> &mut Self {
147 self.modules.push(Box::new(module));
148 self
149 }
150
151 pub fn register_boxed(&mut self, module: Box<dyn Module>) -> &mut Self {
154 self.modules.push(module);
155 self
156 }
157
158 pub fn has(&self, id: ModuleId) -> bool {
160 self.modules.iter().any(|m| m.id() == id)
161 }
162
163 pub fn get(&self, id: ModuleId) -> Option<&dyn Module> {
165 self.modules
166 .iter()
167 .map(|m| m.as_ref())
168 .find(|m| m.id() == id)
169 }
170
171 pub fn iter(&self) -> impl Iterator<Item = &dyn Module> + '_ {
172 self.modules.iter().map(|m| m.as_ref())
173 }
174
175 pub fn len(&self) -> usize {
176 self.modules.len()
177 }
178
179 pub fn is_empty(&self) -> bool {
180 self.modules.is_empty()
181 }
182
183 pub fn ordered(&self) -> CoreResult<Vec<&dyn Module>> {
188 let mut by_id: HashMap<ModuleId, &dyn Module> = HashMap::with_capacity(self.modules.len());
189 for m in &self.modules {
190 if by_id.insert(m.id(), m.as_ref()).is_some() {
191 return Err(CoreError::DuplicateModule {
192 module: m.id().to_string(),
193 });
194 }
195 }
196 for m in &self.modules {
197 for dep in m.requires() {
198 if !by_id.contains_key(dep) {
199 return Err(CoreError::UnknownModuleDependency {
200 module: m.id().to_string(),
201 dependency: dep.to_string(),
202 });
203 }
204 }
205 }
206
207 let mut state: HashMap<ModuleId, Visit> = HashMap::with_capacity(self.modules.len());
208 let mut order: Vec<ModuleId> = Vec::with_capacity(self.modules.len());
209 for m in &self.modules {
210 visit(m.id(), &by_id, &mut state, &mut order)?;
211 }
212 Ok(order.into_iter().map(|id| by_id[&id]).collect())
213 }
214
215 pub fn ordered_migration_sets(&self) -> CoreResult<Vec<MigrationSet>> {
218 Ok(self
219 .ordered()?
220 .into_iter()
221 .map(|m| m.migrations())
222 .collect())
223 }
224}
225
226#[derive(Clone, Copy)]
227enum Visit {
228 Active,
229 Done,
230}
231
232fn visit(
235 id: ModuleId,
236 by_id: &HashMap<ModuleId, &dyn Module>,
237 state: &mut HashMap<ModuleId, Visit>,
238 order: &mut Vec<ModuleId>,
239) -> CoreResult<()> {
240 match state.get(&id) {
241 Some(Visit::Done) => return Ok(()),
242 Some(Visit::Active) => {
243 return Err(CoreError::ModuleDependencyCycle {
244 module: id.to_string(),
245 })
246 }
247 None => {}
248 }
249 state.insert(id, Visit::Active);
250 for dep in by_id[&id].requires() {
251 visit(*dep, by_id, state, order)?;
252 }
253 state.insert(id, Visit::Done);
254 order.push(id);
255 Ok(())
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 struct Mod(ModuleId, &'static [ModuleId]);
264
265 impl Module for Mod {
266 fn id(&self) -> ModuleId {
267 self.0
268 }
269 fn requires(&self) -> &'static [ModuleId] {
270 self.1
271 }
272 }
273
274 const BLOG: ModuleId = ModuleId::new("acme.blog");
275 const USER: ModuleId = ModuleId::new("acme.user");
276 const MEDIA: ModuleId = ModuleId::new("acme.media");
277
278 fn order_of(reg: &ModuleRegistry) -> Vec<&'static str> {
279 reg.ordered()
280 .unwrap()
281 .into_iter()
282 .map(|m| m.id().as_str())
283 .collect()
284 }
285
286 #[test]
287 fn dependencies_come_before_dependents() {
288 let mut reg = ModuleRegistry::new();
289 reg.register(Mod(BLOG, &[USER, MEDIA]))
291 .register(Mod(MEDIA, &[]))
292 .register(Mod(USER, &[]));
293 let order = order_of(®);
294 let pos = |id: &str| order.iter().position(|x| *x == id).unwrap();
295 assert!(pos("acme.user") < pos("acme.blog"));
296 assert!(pos("acme.media") < pos("acme.blog"));
297 }
298
299 #[test]
300 fn registration_order_breaks_ties() {
301 let mut reg = ModuleRegistry::new();
302 reg.register(Mod(ModuleId::new("a"), &[]))
303 .register(Mod(ModuleId::new("b"), &[]))
304 .register(Mod(ModuleId::new("c"), &[]));
305 assert_eq!(order_of(®), ["a", "b", "c"]);
306 }
307
308 #[test]
309 fn has_and_get_find_by_id() {
310 let mut reg = ModuleRegistry::new();
311 reg.register(Mod(MEDIA, &[]));
312 assert!(reg.has(MEDIA));
313 assert!(!reg.has(USER));
314 assert_eq!(reg.get(MEDIA).unwrap().id(), MEDIA);
315 assert!(reg.get(ModuleId::new("missing")).is_none());
316 }
317
318 #[test]
319 fn unknown_dependency_errors() {
320 let mut reg = ModuleRegistry::new();
321 reg.register(Mod(BLOG, &[USER]));
322 assert!(matches!(
323 reg.ordered(),
324 Err(CoreError::UnknownModuleDependency { .. })
325 ));
326 }
327
328 #[test]
329 fn cycle_errors() {
330 const A_REQS: &[ModuleId] = &[ModuleId::new("acme.b")];
333 const B_REQS: &[ModuleId] = &[ModuleId::new("acme.a")];
334 let mut reg = ModuleRegistry::new();
335 reg.register(Mod(ModuleId::new("acme.a"), A_REQS))
336 .register(Mod(ModuleId::new("acme.b"), B_REQS));
337 assert!(matches!(
338 reg.ordered(),
339 Err(CoreError::ModuleDependencyCycle { .. })
340 ));
341 }
342
343 #[test]
344 fn duplicate_id_errors() {
345 let mut reg = ModuleRegistry::new();
346 reg.register(Mod(ModuleId::new("a"), &[]))
347 .register(Mod(ModuleId::new("a"), &[]));
348 assert!(matches!(
349 reg.ordered(),
350 Err(CoreError::DuplicateModule { .. })
351 ));
352 }
353}