1#![doc = include_str!("../README.md")]
2
3use std::{fmt::Debug, sync::Arc};
4
5#[cfg(feature = "serde")]
6use serde::Deserialize;
7use url::Url;
8
9#[derive(Debug, Clone)]
11pub struct Environment(Arc<dyn Env>);
12
13impl std::fmt::Display for Environment {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 f.write_str(self.0.as_ref().as_ref())
16 }
17}
18
19impl Default for Environment {
20 fn default() -> Self {
21 Self::test()
22 }
23}
24
25pub trait IntoEnv {
26 fn into(self) -> Environment;
27}
28
29impl<T: Env> IntoEnv for T {
30 fn into(self) -> Environment {
31 Environment::new(self)
32 }
33}
34
35impl IntoEnv for Environment {
36 fn into(self) -> Environment {
37 self
38 }
39}
40
41impl std::ops::Deref for Environment {
44 type Target = dyn Env;
45
46 fn deref(&self) -> &Self::Target {
47 self.0.as_ref()
48 }
49}
50
51impl Environment {
52 pub fn new<E: Env>(env: E) -> Self {
54 Self(Arc::new(env))
55 }
56
57 pub fn test() -> Self {
59 Self::new(Test)
60 }
61
62 pub fn prod() -> Self {
64 Self::new(Prod)
65 }
66}
67
68#[cfg_attr(feature = "serde", derive(Deserialize), serde(transparent))]
73#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
74pub struct Secret<T>(pub T);
75
76impl<T> From<T> for Secret<T> {
77 fn from(value: T) -> Self {
78 Secret(value)
79 }
80}
81
82impl<T> Secret<T> {
83 pub fn expose(&self) -> &T {
84 &self.0
85 }
86}
87
88impl<T> std::fmt::Display for Secret<T> {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 <Self as std::fmt::Debug>::fmt(self, f)
91 }
92}
93
94impl<T> std::fmt::Debug for Secret<T> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.debug_tuple("Secret").field(&"*****").finish()
97 }
98}
99
100pub trait Env: 'static + AsRef<str> + Debug + Send + Sync + Unpin {
102 fn from_str(val: &str) -> Option<Self>
103 where
104 Self: Sized;
105
106 fn fps_host(&self) -> &str;
108
109 fn freedom_entrypoint(&self) -> Url;
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
119pub struct Test;
120
121impl AsRef<str> for Test {
122 fn as_ref(&self) -> &str {
123 "test"
124 }
125}
126
127impl Env for Test {
128 fn from_str(val: &str) -> Option<Self>
129 where
130 Self: Sized,
131 {
132 val.to_ascii_lowercase().eq("test").then_some(Self)
133 }
134
135 fn fps_host(&self) -> &str {
136 "fps.test.atlasground.com"
137 }
138
139 fn freedom_entrypoint(&self) -> Url {
140 Url::parse("https://test-api.atlasground.com/api/").unwrap()
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
146pub struct Prod;
147
148impl AsRef<str> for Prod {
149 fn as_ref(&self) -> &str {
150 "prod"
151 }
152}
153
154impl Env for Prod {
155 fn from_str(val: &str) -> Option<Self>
156 where
157 Self: Sized,
158 {
159 val.to_ascii_lowercase().eq("prod").then_some(Self)
160 }
161
162 fn fps_host(&self) -> &str {
163 "fps.atlasground.com"
164 }
165
166 fn freedom_entrypoint(&self) -> Url {
167 Url::parse("https://api.atlasground.com/api/").unwrap()
168 }
169}
170
171#[derive(Clone, Debug)]
175pub struct Config {
176 environment: Environment,
177 key: String,
178 secret: Secret<String>,
179}
180
181impl PartialEq for Config {
182 fn eq(&self, other: &Self) -> bool {
183 self.environment_str() == other.environment_str()
184 && self.key == other.key
185 && self.secret == other.secret
186 }
187}
188
189#[non_exhaustive]
191#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
192pub enum Error {
193 ParseEnvironment,
195 MissingSecret,
197 MissingKey,
199 MissingEnvironment,
201}
202
203impl std::fmt::Display for Error {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 <Self as std::fmt::Debug>::fmt(self, f)
206 }
207}
208
209impl std::error::Error for Error {}
210
211#[derive(Default)]
213pub struct ConfigBuilder {
214 environment: Option<Environment>,
215 key: Option<String>,
216 secret: Option<Secret<String>>,
217}
218
219impl ConfigBuilder {
220 pub fn new() -> Self {
222 Self::default()
223 }
224
225 pub fn environment_from_env(&mut self) -> Result<&mut Self, Error> {
227 let var = std::env::var(Config::ATLAS_ENV_VAR).map_err(|_| Error::ParseEnvironment)?;
228
229 if let Some(env) = Test::from_str(&var) {
230 return Ok(self.environment(env));
231 }
232 if let Some(env) = Prod::from_str(&var) {
233 return Ok(self.environment(env));
234 }
235
236 Err(Error::ParseEnvironment)
237 }
238
239 pub fn secret_from_env(&mut self) -> Result<&mut Self, Error> {
241 let var = std::env::var(Config::ATLAS_SECRET_VAR).map_err(|_| Error::ParseEnvironment)?;
242
243 self.secret(var);
244 Ok(self)
245 }
246
247 pub fn key_from_env(&mut self) -> Result<&mut Self, Error> {
249 let var = std::env::var(Config::ATLAS_KEY_VAR).map_err(|_| Error::ParseEnvironment)?;
250
251 self.key(var);
252 Ok(self)
253 }
254
255 pub fn environment(&mut self, environment: impl IntoEnv) -> &mut Self {
257 self.environment = Some(environment.into());
258 self
259 }
260
261 pub fn secret(&mut self, secret: impl Into<String>) -> &mut Self {
263 self.secret = Some(Secret(secret.into()));
264 self
265 }
266
267 pub fn key(&mut self, key: impl Into<String>) -> &mut Self {
269 self.key = Some(key.into());
270 self
271 }
272
273 pub fn build(&mut self) -> Result<Config, Error> {
275 let Some(environment) = self.environment.take() else {
276 return Err(Error::MissingEnvironment);
277 };
278 let Some(key) = self.key.take() else {
279 return Err(Error::MissingKey);
280 };
281 let Some(secret) = self.secret.take() else {
282 return Err(Error::MissingSecret);
283 };
284
285 Ok(Config {
286 environment,
287 key,
288 secret,
289 })
290 }
291}
292
293impl Config {
294 pub const ATLAS_ENV_VAR: &'static str = "ATLAS_ENV";
296
297 pub const ATLAS_KEY_VAR: &'static str = "ATLAS_KEY";
299
300 pub const ATLAS_SECRET_VAR: &'static str = "ATLAS_SECRET";
302
303 pub fn builder() -> ConfigBuilder {
318 ConfigBuilder::new()
319 }
320
321 pub fn from_env() -> Result<Self, Error> {
323 Self::builder()
324 .environment_from_env()?
325 .key_from_env()?
326 .secret_from_env()?
327 .build()
328 }
329
330 pub fn new(environment: impl Env, key: impl Into<String>, secret: impl Into<String>) -> Self {
339 let environment = Environment::new(environment);
340
341 Self {
342 environment,
343 key: key.into(),
344 secret: Secret(secret.into()),
345 }
346 }
347
348 pub fn set_environment(&mut self, environment: impl Env) {
359 self.environment = Environment::new(environment);
360 }
361
362 pub fn environment(&self) -> &Environment {
364 &self.environment
365 }
366
367 pub fn environment_str(&self) -> &str {
369 self.environment.as_ref()
370 }
371
372 pub fn expose_secret(&self) -> &str {
378 self.secret.expose()
379 }
380
381 pub fn key(&self) -> &str {
383 &self.key
384 }
385
386 pub fn set_key(&mut self, key: impl Into<String>) {
396 self.key = key.into();
397 }
398
399 pub fn set_secret(&mut self, secret: impl Into<String>) {
409 self.secret = Secret(secret.into());
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[allow(unused)]
418 fn config_is_send() {
419 fn is_send<T: Send>(_foo: T) {}
420
421 let config = Config::from_env().unwrap();
422 is_send(config);
423 }
424}