Skip to main content

anvil_core/
container.rs

1//! The service container. Two layers:
2//! - Typed fields on `Container` (pool, mailer, cache, queue) — primary mechanism.
3//! - Typemap for user-registered bindings.
4//!
5//! Also exposes a task-local context for facade-style access (`cache::get(...)`).
6
7use std::any::{Any, TypeId};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use parking_lot::RwLock;
12
13use crate::auth::AuthManager;
14use crate::cache::CacheStore;
15use crate::config::{AppConfig, DatabaseConfig, MailConfig, QueueConfig, SessionConfig};
16use crate::event::EventBus;
17use crate::mail::MailerHandle;
18use crate::queue::QueueHandle;
19use crate::storage::StorageManager;
20
21pub type Pool = sqlx::PgPool;
22
23#[derive(Clone)]
24pub struct Container {
25    inner: Arc<ContainerInner>,
26}
27
28pub struct ContainerInner {
29    pub app: AppConfig,
30    pub db: DatabaseConfig,
31    pub session_cfg: SessionConfig,
32    pub mail_cfg: MailConfig,
33    pub queue_cfg: QueueConfig,
34    pub pool: Pool,
35    pub cache: CacheStore,
36    pub mailer: MailerHandle,
37    pub queue: QueueHandle,
38    pub storage: StorageManager,
39    pub events: EventBus,
40    pub auth: AuthManager,
41    bindings: RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
42}
43
44impl Container {
45    pub fn app(&self) -> &AppConfig {
46        &self.inner.app
47    }
48    pub fn pool(&self) -> &Pool {
49        &self.inner.pool
50    }
51    pub fn cache(&self) -> &CacheStore {
52        &self.inner.cache
53    }
54    pub fn mailer(&self) -> &MailerHandle {
55        &self.inner.mailer
56    }
57    pub fn queue(&self) -> &QueueHandle {
58        &self.inner.queue
59    }
60    pub fn storage(&self) -> &StorageManager {
61        &self.inner.storage
62    }
63    pub fn events(&self) -> &EventBus {
64        &self.inner.events
65    }
66    pub fn auth(&self) -> &AuthManager {
67        &self.inner.auth
68    }
69
70    /// Resolve a user-registered binding by type. Returns `None` if not bound.
71    pub fn resolve<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
72        let bindings = self.inner.bindings.read();
73        bindings
74            .get(&TypeId::of::<T>())
75            .and_then(|v| v.clone().downcast::<T>().ok())
76    }
77
78    /// Bind a value into the runtime typemap. Last-write-wins.
79    pub fn bind<T: Send + Sync + 'static>(&self, value: T) {
80        let mut bindings = self.inner.bindings.write();
81        bindings.insert(TypeId::of::<T>(), Arc::new(value));
82    }
83}
84
85pub struct ContainerBuilder {
86    pub app: AppConfig,
87    pub db: DatabaseConfig,
88    pub session_cfg: SessionConfig,
89    pub mail_cfg: MailConfig,
90    pub queue_cfg: QueueConfig,
91    pub pool: Option<Pool>,
92    pub cache: Option<CacheStore>,
93    pub mailer: Option<MailerHandle>,
94    pub queue: Option<QueueHandle>,
95    pub storage: Option<StorageManager>,
96    pub events: Option<EventBus>,
97    pub auth: Option<AuthManager>,
98}
99
100impl ContainerBuilder {
101    pub fn from_env() -> Self {
102        Self {
103            app: AppConfig::from_env(),
104            db: DatabaseConfig::from_env(),
105            session_cfg: SessionConfig::from_env(),
106            mail_cfg: MailConfig::from_env(),
107            queue_cfg: QueueConfig::from_env(),
108            pool: None,
109            cache: None,
110            mailer: None,
111            queue: None,
112            storage: None,
113            events: None,
114            auth: None,
115        }
116    }
117
118    pub fn pool(mut self, pool: Pool) -> Self {
119        self.pool = Some(pool);
120        self
121    }
122    pub fn cache(mut self, c: CacheStore) -> Self {
123        self.cache = Some(c);
124        self
125    }
126    pub fn mailer(mut self, m: MailerHandle) -> Self {
127        self.mailer = Some(m);
128        self
129    }
130    pub fn queue(mut self, q: QueueHandle) -> Self {
131        self.queue = Some(q);
132        self
133    }
134    pub fn storage(mut self, s: StorageManager) -> Self {
135        self.storage = Some(s);
136        self
137    }
138    pub fn events(mut self, e: EventBus) -> Self {
139        self.events = Some(e);
140        self
141    }
142    pub fn auth(mut self, a: AuthManager) -> Self {
143        self.auth = Some(a);
144        self
145    }
146
147    pub fn build(self) -> Container {
148        let pool = self.pool.expect("ContainerBuilder requires a database pool");
149        let inner = ContainerInner {
150            app: self.app,
151            db: self.db,
152            session_cfg: self.session_cfg,
153            mail_cfg: self.mail_cfg,
154            queue_cfg: self.queue_cfg,
155            cache: self.cache.unwrap_or_else(CacheStore::null),
156            mailer: self.mailer.unwrap_or_else(MailerHandle::null),
157            queue: self.queue.unwrap_or_else(|| QueueHandle::in_memory(pool.clone())),
158            storage: self.storage.unwrap_or_else(StorageManager::local_default),
159            events: self.events.unwrap_or_default(),
160            auth: self.auth.unwrap_or_default(),
161            pool,
162            bindings: RwLock::new(HashMap::new()),
163        };
164        Container {
165            inner: Arc::new(inner),
166        }
167    }
168}
169
170/// Trait for types that can be resolved from a `Container` reference.
171pub trait FromContainer: Sized {
172    fn from_container(container: &Container) -> Self;
173}
174
175impl FromContainer for Container {
176    fn from_container(container: &Container) -> Self {
177        container.clone()
178    }
179}
180
181impl FromContainer for Pool {
182    fn from_container(container: &Container) -> Self {
183        container.pool().clone()
184    }
185}
186
187tokio::task_local! {
188    static CURRENT_CONTAINER: Container;
189}
190
191/// Run a future with a container installed in task-local context. Used by
192/// the per-request middleware so facade-style functions can find the container.
193pub async fn with_container<F, T>(container: Container, fut: F) -> T
194where
195    F: std::future::Future<Output = T>,
196{
197    CURRENT_CONTAINER.scope(container, fut).await
198}
199
200/// Access the current request's container from anywhere on the request task.
201/// Panics if called outside a `with_container` scope.
202pub fn current() -> Container {
203    CURRENT_CONTAINER
204        .try_with(|c| c.clone())
205        .expect("container not installed in current task — call inside with_container scope")
206}
207
208pub fn try_current() -> Option<Container> {
209    CURRENT_CONTAINER.try_with(|c| c.clone()).ok()
210}