1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34pub enum Region {
35 #[default]
38 ApGuangzhou,
39 ApBeijing,
41 ApNanjing,
43 ApShanghai,
45 ApChengdu,
47 ApChongqing,
49 ApHongkong,
51
52 ApShanghaiFsi,
55 ApShenzhenFsi,
57
58 ApSingapore,
61 ApBangkok,
63 ApTokyo,
65 ApSeoul,
67 ApMumbai,
69 EuFrankfurt,
71 EuMoscow,
73 NaSiliconvalley,
75 NaAshburn,
77 NaToronto,
79}
80
81impl Region {
82 pub fn as_str(&self) -> &'static str {
84 match self {
85 Self::ApGuangzhou => "ap-guangzhou",
87 Self::ApBeijing => "ap-beijing",
88 Self::ApNanjing => "ap-nanjing",
89 Self::ApShanghai => "ap-shanghai",
90 Self::ApChengdu => "ap-chengdu",
91 Self::ApChongqing => "ap-chongqing",
92 Self::ApHongkong => "ap-hongkong",
93 Self::ApShanghaiFsi => "ap-shanghai-fsi",
95 Self::ApShenzhenFsi => "ap-shenzhen-fsi",
96 Self::ApSingapore => "ap-singapore",
98 Self::ApBangkok => "ap-bangkok",
99 Self::ApTokyo => "ap-tokyo",
100 Self::ApSeoul => "ap-seoul",
101 Self::ApMumbai => "ap-mumbai",
102 Self::EuFrankfurt => "eu-frankfurt",
103 Self::EuMoscow => "eu-moscow",
104 Self::NaSiliconvalley => "na-siliconvalley",
105 Self::NaAshburn => "na-ashburn",
106 Self::NaToronto => "na-toronto",
107 }
108 }
109
110 pub fn is_financial(&self) -> bool {
112 matches!(self, Self::ApShanghaiFsi | Self::ApShenzhenFsi)
113 }
114
115 pub fn host(&self) -> &'static str {
117 match self {
118 Self::ApGuangzhou => "sms.ap-guangzhou.tencentcloudapi.com",
120 Self::ApBeijing => "sms.ap-beijing.tencentcloudapi.com",
121 Self::ApNanjing => "sms.ap-nanjing.tencentcloudapi.com",
122 Self::ApShanghai => "sms.ap-shanghai.tencentcloudapi.com",
123 Self::ApChengdu => "sms.ap-chengdu.tencentcloudapi.com",
124 Self::ApChongqing => "sms.ap-chongqing.tencentcloudapi.com",
125 Self::ApHongkong => "sms.ap-hongkong.tencentcloudapi.com",
126 Self::ApShanghaiFsi => "sms.ap-shanghai-fsi.tencentcloudapi.com",
128 Self::ApShenzhenFsi => "sms.ap-shenzhen-fsi.tencentcloudapi.com",
129 Self::ApSingapore => "sms.ap-singapore.tencentcloudapi.com",
131 Self::ApBangkok => "sms.ap-bangkok.tencentcloudapi.com",
132 Self::ApTokyo => "sms.ap-tokyo.tencentcloudapi.com",
133 Self::ApSeoul => "sms.ap-seoul.tencentcloudapi.com",
134 Self::ApMumbai => "sms.ap-mumbai.tencentcloudapi.com",
135 Self::EuFrankfurt => "sms.eu-frankfurt.tencentcloudapi.com",
136 Self::EuMoscow => "sms.eu-moscow.tencentcloudapi.com",
137 Self::NaSiliconvalley => "sms.na-siliconvalley.tencentcloudapi.com",
138 Self::NaAshburn => "sms.na-ashburn.tencentcloudapi.com",
139 Self::NaToronto => "sms.na-toronto.tencentcloudapi.com",
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, Default)]
161pub enum Endpoint {
162 #[default]
164 Nearest,
165 Region,
167 International,
169 Custom(String),
171}
172
173impl Endpoint {
174 pub fn host<'a>(&'a self, region: &Region) -> &'a str {
176 match self {
177 Self::Nearest => "sms.tencentcloudapi.com",
178 Self::Region => region.host(),
180 Self::International => "sms.intl.tencentcloudapi.com",
181 Self::Custom(host) => host.as_str(),
182 }
183 }
184}
185
186#[derive(Debug, Clone, typed_builder::TypedBuilder)]
187pub struct RetryConfig {
188 #[builder(default = 3)]
189 pub max_retries: u64,
190 #[builder(default = Duration::from_millis(100))]
191 pub initial_backoff: Duration,
192 #[builder(default = Duration::from_secs(10))]
193 pub max_backoff: Duration,
194 #[builder(default = 2.0)]
195 pub backoff_multiplier: f64,
196 #[builder(default = true)]
197 pub add_jitter: bool,
198}
199
200impl Default for RetryConfig {
201 fn default() -> Self {
202 Self::builder().build()
203 }
204}
205
206impl RetryConfig {
207 pub fn new() -> Self {
209 Self::default()
210 }
211}
212
213#[derive(Debug, Clone, typed_builder::TypedBuilder)]
240pub struct ClientConfig {
241 #[builder(default)]
246 pub region: Region,
247
248 #[builder(default)]
255 pub endpoint: Endpoint,
256
257 #[builder(default = Duration::from_secs(30))]
259 pub timeout: Duration,
260
261 #[builder(default = Some(RetryConfig::default()))]
263 pub retry: Option<RetryConfig>,
264
265 #[builder(default)]
267 pub proxy: Option<String>,
268
269 #[builder(default = true)]
271 pub is_https: bool,
272}
273
274impl Default for ClientConfig {
275 fn default() -> Self {
276 Self::builder().build()
277 }
278}
279
280impl ClientConfig {
281 pub fn new() -> Self {
283 Self::default()
284 }
285
286 pub fn validate(&self) -> crate::SmsResult<()> {
293 if self.timeout.as_secs() == 0 && self.timeout.subsec_nanos() == 0 {
295 return Err(crate::SmsError::Configuration(
296 "超时时间不能为 0".to_string(),
297 ));
298 }
299
300 if self.region.is_financial() && !matches!(self.endpoint, Endpoint::Region) {
302 return Err(crate::SmsError::Configuration(format!(
303 "金融地域 {:?} 必须使用 Endpoint::Region 接入点,当前使用的是 {:?}",
304 self.region, self.endpoint
305 )));
306 }
307
308 if let Some(retry) = &self.retry {
310 if retry.max_retries == 0 {
311 return Err(crate::SmsError::Configuration(
312 "最大重试次数至少为 1".to_string(),
313 ));
314 }
315
316 if retry.initial_backoff.as_secs() == 0 && retry.initial_backoff.subsec_nanos() == 0 {
317 return Err(crate::SmsError::Configuration(
318 "初始退避时间不能为 0".to_string(),
319 ));
320 }
321
322 if retry.max_backoff < retry.initial_backoff {
323 return Err(crate::SmsError::Configuration(
324 "最大退避时间不能小于初始退避时间".to_string(),
325 ));
326 }
327
328 if retry.backoff_multiplier < 1.0 {
329 return Err(crate::SmsError::Configuration(
330 "退避倍数必须 >= 1.0".to_string(),
331 ));
332 }
333 }
334
335 Ok(())
336 }
337
338 pub fn with_region(region: Region) -> Self {
347 Self::builder().region(region).build()
348 }
349
350 pub fn financial_shanghai() -> Self {
361 Self::builder()
362 .region(Region::ApShanghaiFsi)
363 .endpoint(Endpoint::Region)
364 .build()
365 }
366
367 pub fn financial_shenzhen() -> Self {
378 Self::builder()
379 .region(Region::ApShenzhenFsi)
380 .endpoint(Endpoint::Region)
381 .build()
382 }
383
384 pub fn international() -> Self {
393 Self::builder().endpoint(Endpoint::International).build()
394 }
395
396 pub fn with_timeout(timeout: Duration) -> Self {
406 Self::builder().timeout(timeout).build()
407 }
408
409 pub fn without_retry() -> Self {
418 Self::builder().retry(None).build()
419 }
420
421 pub fn with_retry(retry: RetryConfig) -> Self {
433 Self::builder().retry(Some(retry)).build()
434 }
435
436 pub fn host(&self) -> &str {
438 self.endpoint.host(&self.region)
439 }
440}