1use crate::error::{CosError, Result};
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
8pub struct Config {
9 pub secret_id: String,
11 pub secret_key: String,
13 pub region: String,
15 pub bucket: String,
17 pub timeout: Duration,
19 pub use_https: bool,
21 pub domain: Option<String>,
23 pub app_id: Option<String>,
25}
26
27impl Config {
28 pub fn new<S: Into<String>>(
30 secret_id: S,
31 secret_key: S,
32 region: S,
33 bucket: S,
34 ) -> Self {
35 let bucket_name = bucket.into();
36 let app_id = extract_app_id(&bucket_name);
37
38 Self {
39 secret_id: secret_id.into(),
40 secret_key: secret_key.into(),
41 region: region.into(),
42 bucket: bucket_name,
43 timeout: Duration::from_secs(30),
44 use_https: true,
45 domain: None,
46 app_id,
47 }
48 }
49
50 pub fn with_timeout(mut self, timeout: Duration) -> Self {
52 self.timeout = timeout;
53 self
54 }
55
56 pub fn with_https(mut self, use_https: bool) -> Self {
58 self.use_https = use_https;
59 self
60 }
61
62 pub fn with_domain<S: Into<String>>(mut self, domain: S) -> Self {
64 self.domain = Some(domain.into());
65 self
66 }
67
68 pub fn bucket_url(&self) -> Result<String> {
70 if let Some(ref domain) = self.domain {
71 Ok(format!(
72 "{}://{}",
73 if self.use_https { "https" } else { "http" },
74 domain
75 ))
76 } else {
77 Ok(format!(
78 "{}://{}.cos.{}.myqcloud.com",
79 if self.use_https { "https" } else { "http" },
80 self.bucket,
81 self.region
82 ))
83 }
84 }
85
86 pub fn service_url(&self) -> String {
88 format!(
89 "{}://cos.{}.myqcloud.com",
90 if self.use_https { "https" } else { "http" },
91 self.region
92 )
93 }
94
95 pub fn validate(&self) -> Result<()> {
97 if self.secret_id.is_empty() {
98 return Err(CosError::config("SecretId cannot be empty"));
99 }
100 if self.secret_key.is_empty() {
101 return Err(CosError::config("SecretKey cannot be empty"));
102 }
103 if self.region.is_empty() {
104 return Err(CosError::config("Region cannot be empty"));
105 }
106 if self.bucket.is_empty() {
107 return Err(CosError::config("Bucket cannot be empty"));
108 }
109 Ok(())
110 }
111}
112
113fn extract_app_id(bucket_name: &str) -> Option<String> {
116 bucket_name
117 .rfind('-')
118 .and_then(|pos| {
119 let app_id = &bucket_name[pos + 1..];
120 if app_id.chars().all(|c| c.is_ascii_digit()) && !app_id.is_empty() {
121 Some(app_id.to_string())
122 } else {
123 None
124 }
125 })
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_extract_app_id() {
134 assert_eq!(extract_app_id("mybucket-1234567890"), Some("1234567890".to_string()));
135 assert_eq!(extract_app_id("test-bucket-1234567890"), Some("1234567890".to_string()));
136 assert_eq!(extract_app_id("mybucket"), None);
137 assert_eq!(extract_app_id("mybucket-abc"), None);
138 }
139
140 #[test]
141 fn test_config_validation() {
142 let config = Config::new("id", "key", "region", "bucket-123");
143 assert!(config.validate().is_ok());
144
145 let config = Config::new("", "key", "region", "bucket-123");
146 assert!(config.validate().is_err());
147 }
148}