search_in_terminal/core/
config.rs1use anyhow::Result;
2use once_cell::sync::Lazy;
3use serde::Deserialize;
4use std::{
5 fs,
6 path::{Path, PathBuf},
7};
8
9use crate::error::types::ConfigError;
10
11pub static CONFIG: Lazy<Config> =
13 Lazy::new(|| Config::new().expect("Failed to load configuration"));
14
15#[derive(Debug, Deserialize)]
17pub struct CacheConfig {
18 #[serde(default = "default_max_capacity")]
20 pub max_capacity: u64,
21
22 #[serde(default = "default_time_to_live")]
24 pub time_to_live: u64,
25}
26
27impl CacheConfig {
28 fn validate(&self) -> Result<()> {
29 if self.time_to_live == 0 {
30 return Err(anyhow::anyhow!(ConfigError::ValidationError(
31 "Cache time-to-live must be greater than 0".to_string(),
32 )));
33 }
34 Ok(())
35 }
36}
37
38#[derive(Debug, Deserialize)]
40pub struct EngineConfig {
41 #[serde(default = "default_favor")]
43 pub favor: String,
44}
45
46impl EngineConfig {
47 fn validate(&self) -> Result<()> {
48 match self.favor.as_str() {
49 "google" | "bing" | "duckduckgo" => Ok(()),
50 _ => Err(anyhow::anyhow!(ConfigError::ValidationError(
51 "Invalid search engine specified".to_string(),
52 ))),
53 }
54 }
55}
56
57#[derive(Debug, Deserialize)]
59pub struct SearchConfig {
60 #[serde(default = "default_user_agents")]
62 pub user_agents: Vec<String>,
63
64 #[serde(default = "default_max_retries")]
66 pub max_retries: u32,
67
68 #[serde(default = "default_base_delay")]
70 pub base_delay: u64,
71
72 #[serde(default = "default_max_jitter")]
74 pub max_jitter: u64,
75
76 #[serde(default = "default_request_timeout")]
78 pub request_timeout: u64,
79
80 #[serde(default = "default_response_timeout")]
82 pub response_timeout: u64,
83}
84
85impl SearchConfig {
86 fn validate(&self) -> Result<()> {
87 if self.max_retries > 10 {
88 return Err(anyhow::anyhow!(ConfigError::ValidationError(
89 "Max retries should not exceed 10".to_string(),
90 )));
91 }
92 if self.request_timeout == 0 || self.response_timeout == 0 {
93 return Err(anyhow::anyhow!(ConfigError::ValidationError(
94 "Timeouts must be greater than 0".to_string(),
95 )));
96 }
97 Ok(())
98 }
99}
100
101#[derive(Debug, Deserialize, Default)]
103pub struct Config {
104 #[serde(default)]
106 pub search: SearchConfig,
107
108 #[serde(default)]
110 pub cache: CacheConfig,
111
112 #[serde(default)]
114 pub engine: EngineConfig,
115}
116
117impl Config {
118 pub fn new() -> Result<Self> {
132 let config_path = Self::config_path()?;
133 Self::ensure_config_dir(&config_path)?;
134
135 let config = if let Ok(content) = fs::read_to_string(&config_path) {
136 toml::from_str(&content).unwrap_or(Config::default())
137 } else {
138 Config::default()
139 };
140
141 config.validate()?;
142 Ok(config)
143 }
144
145 fn config_path() -> Result<PathBuf> {
147 let config_dir = if cfg!(target_os = "windows") {
148 dirs::config_dir().unwrap_or_else(|| {
149 dirs::home_dir()
150 .expect("Home directory not found")
151 .join("AppData")
152 .join("Roaming")
153 })
154 } else {
155 dirs::home_dir()
156 .ok_or(ConfigError::NoHomeDir)?
157 .join(".config")
158 };
159
160 Ok(config_dir.join("st").join("config.toml"))
161 }
162
163 fn ensure_config_dir(config_path: &Path) -> Result<()> {
165 if let Some(parent) = config_path.parent() {
166 if !parent.exists() {
167 fs::create_dir_all(parent)?;
168 }
169 }
170 Ok(())
171 }
172
173 fn validate(&self) -> Result<()> {
175 self.search.validate()?;
176 self.cache.validate()?;
177 self.engine.validate()?;
178 Ok(())
179 }
180}
181
182fn default_user_agents() -> Vec<String> {
184 vec![
185 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36".to_string(),
186 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36".to_string(),
187 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0".to_string(),
188 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15".to_string(),
189 "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36".to_string(),
190 ]
191}
192
193fn default_max_retries() -> u32 {
195 3
196}
197fn default_base_delay() -> u64 {
198 1000
199}
200fn default_max_jitter() -> u64 {
201 1000
202}
203fn default_request_timeout() -> u64 {
204 10
205}
206fn default_response_timeout() -> u64 {
207 10
208}
209fn default_max_capacity() -> u64 {
210 100
211}
212fn default_time_to_live() -> u64 {
213 600
214}
215fn default_favor() -> String {
216 "google".to_string()
217}
218
219impl Default for SearchConfig {
221 fn default() -> Self {
222 Self {
223 user_agents: default_user_agents(),
224 max_retries: default_max_retries(),
225 base_delay: default_base_delay(),
226 max_jitter: default_max_jitter(),
227 request_timeout: default_request_timeout(),
228 response_timeout: default_response_timeout(),
229 }
230 }
231}
232
233impl Default for CacheConfig {
235 fn default() -> Self {
236 Self {
237 max_capacity: default_max_capacity(),
238 time_to_live: default_time_to_live(),
239 }
240 }
241}
242
243impl Default for EngineConfig {
245 fn default() -> Self {
246 Self {
247 favor: default_favor(),
248 }
249 }
250}