1#[cfg(feature = "cli")]
2use crate::api::cli::RunCommand;
3use crate::app::context::AppContext;
4use crate::app::metadata::AppMetadata;
5use crate::app::{run, App};
6use crate::config::environment::Environment;
7use crate::config::AppConfig;
8#[cfg(feature = "db-sql")]
9use crate::db::migration::Migrator;
10use crate::error::RoadsterResult;
11use crate::health::check::registry::HealthCheckRegistry;
12use crate::health::check::HealthCheck;
13use crate::lifecycle::registry::LifecycleHandlerRegistry;
14use crate::lifecycle::AppLifecycleHandler;
15use crate::service::registry::ServiceRegistry;
16use crate::service::AppService;
17use crate::util::empty::Empty;
18use anyhow::anyhow;
19use async_trait::async_trait;
20use axum_core::extract::FromRef;
21use config::AsyncSource;
22#[cfg(feature = "db-sea-orm")]
23use sea_orm::ConnectOptions;
24use std::future;
25use std::future::Future;
26use std::pin::Pin;
27use std::sync::{Arc, Mutex};
28
29type StateBuilder<S> = dyn Send + Sync + Fn(AppContext) -> RoadsterResult<S>;
30type TracingInitializer = dyn Send + Sync + Fn(&AppConfig) -> RoadsterResult<()>;
31type AsyncConfigSourceProvider =
32 dyn Send + Sync + Fn(&Environment) -> RoadsterResult<Box<dyn AsyncSource + Send + Sync>>;
33type MetadataProvider = dyn Send + Sync + Fn(&AppConfig) -> RoadsterResult<AppMetadata>;
34#[cfg(feature = "db-sea-orm")]
35type DbConnOptionsProvider = dyn Send + Sync + Fn(&AppConfig) -> RoadsterResult<ConnectOptions>;
36#[cfg(any(
37 feature = "db-diesel-postgres-pool",
38 feature = "db-diesel-mysql-pool",
39 feature = "db-diesel-sqlite-pool"
40))]
41type DieselConnectionCustomizer<C> = Box<dyn r2d2::CustomizeConnection<C, diesel::r2d2::Error>>;
42#[cfg(any(
43 feature = "db-diesel-postgres-pool",
44 feature = "db-diesel-mysql-pool",
45 feature = "db-diesel-sqlite-pool"
46))]
47type DieselConnectionCustomizerProvider<C> =
48 dyn Send + Sync + Fn(&AppConfig) -> RoadsterResult<DieselConnectionCustomizer<C>>;
49#[cfg(any(
50 feature = "db-diesel-postgres-pool-async",
51 feature = "db-diesel-mysql-pool-async"
52))]
53type DieselAsyncConnectionCustomizer<C> =
54 Box<dyn bb8_8::CustomizeConnection<C, diesel_async::pooled_connection::PoolError>>;
55#[cfg(any(
56 feature = "db-diesel-postgres-pool-async",
57 feature = "db-diesel-mysql-pool-async"
58))]
59type DieselAsyncConnectionCustomizerProvider<C> =
60 dyn Send + Sync + Fn(&AppConfig) -> RoadsterResult<DieselAsyncConnectionCustomizer<C>>;
61#[cfg(feature = "db-sql")]
62type MigratorProvider<S> = dyn Send + Sync + Fn(&S) -> RoadsterResult<Box<dyn Migrator<S>>>;
63type LifecycleHandlers<A, S> = Vec<Box<dyn AppLifecycleHandler<A, S>>>;
64type LifecycleHandlerProviders<A, S> =
65 Vec<Box<dyn Send + Sync + Fn(&mut LifecycleHandlerRegistry<A, S>, &S) -> RoadsterResult<()>>>;
66type HealthCheckProviders<S> =
67 Vec<Box<dyn Send + Sync + Fn(&mut HealthCheckRegistry, &S) -> RoadsterResult<()>>>;
68type Services<A, S> = Vec<Box<dyn AppService<A, S>>>;
69type ServiceProviders<A, S> = Vec<
70 Box<
71 dyn Send
72 + Sync
73 + for<'a> Fn(
74 &'a mut ServiceRegistry<A, S>,
75 &'a S,
76 ) -> Pin<Box<dyn 'a + Send + Future<Output = RoadsterResult<()>>>>,
77 >,
78>;
79type GracefulShutdownSignalProvider<S> =
80 Option<Box<dyn Send + Sync + Fn(&S) -> Pin<Box<dyn Send + Future<Output = ()>>>>>;
81
82struct Inner<
84 S,
85 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync = Empty,
86 #[cfg(not(feature = "cli"))] Cli: 'static = Empty,
87> where
88 S: 'static + Clone + Send + Sync,
89 AppContext: FromRef<S>,
90{
91 state_provider: Option<Box<StateBuilder<S>>>,
92 tracing_initializer: Option<Box<TracingInitializer>>,
93 async_config_source_providers: Vec<Box<AsyncConfigSourceProvider>>,
94 metadata: Option<AppMetadata>,
95 metadata_provider: Option<Box<MetadataProvider>>,
96 #[cfg(feature = "db-sea-orm")]
97 sea_orm_conn_options: Option<ConnectOptions>,
98 #[cfg(feature = "db-sea-orm")]
99 sea_orm_conn_options_provider: Option<Box<DbConnOptionsProvider>>,
100 #[cfg(feature = "db-diesel-postgres-pool")]
101 diesel_pg_connection_customizer_provider:
102 Option<Box<DieselConnectionCustomizerProvider<crate::db::DieselPgConn>>>,
103 #[cfg(feature = "db-diesel-mysql-pool")]
104 diesel_mysql_connection_customizer_provider:
105 Option<Box<DieselConnectionCustomizerProvider<crate::db::DieselMysqlConn>>>,
106 #[cfg(feature = "db-diesel-sqlite-pool")]
107 diesel_sqlite_connection_customizer_provider:
108 Option<Box<DieselConnectionCustomizerProvider<crate::db::DieselSqliteConn>>>,
109 #[cfg(feature = "db-diesel-postgres-pool-async")]
110 diesel_pg_async_connection_customizer_provider:
111 Option<Box<DieselAsyncConnectionCustomizerProvider<crate::db::DieselPgConnAsync>>>,
112 #[cfg(feature = "db-diesel-mysql-pool-async")]
113 diesel_mysql_async_connection_customizer_provider:
114 Option<Box<DieselAsyncConnectionCustomizerProvider<crate::db::DieselMysqlConnAsync>>>,
115 #[cfg(feature = "db-sql")]
116 migrator_providers: Vec<Box<MigratorProvider<S>>>,
117 health_checks: Vec<Arc<dyn HealthCheck>>,
118 health_check_providers: HealthCheckProviders<S>,
119 graceful_shutdown_signal_provider: GracefulShutdownSignalProvider<S>,
120 lifecycle_handler_providers: LifecycleHandlerProviders<RoadsterApp<S, Cli>, S>,
121 service_providers: ServiceProviders<RoadsterApp<S, Cli>, S>,
122}
123
124impl<
125 S,
126 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync,
127 #[cfg(not(feature = "cli"))] Cli: 'static,
128 > Inner<S, Cli>
129where
130 S: 'static + Clone + Send + Sync,
131 AppContext: FromRef<S>,
132{
133 fn new() -> Self {
134 Self {
135 state_provider: Default::default(),
136 tracing_initializer: Default::default(),
137 async_config_source_providers: Default::default(),
138 metadata: Default::default(),
139 metadata_provider: Default::default(),
140 #[cfg(feature = "db-sea-orm")]
141 sea_orm_conn_options: Default::default(),
142 #[cfg(feature = "db-sea-orm")]
143 sea_orm_conn_options_provider: Default::default(),
144 #[cfg(feature = "db-diesel-postgres-pool")]
145 diesel_pg_connection_customizer_provider: Default::default(),
146 #[cfg(feature = "db-diesel-mysql-pool")]
147 diesel_mysql_connection_customizer_provider: Default::default(),
148 #[cfg(feature = "db-diesel-sqlite-pool")]
149 diesel_sqlite_connection_customizer_provider: Default::default(),
150 #[cfg(feature = "db-diesel-postgres-pool-async")]
151 diesel_pg_async_connection_customizer_provider: Default::default(),
152 #[cfg(feature = "db-diesel-mysql-pool-async")]
153 diesel_mysql_async_connection_customizer_provider: Default::default(),
154 #[cfg(feature = "db-sql")]
155 migrator_providers: Default::default(),
156 health_checks: Default::default(),
157 health_check_providers: Default::default(),
158 graceful_shutdown_signal_provider: Default::default(),
159 lifecycle_handler_providers: Default::default(),
160 service_providers: Default::default(),
161 }
162 }
163
164 fn tracing_initializer(
165 &mut self,
166 tracing_initializer: impl 'static + Send + Sync + Fn(&AppConfig) -> RoadsterResult<()>,
167 ) {
168 self.tracing_initializer = Some(Box::new(tracing_initializer));
169 }
170
171 fn add_async_config_source_provider(
172 &mut self,
173 async_config_source_provider: impl 'static
174 + Send
175 + Sync
176 + Fn(&Environment) -> RoadsterResult<Box<dyn AsyncSource + Send + Sync>>,
177 ) {
178 self.async_config_source_providers
179 .push(Box::new(async_config_source_provider));
180 }
181
182 fn set_metadata(&mut self, metadata: AppMetadata) {
183 self.metadata = Some(metadata);
184 }
185
186 fn metadata_provider(
187 &mut self,
188 metadata_provider: impl 'static + Send + Sync + Fn(&AppConfig) -> RoadsterResult<AppMetadata>,
189 ) {
190 self.metadata_provider = Some(Box::new(metadata_provider));
191 }
192
193 #[cfg(feature = "db-sea-orm")]
194 fn sea_orm_conn_options(&mut self, sea_orm_conn_options: ConnectOptions) {
195 self.sea_orm_conn_options = Some(sea_orm_conn_options);
196 }
197
198 #[cfg(feature = "db-sea-orm")]
199 fn sea_orm_conn_options_provider(
200 &mut self,
201 sea_orm_conn_options_provider: impl 'static
202 + Send
203 + Sync
204 + Fn(&AppConfig) -> RoadsterResult<ConnectOptions>,
205 ) {
206 self.sea_orm_conn_options_provider = Some(Box::new(sea_orm_conn_options_provider));
207 }
208
209 fn state_provider(
210 &mut self,
211 builder: impl 'static + Send + Sync + Fn(AppContext) -> RoadsterResult<S>,
212 ) {
213 self.state_provider = Some(Box::new(builder));
214 }
215
216 #[cfg(feature = "db-diesel-postgres-pool")]
217 fn diesel_pg_connection_customizer_provider(
218 &mut self,
219 connection_customizer: impl 'static
220 + Send
221 + Sync
222 + Fn(
223 &AppConfig,
224 ) -> RoadsterResult<
225 Box<dyn r2d2::CustomizeConnection<crate::db::DieselPgConn, diesel::r2d2::Error>>,
226 >,
227 ) {
228 self.diesel_pg_connection_customizer_provider = Some(Box::new(connection_customizer));
229 }
230
231 #[cfg(feature = "db-diesel-mysql-pool")]
232 fn diesel_mysql_connection_customizer_provider(
233 &mut self,
234 connection_customizer: impl 'static
235 + Send
236 + Sync
237 + Fn(
238 &AppConfig,
239 ) -> RoadsterResult<
240 Box<dyn r2d2::CustomizeConnection<crate::db::DieselMysqlConn, diesel::r2d2::Error>>,
241 >,
242 ) {
243 self.diesel_mysql_connection_customizer_provider = Some(Box::new(connection_customizer));
244 }
245
246 #[cfg(feature = "db-diesel-sqlite-pool")]
247 fn diesel_sqlite_connection_customizer_provider(
248 &mut self,
249 connection_customizer: impl 'static
250 + Send
251 + Sync
252 + Fn(
253 &AppConfig,
254 ) -> RoadsterResult<
255 Box<
256 dyn r2d2::CustomizeConnection<crate::db::DieselSqliteConn, diesel::r2d2::Error>,
257 >,
258 >,
259 ) {
260 self.diesel_sqlite_connection_customizer_provider = Some(Box::new(connection_customizer));
261 }
262
263 #[cfg(feature = "db-diesel-postgres-pool-async")]
264 fn diesel_pg_async_connection_customizer_provider(
265 &mut self,
266 connection_customizer: impl 'static
267 + Send
268 + Sync
269 + Fn(
270 &AppConfig,
271 ) -> RoadsterResult<
272 Box<
273 dyn bb8_8::CustomizeConnection<
274 crate::db::DieselPgConnAsync,
275 diesel_async::pooled_connection::PoolError,
276 >,
277 >,
278 >,
279 ) {
280 self.diesel_pg_async_connection_customizer_provider = Some(Box::new(connection_customizer));
281 }
282
283 #[cfg(feature = "db-diesel-mysql-pool-async")]
284 fn diesel_mysql_async_connection_customizer_provider(
285 &mut self,
286 connection_customizer: impl 'static
287 + Send
288 + Sync
289 + Fn(
290 &AppConfig,
291 ) -> RoadsterResult<
292 Box<
293 dyn bb8_8::CustomizeConnection<
294 crate::db::DieselMysqlConnAsync,
295 diesel_async::pooled_connection::PoolError,
296 >,
297 >,
298 >,
299 ) {
300 self.diesel_mysql_async_connection_customizer_provider =
301 Some(Box::new(connection_customizer));
302 }
303
304 #[cfg(feature = "db-sql")]
305 fn add_migrator_provider(
306 &mut self,
307 migrator_provider: impl 'static + Send + Sync + Fn(&S) -> RoadsterResult<Box<dyn Migrator<S>>>,
308 ) {
309 self.migrator_providers.push(Box::new(migrator_provider))
310 }
311
312 fn add_health_check(&mut self, health_check: impl 'static + HealthCheck) {
313 self.health_checks.push(Arc::new(health_check));
314 }
315
316 fn add_health_check_provider(
317 &mut self,
318 health_check_provider: impl 'static
319 + Send
320 + Sync
321 + Fn(&mut HealthCheckRegistry, &S) -> RoadsterResult<()>,
322 ) {
323 self.health_check_providers
324 .push(Box::new(health_check_provider));
325 }
326
327 fn provide_graceful_shutdown_signal(
328 &mut self,
329 graceful_shutdown_signal_provider: impl 'static
330 + Send
331 + Sync
332 + Fn(&S) -> Pin<Box<dyn Send + Future<Output = ()>>>,
333 ) {
334 self.graceful_shutdown_signal_provider = Some(Box::new(graceful_shutdown_signal_provider));
335 }
336
337 fn init_tracing(&self, config: &AppConfig) -> RoadsterResult<()> {
338 if let Some(tracing_initializer) = self.tracing_initializer.as_ref() {
339 tracing_initializer(config)
340 } else {
341 crate::tracing::init_tracing(config, &self.get_metadata(config)?)
342 }
343 }
344
345 fn get_metadata(&self, config: &AppConfig) -> RoadsterResult<AppMetadata> {
346 if let Some(metadata) = self.metadata.as_ref() {
347 Ok(metadata.clone())
348 } else if let Some(metadata_provider) = self.metadata_provider.as_ref() {
349 metadata_provider(config)
350 } else {
351 Ok(Default::default())
352 }
353 }
354
355 #[cfg(feature = "db-sea-orm")]
356 fn sea_orm_connection_options(&self, config: &AppConfig) -> RoadsterResult<ConnectOptions> {
357 if let Some(sea_orm_conn_options) = self.sea_orm_conn_options.as_ref() {
358 Ok(sea_orm_conn_options.clone())
359 } else if let Some(sea_orm_conn_options_provider) =
360 self.sea_orm_conn_options_provider.as_ref()
361 {
362 sea_orm_conn_options_provider(config)
363 } else {
364 Ok(ConnectOptions::from(&config.database))
365 }
366 }
367
368 async fn provide_state(&self, context: AppContext) -> RoadsterResult<S> {
369 let state_provider = self
370 .state_provider
371 .as_ref()
372 .ok_or_else(|| anyhow!("State builder missing"))?;
373 state_provider(context)
374 }
375
376 async fn health_checks(
377 &self,
378 registry: &mut HealthCheckRegistry,
379 state: &S,
380 ) -> RoadsterResult<()> {
381 for health_check in self.health_checks.iter() {
382 registry.register_arc(health_check.clone())?;
383 }
384 for provider in self.health_check_providers.iter() {
385 provider(registry, state)?;
386 }
387 Ok(())
388 }
389
390 async fn graceful_shutdown_signal(&self, state: &S) {
391 if let Some(signal) = self.graceful_shutdown_signal_provider.as_ref() {
392 signal(state).await;
393 } else {
394 let _output: () = future::pending().await;
395 }
396 }
397}
398
399pub struct RoadsterApp<
400 S,
401 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync = Empty,
402 #[cfg(not(feature = "cli"))] Cli: 'static = Empty,
403> where
404 S: Clone + Send + Sync + 'static,
405 AppContext: FromRef<S>,
406{
407 inner: Inner<S, Cli>,
408 async_config_sources: Mutex<Vec<Box<dyn AsyncSource + Send + Sync>>>,
409 #[cfg(feature = "db-sea-orm")]
410 sea_orm_migrator: Mutex<Option<Box<dyn Migrator<S>>>>,
411 #[cfg(feature = "db-diesel")]
412 diesel_migrator: Mutex<Option<Box<dyn Migrator<S>>>>,
413 #[cfg(feature = "db-sql")]
414 migrators: Mutex<Vec<Box<dyn Migrator<S>>>>,
415 #[cfg(feature = "db-diesel-postgres-pool")]
416 diesel_pg_connection_customizer:
417 Mutex<Option<DieselConnectionCustomizer<crate::db::DieselPgConn>>>,
418 #[cfg(feature = "db-diesel-mysql-pool")]
419 diesel_mysql_connection_customizer:
420 Mutex<Option<DieselConnectionCustomizer<crate::db::DieselMysqlConn>>>,
421 #[cfg(feature = "db-diesel-sqlite-pool")]
422 diesel_sqlite_connection_customizer:
423 Mutex<Option<DieselConnectionCustomizer<crate::db::DieselSqliteConn>>>,
424 #[cfg(feature = "db-diesel-postgres-pool-async")]
425 diesel_pg_async_connection_customizer:
426 Mutex<Option<DieselAsyncConnectionCustomizer<crate::db::DieselPgConnAsync>>>,
427 #[cfg(feature = "db-diesel-mysql-pool-async")]
428 diesel_mysql_async_connection_customizer:
429 Mutex<Option<DieselAsyncConnectionCustomizer<crate::db::DieselMysqlConnAsync>>>,
430 lifecycle_handlers: Mutex<LifecycleHandlers<RoadsterApp<S, Cli>, S>>,
433 services: Mutex<Services<RoadsterApp<S, Cli>, S>>,
436}
437
438pub struct RoadsterAppBuilder<
439 S,
440 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync = Empty,
441 #[cfg(not(feature = "cli"))] Cli: 'static = Empty,
442> where
443 S: Clone + Send + Sync + 'static,
444 AppContext: FromRef<S>,
445{
446 inner: Inner<S, Cli>,
447 async_config_sources: Vec<Box<dyn AsyncSource + Send + Sync>>,
448 #[cfg(feature = "db-sea-orm")]
449 sea_orm_migrator: Option<Box<dyn Migrator<S>>>,
450 #[cfg(feature = "db-diesel")]
451 diesel_migrator: Option<Box<dyn Migrator<S>>>,
452 #[cfg(feature = "db-sql")]
453 migrators: Vec<Box<dyn Migrator<S>>>,
454 #[cfg(feature = "db-diesel-postgres-pool")]
455 diesel_pg_connection_customizer: Option<DieselConnectionCustomizer<crate::db::DieselPgConn>>,
456 #[cfg(feature = "db-diesel-mysql-pool")]
457 diesel_mysql_connection_customizer:
458 Option<DieselConnectionCustomizer<crate::db::DieselMysqlConn>>,
459 #[cfg(feature = "db-diesel-sqlite-pool")]
460 diesel_sqlite_connection_customizer:
461 Option<DieselConnectionCustomizer<crate::db::DieselSqliteConn>>,
462 #[cfg(feature = "db-diesel-postgres-pool-async")]
463 diesel_pg_async_connection_customizer:
464 Option<DieselAsyncConnectionCustomizer<crate::db::DieselPgConnAsync>>,
465 #[cfg(feature = "db-diesel-mysql-pool-async")]
466 diesel_mysql_async_connection_customizer:
467 Option<DieselAsyncConnectionCustomizer<crate::db::DieselMysqlConnAsync>>,
468 lifecycle_handlers: LifecycleHandlers<RoadsterApp<S, Cli>, S>,
469 services: Services<RoadsterApp<S, Cli>, S>,
470}
471
472impl<
473 S,
474 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync,
475 #[cfg(not(feature = "cli"))] Cli: 'static,
476 > RoadsterApp<S, Cli>
477where
478 S: Clone + Send + Sync + 'static,
479 AppContext: FromRef<S>,
480{
481 pub fn builder() -> RoadsterAppBuilder<S, Cli> {
483 RoadsterAppBuilder::new()
484 }
485
486 pub async fn run(self) -> RoadsterResult<()> {
491 run::run(self).await?;
492
493 Ok(())
494 }
495}
496
497impl<
498 S,
499 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync,
500 #[cfg(not(feature = "cli"))] Cli: 'static,
501 > Default for RoadsterAppBuilder<S, Cli>
502where
503 S: 'static + Clone + Send + Sync,
504 AppContext: FromRef<S>,
505{
506 fn default() -> Self {
507 Self::new()
508 }
509}
510
511impl<
512 S,
513 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync,
514 #[cfg(not(feature = "cli"))] Cli: 'static,
515 > RoadsterAppBuilder<S, Cli>
516where
517 S: 'static + Clone + Send + Sync,
518 AppContext: FromRef<S>,
519{
520 pub fn new() -> Self {
521 Self {
522 inner: Inner::new(),
523 async_config_sources: Default::default(),
524 #[cfg(feature = "db-sea-orm")]
525 sea_orm_migrator: Default::default(),
526 #[cfg(feature = "db-diesel")]
527 diesel_migrator: Default::default(),
528 #[cfg(feature = "db-sql")]
529 migrators: Default::default(),
530 #[cfg(feature = "db-diesel-postgres-pool")]
531 diesel_pg_connection_customizer: Default::default(),
532 #[cfg(feature = "db-diesel-mysql-pool")]
533 diesel_mysql_connection_customizer: Default::default(),
534 #[cfg(feature = "db-diesel-sqlite-pool")]
535 diesel_sqlite_connection_customizer: Default::default(),
536 #[cfg(feature = "db-diesel-postgres-pool-async")]
537 diesel_pg_async_connection_customizer: Default::default(),
538 #[cfg(feature = "db-diesel-mysql-pool-async")]
539 diesel_mysql_async_connection_customizer: Default::default(),
540 lifecycle_handlers: Default::default(),
541 services: Default::default(),
542 }
543 }
544
545 pub fn add_async_config_source(mut self, source: impl AsyncSource + Send + 'static) -> Self {
548 self.async_config_sources.push(Box::new(source));
549 self
550 }
551
552 pub fn add_async_config_source_provider(
555 mut self,
556 source_provider: impl 'static
557 + Send
558 + Sync
559 + Fn(&Environment) -> RoadsterResult<Box<dyn AsyncSource + Send + Sync>>,
560 ) -> Self {
561 self.inner.add_async_config_source_provider(source_provider);
562 self
563 }
564
565 pub fn tracing_initializer(
567 mut self,
568 tracing_initializer: impl 'static + Send + Sync + Fn(&AppConfig) -> RoadsterResult<()>,
569 ) -> Self {
570 self.inner.tracing_initializer(tracing_initializer);
571 self
572 }
573
574 pub fn metadata(mut self, metadata: AppMetadata) -> Self {
576 self.inner.set_metadata(metadata);
577 self
578 }
579
580 pub fn metadata_provider(
582 mut self,
583 metadata_provider: impl 'static + Send + Sync + Fn(&AppConfig) -> RoadsterResult<AppMetadata>,
584 ) -> Self {
585 self.inner.metadata_provider(metadata_provider);
586 self
587 }
588
589 #[cfg(feature = "db-sea-orm")]
591 pub fn sea_orm_conn_options(mut self, sea_orm_conn_options: ConnectOptions) -> Self {
592 self.inner.sea_orm_conn_options(sea_orm_conn_options);
593 self
594 }
595
596 #[cfg(feature = "db-sea-orm")]
598 pub fn sea_orm_conn_options_provider(
599 mut self,
600 sea_orm_conn_options_provider: impl 'static
601 + Send
602 + Sync
603 + Fn(&AppConfig) -> RoadsterResult<ConnectOptions>,
604 ) -> Self {
605 self.inner
606 .sea_orm_conn_options_provider(sea_orm_conn_options_provider);
607 self
608 }
609
610 #[cfg(feature = "db-diesel-postgres-pool")]
611 pub fn diesel_pg_connection_customizer(
612 mut self,
613 connection_customizer: impl 'static
614 + r2d2::CustomizeConnection<crate::db::DieselPgConn, diesel::r2d2::Error>,
615 ) -> Self {
616 self.diesel_pg_connection_customizer = Some(Box::new(connection_customizer));
617 self
618 }
619
620 #[cfg(feature = "db-diesel-postgres-pool")]
621 pub fn diesel_pg_connection_customizer_provider(
622 mut self,
623 connection_customizer: impl 'static
624 + Send
625 + Sync
626 + Fn(
627 &AppConfig,
628 ) -> RoadsterResult<
629 Box<dyn r2d2::CustomizeConnection<crate::db::DieselPgConn, diesel::r2d2::Error>>,
630 >,
631 ) -> Self {
632 self.inner
633 .diesel_pg_connection_customizer_provider(connection_customizer);
634 self
635 }
636
637 #[cfg(feature = "db-diesel-mysql-pool")]
638 pub fn diesel_mysql_connection_customizer(
639 mut self,
640 connection_customizer: impl 'static
641 + r2d2::CustomizeConnection<crate::db::DieselMysqlConn, diesel::r2d2::Error>,
642 ) -> Self {
643 self.diesel_mysql_connection_customizer = Some(Box::new(connection_customizer));
644 self
645 }
646
647 #[cfg(feature = "db-diesel-mysql-pool")]
648 pub fn diesel_mysql_connection_customizer_provider(
649 mut self,
650 connection_customizer: impl 'static
651 + Send
652 + Sync
653 + Fn(
654 &AppConfig,
655 ) -> RoadsterResult<
656 Box<dyn r2d2::CustomizeConnection<crate::db::DieselMysqlConn, diesel::r2d2::Error>>,
657 >,
658 ) -> Self {
659 self.inner
660 .diesel_mysql_connection_customizer_provider(connection_customizer);
661 self
662 }
663
664 #[cfg(feature = "db-diesel-sqlite-pool")]
665 pub fn diesel_sqlite_connection_customizer(
666 mut self,
667 connection_customizer: impl 'static
668 + r2d2::CustomizeConnection<crate::db::DieselSqliteConn, diesel::r2d2::Error>,
669 ) -> Self {
670 self.diesel_sqlite_connection_customizer = Some(Box::new(connection_customizer));
671 self
672 }
673
674 #[cfg(feature = "db-diesel-sqlite-pool")]
675 pub fn diesel_sqlite_connection_customizer_provider(
676 mut self,
677 connection_customizer: impl 'static
678 + Send
679 + Sync
680 + Fn(
681 &AppConfig,
682 ) -> RoadsterResult<
683 Box<
684 dyn r2d2::CustomizeConnection<crate::db::DieselSqliteConn, diesel::r2d2::Error>,
685 >,
686 >,
687 ) -> Self {
688 self.inner
689 .diesel_sqlite_connection_customizer_provider(connection_customizer);
690 self
691 }
692
693 #[cfg(feature = "db-diesel-postgres-pool-async")]
694 pub fn diesel_pg_async_connection_customizer(
695 mut self,
696 connection_customizer: impl 'static
697 + bb8_8::CustomizeConnection<
698 crate::db::DieselPgConnAsync,
699 diesel_async::pooled_connection::PoolError,
700 >,
701 ) -> Self {
702 self.diesel_pg_async_connection_customizer = Some(Box::new(connection_customizer));
703 self
704 }
705
706 #[cfg(feature = "db-diesel-postgres-pool-async")]
707 pub fn diesel_pg_async_connection_customizer_provider(
708 mut self,
709 connection_customizer: impl 'static
710 + Send
711 + Sync
712 + Fn(
713 &AppConfig,
714 ) -> RoadsterResult<
715 Box<
716 dyn bb8_8::CustomizeConnection<
717 crate::db::DieselPgConnAsync,
718 diesel_async::pooled_connection::PoolError,
719 >,
720 >,
721 >,
722 ) -> Self {
723 self.inner
724 .diesel_pg_async_connection_customizer_provider(connection_customizer);
725 self
726 }
727
728 #[cfg(feature = "db-diesel-mysql-pool-async")]
729 pub fn diesel_mysql_async_connection_customizer(
730 mut self,
731 connection_customizer: impl 'static
732 + bb8_8::CustomizeConnection<
733 crate::db::DieselMysqlConnAsync,
734 diesel_async::pooled_connection::PoolError,
735 >,
736 ) -> Self {
737 self.diesel_mysql_async_connection_customizer = Some(Box::new(connection_customizer));
738 self
739 }
740
741 #[cfg(feature = "db-diesel-mysql-pool-async")]
742 pub fn diesel_mysql_async_connection_customizer_provider(
743 mut self,
744 connection_customizer: impl 'static
745 + Send
746 + Sync
747 + Fn(
748 &AppConfig,
749 ) -> RoadsterResult<
750 Box<
751 dyn bb8_8::CustomizeConnection<
752 crate::db::DieselMysqlConnAsync,
753 diesel_async::pooled_connection::PoolError,
754 >,
755 >,
756 >,
757 ) -> Self {
758 self.inner
759 .diesel_mysql_async_connection_customizer_provider(connection_customizer);
760 self
761 }
762
763 pub fn state_provider(
765 mut self,
766 builder: impl 'static + Send + Sync + Fn(AppContext) -> RoadsterResult<S>,
767 ) -> Self {
768 self.inner.state_provider(builder);
769 self
770 }
771
772 #[cfg(feature = "db-sea-orm")]
778 pub fn sea_orm_migrator(
779 mut self,
780 migrator: impl 'static + Sync + sea_orm_migration::MigratorTrait,
781 ) -> Self {
782 self.sea_orm_migrator = Some(Box::new(
783 crate::db::migration::sea_orm::SeaOrmMigrator::new(migrator),
784 ));
785 self
786 }
787
788 #[cfg(feature = "db-diesel")]
794 pub fn diesel_migrator<C>(
795 mut self,
796 migrator: impl 'static + Send + Sync + diesel::migration::MigrationSource<C::Backend>,
797 ) -> Self
798 where
799 C: 'static
800 + diesel::connection::Connection
801 + Send
802 + diesel_migrations::MigrationHarness<C::Backend>,
803 {
804 self.diesel_migrator = Some(Box::new(
805 crate::db::migration::diesel::DieselMigrator::<C>::new(migrator),
806 ));
807 self
808 }
809
810 #[cfg(feature = "db-sql")]
817 pub fn add_migrator(mut self, migrator: impl Migrator<S> + 'static) -> Self {
818 self.migrators.push(Box::new(migrator));
819 self
820 }
821
822 #[cfg(feature = "db-sql")]
832 pub fn add_migrator_provider(
833 mut self,
834 migrator_provider: impl 'static + Send + Sync + Fn(&S) -> RoadsterResult<Box<dyn Migrator<S>>>,
835 ) -> Self {
836 self.inner.add_migrator_provider(migrator_provider);
837 self
838 }
839
840 pub fn add_lifecycle_handler(
844 mut self,
845 lifecycle_handler: impl 'static + AppLifecycleHandler<RoadsterApp<S, Cli>, S>,
846 ) -> Self {
847 self.lifecycle_handlers.push(Box::new(lifecycle_handler));
848 self
849 }
850
851 pub fn add_lifecycle_handler_provider(
856 mut self,
857 lifecycle_handler_provider: impl 'static
858 + Send
859 + Sync
860 + Fn(&mut LifecycleHandlerRegistry<RoadsterApp<S, Cli>, S>, &S) -> RoadsterResult<()>,
861 ) -> Self {
862 self.inner
863 .lifecycle_handler_providers
864 .push(Box::new(lifecycle_handler_provider));
865 self
866 }
867
868 pub fn add_health_check(mut self, health_check: impl 'static + HealthCheck) -> Self {
872 self.inner.add_health_check(health_check);
873 self
874 }
875
876 pub fn add_health_check_provider(
881 mut self,
882 health_check_provider: impl 'static
883 + Send
884 + Sync
885 + Fn(&mut HealthCheckRegistry, &S) -> RoadsterResult<()>,
886 ) -> Self {
887 self.inner.add_health_check_provider(health_check_provider);
888 self
889 }
890
891 pub fn add_service(
895 mut self,
896 service: impl 'static + AppService<RoadsterApp<S, Cli>, S>,
897 ) -> Self {
898 self.services.push(Box::new(service));
899 self
900 }
901
902 pub fn add_service_provider(
907 mut self,
908 service_provider: impl 'static
909 + Send
910 + Sync
911 + for<'a> Fn(
912 &'a mut ServiceRegistry<RoadsterApp<S, Cli>, S>,
913 &'a S,
914 ) -> Pin<Box<dyn 'a + Send + Future<Output = RoadsterResult<()>>>>,
915 ) -> Self {
916 self.inner
917 .service_providers
918 .push(Box::new(service_provider));
919 self
920 }
921
922 pub fn graceful_shutdown_signal_provider(
924 mut self,
925 graceful_shutdown_signal_provider: impl 'static
926 + Send
927 + Sync
928 + Fn(&S) -> Pin<Box<dyn Send + Future<Output = ()>>>,
929 ) -> Self {
930 self.inner
931 .provide_graceful_shutdown_signal(graceful_shutdown_signal_provider);
932 self
933 }
934
935 pub fn build(self) -> RoadsterApp<S, Cli> {
937 RoadsterApp {
938 inner: self.inner,
939 async_config_sources: Mutex::new(self.async_config_sources),
940 #[cfg(feature = "db-sea-orm")]
941 sea_orm_migrator: Mutex::new(self.sea_orm_migrator),
942 #[cfg(feature = "db-diesel")]
943 diesel_migrator: Mutex::new(self.diesel_migrator),
944 #[cfg(feature = "db-sql")]
945 migrators: Mutex::new(self.migrators),
946 #[cfg(feature = "db-diesel-postgres-pool")]
947 diesel_pg_connection_customizer: Mutex::new(self.diesel_pg_connection_customizer),
948 #[cfg(feature = "db-diesel-mysql-pool")]
949 diesel_mysql_connection_customizer: Mutex::new(self.diesel_mysql_connection_customizer),
950 #[cfg(feature = "db-diesel-sqlite-pool")]
951 diesel_sqlite_connection_customizer: Mutex::new(
952 self.diesel_sqlite_connection_customizer,
953 ),
954 #[cfg(feature = "db-diesel-postgres-pool-async")]
955 diesel_pg_async_connection_customizer: Mutex::new(
956 self.diesel_pg_async_connection_customizer,
957 ),
958 #[cfg(feature = "db-diesel-mysql-pool-async")]
959 diesel_mysql_async_connection_customizer: Mutex::new(
960 self.diesel_mysql_async_connection_customizer,
961 ),
962 lifecycle_handlers: Mutex::new(self.lifecycle_handlers),
963 services: Mutex::new(self.services),
964 }
965 }
966}
967
968#[async_trait]
969impl<
970 S,
971 #[cfg(feature = "cli")] Cli: 'static + clap::Args + RunCommand<RoadsterApp<S, Cli>, S> + Send + Sync,
972 #[cfg(not(feature = "cli"))] Cli: 'static,
973 > App<S> for RoadsterApp<S, Cli>
974where
975 S: Clone + Send + Sync + 'static,
976 AppContext: FromRef<S>,
977{
978 type Cli = Cli;
979
980 fn async_config_sources(
981 &self,
982 environment: &Environment,
983 ) -> RoadsterResult<Vec<Box<dyn AsyncSource + Send + Sync>>> {
984 let mut async_config_sources = self
985 .async_config_sources
986 .lock()
987 .map_err(crate::error::Error::from)?;
988
989 let mut sources: Vec<Box<dyn AsyncSource + Send + Sync>> = Default::default();
990 for source in async_config_sources.drain(..) {
991 sources.push(source);
992 }
993
994 for provider in self.inner.async_config_source_providers.iter() {
995 let source = provider(environment)?;
996 sources.push(source);
997 }
998
999 Ok(sources)
1000 }
1001
1002 fn init_tracing(&self, config: &AppConfig) -> RoadsterResult<()> {
1003 self.inner.init_tracing(config)
1004 }
1005
1006 fn metadata(&self, config: &AppConfig) -> RoadsterResult<AppMetadata> {
1007 self.inner.get_metadata(config)
1008 }
1009
1010 #[cfg(feature = "db-sea-orm")]
1011 fn sea_orm_connection_options(&self, config: &AppConfig) -> RoadsterResult<ConnectOptions> {
1012 self.inner.sea_orm_connection_options(config)
1013 }
1014
1015 async fn provide_state(&self, context: AppContext) -> RoadsterResult<S> {
1016 self.inner.provide_state(context).await
1017 }
1018
1019 #[cfg(feature = "db-diesel-postgres-pool")]
1020 fn diesel_pg_connection_customizer(
1021 &self,
1022 config: &AppConfig,
1023 ) -> RoadsterResult<
1024 Box<dyn r2d2::CustomizeConnection<crate::db::DieselPgConn, diesel::r2d2::Error>>,
1025 > {
1026 let mut connection_customizer = self
1027 .diesel_pg_connection_customizer
1028 .lock()
1029 .map_err(crate::error::Error::from)?;
1030
1031 if let Some(connection_customizer) = connection_customizer.take() {
1032 return Ok(connection_customizer);
1033 }
1034
1035 if let Some(connection_customizer_provider) =
1036 self.inner.diesel_pg_connection_customizer_provider.as_ref()
1037 {
1038 return connection_customizer_provider(config);
1039 };
1040
1041 Ok(Box::new(r2d2::NopConnectionCustomizer))
1042 }
1043
1044 #[cfg(feature = "db-diesel-mysql-pool")]
1045 fn diesel_mysql_connection_customizer(
1046 &self,
1047 config: &AppConfig,
1048 ) -> RoadsterResult<
1049 Box<dyn r2d2::CustomizeConnection<crate::db::DieselMysqlConn, diesel::r2d2::Error>>,
1050 > {
1051 let mut connection_customizer = self
1052 .diesel_mysql_connection_customizer
1053 .lock()
1054 .map_err(crate::error::Error::from)?;
1055
1056 if let Some(connection_customizer) = connection_customizer.take() {
1057 return Ok(connection_customizer);
1058 }
1059
1060 if let Some(connection_customizer_provider) = self
1061 .inner
1062 .diesel_mysql_connection_customizer_provider
1063 .as_ref()
1064 {
1065 return connection_customizer_provider(config);
1066 };
1067
1068 Ok(Box::new(r2d2::NopConnectionCustomizer))
1069 }
1070
1071 #[cfg(feature = "db-diesel-sqlite-pool")]
1072 fn diesel_sqlite_connection_customizer(
1073 &self,
1074 config: &AppConfig,
1075 ) -> RoadsterResult<
1076 Box<dyn r2d2::CustomizeConnection<crate::db::DieselSqliteConn, diesel::r2d2::Error>>,
1077 > {
1078 let mut connection_customizer = self
1079 .diesel_sqlite_connection_customizer
1080 .lock()
1081 .map_err(crate::error::Error::from)?;
1082
1083 if let Some(connection_customizer) = connection_customizer.take() {
1084 return Ok(connection_customizer);
1085 }
1086
1087 if let Some(connection_customizer_provider) = self
1088 .inner
1089 .diesel_sqlite_connection_customizer_provider
1090 .as_ref()
1091 {
1092 return connection_customizer_provider(config);
1093 };
1094
1095 Ok(Box::new(r2d2::NopConnectionCustomizer))
1096 }
1097
1098 #[cfg(feature = "db-diesel-postgres-pool-async")]
1099 fn diesel_pg_async_connection_customizer(
1100 &self,
1101 config: &AppConfig,
1102 ) -> RoadsterResult<
1103 Box<
1104 dyn bb8_8::CustomizeConnection<
1105 crate::db::DieselPgConnAsync,
1106 diesel_async::pooled_connection::PoolError,
1107 >,
1108 >,
1109 > {
1110 let mut connection_customizer = self
1111 .diesel_pg_async_connection_customizer
1112 .lock()
1113 .map_err(crate::error::Error::from)?;
1114
1115 if let Some(connection_customizer) = connection_customizer.take() {
1116 return Ok(connection_customizer);
1117 }
1118
1119 if let Some(connection_customizer_provider) = self
1120 .inner
1121 .diesel_pg_async_connection_customizer_provider
1122 .as_ref()
1123 {
1124 return connection_customizer_provider(config);
1125 };
1126
1127 Ok(Box::new(Empty))
1128 }
1129
1130 #[cfg(feature = "db-diesel-mysql-pool-async")]
1131 fn diesel_mysql_async_connection_customizer(
1132 &self,
1133 config: &AppConfig,
1134 ) -> RoadsterResult<
1135 Box<
1136 dyn bb8_8::CustomizeConnection<
1137 crate::db::DieselMysqlConnAsync,
1138 diesel_async::pooled_connection::PoolError,
1139 >,
1140 >,
1141 > {
1142 let mut connection_customizer = self
1143 .diesel_mysql_async_connection_customizer
1144 .lock()
1145 .map_err(crate::error::Error::from)?;
1146
1147 if let Some(connection_customizer) = connection_customizer.take() {
1148 return Ok(connection_customizer);
1149 }
1150
1151 if let Some(connection_customizer_provider) = self
1152 .inner
1153 .diesel_mysql_async_connection_customizer_provider
1154 .as_ref()
1155 {
1156 return connection_customizer_provider(config);
1157 };
1158
1159 Ok(Box::new(Empty))
1160 }
1161
1162 #[cfg(feature = "db-sql")]
1163 fn migrators(&self, state: &S) -> RoadsterResult<Vec<Box<dyn Migrator<S>>>> {
1164 let mut result = Vec::new();
1165
1166 #[cfg(feature = "db-sea-orm")]
1167 {
1168 let mut sea_orm_migrator = self
1169 .sea_orm_migrator
1170 .lock()
1171 .map_err(crate::error::Error::from)?;
1172 if let Some(sea_orm_migrator) = sea_orm_migrator.take() {
1173 result.push(sea_orm_migrator);
1174 }
1175 }
1176
1177 #[cfg(feature = "db-diesel")]
1178 {
1179 let mut diesel_migrator = self
1180 .diesel_migrator
1181 .lock()
1182 .map_err(crate::error::Error::from)?;
1183 if let Some(diesel_migrator) = diesel_migrator.take() {
1184 result.push(diesel_migrator);
1185 }
1186 }
1187
1188 let mut migrators = self.migrators.lock().map_err(crate::error::Error::from)?;
1189 for migrator in migrators.drain(..) {
1190 result.push(migrator);
1191 }
1192
1193 for migrator_provider in self.inner.migrator_providers.iter() {
1194 result.push(migrator_provider(state)?);
1195 }
1196
1197 Ok(result)
1198 }
1199
1200 async fn lifecycle_handlers(
1201 &self,
1202 registry: &mut LifecycleHandlerRegistry<Self, S>,
1203 state: &S,
1204 ) -> RoadsterResult<()> {
1205 {
1206 let mut lifecycle_handlers = self
1207 .lifecycle_handlers
1208 .lock()
1209 .map_err(crate::error::Error::from)?;
1210 for lifecycle_handler in lifecycle_handlers.drain(..) {
1211 registry.register_boxed(lifecycle_handler)?;
1212 }
1213 }
1214
1215 for provider in self.inner.lifecycle_handler_providers.iter() {
1216 provider(registry, state)?;
1217 }
1218 Ok(())
1219 }
1220
1221 async fn health_checks(
1222 &self,
1223 registry: &mut HealthCheckRegistry,
1224 state: &S,
1225 ) -> RoadsterResult<()> {
1226 self.inner.health_checks(registry, state).await
1227 }
1228
1229 async fn services(
1230 &self,
1231 registry: &mut ServiceRegistry<Self, S>,
1232 state: &S,
1233 ) -> RoadsterResult<()> {
1234 {
1235 let mut services = self.services.lock().map_err(crate::error::Error::from)?;
1236 for service in services.drain(..) {
1237 registry.register_boxed(service)?;
1238 }
1239 }
1240
1241 for provider in self.inner.service_providers.iter() {
1242 provider(registry, state).await?;
1243 }
1244 Ok(())
1245 }
1246
1247 async fn graceful_shutdown_signal(self: Arc<Self>, state: &S) {
1248 self.inner.graceful_shutdown_signal(state).await
1249 }
1250}