Skip to main content

serenade_kernel/
environment.rs

1//! Runtime environment.
2
3use std::fmt::{Display, Formatter};
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8use crate::KernelError;
9
10/// Runtime environment for a Serenade application.
11///
12/// Well-known values are [`Self::Dev`], [`Self::Test`], and [`Self::Prod`].
13/// Any other non-empty name becomes [`Self::Custom`] (for example `staging` or
14/// `recette`), matching Symfony's free-form `APP_ENV` habit.
15#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
16#[serde(try_from = "String", into = "String")]
17pub enum Environment {
18    /// Local development.
19    Dev,
20    /// Automated tests.
21    Test,
22    /// Production.
23    Prod,
24    /// Application-defined environment (`staging`, `recette`, …).
25    Custom(String),
26}
27
28impl Environment {
29    /// Returns whether this environment enables debug by default.
30    ///
31    /// Only [`Self::Dev`] and [`Self::Test`] are debug. [`Self::Prod`] and
32    /// [`Self::Custom`] are not; override with [`crate::Kernel::with_debug`].
33    #[must_use]
34    pub const fn is_debug(&self) -> bool {
35        matches!(self, Self::Dev | Self::Test)
36    }
37
38    /// Parses an environment name (ASCII case-insensitive).
39    ///
40    /// `dev`, `test`, and `prod` map to the well-known variants. Any other
41    /// non-empty name becomes [`Self::Custom`] with a lowercase value.
42    ///
43    /// # Errors
44    ///
45    /// Returns [`KernelError::UnknownEnvironment`] when `name` is empty after trim.
46    pub fn from_name(name: &str) -> Result<Self, KernelError> {
47        let trimmed = name.trim();
48        if trimmed.is_empty() {
49            return Err(KernelError::UnknownEnvironment(name.to_owned()));
50        }
51        let lower = trimmed.to_ascii_lowercase();
52        Ok(match lower.as_str() {
53            "dev" => Self::Dev,
54            "test" => Self::Test,
55            "prod" => Self::Prod,
56            _ => Self::Custom(lower),
57        })
58    }
59
60    /// Stable lowercase name used on the wire and in logs.
61    #[must_use]
62    pub fn as_str(&self) -> &str {
63        match self {
64            Self::Dev => "dev",
65            Self::Test => "test",
66            Self::Prod => "prod",
67            Self::Custom(name) => name.as_str(),
68        }
69    }
70}
71
72impl Display for Environment {
73    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
74        f.write_str(self.as_str())
75    }
76}
77
78impl FromStr for Environment {
79    type Err = KernelError;
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        Self::from_name(s)
83    }
84}
85
86impl From<Environment> for String {
87    fn from(value: Environment) -> Self {
88        value.to_string()
89    }
90}
91
92impl TryFrom<String> for Environment {
93    type Error = KernelError;
94
95    fn try_from(value: String) -> Result<Self, Self::Error> {
96        Self::from_name(&value)
97    }
98}