1use 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;
24pub use cast_core::ConnectionManager;
25
26#[derive(Clone)]
27pub struct Container {
28 inner: Arc<ContainerInner>,
29}
30
31pub struct ContainerInner {
32 pub app: AppConfig,
33 pub db: DatabaseConfig,
34 pub session_cfg: SessionConfig,
35 pub mail_cfg: MailConfig,
36 pub queue_cfg: QueueConfig,
37 pub connections: ConnectionManager,
38 pub default_pool: Option<Pool>,
42 pub cache: CacheStore,
43 pub mailer: MailerHandle,
44 pub queue: QueueHandle,
45 pub storage: StorageManager,
46 pub events: EventBus,
47 pub auth: AuthManager,
48 bindings: RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
49}
50
51impl Container {
52 pub fn app(&self) -> &AppConfig {
53 &self.inner.app
54 }
55 pub fn pool(&self) -> &Pool {
59 self.inner.default_pool.as_ref().unwrap_or_else(|| {
60 panic!(
61 "c.pool() called but default connection is not Postgres ({:?}). \
62 Use c.driver_pool() or c.connection(name) instead.",
63 self.driver()
64 )
65 })
66 }
67
68 pub fn try_pool(&self) -> Option<&Pool> {
70 self.inner.default_pool.as_ref()
71 }
72
73 pub fn driver_pool(&self) -> cast_core::Pool {
76 self.inner.connections.default_pool()
77 }
78
79 pub fn driver(&self) -> cast_core::Driver {
81 self.inner.connections.default_driver()
82 }
83
84 pub fn connection(&self, name: &str) -> Option<cast_core::Connection> {
86 self.inner.connections.get(name)
87 }
88 pub fn connections(&self) -> &ConnectionManager {
90 &self.inner.connections
91 }
92 pub fn cache(&self) -> &CacheStore {
93 &self.inner.cache
94 }
95 pub fn mailer(&self) -> &MailerHandle {
96 &self.inner.mailer
97 }
98 pub fn queue(&self) -> &QueueHandle {
99 &self.inner.queue
100 }
101 pub fn storage(&self) -> &StorageManager {
102 &self.inner.storage
103 }
104 pub fn events(&self) -> &EventBus {
105 &self.inner.events
106 }
107 pub fn auth(&self) -> &AuthManager {
108 &self.inner.auth
109 }
110
111 pub fn resolve<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
113 let bindings = self.inner.bindings.read();
114 bindings
115 .get(&TypeId::of::<T>())
116 .and_then(|v| v.clone().downcast::<T>().ok())
117 }
118
119 pub fn bind<T: Send + Sync + 'static>(&self, value: T) {
121 let mut bindings = self.inner.bindings.write();
122 bindings.insert(TypeId::of::<T>(), Arc::new(value));
123 }
124}
125
126pub struct ContainerBuilder {
127 pub app: AppConfig,
128 pub db: DatabaseConfig,
129 pub session_cfg: SessionConfig,
130 pub mail_cfg: MailConfig,
131 pub queue_cfg: QueueConfig,
132 pub connections: Option<ConnectionManager>,
133 pub cache: Option<CacheStore>,
134 pub mailer: Option<MailerHandle>,
135 pub queue: Option<QueueHandle>,
136 pub storage: Option<StorageManager>,
137 pub events: Option<EventBus>,
138 pub auth: Option<AuthManager>,
139}
140
141impl ContainerBuilder {
142 pub fn from_env() -> Self {
143 Self {
144 app: AppConfig::from_env(),
145 db: DatabaseConfig::from_env(),
146 session_cfg: SessionConfig::from_env(),
147 mail_cfg: MailConfig::from_env(),
148 queue_cfg: QueueConfig::from_env(),
149 connections: None,
150 cache: None,
151 mailer: None,
152 queue: None,
153 storage: None,
154 events: None,
155 auth: None,
156 }
157 }
158
159 pub fn pool(mut self, pool: Pool) -> Self {
162 self.connections = Some(ConnectionManager::from_pool(cast_core::Pool::Postgres(
163 pool,
164 )));
165 self
166 }
167
168 pub fn driver_pool(mut self, pool: cast_core::Pool) -> Self {
170 self.connections = Some(ConnectionManager::from_pool(pool));
171 self
172 }
173
174 pub fn connections(mut self, manager: ConnectionManager) -> Self {
176 self.connections = Some(manager);
177 self
178 }
179
180 pub fn cache(mut self, c: CacheStore) -> Self {
181 self.cache = Some(c);
182 self
183 }
184 pub fn mailer(mut self, m: MailerHandle) -> Self {
185 self.mailer = Some(m);
186 self
187 }
188 pub fn queue(mut self, q: QueueHandle) -> Self {
189 self.queue = Some(q);
190 self
191 }
192 pub fn storage(mut self, s: StorageManager) -> Self {
193 self.storage = Some(s);
194 self
195 }
196 pub fn events(mut self, e: EventBus) -> Self {
197 self.events = Some(e);
198 self
199 }
200 pub fn auth(mut self, a: AuthManager) -> Self {
201 self.auth = Some(a);
202 self
203 }
204
205 pub fn build(self) -> Container {
206 let connections = self
207 .connections
208 .expect("ContainerBuilder requires a database connection — call .pool(pool) or .connections(manager)");
209 let default_driver_pool = connections.default_pool();
210 let pg_default = default_driver_pool.as_postgres().cloned();
211 let queue = self.queue.unwrap_or_else(|| match &pg_default {
212 Some(pg) => QueueHandle::in_memory(pg.clone()),
213 None => QueueHandle::in_memory_no_pool(),
214 });
215 if pg_default.is_none() {
216 tracing::debug!(
217 driver = ?default_driver_pool.driver(),
218 "default connection is non-Postgres — `c.pool()` will panic; use `c.driver_pool()` instead."
219 );
220 }
221 let default_pool = pg_default;
222 let inner = ContainerInner {
223 app: self.app,
224 db: self.db,
225 session_cfg: self.session_cfg,
226 mail_cfg: self.mail_cfg,
227 queue_cfg: self.queue_cfg,
228 cache: self.cache.unwrap_or_else(CacheStore::null),
229 mailer: self.mailer.unwrap_or_else(MailerHandle::null),
230 queue,
231 storage: self.storage.unwrap_or_else(StorageManager::local_default),
232 events: self.events.unwrap_or_default(),
233 auth: self.auth.unwrap_or_default(),
234 default_pool,
235 connections,
236 bindings: RwLock::new(HashMap::new()),
237 };
238 Container {
239 inner: Arc::new(inner),
240 }
241 }
242}
243
244pub trait FromContainer: Sized {
246 fn from_container(container: &Container) -> Self;
247}
248
249impl FromContainer for Container {
250 fn from_container(container: &Container) -> Self {
251 container.clone()
252 }
253}
254
255impl FromContainer for Pool {
256 fn from_container(container: &Container) -> Self {
257 container.pool().clone()
258 }
259}
260
261tokio::task_local! {
262 static CURRENT_CONTAINER: Container;
263}
264
265pub async fn with_container<F, T>(container: Container, fut: F) -> T
268where
269 F: std::future::Future<Output = T>,
270{
271 CURRENT_CONTAINER.scope(container, fut).await
272}
273
274pub fn current() -> Container {
277 CURRENT_CONTAINER
278 .try_with(|c| c.clone())
279 .expect("container not installed in current task — call inside with_container scope")
280}
281
282pub fn try_current() -> Option<Container> {
283 CURRENT_CONTAINER.try_with(|c| c.clone()).ok()
284}