Skip to main content

klauthed_data/
starter.rs

1//! [`DataStarter`] — wire data-layer resources from config into an `AppContext`
2//! (`feature = "sql"`).
3
4use async_trait::async_trait;
5use klauthed_core::config::{Config, DatabaseConfig};
6use klauthed_core::wiring::{AppContext, Starter, StarterError};
7
8/// A [`Starter`] that builds the relational
9/// connection pool from the `database` config section and registers it
10/// ([`sqlx::AnyPool`]) in the [`AppContext`] — so components resolve it with
11/// `ctx.require::<sqlx::AnyPool>()` instead of connecting by hand.
12///
13/// A missing `database` section is a no-op. The configured `system` must be
14/// relational and its driver feature (`postgres` / `mysql` / `sqlite`) enabled.
15/// (Cache / Mongo / messaging wiring is planned; this currently wires the SQL
16/// pool.)
17///
18/// ```no_run
19/// use klauthed_core::wiring::AppBuilder;
20/// use klauthed_data::DataStarter;
21///
22/// # async fn run(config: klauthed_core::config::Config)
23/// #     -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
24/// let ctx = AppBuilder::new(config).with_starter(DataStarter).build().await?;
25/// // Components can now resolve the pool: `ctx.require::<sqlx::AnyPool>()`.
26/// # let _ = ctx;
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Debug, Default, Clone)]
31pub struct DataStarter;
32
33#[async_trait]
34impl Starter for DataStarter {
35    fn name(&self) -> &str {
36        "data"
37    }
38
39    async fn configure(&self, config: &Config, ctx: &mut AppContext) -> Result<(), StarterError> {
40        if let Some(database) = config.get_optional::<DatabaseConfig>("database")? {
41            let pool = crate::db::connect(&database).await?;
42            ctx.register(pool);
43        }
44        Ok(())
45    }
46}
47
48#[cfg(all(test, feature = "sqlite"))]
49mod tests {
50    use super::*;
51    use klauthed_core::config::provider::MemoryProvider;
52    use klauthed_core::config::{ConfigBuilder, Profile};
53    use klauthed_core::wiring::AppBuilder;
54    use serde_json::json;
55
56    #[tokio::test]
57    async fn registers_an_anypool_from_the_database_section() {
58        let config = ConfigBuilder::new(Profile::Test)
59            .with_provider(
60                MemoryProvider::new()
61                    .set("database", json!({ "system": "sqlite", "url": "sqlite::memory:" })),
62            )
63            .build()
64            .await
65            .unwrap();
66
67        let ctx = AppBuilder::new(config).with_starter(DataStarter).build().await.unwrap();
68
69        let pool = ctx.require::<sqlx::AnyPool>().unwrap();
70        assert!(!pool.is_closed());
71    }
72
73    #[tokio::test]
74    async fn no_database_section_is_a_noop() {
75        let config = ConfigBuilder::new(Profile::Test)
76            .with_provider(MemoryProvider::new().set("unrelated", json!(true)))
77            .build()
78            .await
79            .unwrap();
80
81        let ctx = AppBuilder::new(config).with_starter(DataStarter).build().await.unwrap();
82        assert!(ctx.get::<sqlx::AnyPool>().is_none());
83    }
84}