rainier_container/
application.rs1use std::path::{Path, PathBuf};
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Mutex, RwLock};
24
25use rainier_support::Result;
26
27use crate::container::Container;
28use crate::provider::ServiceProvider;
29
30pub type LifecycleHook = Box<dyn Fn(&Application) + Send + Sync>;
32
33pub struct Application {
36 container: Arc<Container>,
37 providers: Mutex<Vec<Arc<dyn ServiceProvider>>>,
38 booted_upto: Mutex<usize>,
41 booting_hooks: Mutex<Vec<LifecycleHook>>,
42 booted_hooks: Mutex<Vec<LifecycleHook>>,
43 terminating_hooks: Mutex<Vec<LifecycleHook>>,
44 environment: RwLock<String>,
45 base_path: PathBuf,
46 booted: AtomicBool,
47}
48
49impl Application {
50 pub fn new(base_path: impl Into<PathBuf>) -> Self {
53 Self {
54 container: Arc::new(Container::new()),
55 providers: Mutex::new(Vec::new()),
56 booted_upto: Mutex::new(0),
57 booting_hooks: Mutex::new(Vec::new()),
58 booted_hooks: Mutex::new(Vec::new()),
59 terminating_hooks: Mutex::new(Vec::new()),
60 environment: RwLock::new("production".to_string()),
61 base_path: base_path.into(),
62 booted: AtomicBool::new(false),
63 }
64 }
65
66 pub fn with_environment(self, environment: impl Into<String>) -> Self {
68 self.set_environment(environment);
69 self
70 }
71
72 pub fn container(&self) -> &Arc<Container> {
75 &self.container
76 }
77
78 pub fn register(&self, provider: impl ServiceProvider) -> Result<()> {
89 self.register_arc(Arc::new(provider))
90 }
91
92 pub fn register_arc(&self, provider: Arc<dyn ServiceProvider>) -> Result<()> {
94 provider.register(self)?;
95 self.providers.lock().expect("providers lock poisoned").push(provider);
96 Ok(())
97 }
98
99 pub fn provider_names(&self) -> Vec<&'static str> {
101 self.providers.lock().expect("providers lock poisoned").iter().map(|p| p.name()).collect()
102 }
103
104 pub async fn boot(&self) -> Result<()> {
112 let first_boot = !self.booted.load(Ordering::SeqCst);
113 if first_boot {
114 self.run_hooks(&self.booting_hooks);
115 }
116
117 loop {
118 let next = {
122 let providers = self.providers.lock().expect("providers lock poisoned");
123 let mut upto = self.booted_upto.lock().expect("providers lock poisoned");
124 if *upto >= providers.len() {
125 break;
126 }
127 let provider = Arc::clone(&providers[*upto]);
128 *upto += 1;
129 provider
130 };
131
132 next.boot(self).await.map_err(|e| {
133 rainier_support::Error::internal(format!(
134 "service provider `{}` failed to boot: {e}",
135 next.name()
136 ))
137 })?;
138 }
139
140 self.booted.store(true, Ordering::SeqCst);
141 if first_boot {
142 self.run_hooks(&self.booted_hooks);
143 }
144 Ok(())
145 }
146
147 pub fn is_booted(&self) -> bool {
149 self.booted.load(Ordering::SeqCst)
150 }
151
152 pub fn booting(&self, hook: impl Fn(&Application) + Send + Sync + 'static) {
157 if self.is_booted() {
158 tracing::warn!("`booting` hook registered after the application booted; ignoring");
159 return;
160 }
161 self.booting_hooks.lock().expect("hooks lock poisoned").push(Box::new(hook));
162 }
163
164 pub fn booted(&self, hook: impl Fn(&Application) + Send + Sync + 'static) {
168 if self.is_booted() {
169 hook(self);
170 return;
171 }
172 self.booted_hooks.lock().expect("hooks lock poisoned").push(Box::new(hook));
173 }
174
175 pub fn terminating(&self, hook: impl Fn(&Application) + Send + Sync + 'static) {
178 self.terminating_hooks.lock().expect("hooks lock poisoned").push(Box::new(hook));
179 }
180
181 pub fn terminate(&self) {
184 self.run_hooks(&self.terminating_hooks);
185 }
186
187 fn run_hooks(&self, hooks: &Mutex<Vec<LifecycleHook>>) {
188 let drained: Vec<LifecycleHook> =
191 hooks.lock().expect("hooks lock poisoned").drain(..).collect();
192 for hook in drained {
193 hook(self);
194 }
195 }
196
197 pub fn environment(&self) -> String {
201 self.environment.read().expect("environment lock poisoned").clone()
202 }
203
204 pub fn set_environment(&self, environment: impl Into<String>) {
206 *self.environment.write().expect("environment lock poisoned") = environment.into();
207 }
208
209 pub fn environment_is(&self, names: &[&str]) -> bool {
211 let current = self.environment();
212 names.iter().any(|n| *n == current)
213 }
214
215 pub fn is_local(&self) -> bool {
217 self.environment_is(&["local"])
218 }
219
220 pub fn is_testing(&self) -> bool {
222 self.environment_is(&["testing", "test"])
223 }
224
225 pub fn is_production(&self) -> bool {
227 self.environment_is(&["production", "prod"])
228 }
229
230 pub fn base_path(&self) -> &Path {
234 &self.base_path
235 }
236
237 pub fn path(&self, relative: impl AsRef<Path>) -> PathBuf {
239 self.base_path.join(relative)
240 }
241
242 pub fn config_path(&self) -> PathBuf {
244 self.path("config")
245 }
246
247 pub fn storage_path(&self) -> PathBuf {
249 self.path("storage")
250 }
251
252 pub fn resource_path(&self) -> PathBuf {
254 self.path("resources")
255 }
256
257 pub fn database_path(&self) -> PathBuf {
259 self.path("database")
260 }
261}
262
263impl std::ops::Deref for Application {
264 type Target = Container;
265
266 fn deref(&self) -> &Self::Target {
267 &self.container
268 }
269}
270
271impl std::fmt::Debug for Application {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 f.debug_struct("Application")
274 .field("environment", &self.environment())
275 .field("base_path", &self.base_path)
276 .field("booted", &self.is_booted())
277 .field("providers", &self.provider_names().len())
278 .finish()
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use rainier_support::BoxFuture;
286 use std::sync::atomic::AtomicUsize;
287
288 static SERIAL: Mutex<()> = Mutex::new(());
292 static ORDER: Mutex<Vec<&'static str>> = Mutex::new(Vec::new());
293
294 struct Recording(#[allow(dead_code)] std::sync::MutexGuard<'static, ()>);
295
296 impl Recording {
297 fn start() -> Self {
298 let guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
299 ORDER.lock().unwrap().clear();
300 Self(guard)
301 }
302
303 fn steps(&self) -> Vec<&'static str> {
304 ORDER.lock().unwrap().clone()
305 }
306 }
307
308 fn record(step: &'static str) {
309 ORDER.lock().unwrap().push(step);
310 }
311
312 struct First;
313 struct Second;
314 struct Marker(u32);
315
316 impl ServiceProvider for First {
317 fn register(&self, app: &Application) -> Result<()> {
318 record("register:first");
319 app.instance(Marker(1));
320 Ok(())
321 }
322 fn boot<'a>(&'a self, _app: &'a Application) -> BoxFuture<'a, Result<()>> {
323 Box::pin(async {
324 record("boot:first");
325 Ok(())
326 })
327 }
328 }
329
330 impl ServiceProvider for Second {
331 fn register(&self, _app: &Application) -> Result<()> {
332 record("register:second");
333 Ok(())
334 }
335 fn boot<'a>(&'a self, app: &'a Application) -> BoxFuture<'a, Result<()>> {
336 Box::pin(async move {
337 record("boot:second");
338 assert_eq!(app.resolve::<Marker>()?.0, 1);
340 Ok(())
341 })
342 }
343 }
344
345 #[tokio::test]
346 async fn every_provider_registers_before_any_boots() {
347 let log = Recording::start();
348 let app = Application::new(".");
349 app.register(First).unwrap();
350 app.register(Second).unwrap();
351 app.boot().await.unwrap();
352
353 assert_eq!(
354 log.steps(),
355 vec!["register:first", "register:second", "boot:first", "boot:second"]
356 );
357 }
358
359 #[tokio::test]
360 async fn boot_is_idempotent() {
361 let log = Recording::start();
362 let app = Application::new(".");
363 app.register(First).unwrap();
364 app.boot().await.unwrap();
365 app.boot().await.unwrap();
366
367 assert_eq!(log.steps().iter().filter(|s| **s == "boot:first").count(), 1);
368 }
369
370 #[tokio::test]
371 async fn providers_registered_after_boot_are_booted_by_the_next_boot() {
372 let log = Recording::start();
373 let app = Application::new(".");
374 app.boot().await.unwrap();
375 app.register(First).unwrap();
376 app.boot().await.unwrap();
377
378 assert!(log.steps().contains(&"boot:first"));
379 }
380
381 #[tokio::test]
382 async fn hooks_run_around_booting() {
383 let app = Application::new(".");
384 let seen = Arc::new(Mutex::new(Vec::new()));
385
386 let s = Arc::clone(&seen);
387 app.booting(move |_| s.lock().unwrap().push("booting"));
388 let s = Arc::clone(&seen);
389 app.booted(move |_| s.lock().unwrap().push("booted"));
390 let s = Arc::clone(&seen);
391 app.terminating(move |_| s.lock().unwrap().push("terminating"));
392
393 app.boot().await.unwrap();
394 assert_eq!(*seen.lock().unwrap(), vec!["booting", "booted"]);
395
396 app.terminate();
397 assert_eq!(*seen.lock().unwrap(), vec!["booting", "booted", "terminating"]);
398 }
399
400 #[tokio::test]
401 async fn a_booted_hook_added_late_fires_immediately() {
402 let app = Application::new(".");
403 app.boot().await.unwrap();
404
405 let fired = Arc::new(AtomicUsize::new(0));
406 let f = Arc::clone(&fired);
407 app.booted(move |_| {
408 f.fetch_add(1, Ordering::SeqCst);
409 });
410 assert_eq!(fired.load(Ordering::SeqCst), 1);
411 }
412
413 #[tokio::test]
414 async fn a_failing_provider_names_itself() {
415 struct Broken;
416 impl ServiceProvider for Broken {
417 fn name(&self) -> &'static str {
418 "BrokenProvider"
419 }
420 fn boot<'a>(&'a self, _: &'a Application) -> BoxFuture<'a, Result<()>> {
421 Box::pin(async { Err(rainier_support::Error::internal("no socket")) })
422 }
423 }
424
425 let app = Application::new(".");
426 app.register(Broken).unwrap();
427 let err = app.boot().await.unwrap_err();
428 assert!(err.message().contains("BrokenProvider"), "{}", err.message());
429 assert!(err.message().contains("no socket"), "{}", err.message());
430 }
431
432 #[test]
433 fn the_application_derefs_to_its_container() {
434 let app = Application::new(".");
435 app.instance(Marker(9));
436 assert_eq!(app.resolve::<Marker>().unwrap().0, 9);
437 }
438
439 #[test]
440 fn environment_predicates() {
441 let app = Application::new(".").with_environment("local");
442 assert!(app.is_local());
443 assert!(!app.is_production());
444 app.set_environment("testing");
445 assert!(app.is_testing());
446 }
447
448 #[test]
449 fn paths_hang_off_the_base_path() {
450 let app = Application::new("/srv/app");
451 assert_eq!(app.config_path(), PathBuf::from("/srv/app/config"));
452 assert_eq!(app.path("routes/web.rs"), PathBuf::from("/srv/app/routes/web.rs"));
453 }
454}