dogy_backend_api/
config.rs

1use crate::{Error, Result};
2use dotenv::dotenv;
3use std::env;
4use std::sync::OnceLock;
5use tracing::warn;
6
7pub fn load_config() -> &'static Config {
8    #[cfg(not(test))]
9    dotenv().ok();
10
11    static INSTANCE: OnceLock<Config> = OnceLock::new();
12
13    INSTANCE.get_or_init(|| {
14        Config::from_env().unwrap_or_else(|e| panic!("FATAL: Failed loading env variable. {:?}", e))
15    })
16}
17
18#[allow(non_snake_case)]
19#[derive(Debug, Clone, PartialEq)]
20pub struct Config {
21    pub DATABASE_URL: String,
22    pub LANGGRAPH_ASSISTANT_ENDPOINT: String,
23    pub AZURE_OPENAI_ENDPOINT: String,
24    pub AZURE_OPENAI_KEY: String,
25    pub CLERK_RSA_MODULUS: String,
26    pub CLERK_RSA_EXPONENT: String,
27    pub STORAGE_ACCOUNT: String,
28    pub STORAGE_ACCESS_KEY: String,
29    pub STORAGE_CONTAINER: String,
30    pub PORT: String,
31}
32
33impl Config {
34    fn from_env() -> Result<Self> {
35        Ok(Self {
36            DATABASE_URL: get_env("DATABASE_URL")?,
37            LANGGRAPH_ASSISTANT_ENDPOINT: get_env("LANGGRAPH_ASSISTANT_ENDPOINT")?,
38            AZURE_OPENAI_ENDPOINT: get_env("AZURE_OPENAI_ENDPOINT")?,
39            AZURE_OPENAI_KEY: get_env("AZURE_OPENAI_KEY")?,
40            CLERK_RSA_MODULUS: get_env("CLERK_RSA_MODULUS")?,
41            CLERK_RSA_EXPONENT: get_env("CLERK_RSA_EXPONENT")?,
42            STORAGE_ACCOUNT: get_env("STORAGE_ACCOUNT")?,
43            STORAGE_ACCESS_KEY: get_env("STORAGE_ACCESS_KEY")?,
44            STORAGE_CONTAINER: get_env("STORAGE_CONTAINER")?,
45            PORT: get_env_opt("PORT", "8080"),
46        })
47    }
48}
49
50fn get_env(key: &'static str) -> Result<String> {
51    env::var(key).map_err(|_| Error::ConfigMissingEnv(key))
52}
53
54fn get_env_opt(key: &str, default: &str) -> String {
55    warn!(
56        "Failed loading env variable: {}. Using fallback value: {}",
57        key, default
58    );
59    env::var(key).unwrap_or_else(|_| default.to_string())
60}
61
62#[cfg(test)]
63mod test {
64    use super::*;
65    use std::env;
66
67    #[test]
68    fn test_get_env_ok() {
69        // In Rust 2024 Edition, env::set_var is considered unsafe.
70        unsafe {
71            env::set_var("DATABASE_URL", "test_url");
72        }
73        let database_url = get_env("DATABASE_URL").unwrap();
74        assert_eq!(database_url, "test_url".to_string());
75    }
76
77    #[test]
78    fn test_get_env_config_missing_err() {
79        let database_url = get_env("DATABASE_URL");
80        assert!(
81            matches!(database_url, Err(Error::ConfigMissingEnv("DATABASE_URL"))),
82            "Expected ConfigMissingEnv error"
83        );
84    }
85
86    #[test]
87    fn test_get_env_opt_default() {
88        let database_url = get_env_opt("DATABASE_URL", "test_url");
89        assert_eq!(database_url, "test_url".to_string());
90    }
91
92    #[test]
93    fn test_get_env_opt_override() {
94        unsafe {
95            env::set_var("DATABASE_URL", "override_test_url");
96        }
97        let database_url = get_env_opt("DATABASE_URL", "test_url");
98        assert_eq!(database_url, "override_test_url".to_string());
99    }
100
101    #[test]
102    fn test_config_from_env_ok() {
103        unsafe {
104            env::set_var("DATABASE_URL", "test_db_url");
105            env::set_var("LANGGRAPH_ASSISTANT_ENDPOINT", "test_assistant_url");
106            env::set_var("AZURE_OPENAI_ENDPOINT", "test_azure_openai_endpoint");
107            env::set_var("AZURE_OPENAI_KEY", "test_azure_openai_key");
108            env::set_var("CLERK_RSA_MODULUS", "test_modulus");
109            env::set_var("CLERK_RSA_EXPONENT", "test_exponent");
110            env::set_var("STORAGE_ACCOUNT", "test_storage_acc");
111            env::set_var("STORAGE_ACCESS_KEY", "test_storage_key");
112            env::set_var("STORAGE_CONTAINER", "test_storage_container");
113            env::set_var("STORAGE_URL", "test_storage_url");
114        }
115
116        let config = Config::from_env().unwrap();
117        assert_eq!(
118            config,
119            Config {
120                PORT: "8080".to_string(),
121                CLERK_RSA_EXPONENT: "test_exponent".to_string(),
122                CLERK_RSA_MODULUS: "test_modulus".to_string(),
123                LANGGRAPH_ASSISTANT_ENDPOINT: "test_assistant_url".to_string(),
124                AZURE_OPENAI_ENDPOINT: "test_azure_openai_endpoint".to_string(),
125                AZURE_OPENAI_KEY: "test_azure_openai_key".to_string(),
126                DATABASE_URL: "test_db_url".to_string(),
127                STORAGE_ACCOUNT: "test_storage_acc".to_string(),
128                STORAGE_ACCESS_KEY: "test_storage_key".to_string(),
129                STORAGE_CONTAINER: "test_storage_container".to_string(),
130            }
131        );
132    }
133
134    #[test]
135    fn test_config_from_env_missing_db_url_err() {
136        unsafe {
137            env::set_var("LANGGRAPH_ASSISTANT_ENDPOINT", "test_assistant_url");
138            env::set_var("CLERK_RSA_MODULUS", "test_modulus");
139            env::set_var("CLERK_RSA_EXPONENT", "test_exponent");
140        }
141
142        let config = Config::from_env();
143        assert!(
144            matches!(config, Err(Error::ConfigMissingEnv("DATABASE_URL"))),
145            "Expected ConfigMissingEnv error"
146        );
147    }
148
149    #[test]
150    fn test_load_config_ok() {
151        unsafe {
152            env::set_var("DATABASE_URL", "test_db_url");
153            env::set_var("LANGGRAPH_ASSISTANT_ENDPOINT", "test_assistant_url");
154            env::set_var("AZURE_OPENAI_ENDPOINT", "test_azure_openai_endpoint");
155            env::set_var("AZURE_OPENAI_KEY", "test_azure_openai_key");
156            env::set_var("CLERK_RSA_MODULUS", "test_modulus");
157            env::set_var("CLERK_RSA_EXPONENT", "test_exponent");
158            env::set_var("STORAGE_ACCOUNT", "test_storage_acc");
159            env::set_var("STORAGE_ACCESS_KEY", "test_storage_key");
160            env::set_var("STORAGE_CONTAINER", "test_storage_container");
161        }
162
163        let config = load_config();
164        assert_eq!(
165            config,
166            &Config {
167                PORT: "8080".to_string(),
168                CLERK_RSA_EXPONENT: "test_exponent".to_string(),
169                CLERK_RSA_MODULUS: "test_modulus".to_string(),
170                LANGGRAPH_ASSISTANT_ENDPOINT: "test_assistant_url".to_string(),
171                AZURE_OPENAI_ENDPOINT: "test_azure_openai_endpoint".to_string(),
172                AZURE_OPENAI_KEY: "test_azure_openai_key".to_string(),
173                DATABASE_URL: "test_db_url".to_string(),
174                STORAGE_ACCOUNT: "test_storage_acc".to_string(),
175                STORAGE_ACCESS_KEY: "test_storage_key".to_string(),
176                STORAGE_CONTAINER: "test_storage_container".to_string(),
177            }
178        );
179    }
180
181    #[test]
182    #[should_panic]
183    fn test_load_config_panic() {
184        unsafe {
185            env::set_var("LANGGRAPH_ASSISTANT_ENDPOINT", "test_assistant_url");
186            env::set_var("CLERK_RSA_MODULUS", "test_modulus");
187            env::set_var("CLERK_RSA_EXPONENT", "test_exponent");
188        }
189
190        let _ = load_config();
191    }
192}