use std::time::Duration;
use crate::error::{Error, Result};
use crate::isolation::IsolationLevel;
#[derive(Debug, Clone)]
pub struct Config {
pub timeout: Duration,
pub max_connections: usize,
pub max_response_size: usize,
pub isolation: IsolationLevel,
pub security: SecurityConfig,
pub user_agent: String,
pub follow_redirects: bool,
pub max_redirects: u8,
}
impl Default for Config {
fn default() -> Self {
Self {
timeout: Duration::from_secs(30),
max_connections: 10,
max_response_size: 10 * 1024 * 1024,
isolation: IsolationLevel::None,
security: SecurityConfig::default(),
user_agent: "Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0"
.to_string(),
follow_redirects: false,
max_redirects: 5,
}
}
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct SecurityConfig {
pub strip_referer: bool,
pub verify_tls: bool,
pub min_tls_version: TlsVersion,
pub cipher_suites: Vec<String>,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
strip_referer: true,
verify_tls: true,
min_tls_version: TlsVersion::Tls12,
cipher_suites: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsVersion {
Tls12,
Tls13,
}
#[derive(Debug, Clone)]
pub struct ConfigBuilder {
config: Config,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self {
config: Config::default(),
}
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = timeout;
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.config.max_connections = max;
self
}
pub fn max_response_size(mut self, size: usize) -> Self {
self.config.max_response_size = size;
self
}
pub fn isolation(mut self, level: IsolationLevel) -> Self {
self.config.isolation = level;
self
}
pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
self.config.user_agent = ua.into();
self
}
pub fn follow_redirects(mut self, follow: bool) -> Self {
self.config.follow_redirects = follow;
self
}
pub fn max_redirects(mut self, max: u8) -> Self {
self.config.max_redirects = max;
self
}
pub fn strip_referer(mut self, strip: bool) -> Self {
self.config.security.strip_referer = strip;
self
}
pub fn verify_tls(mut self, verify: bool) -> Self {
self.config.security.verify_tls = verify;
self
}
pub fn min_tls_version(mut self, version: TlsVersion) -> Self {
self.config.security.min_tls_version = version;
self
}
pub fn build(self) -> Result<Config> {
if self.config.timeout.is_zero() {
return Err(Error::config("timeout cannot be zero"));
}
if self.config.max_connections == 0 {
return Err(Error::config("max_connections must be at least 1"));
}
if self.config.max_response_size == 0 {
return Err(Error::config("max_response_size must be at least 1"));
}
Ok(self.config)
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.timeout, Duration::from_secs(30));
assert_eq!(config.max_connections, 10);
assert!(!config.follow_redirects);
assert!(config.security.verify_tls);
}
#[test]
fn test_builder() {
let config = Config::builder()
.timeout(Duration::from_secs(60))
.max_connections(20)
.follow_redirects(true)
.build()
.expect("config should be valid");
assert_eq!(config.timeout, Duration::from_secs(60));
assert_eq!(config.max_connections, 20);
assert!(config.follow_redirects);
}
#[test]
fn test_builder_validation() {
let result = Config::builder().max_connections(0).build();
assert!(result.is_err());
}
}