use crate::error::{Error, Result};
use std::time::Duration;
use url::Url;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Config {
pub(crate) api_key: String,
pub(crate) base_url: Url,
pub(crate) timeout: Duration,
pub(crate) max_retries: u32,
}
impl Config {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
base_url: Url::parse("https://app.flowglad.com/api/v1/").unwrap(),
timeout: Duration::from_secs(30),
max_retries: 3,
}
}
pub fn from_env() -> Result<Self> {
let api_key = std::env::var("FLOWGLAD_API_KEY")
.map_err(|_| Error::Config("FLOWGLAD_API_KEY environment variable not set".into()))?;
Ok(Self::new(api_key))
}
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
pub fn api_key(&self) -> &str {
&self.api_key
}
pub fn base_url(&self) -> &Url {
&self.base_url
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn max_retries(&self) -> u32 {
self.max_retries
}
}
#[derive(Debug, Default)]
pub struct ConfigBuilder {
api_key: Option<String>,
base_url: Option<String>,
timeout: Option<Duration>,
max_retries: Option<u32>,
}
impl ConfigBuilder {
pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = Some(base_url.into());
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = Some(max_retries);
self
}
pub fn build(self) -> Result<Config> {
let api_key = self
.api_key
.ok_or_else(|| Error::Config("API key is required".into()))?;
let base_url = if let Some(url) = self.base_url {
Url::parse(&url)?
} else {
Url::parse("https://app.flowglad.com/api/v1/").unwrap()
};
Ok(Config {
api_key,
base_url,
timeout: self.timeout.unwrap_or_else(|| Duration::from_secs(30)),
max_retries: self.max_retries.unwrap_or(3),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_new() {
let config = Config::new("sk_test_123");
assert_eq!(config.api_key(), "sk_test_123");
assert!(config
.base_url()
.as_str()
.starts_with("https://app.flowglad.com/api/v1"));
assert_eq!(config.timeout(), Duration::from_secs(30));
assert_eq!(config.max_retries(), 3);
}
#[test]
fn test_config_builder() {
let config = Config::builder()
.api_key("sk_test_456")
.timeout(Duration::from_secs(60))
.max_retries(5)
.build()
.unwrap();
assert_eq!(config.api_key(), "sk_test_456");
assert_eq!(config.timeout(), Duration::from_secs(60));
assert_eq!(config.max_retries(), 5);
}
#[test]
fn test_config_builder_custom_url() {
let config = Config::builder()
.api_key("sk_test_789")
.base_url("https://test.example.com")
.build()
.unwrap();
assert_eq!(config.base_url().as_str(), "https://test.example.com/");
}
#[test]
fn test_config_builder_missing_api_key() {
let result = Config::builder().timeout(Duration::from_secs(60)).build();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::Config(_)));
}
}