1pub mod logger;
2pub mod openapi;
3pub mod server;
4
5use std::{
6 env,
7 ops::Deref,
8 path::{Path, PathBuf},
9 sync::LazyLock,
10};
11
12use config::{ConfigError, File, ValueKind};
13use serde::Deserialize;
14
15use crate::environment::Environment;
16
17#[derive(Clone)]
18pub struct Config {
19 inner: config::Config,
20}
21
22impl Config {
23 pub fn new(config: config::Config) -> Self {
24 Self { inner: config }
25 }
26
27 pub fn load(env: &Environment) -> Result<Self, ConfigError> {
28 static DEFAULT_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
29 let mut dir_path = match env::var("CARGO_MANIFEST_DIR") {
30 Ok(dir) => PathBuf::from(dir),
31 Err(_) => {
32 let binary_file_path =
33 env::args().next().expect("failed to get binary file path");
34
35 Path::new(&binary_file_path)
36 .parent()
37 .expect("failed to get parent directory of binary file")
38 .canonicalize()
39 .expect("failed to get canonical, absolute form of the path to the parent directory of binary file")
40 }
41 };
42
43 dir_path.push("config");
44 dir_path
45 });
46
47 Self::from_folder(env, DEFAULT_FOLDER.as_path())
48 }
49
50 pub fn from_folder(env: &Environment, path: &Path) -> Result<Self, ConfigError> {
51 let app_cfg = path.join("app.toml");
52 let env_cfg = path.join(format!("app-{}.toml", env));
53 let env = config::Environment::default().separator("_");
54
55 let mut builder = config::Config::builder();
56
57 for cfg in [app_cfg, env_cfg].into_iter() {
58 tracing::info!("try to load config `{}`", cfg.display());
59
60 if cfg.exists() {
61 builder = builder.add_source(File::from(cfg))
62 } else {
63 tracing::info!("not found config `{}`", cfg.display());
64 }
65 }
66
67 let config = builder.add_source(env).build()?;
68
69 Ok(Self { inner: config })
70 }
71
72 pub fn is_debug(&self) -> bool {
73 self.inner.get_bool("debug").unwrap_or_default()
74 }
75
76 pub fn get<'de, T>(&self) -> Result<T, ConfigError>
77 where
78 T: ConfigPrefix + Deserialize<'de>,
79 {
80 match self.inner.get::<T>(T::PREFIX) {
81 Ok(o) => Ok(o),
82 Err(e) => {
83 let ConfigError::NotFound(_) = &e else {
84 return Err(e);
85 };
86
87 let v = config::Value::new(None, ValueKind::Table(Default::default()));
88
89 match T::deserialize(v) {
90 Ok(o) => Ok(o),
91 Err(_) => Err(e),
92 }
93 }
94 }
95 }
96}
97
98impl Deref for Config {
99 type Target = config::Config;
100
101 fn deref(&self) -> &Self::Target {
102 &self.inner
103 }
104}
105
106pub trait ConfigPrefix {
107 const PREFIX: &'static str;
108}