Skip to main content

rws_http/entry_point/
rws_env.rs

1//! `RWS_ENV`: which kind of environment the process runs in — `dev`,
2//! `test` or `prod` (the default). Since 18.28.0.
3//!
4//! Read from the process environment only (no config-file key, no flag):
5//! it says how the rest of the configuration should be treated, so it is
6//! set where the process is started. It decides whether `dev_mcp()`
7//! mounts its tools without a token and, since 18.35.0 (#95), the
8//! defaults that differ between a developer's machine and production:
9//! [`shows_error_details`](RwsEnv::shows_error_details) (the error pages of
10//! `ErrorPages`) and [`reloads_templates`](RwsEnv::reloads_templates)
11//! (templates re-read before each render).
12
13/// The environment kind named by `RWS_ENV`.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum RwsEnv {
16    /// `RWS_ENV=dev` (or `development`): a developer's machine.
17    Dev,
18    /// `RWS_ENV=test`: an automated test run.
19    Test,
20    /// `RWS_ENV=prod` (or `production`), unset, or anything else.
21    #[default]
22    Prod,
23}
24
25impl RwsEnv {
26    /// The environment variable read by [`RwsEnv::current`].
27    pub const VAR: &'static str = "RWS_ENV";
28
29    /// `RWS_ENV` as it is now; unset or unrecognised is [`RwsEnv::Prod`],
30    /// so nothing is opened up by a typo.
31    pub fn current() -> RwsEnv {
32        std::env::var(Self::VAR).ok().and_then(|v| Self::parse(&v)).unwrap_or_default()
33    }
34
35    /// `dev`/`development`, `test`, `prod`/`production` (case-insensitive,
36    /// surrounding spaces ignored); `None` for anything else.
37    pub fn parse(value: &str) -> Option<RwsEnv> {
38        match value.trim().to_ascii_lowercase().as_str() {
39            "dev" | "development" => Some(RwsEnv::Dev),
40            "test" => Some(RwsEnv::Test),
41            "prod" | "production" => Some(RwsEnv::Prod),
42            _ => None,
43        }
44    }
45
46    /// `"dev"`, `"test"` or `"prod"`.
47    pub fn as_str(self) -> &'static str {
48        match self {
49            RwsEnv::Dev => "dev",
50            RwsEnv::Test => "test",
51            RwsEnv::Prod => "prod",
52        }
53    }
54
55    /// `true` for [`RwsEnv::Dev`].
56    pub fn is_dev(self) -> bool {
57        self == RwsEnv::Dev
58    }
59
60    /// Whether errors show their details (the panic message, where it
61    /// happened, the backtrace) to the client: in `dev` and `test`, never in
62    /// `prod` (18.35.0).
63    pub fn shows_error_details(self) -> bool {
64        self != RwsEnv::Prod
65    }
66
67    /// Whether templates are re-read from disk before each render, so an
68    /// edit shows on the next reload of the page: in `dev` (18.35.0).
69    pub fn reloads_templates(self) -> bool {
70        self == RwsEnv::Dev
71    }
72}
73
74impl std::fmt::Display for RwsEnv {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        f.write_str(self.as_str())
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::RwsEnv;
83
84    #[test]
85    fn profile_defaults_per_environment() {
86        assert!(RwsEnv::Dev.shows_error_details() && RwsEnv::Test.shows_error_details() && !RwsEnv::Prod.shows_error_details());
87        assert!(RwsEnv::Dev.reloads_templates() && !RwsEnv::Test.reloads_templates() && !RwsEnv::Prod.reloads_templates());
88    }
89
90    #[test]
91    fn parses_the_three_names_and_their_long_forms() {
92        assert_eq!(RwsEnv::parse("dev"), Some(RwsEnv::Dev));
93        assert_eq!(RwsEnv::parse(" Development "), Some(RwsEnv::Dev));
94        assert_eq!(RwsEnv::parse("TEST"), Some(RwsEnv::Test));
95        assert_eq!(RwsEnv::parse("prod"), Some(RwsEnv::Prod));
96        assert_eq!(RwsEnv::parse("production"), Some(RwsEnv::Prod));
97    }
98
99    #[test]
100    fn anything_else_is_not_an_environment() {
101        assert_eq!(RwsEnv::parse(""), None);
102        assert_eq!(RwsEnv::parse("staging"), None);
103        assert_eq!(RwsEnv::parse("devel"), None);
104    }
105
106    #[test]
107    fn default_is_prod_and_names_round_trip() {
108        assert_eq!(RwsEnv::default(), RwsEnv::Prod);
109        for env in [RwsEnv::Dev, RwsEnv::Test, RwsEnv::Prod] {
110            assert_eq!(RwsEnv::parse(env.as_str()), Some(env));
111            assert_eq!(env.to_string(), env.as_str());
112        }
113        assert!(RwsEnv::Dev.is_dev());
114        assert!(!RwsEnv::Test.is_dev());
115    }
116}