Skip to main content

origin_app/
module.rs

1use crate::Platform;
2use origin_domain::{AppError, Result};
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::fmt;
6use std::sync::Arc;
7
8/// A cohesive feature area — Inbox, Projects, Traffic, Health.
9///
10/// Modules are compile-time components. Origin has no dynamic plugin loading: a module
11/// is code that was linked in, which keeps the dependency graph honest and the binary
12/// auditable.
13pub trait ApplicationModule: fmt::Debug + Send + Sync + 'static {
14    /// Stable identifier, used in logs and in the app manifest.
15    fn id(&self) -> &'static str;
16
17    /// Wire the module up: read settings, provide services, subscribe to events.
18    fn register(&self, registry: &mut ModuleRegistry) -> Result<()>;
19}
20
21/// What a module registers into during startup.
22#[derive(Default)]
23pub struct ModuleRegistry {
24    platform: Option<Platform>,
25    services: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
26    module_ids: Vec<&'static str>,
27}
28
29impl fmt::Debug for ModuleRegistry {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.debug_struct("ModuleRegistry")
32            .field("modules", &self.module_ids)
33            .field("services", &self.services.len())
34            .finish()
35    }
36}
37
38impl ModuleRegistry {
39    pub(crate) fn new(platform: Platform) -> Self {
40        Self {
41            platform: Some(platform),
42            services: HashMap::new(),
43            module_ids: Vec::new(),
44        }
45    }
46
47    /// Platform services available to every module.
48    pub fn platform(&self) -> &Platform {
49        self.platform
50            .as_ref()
51            .expect("registry is always constructed with a platform")
52    }
53
54    /// Publish a service so other modules and the host layer can resolve it by type.
55    ///
56    /// Registering the same type twice replaces the previous instance — the later
57    /// module in the composition root wins, which is what an explicit override means.
58    pub fn provide<T: Send + Sync + 'static>(&mut self, service: Arc<T>) {
59        self.services.insert(TypeId::of::<T>(), Box::new(service));
60    }
61
62    pub fn service<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
63        self.services
64            .get(&TypeId::of::<T>())
65            .and_then(|entry| entry.downcast_ref::<Arc<T>>())
66            .cloned()
67    }
68
69    /// Resolve a service or fail with a configuration error naming the missing type.
70    pub fn require<T: Send + Sync + 'static>(&self) -> Result<Arc<T>> {
71        self.service::<T>().ok_or_else(|| {
72            AppError::configuration(format!(
73                "no module provided the service `{}`",
74                std::any::type_name::<T>()
75            ))
76        })
77    }
78
79    pub(crate) fn record_module(&mut self, id: &'static str) {
80        self.module_ids.push(id);
81    }
82
83    pub(crate) fn module_ids(&self) -> &[&'static str] {
84        &self.module_ids
85    }
86}