1mod env;
2mod error;
3mod registrar;
4mod utils;
5
6use env::expand_env_variables;
7use serde::de::{DeserializeOwned, IntoDeserializer};
8use std::{env::current_exe, fs, path::Path, str::FromStr, sync::Arc};
9use toml::{Table, Value};
10
11pub use error::ConfigError;
12pub use registrar::*;
13pub use sword_macros::config;
14pub use utils::{ByteConfig, TimeConfig};
15
16const DEFAULT_CONFIG_PATH: &str = "config/config.toml";
17const CONFIG_ENV_VAR: &str = "SWORD_CONFIG_PATH";
18
19#[derive(Debug, Clone, Default)]
27pub struct Config {
28 inner: Arc<Table>,
29}
30
31impl Config {
32 pub fn new() -> Result<Self, ConfigError> {
38 let content = Self::load_config_file(None)?;
39
40 let expanded = expand_env_variables(&content)
41 .map_err(ConfigError::interpolation_error)?;
42
43 Ok(Self {
44 inner: Arc::new(Table::from_str(&expanded)?),
45 })
46 }
47
48 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
54 let content = Self::load_config_file(Some(path.as_ref()))?;
55
56 let expanded = expand_env_variables(&content)
57 .map_err(ConfigError::interpolation_error)?;
58
59 Ok(Self {
60 inner: Arc::new(Table::from_str(&expanded)?),
61 })
62 }
63
64 pub fn get<T: DeserializeOwned + ConfigItem>(&self) -> Result<T, ConfigError> {
68 let key = T::toml_key();
69
70 let Some(config_item) = self.inner.get(key).cloned() else {
71 return Err(ConfigError::key_not_found(key));
72 };
73
74 let value = Value::into_deserializer(config_item);
75
76 Ok(T::deserialize(value)?)
77 }
78
79 pub fn get_or_panic<T: DeserializeOwned + ConfigItem>(&self) -> T {
81 self.get::<T>().unwrap_or_else(|_| {
82 panic!("Failed to load configuration for key '{}'", T::toml_key())
83 })
84 }
85
86 pub fn get_or_default<T: DeserializeOwned + ConfigItem + Default>(&self) -> T {
88 self.get::<T>().unwrap_or_default()
89 }
90
91 fn load_config_file(path: Option<&Path>) -> Result<String, ConfigError> {
92 if let Some(p) = path {
93 if p.exists() {
94 return Ok(fs::read_to_string(p)?);
95 }
96 return Err(ConfigError::FileNotFound);
97 }
98
99 if let Ok(env_path) = std::env::var(CONFIG_ENV_VAR) {
100 let env_path = Path::new(&env_path);
101
102 if env_path.exists() {
103 return Ok(fs::read_to_string(env_path)?);
104 }
105
106 eprintln!(
107 "Warning: {} is set to '{}' but file does not exist. Falling back to default paths.",
108 CONFIG_ENV_VAR,
109 env_path.display()
110 );
111 }
112
113 let default_path = Path::new(DEFAULT_CONFIG_PATH);
114
115 if default_path.exists() {
116 return Ok(fs::read_to_string(default_path)?);
117 }
118
119 Self::load_from_exe_directory()
120 }
121
122 fn load_from_exe_directory() -> Result<String, ConfigError> {
123 let exe_path = current_exe().map_err(|_| ConfigError::FileNotFound)?;
124 let exe_dir = exe_path.parent().ok_or(ConfigError::FileNotFound)?;
125
126 let fallback_path = exe_dir.join(DEFAULT_CONFIG_PATH);
127
128 if !fallback_path.exists() {
129 return Err(ConfigError::FileNotFound);
130 }
131
132 Ok(fs::read_to_string(fallback_path)?)
133 }
134}