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}