1#![doc = include_str!("../README.md")]
2
3use std::{fmt::Debug, sync::Arc};
4
5use url::Url;
6
7#[cfg(feature = "serde")]
8mod ser;
9
10#[derive(Debug, Clone)]
12pub struct Environment(Arc<dyn Env>);
13
14impl std::fmt::Display for Environment {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 f.write_str(self.0.as_ref().as_ref())
17 }
18}
19
20impl Default for Environment {
21 fn default() -> Self {
22 Self::test()
23 }
24}
25
26pub trait IntoEnv {
27 fn into(self) -> Environment;
28}
29
30impl<T: Env> IntoEnv for T {
31 fn into(self) -> Environment {
32 Environment::new(self)
33 }
34}
35
36impl IntoEnv for Environment {
37 fn into(self) -> Environment {
38 self
39 }
40}
41
42impl std::ops::Deref for Environment {
45 type Target = dyn Env;
46
47 fn deref(&self) -> &Self::Target {
48 self.0.as_ref()
49 }
50}
51
52impl Environment {
53 pub fn new<E: Env>(env: E) -> Self {
55 Self(Arc::new(env))
56 }
57
58 pub fn test() -> Self {
60 Self::new(Test)
61 }
62
63 pub fn prod() -> Self {
65 Self::new(Prod)
66 }
67}
68
69#[cfg_attr(
74 feature = "serde",
75 derive(serde::Serialize, serde::Deserialize),
76 serde(transparent)
77)]
78#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct Secret<T>(pub T);
80
81impl<T> From<T> for Secret<T> {
82 fn from(value: T) -> Self {
83 Secret(value)
84 }
85}
86
87impl<T> Secret<T> {
88 pub fn expose(&self) -> &T {
89 &self.0
90 }
91}
92
93impl<T> std::fmt::Display for Secret<T> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 <Self as std::fmt::Debug>::fmt(self, f)
96 }
97}
98
99impl<T> std::fmt::Debug for Secret<T> {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 f.debug_tuple("Secret").field(&"*****").finish()
102 }
103}
104
105pub trait Env: 'static + AsRef<str> + Debug + Send + Sync + Unpin {
107 fn from_str(val: &str) -> Option<Self>
108 where
109 Self: Sized;
110
111 fn fps_host(&self) -> &str;
113
114 fn freedom_entrypoint(&self) -> Url;
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
124pub struct Test;
125
126impl AsRef<str> for Test {
127 fn as_ref(&self) -> &str {
128 "test"
129 }
130}
131
132impl Env for Test {
133 fn from_str(val: &str) -> Option<Self>
134 where
135 Self: Sized,
136 {
137 val.to_ascii_lowercase().eq("test").then_some(Self)
138 }
139
140 fn fps_host(&self) -> &str {
141 "fps.test.atlasground.com"
142 }
143
144 fn freedom_entrypoint(&self) -> Url {
145 Url::parse("https://test-api.atlasground.com/api/").unwrap()
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
151pub struct Prod;
152
153impl AsRef<str> for Prod {
154 fn as_ref(&self) -> &str {
155 "prod"
156 }
157}
158
159impl Env for Prod {
160 fn from_str(val: &str) -> Option<Self>
161 where
162 Self: Sized,
163 {
164 val.to_ascii_lowercase().eq("prod").then_some(Self)
165 }
166
167 fn fps_host(&self) -> &str {
168 "fps.atlasground.com"
169 }
170
171 fn freedom_entrypoint(&self) -> Url {
172 Url::parse("https://api.atlasground.com/api/").unwrap()
173 }
174}
175
176#[derive(Clone, Debug)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub struct Config {
182 environment: Environment,
183 key: String,
184 secret: Secret<String>,
185}
186
187impl PartialEq for Config {
188 fn eq(&self, other: &Self) -> bool {
189 self.environment_str() == other.environment_str()
190 && self.key == other.key
191 && self.secret == other.secret
192 }
193}
194
195#[non_exhaustive]
197#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
198pub enum Error {
199 ParseEnvironment,
201 MissingSecret,
203 MissingKey,
205 MissingEnvironment,
207}
208
209impl std::fmt::Display for Error {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 <Self as std::fmt::Debug>::fmt(self, f)
212 }
213}
214
215impl std::error::Error for Error {}
216
217#[derive(Default)]
219pub struct ConfigBuilder {
220 environment: Option<Environment>,
221 key: Option<String>,
222 secret: Option<Secret<String>>,
223}
224
225impl ConfigBuilder {
226 pub fn new() -> Self {
228 Self::default()
229 }
230
231 pub fn environment_from_env(&mut self) -> Result<&mut Self, Error> {
233 let var = std::env::var(Config::ATLAS_ENV_VAR).map_err(|_| Error::ParseEnvironment)?;
234
235 if let Some(env) = Test::from_str(&var) {
236 return Ok(self.environment(env));
237 }
238 if let Some(env) = Prod::from_str(&var) {
239 return Ok(self.environment(env));
240 }
241
242 Err(Error::ParseEnvironment)
243 }
244
245 pub fn secret_from_env(&mut self) -> Result<&mut Self, Error> {
247 let var = std::env::var(Config::ATLAS_SECRET_VAR).map_err(|_| Error::ParseEnvironment)?;
248
249 self.secret(var);
250 Ok(self)
251 }
252
253 pub fn key_from_env(&mut self) -> Result<&mut Self, Error> {
255 let var = std::env::var(Config::ATLAS_KEY_VAR).map_err(|_| Error::ParseEnvironment)?;
256
257 self.key(var);
258 Ok(self)
259 }
260
261 pub fn environment(&mut self, environment: impl IntoEnv) -> &mut Self {
263 self.environment = Some(environment.into());
264 self
265 }
266
267 pub fn secret(&mut self, secret: impl Into<String>) -> &mut Self {
269 self.secret = Some(Secret(secret.into()));
270 self
271 }
272
273 pub fn key(&mut self, key: impl Into<String>) -> &mut Self {
275 self.key = Some(key.into());
276 self
277 }
278
279 pub fn build(&mut self) -> Result<Config, Error> {
281 let Some(environment) = self.environment.take() else {
282 return Err(Error::MissingEnvironment);
283 };
284 let Some(key) = self.key.take() else {
285 return Err(Error::MissingKey);
286 };
287 let Some(secret) = self.secret.take() else {
288 return Err(Error::MissingSecret);
289 };
290
291 Ok(Config {
292 environment,
293 key,
294 secret,
295 })
296 }
297}
298
299impl Config {
300 pub const ATLAS_ENV_VAR: &'static str = "ATLAS_ENV";
302
303 pub const ATLAS_KEY_VAR: &'static str = "ATLAS_KEY";
305
306 pub const ATLAS_SECRET_VAR: &'static str = "ATLAS_SECRET";
308
309 pub fn builder() -> ConfigBuilder {
324 ConfigBuilder::new()
325 }
326
327 pub fn from_env() -> Result<Self, Error> {
329 Self::builder()
330 .environment_from_env()?
331 .key_from_env()?
332 .secret_from_env()?
333 .build()
334 }
335
336 pub fn new(environment: impl Env, key: impl Into<String>, secret: impl Into<String>) -> Self {
345 let environment = Environment::new(environment);
346
347 Self {
348 environment,
349 key: key.into(),
350 secret: Secret(secret.into()),
351 }
352 }
353
354 pub fn set_environment(&mut self, environment: impl Env) {
365 self.environment = Environment::new(environment);
366 }
367
368 pub fn environment(&self) -> &Environment {
370 &self.environment
371 }
372
373 pub fn environment_str(&self) -> &str {
375 self.environment.as_ref()
376 }
377
378 pub fn expose_secret(&self) -> &str {
384 self.secret.expose()
385 }
386
387 pub fn key(&self) -> &str {
389 &self.key
390 }
391
392 pub fn set_key(&mut self, key: impl Into<String>) {
402 self.key = key.into();
403 }
404
405 pub fn set_secret(&mut self, secret: impl Into<String>) {
415 self.secret = Secret(secret.into());
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[allow(unused)]
424 fn config_is_send() {
425 fn is_send<T: Send>(_foo: T) {}
426
427 let config = Config::from_env().unwrap();
428 is_send(config);
429 }
430
431 #[cfg(feature = "serde")]
432 mod serde {
433 use super::*;
434
435 #[test]
436 fn deserialize_config() {
437 let json = r#"{"key": "foo", "secret": "bar", "environment": "tEsT"}"#;
438 let config: Config = serde_json::from_str(json).unwrap();
439 assert_eq!(config.key(), "foo");
440 assert_eq!(config.expose_secret(), "bar");
441 assert_eq!(config.environment_str(), "test");
442 }
443
444 #[test]
445 fn serialize_config() {
446 let config = Config::builder()
447 .key("foo")
448 .secret("bar")
449 .environment(Test)
450 .build()
451 .unwrap();
452 let value = serde_json::to_value(&config).unwrap();
453 assert_eq!(value.get("key").unwrap().as_str().unwrap(), "foo");
454 assert_eq!(value.get("secret").unwrap().as_str().unwrap(), "bar");
455 assert_eq!(value.get("environment").unwrap().as_str().unwrap(), "test");
456 }
457
458 #[test]
459 fn deserialize_config_prod() {
460 let json = r#"{"key": "foo", "secret": "bar", "environment": "prod"}"#;
461 let config: Config = serde_json::from_str(json).unwrap();
462 assert_eq!(config.key(), "foo");
463 assert_eq!(config.expose_secret(), "bar");
464 assert_eq!(config.environment_str(), "prod");
465 }
466
467 #[test]
468 fn serialize_config_prod() {
469 let config = Config::builder()
470 .key("foo")
471 .secret("bar")
472 .environment(Prod)
473 .build()
474 .unwrap();
475 let value = serde_json::to_value(&config).unwrap();
476 assert_eq!(value.get("key").unwrap().as_str().unwrap(), "foo");
477 assert_eq!(value.get("secret").unwrap().as_str().unwrap(), "bar");
478 assert_eq!(value.get("environment").unwrap().as_str().unwrap(), "prod");
479 }
480 }
481}