1use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct TurboCdnConfig {
14 pub general: GeneralConfig,
16 pub performance: PerformanceConfig,
18 pub security: SecurityConfig,
20 pub geo_detection: GeoDetectionConfig,
22 pub testing: TestingConfig,
24 pub url_mapping_rules: Vec<UrlMappingRuleConfig>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct UrlMappingRuleConfig {
31 pub name: String,
33 pub pattern: String,
35 pub replacements: Vec<String>,
37 pub regions: Vec<Region>,
39 pub priority: u32,
41 pub enabled: bool,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct GeneralConfig {
48 pub debug: bool,
50 pub default_region: Region,
52 pub user_agent: String,
54 pub enable_url_cache: bool,
56 pub url_cache_ttl: u64,
58 pub max_cache_entries: usize,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct PerformanceConfig {
65 pub max_concurrent_downloads: usize,
67 pub chunk_size: u64,
69 pub timeout: u64,
71 pub retry_attempts: usize,
73 pub adaptive_chunking: bool,
75 pub pool_max_idle_per_host: usize,
77 pub pool_idle_timeout: u64,
79 pub tcp_keepalive: u64,
81 pub http2_prior_knowledge: bool,
83 pub min_chunk_size: u64,
85 pub max_chunk_size: u64,
87 pub speed_threshold_bytes_per_sec: u64,
89 pub adaptive_concurrency: Option<bool>,
91 pub min_concurrent_downloads: Option<u32>,
93 pub max_concurrent_downloads_limit: Option<u32>,
95 pub network_congestion_threshold: Option<f64>,
97 pub dns_cache_enabled: Option<bool>,
99 pub dns_cache_ttl_seconds: Option<u64>,
101 pub dns_cache_max_entries: Option<usize>,
103 pub smart_chunking_enabled: Option<bool>,
105 pub chunk_performance_history_size: Option<usize>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct SecurityConfig {
112 pub verify_ssl: bool,
114 pub allowed_protocols: Vec<String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct GeoDetectionConfig {
121 pub ip_apis: Vec<String>,
123 pub ip_detection_timeout: u64,
125 pub auto_detect_region: bool,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct TestingConfig {
132 pub test_urls: Vec<String>,
134 pub speed_test_sizes: Vec<u64>,
136}
137
138#[allow(clippy::derivable_impls)]
139impl Default for TurboCdnConfig {
140 fn default() -> Self {
141 Self {
142 general: GeneralConfig::default(),
143 performance: PerformanceConfig::default(),
144 security: SecurityConfig::default(),
145 geo_detection: GeoDetectionConfig::default(),
146 testing: TestingConfig::default(),
147 url_mapping_rules: Vec::new(), }
149 }
150}
151
152impl Default for GeneralConfig {
153 fn default() -> Self {
154 Self {
155 debug: false,
156 default_region: Region::Global,
157 user_agent: format!("turbo-cdn/{}", env!("CARGO_PKG_VERSION")),
158 enable_url_cache: true,
159 url_cache_ttl: 3600, max_cache_entries: 1000,
161 }
162 }
163}
164
165impl Default for PerformanceConfig {
166 fn default() -> Self {
167 Self {
168 max_concurrent_downloads: 32, chunk_size: 1024 * 1024, timeout: 30,
171 retry_attempts: 3,
172 adaptive_chunking: true,
173 pool_max_idle_per_host: 50, pool_idle_timeout: 90,
175 tcp_keepalive: 60,
176 http2_prior_knowledge: true,
177 min_chunk_size: 128 * 1024, max_chunk_size: 5 * 1024 * 1024, speed_threshold_bytes_per_sec: 1024 * 1024, adaptive_concurrency: Some(true),
181 min_concurrent_downloads: Some(8), max_concurrent_downloads_limit: Some(64), network_congestion_threshold: Some(0.3), dns_cache_enabled: Some(true),
185 dns_cache_ttl_seconds: Some(300),
186 dns_cache_max_entries: Some(1000),
187 smart_chunking_enabled: Some(true),
188 chunk_performance_history_size: Some(100),
189 }
190 }
191}
192
193impl Default for SecurityConfig {
194 fn default() -> Self {
195 Self {
196 verify_ssl: true,
197 allowed_protocols: vec!["https".to_string(), "http".to_string()],
198 }
199 }
200}
201
202impl Default for GeoDetectionConfig {
203 fn default() -> Self {
204 Self {
205 ip_apis: vec![
206 "https://ipapi.co/json/".to_string(),
207 "https://ip-api.com/json/".to_string(),
208 "https://ipinfo.io/json".to_string(),
209 "https://api.ipify.org?format=json".to_string(),
210 ],
211 ip_detection_timeout: 5,
212 auto_detect_region: true,
213 }
214 }
215}
216
217impl Default for TestingConfig {
218 fn default() -> Self {
219 Self {
220 test_urls: vec![
221 "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-pc-windows-msvc.zip".to_string(),
222 "https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js".to_string(),
223 ],
224 speed_test_sizes: vec![1048576, 10485760, 104857600], }
226 }
227}
228
229impl TurboCdnConfig {
230 pub fn load() -> Result<Self, toml::de::Error> {
232 let config_content = include_str!("default.toml");
233 toml::from_str(config_content)
234 }
235
236 pub fn load_from_file<P: Into<PathBuf>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
238 let path = path.into();
239 if path.exists() {
240 let file_content = std::fs::read_to_string(&path)?;
241 Ok(toml::from_str(&file_content)?)
242 } else {
243 let config_content = include_str!("default.toml");
244 Ok(toml::from_str(config_content)?)
245 }
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
251pub enum Region {
252 China,
253 Asia,
254 #[default]
255 Global,
256 AsiaPacific,
257 Europe,
258 NorthAmerica,
259 Custom(String),
260}
261
262impl std::fmt::Display for Region {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 match self {
265 Region::China => write!(f, "China"),
266 Region::Asia => write!(f, "Asia"),
267 Region::Global => write!(f, "Global"),
268 Region::AsiaPacific => write!(f, "AsiaPacific"),
269 Region::Europe => write!(f, "Europe"),
270 Region::NorthAmerica => write!(f, "NorthAmerica"),
271 Region::Custom(name) => write!(f, "{name}"),
272 }
273 }
274}
275
276impl std::str::FromStr for Region {
277 type Err = String;
278
279 fn from_str(s: &str) -> Result<Self, Self::Err> {
280 match s {
281 "China" => Ok(Region::China),
282 "Asia" => Ok(Region::Asia),
283 "Global" => Ok(Region::Global),
284 "AsiaPacific" => Ok(Region::AsiaPacific),
285 "Europe" => Ok(Region::Europe),
286 "NorthAmerica" => Ok(Region::NorthAmerica),
287 other => Ok(Region::Custom(other.to_string())),
288 }
289 }
290}