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 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}