spring_boot/config/
env.rs

1use crate::error::{AppError, Result};
2use anyhow::Context;
3use std::{
4    env,
5    ffi::OsStr,
6    io::ErrorKind,
7    path::{Path, PathBuf},
8};
9
10/// App environment
11#[derive(Debug)]
12pub enum Env {
13    /// Development
14    Dev,
15    /// Test
16    Test,
17    /// production
18    Prod,
19}
20
21impl Env {
22    pub fn from_env() -> Self {
23        match env::var("SPRING_ENV") {
24            Ok(var) => Self::from_string(var),
25            Err(_) => Self::Dev,
26        }
27    }
28
29    pub fn from_string<S: Into<String>>(str: S) -> Self {
30        match str.into() {
31            s if s.eq_ignore_ascii_case("dev") => Self::Dev,
32            s if s.eq_ignore_ascii_case("test") => Self::Test,
33            s if s.eq_ignore_ascii_case("prod") => Self::Prod,
34            _ => Self::Dev,
35        }
36    }
37
38    pub(crate) fn get_config_path(&self, path: &Path) -> Result<PathBuf> {
39        let stem = path.file_stem().and_then(OsStr::to_str).unwrap_or("");
40        let ext = path.extension().and_then(OsStr::to_str).unwrap_or("");
41        let canonicalize = path
42            .canonicalize()
43            .with_context(|| format!("canonicalize {:?} failed", path))?;
44        let parent = canonicalize
45            .parent()
46            .ok_or_else(|| AppError::from_io(ErrorKind::NotFound, "config file path not found"))?;
47        Ok(match self {
48            Self::Dev => parent.join(format!("{}-dev.{}", stem, ext)),
49            Self::Test => parent.join(format!("{}-test.{}", stem, ext)),
50            Self::Prod => parent.join(format!("{}-prod.{}", stem, ext)),
51        })
52    }
53}
54
55pub fn init() -> Result<Env> {
56    match dotenvy::dotenv() {
57        Ok(path) => log::debug!(
58            "Loaded the environment variable file under the path: \"{:?}\"",
59            path
60        ),
61        Err(e) => log::debug!("Environment variable file not found: {}", e),
62    }
63
64    Ok(Env::from_env())
65}
66
67mod tests {
68    #[allow(unused_imports)]
69    use super::Env;
70    use crate::error::Result;
71    use std::{fs, path::PathBuf};
72
73    #[test]
74    fn test_get_config_path() -> Result<()> {
75        let temp_dir = tempfile::tempdir()?;
76
77        let foo = temp_dir.path().join("foo.toml");
78        let _ = touch(&foo);
79
80        assert_eq!(
81            Env::from_string("dev").get_config_path(&foo.as_path())?,
82            temp_dir.path().join("foo-dev.toml")
83        );
84
85        assert_eq!(
86            Env::from_string("test").get_config_path(&foo.as_path())?,
87            temp_dir.path().join("foo-test.toml")
88        );
89
90        assert_eq!(
91            Env::from_string("prod").get_config_path(&foo.as_path())?,
92            temp_dir.path().join("foo-prod.toml")
93        );
94
95        assert_eq!(
96            Env::from_string("other").get_config_path(&foo.as_path())?,
97            temp_dir.path().join("foo-dev.toml")
98        );
99
100        Ok(())
101    }
102
103    #[test]
104    fn test_env() -> Result<()> {
105        let temp_dir = tempfile::tempdir()?;
106        let foo = temp_dir.path().join("foo.toml");
107        let _ = touch(&foo);
108
109        std::env::set_var("SPRING_ENV", "dev");
110        assert_eq!(
111            Env::from_env().get_config_path(&foo.as_path())?,
112            temp_dir.path().join("foo-dev.toml")
113        );
114
115        std::env::set_var("SPRING_ENV", "TEST");
116        assert_eq!(
117            Env::from_env().get_config_path(&foo.as_path())?,
118            temp_dir.path().join("foo-test.toml")
119        );
120
121        std::env::set_var("SPRING_ENV", "Prod");
122        assert_eq!(
123            Env::from_env().get_config_path(&foo.as_path())?,
124            temp_dir.path().join("foo-prod.toml")
125        );
126
127        std::env::set_var("SPRING_ENV", "Other");
128        assert_eq!(
129            Env::from_env().get_config_path(&foo.as_path())?,
130            temp_dir.path().join("foo-dev.toml")
131        );
132
133        Ok(())
134    }
135
136    #[allow(dead_code)]
137    fn touch(path: &PathBuf) -> Result<()> {
138        let _ = fs::OpenOptions::new()
139            .truncate(true)
140            .create(true)
141            .write(true)
142            .open(path)?;
143        Ok(())
144    }
145}