Skip to main content

rws_server/model/
backend.rs

1//! [`Backend`] — which compiled-in database driver a [`super::DbPool`] uses.
2
3use super::DbError;
4
5/// Selects which compiled-in database backend a [`super::DbPool`] talks to.
6///
7/// Unlike most of this crate's feature flags, `model-sqlite`, `model-postgres`,
8/// and `model-mysql` are not mutually exclusive: enabling more than one in the
9/// same binary compiles a `DbPool` variant for each, so e.g. a hot-path SQLite
10/// pool and an analytics-tier Postgres pool can coexist in one process. Each
11/// `DbPool`/`DbConfig` picks one backend to talk to via this enum.
12///
13/// `Backend` only has variants for the `model-*` features actually compiled
14/// in — matching on it is exhaustive without a wildcard arm, so enabling a
15/// backend and forgetting a match arm for it is a compile error, not a silent
16/// gap.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Backend {
19    #[cfg(feature = "model-sqlite")]
20    Sqlite,
21    #[cfg(feature = "model-postgres")]
22    Postgres,
23    #[cfg(feature = "model-mysql")]
24    MySql,
25}
26
27impl Backend {
28    /// Parse a backend name as read from `RWS_DB_BACKEND`: `"sqlite"`,
29    /// `"postgres"`/`"postgresql"`, or `"mysql"` (case-insensitive). Errors
30    /// (naming the backends actually compiled in) if `s` names a backend that
31    /// either doesn't exist or wasn't compiled in.
32    pub fn parse(s: &str) -> Result<Backend, DbError> {
33        match s.to_ascii_lowercase().as_str() {
34            #[cfg(feature = "model-sqlite")]
35            "sqlite" => Ok(Backend::Sqlite),
36            #[cfg(feature = "model-postgres")]
37            "postgres" | "postgresql" => Ok(Backend::Postgres),
38            #[cfg(feature = "model-mysql")]
39            "mysql" => Ok(Backend::MySql),
40            other => Err(DbError::new(format!(
41                "unknown or not-compiled-in database backend '{}' (compiled in: {})",
42                other,
43                Backend::compiled_in_names(),
44            ))),
45        }
46    }
47
48    /// Lowercase name, e.g. for error messages and logging.
49    pub fn name(&self) -> &'static str {
50        match self {
51            #[cfg(feature = "model-sqlite")]
52            Backend::Sqlite => "sqlite",
53            #[cfg(feature = "model-postgres")]
54            Backend::Postgres => "postgres",
55            #[cfg(feature = "model-mysql")]
56            Backend::MySql => "mysql",
57        }
58    }
59
60    fn compiled_in_names() -> String {
61        let mut names: Vec<&str> = Vec::new();
62        #[cfg(feature = "model-sqlite")]
63        names.push("sqlite");
64        #[cfg(feature = "model-postgres")]
65        names.push("postgres");
66        #[cfg(feature = "model-mysql")]
67        names.push("mysql");
68        names.join(", ")
69    }
70
71    /// `Some(backend)` if exactly one `model-*` feature is compiled in — the
72    /// unambiguous default used by [`super::DbConfig::from_env`] when
73    /// `RWS_DB_BACKEND` isn't set. `None` when more than one is compiled in,
74    /// which requires the caller to say explicitly which one they mean.
75    #[doc(hidden)]
76    pub fn unambiguous_default() -> Option<Backend> {
77        let candidates: &[Backend] = &[
78            #[cfg(feature = "model-sqlite")]
79            Backend::Sqlite,
80            #[cfg(feature = "model-postgres")]
81            Backend::Postgres,
82            #[cfg(feature = "model-mysql")]
83            Backend::MySql,
84        ];
85        if candidates.len() == 1 { Some(candidates[0]) } else { None }
86    }
87}