1use crate::error::{LastFmError, Result};
2use std::env;
3use std::time::Duration;
4
5#[derive(Debug, Clone)]
7pub struct RateLimit {
8 pub max_requests: u32,
9 pub per_duration: Duration,
10}
11
12#[derive(Debug, Clone)]
14pub struct Config {
15 pub(crate) api_key: String,
16 pub(crate) user_agent: String,
17 pub(crate) timeout: Duration,
18 pub(crate) max_concurrent_requests: usize,
19 pub(crate) retry_attempts: u32,
20 pub(crate) rate_limit: Option<RateLimit>,
21}
22
23impl Config {
24 #[must_use]
26 pub fn api_key(&self) -> &str {
27 &self.api_key
28 }
29
30 #[must_use]
32 pub fn user_agent(&self) -> &str {
33 &self.user_agent
34 }
35
36 #[must_use]
38 pub fn timeout(&self) -> Duration {
39 self.timeout
40 }
41
42 #[must_use]
44 pub fn max_concurrent_requests(&self) -> usize {
45 self.max_concurrent_requests
46 }
47
48 #[must_use]
50 pub fn retry_attempts(&self) -> u32 {
51 self.retry_attempts
52 }
53
54 #[must_use]
56 pub fn rate_limit(&self) -> Option<&RateLimit> {
57 self.rate_limit.as_ref()
58 }
59}
60
61#[derive(Debug, Default)]
63pub struct ConfigBuilder {
64 api_key: Option<String>,
65 user_agent: Option<String>,
66 timeout: Option<Duration>,
67 max_concurrent_requests: Option<usize>,
68 retry_attempts: Option<u32>,
69 rate_limit: Option<RateLimit>,
70}
71
72impl ConfigBuilder {
73 #[must_use]
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 #[must_use]
88 pub fn api_key(mut self, key: impl Into<String>) -> Self {
89 self.api_key = Some(key.into());
90 self
91 }
92
93 pub fn from_env(mut self) -> Result<Self> {
107 let api_key = env::var("LAST_FM_API_KEY")
108 .map_err(|_| LastFmError::MissingEnvVar("LAST_FM_API_KEY".to_string()))?;
109 self.api_key = Some(api_key);
110 Ok(self)
111 }
112
113 #[must_use]
117 pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
118 self.user_agent = Some(agent.into());
119 self
120 }
121
122 #[must_use]
126 pub fn timeout(mut self, duration: Duration) -> Self {
127 self.timeout = Some(duration);
128 self
129 }
130
131 #[must_use]
135 pub fn max_concurrent_requests(mut self, max: usize) -> Self {
136 self.max_concurrent_requests = Some(max);
137 self
138 }
139
140 #[must_use]
144 pub fn retry_attempts(mut self, attempts: u32) -> Self {
145 self.retry_attempts = Some(attempts);
146 self
147 }
148
149 #[must_use]
161 pub fn rate_limit(mut self, max_requests: u32, per_duration: Duration) -> Self {
162 self.rate_limit = Some(RateLimit {
163 max_requests,
164 per_duration,
165 });
166 self
167 }
168
169 pub fn build(self) -> Result<Config> {
174 let api_key = self.api_key.or_else(|| {
176 env::var("LAST_FM_API_KEY").ok()
177 }).ok_or_else(|| LastFmError::Config(
178 "API key is required. Set it via .api_key() or LAST_FM_API_KEY environment variable".to_string()
179 ))?;
180
181 Ok(Config {
182 api_key,
183 user_agent: self
184 .user_agent
185 .unwrap_or_else(|| format!("async_lastfm/{}", env!("CARGO_PKG_VERSION"))),
186 timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
187 max_concurrent_requests: self.max_concurrent_requests.unwrap_or(5),
188 retry_attempts: self.retry_attempts.unwrap_or(3),
189 rate_limit: self.rate_limit,
190 })
191 }
192
193 pub fn build_with_defaults() -> Result<Config> {
201 Self::new().build()
202 }
203}
204
205pub fn validate_env_vars() -> Result<()> {
210 const REQUIRED_ENV_VARS: &[&str] = &["LAST_FM_API_KEY"];
211
212 let mut missing_vars = Vec::new();
213
214 for var_name in REQUIRED_ENV_VARS {
215 if env::var(var_name).is_err() {
216 missing_vars.push(*var_name);
217 }
218 }
219
220 if !missing_vars.is_empty() {
221 return Err(LastFmError::MissingEnvVar(missing_vars.join(", ")));
222 }
223
224 Ok(())
225}
226
227pub fn get_required_env_var(var_name: &str) -> Result<String> {
232 env::var(var_name).map_err(|_| LastFmError::MissingEnvVar(var_name.to_string()))
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_config_builder() {
241 let config = ConfigBuilder::new()
242 .api_key("test_key")
243 .user_agent("test_agent")
244 .timeout(Duration::from_secs(60))
245 .max_concurrent_requests(10)
246 .retry_attempts(5)
247 .build()
248 .unwrap();
249
250 assert_eq!(config.api_key(), "test_key");
251 assert_eq!(config.user_agent(), "test_agent");
252 assert_eq!(config.timeout(), Duration::from_secs(60));
253 assert_eq!(config.max_concurrent_requests(), 10);
254 assert_eq!(config.retry_attempts(), 5);
255 }
256
257 #[test]
258 fn test_config_builder_defaults() {
259 let config = ConfigBuilder::new().api_key("test_key").build().unwrap();
260
261 assert_eq!(config.api_key(), "test_key");
262 assert!(config.user_agent().starts_with("async_lastfm/"));
263 assert_eq!(config.timeout(), Duration::from_secs(30));
264 assert_eq!(config.max_concurrent_requests(), 5);
265 assert_eq!(config.retry_attempts(), 3);
266 }
267
268 #[test]
269 fn test_config_builder_missing_api_key() {
270 let result = ConfigBuilder::new().build();
271 assert!(result.is_err());
272 assert!(matches!(result.unwrap_err(), LastFmError::Config(_)));
273 }
274
275 #[test]
276 fn test_rate_limit() {
277 let config = ConfigBuilder::new()
278 .api_key("test_key")
279 .rate_limit(10, Duration::from_secs(1))
280 .build()
281 .unwrap();
282
283 let rate_limit = config.rate_limit().unwrap();
284 assert_eq!(rate_limit.max_requests, 10);
285 assert_eq!(rate_limit.per_duration, Duration::from_secs(1));
286 }
287}