Skip to main content

roadster/app/
mod.rs

1pub mod context;
2pub mod metadata;
3mod prepare;
4mod roadster_app;
5mod run;
6#[cfg(feature = "testing")]
7mod test;
8
9/// A default implementation of [`App`] that is customizable via a builder-style API.
10///
11/// See <https://github.com/roadster-rs/roadster/tree/main/examples/app-builder/src/main.rs> for
12/// an example of how to use the [`RoadsterApp`].
13///
14/// The `Cli` type parameter is only required when the using a custom CLI.
15pub use roadster_app::RoadsterApp;
16
17/// Builder-style API to build/customize a [`RoadsterApp`].
18///
19/// See <https://github.com/roadster-rs/roadster/tree/main/examples/app-builder/src/main.rs> for
20/// an example of how to use the [`RoadsterAppBuilder`].
21///
22/// The `Cli` type parameter is only required when the using a custom CLI.
23pub use roadster_app::RoadsterAppBuilder;
24
25pub use prepare::{PrepareOptions, PreparedApp, PreparedAppCli, PreparedAppWithoutCli, prepare};
26pub use run::{run, run_prepared};
27#[cfg(feature = "testing")]
28pub use test::{TestAppState, run_test, run_test_with_result, test_state};
29
30#[cfg(all(test, feature = "cli"))]
31use crate::api::cli::MockTestCli;
32#[cfg(feature = "cli")]
33use crate::api::cli::RunCommand;
34use crate::app::context::extension::ExtensionRegistry;
35use crate::app::metadata::AppMetadata;
36use crate::config::AppConfig;
37use crate::config::environment::Environment;
38#[cfg(feature = "db-sql")]
39use crate::db::migration::Migrator;
40use crate::error::RoadsterResult;
41use crate::health::check::registry::HealthCheckRegistry;
42use crate::lifecycle::registry::LifecycleHandlerRegistry;
43use crate::service::registry::ServiceRegistry;
44use crate::tracing::init_tracing;
45use async_trait::async_trait;
46use axum_core::extract::FromRef;
47use context::AppContext;
48#[cfg(feature = "db-sea-orm")]
49use sea_orm::ConnectOptions;
50use std::future;
51use std::sync::Arc;
52
53#[cfg_attr(all(test, feature = "cli"), mockall::automock(type Cli = MockTestCli<S>;))]
54#[cfg_attr(
55    all(test, not(feature = "cli")),
56    mockall::automock(type Cli = crate::util::empty::Empty;)
57)]
58#[async_trait]
59pub trait App<S>: Send + Sync + Sized
60where
61    S: Clone + Send + Sync + 'static,
62    AppContext: FromRef<S>,
63{
64    #[cfg(feature = "cli")]
65    type Cli: clap::Args + RunCommand<Self, S> + Send + Sync;
66    #[cfg(not(feature = "cli"))]
67    type Cli;
68
69    fn async_config_sources(
70        &self,
71        _environment: &Environment,
72    ) -> RoadsterResult<Vec<Box<dyn config::AsyncSource + Send + Sync>>> {
73        Ok(vec![])
74    }
75
76    fn init_tracing(&self, config: &AppConfig) -> RoadsterResult<()> {
77        init_tracing(config, &self.metadata(config)?)?;
78
79        Ok(())
80    }
81
82    fn metadata(&self, _config: &AppConfig) -> RoadsterResult<AppMetadata> {
83        Ok(Default::default())
84    }
85
86    async fn provide_context_extensions(
87        &self,
88        _config: &AppConfig,
89        _extension_registry: &mut ExtensionRegistry,
90    ) -> RoadsterResult<()> {
91        Ok(())
92    }
93
94    #[cfg(feature = "db-sea-orm")]
95    fn sea_orm_connection_options(&self, config: &AppConfig) -> RoadsterResult<ConnectOptions> {
96        Ok(ConnectOptions::from(&config.database))
97    }
98
99    #[cfg(feature = "db-diesel-pool")]
100    fn diesel_connection_customizer<C>(
101        &self,
102        _config: &AppConfig,
103    ) -> RoadsterResult<Option<Box<dyn r2d2::CustomizeConnection<C, diesel::r2d2::Error>>>>
104    where
105        C: 'static + diesel::connection::Connection + diesel::r2d2::R2D2Connection,
106    {
107        Ok(None)
108    }
109
110    #[cfg(feature = "db-diesel-postgres-pool")]
111    fn diesel_pg_connection_customizer(
112        &self,
113        _config: &AppConfig,
114    ) -> RoadsterResult<
115        Box<dyn r2d2::CustomizeConnection<crate::db::DieselPgConn, diesel::r2d2::Error>>,
116    > {
117        Ok(Box::new(r2d2::NopConnectionCustomizer))
118    }
119
120    #[cfg(feature = "db-diesel-mysql-pool")]
121    fn diesel_mysql_connection_customizer(
122        &self,
123        _config: &AppConfig,
124    ) -> RoadsterResult<
125        Box<dyn r2d2::CustomizeConnection<crate::db::DieselMysqlConn, diesel::r2d2::Error>>,
126    > {
127        Ok(Box::new(r2d2::NopConnectionCustomizer))
128    }
129
130    #[cfg(feature = "db-diesel-sqlite-pool")]
131    fn diesel_sqlite_connection_customizer(
132        &self,
133        _config: &AppConfig,
134    ) -> RoadsterResult<
135        Box<dyn r2d2::CustomizeConnection<crate::db::DieselSqliteConn, diesel::r2d2::Error>>,
136    > {
137        Ok(Box::new(r2d2::NopConnectionCustomizer))
138    }
139
140    #[cfg(feature = "db-diesel-postgres-pool-async")]
141    fn diesel_pg_async_connection_customizer(
142        &self,
143        _config: &AppConfig,
144    ) -> RoadsterResult<
145        Box<
146            dyn bb8::CustomizeConnection<
147                    crate::db::DieselPgConnAsync,
148                    diesel_async::pooled_connection::PoolError,
149                >,
150        >,
151    > {
152        Ok(Box::new(crate::util::empty::Empty))
153    }
154
155    #[cfg(feature = "db-diesel-mysql-pool-async")]
156    fn diesel_mysql_async_connection_customizer(
157        &self,
158        _config: &AppConfig,
159    ) -> RoadsterResult<
160        Box<
161            dyn bb8::CustomizeConnection<
162                    crate::db::DieselMysqlConnAsync,
163                    diesel_async::pooled_connection::PoolError,
164                >,
165        >,
166    > {
167        Ok(Box::new(crate::util::empty::Empty))
168    }
169
170    /// Allows customizing the pool options used for the `sqlx` Postgres connection used for `pgmq`.
171    #[cfg(feature = "worker-pg")]
172    fn worker_pg_sqlx_pool_options(
173        &self,
174        config: &AppConfig,
175    ) -> RoadsterResult<sqlx::pool::PoolOptions<sqlx::Postgres>> {
176        if let Some(pool_config) = config
177            .service
178            .worker
179            .pg
180            .custom
181            .custom
182            .database
183            .as_ref()
184            .and_then(|config| config.pool_config.as_ref())
185        {
186            Ok(pool_config.into())
187        } else {
188            Ok((&config.database).into())
189        }
190    }
191
192    /// Provide the app state that will be used throughout the app. The state can simply be the
193    /// provided [`AppContext`], or a custom type that implements [`FromRef`] to allow Roadster to
194    /// extract its [`AppContext`] when needed.
195    ///
196    /// See the following for more details regarding [`FromRef`]: <https://docs.rs/axum/0.7.5/axum/extract/trait.FromRef.html>
197    async fn provide_state(&self, context: AppContext) -> RoadsterResult<S>;
198
199    /// Note: SeaORM and Diesel migrations expect all of the applied migrations to be available
200    /// to the provided migrator, so multiple SeaORM or Diesel migrators should not be provided
201    /// via this method.
202    #[cfg(feature = "db-sql")]
203    fn migrators(&self, _state: &S) -> RoadsterResult<Vec<Box<dyn Migrator<S>>>> {
204        Ok(Default::default())
205    }
206
207    async fn lifecycle_handlers(
208        &self,
209        _registry: &mut LifecycleHandlerRegistry<Self, S>,
210        _state: &S,
211    ) -> RoadsterResult<()> {
212        Ok(())
213    }
214
215    /// Provide the [`crate::health::check::HealthCheck`]s to use throughout the app.
216    async fn health_checks(
217        &self,
218        _registry: &mut HealthCheckRegistry,
219        _state: &S,
220    ) -> RoadsterResult<()> {
221        Ok(())
222    }
223
224    /// Provide the [`crate::service::Service`]s to run in the app.
225    async fn services(&self, _registry: &mut ServiceRegistry<S>, _state: &S) -> RoadsterResult<()> {
226        Ok(())
227    }
228
229    /// Override to provide a custom shutdown signal. Roadster provides some default shutdown
230    /// signals, but it may be desirable to provide a custom signal in order to, e.g., shutdown the
231    /// server when a particular API is called.
232    async fn graceful_shutdown_signal(self: Arc<Self>, _state: &S) {
233        let _output: () = future::pending().await;
234    }
235}