Skip to main content

webdav_server/app/
builder.rs

1use std::{path::PathBuf, time::Duration};
2
3use moka::future::Cache;
4use sqlx::SqlitePool;
5
6use crate::{
7    app::{State as AppState, state::UserId},
8    model::session::TokenHash,
9    storage,
10};
11
12/// A builder for constructing [`AppState`].
13///
14/// This is the intended way to create an `AppState` both in `main.rs` and in tests.
15/// Using a builder makes it easy to inject different databases and vault paths
16/// in test environments without modifying the production code path.
17///
18/// # Example
19///
20/// ```rust,no_run
21/// use webdav_server::app::AppStateBuilder;
22///
23/// # let pool = todo!(); // SqlitePool — provided at startup
24/// let state = AppStateBuilder::new()
25///     .vault_path("./vault")
26///     .db(pool)
27///     .build();
28/// ```
29///
30/// [`AppState`]: crate::app::State
31pub struct AppStateBuilder {
32    vault_path: Option<PathBuf>,
33    db: Option<SqlitePool>,
34    session_cache: Option<Cache<TokenHash, UserId>>,
35}
36
37impl AppStateBuilder {
38    /// Creates a new builder with no fields configured.
39    #[must_use]
40    pub const fn new() -> Self {
41        Self {
42            vault_path: None,
43            db: None,
44            session_cache: None,
45        }
46    }
47
48    /// Sets the path to the vault directory where blobs are stored.
49    ///
50    /// This field is required. Calling `build()` without setting it will panic.
51    #[must_use]
52    pub fn vault_path<P>(mut self, path: P) -> Self
53    where
54        P: Into<PathBuf>,
55    {
56        self.vault_path = Some(path.into());
57        self
58    }
59
60    /// Sets the `SqlitePool` for all database operations.
61    ///
62    /// This field is required. Calling `build()` without setting it will panic.
63    #[must_use]
64    pub fn db(mut self, pool: SqlitePool) -> Self {
65        self.db = Some(pool);
66        self
67    }
68
69    /// Optionally provides a pre-configured `moka` session cache.
70    ///
71    /// If not set, `build()` will create a default cache with a 10-minute
72    /// Time-To-Idle (TTI) policy and a maximum capacity of 10,000 entries.
73    /// This default is suitable for most deployments.
74    #[must_use]
75    pub fn session_cache(
76        mut self,
77        session_cache: Cache<TokenHash, UserId>,
78    ) -> Self {
79        self.session_cache = Some(session_cache);
80        self
81    }
82
83    /// Consumes the builder and returns the fully configured `AppState`.
84    ///
85    /// If `session_cache` was not set, a default `moka` cache is created with
86    /// a 10-minute TTI and 10,000-entry capacity.
87    ///
88    /// # Panics
89    /// Panics if `vault_path` or `db` were not configured before calling `build`.
90    #[allow(clippy::expect_used)]
91    #[must_use]
92    pub fn build(self) -> AppState {
93        AppState {
94            storage: storage::Service::new(
95                self.vault_path.expect("FATAL: vault_path is required!"),
96            ),
97            db: self.db.expect("FATAL: database pool is required!"),
98            session_cache: self.session_cache.unwrap_or_else(|| {
99                Cache::builder()
100                    .time_to_idle(Duration::from_mins(10))
101                    .max_capacity(10_000)
102                    .build()
103            }),
104        }
105    }
106}
107
108impl Default for AppStateBuilder {
109    fn default() -> Self {
110        Self::new()
111    }
112}