• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tu6ge / oss-rs / 5911409565

19 Aug 2023 12:34PM UTC coverage: 94.821% (-0.1%) from 94.939%
5911409565

push

github

tu6ge
fix(test)

7268 of 7665 relevant lines covered (94.82%)

9.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.76
/src/types.rs
1
//! lib 内置类型的定义模块
36✔
2

3
use std::{
4
    borrow::Cow,
5
    error::Error,
6
    fmt::{self, Debug, Display, Formatter},
7
    str::FromStr,
8
};
9

10
use chrono::{DateTime, TimeZone};
11
use http::header::{HeaderValue, InvalidHeaderValue, ToStrError};
12
use url::Url;
13

14
#[cfg(feature = "core")]
15
pub mod core;
16

17
pub mod object;
18
#[cfg(test)]
19
mod test;
20

21
#[cfg(feature = "core")]
22
use self::core::IntoQuery;
23
use crate::consts::{TRUE1, TRUE2, TRUE3, TRUE4};
24

25
#[cfg(feature = "core")]
26
pub use self::core::{ContentRange, Query, QueryKey, QueryValue, SetOssQuery};
27
use self::object::{ObjectPathInner, SetObjectPath};
28

29
const OSS_DOMAIN_PREFIX: &str = "https://oss-";
30
#[allow(dead_code)]
31
const OSS_INTERNAL: &str = "-internal";
32
const OSS_DOMAIN_MAIN: &str = ".aliyuncs.com";
33
const OSS_HYPHEN: &str = "oss-";
34

35
const HANGZHOU: &str = "cn-hangzhou";
36
const SHANGHAI: &str = "cn-shanghai";
37
const QINGDAO: &str = "cn-qingdao";
38
const BEIJING: &str = "cn-beijing";
39
const ZHANGJIAKOU: &str = "cn-zhangjiakou";
40
const HONGKONG: &str = "cn-hongkong";
41
const SHENZHEN: &str = "cn-shenzhen";
42
const US_WEST1: &str = "us-west-1";
43
const US_EAST1: &str = "us-east-1";
44
const AP_SOUTH_EAST1: &str = "ap-southeast-1";
45

46
const HANGZHOU_L: &str = "hangzhou";
47
const SHANGHAI_L: &str = "shanghai";
48
const QINGDAO_L: &str = "qingdao";
49
const BEIJING_L: &str = "beijing";
50
const ZHANGJIAKOU_L: &str = "zhangjiakou";
51
const HONGKONG_L: &str = "hongkong";
52
const SHENZHEN_L: &str = "shenzhen";
53

54
const COM: &str = "com";
55
const ALIYUNCS: &str = "aliyuncs";
56

57
/// 阿里云 OSS 的签名 key
58
#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)]
482✔
59
pub struct InnerKeyId<'a>(Cow<'a, str>);
241✔
60

61
/// 静态作用域的 InnerKeyId
62
pub type KeyId = InnerKeyId<'static>;
63

64
impl AsRef<str> for InnerKeyId<'_> {
65
    fn as_ref(&self) -> &str {
162✔
66
        &self.0
162✔
67
    }
162✔
68
}
69

70
impl Display for InnerKeyId<'_> {
71
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
72
        write!(f, "{}", self.0)
2✔
73
    }
2✔
74
}
75

76
impl TryInto<HeaderValue> for InnerKeyId<'_> {
77
    type Error = InvalidHeaderValue;
78
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
79
        HeaderValue::from_str(self.as_ref())
1✔
80
    }
1✔
81
}
82

83
impl From<String> for KeyId {
84
    fn from(s: String) -> KeyId {
22✔
85
        Self(Cow::Owned(s))
22✔
86
    }
22✔
87
}
88

89
impl<'a: 'b, 'b> From<&'a str> for InnerKeyId<'b> {
90
    fn from(key_id: &'a str) -> Self {
102✔
91
        Self(Cow::Borrowed(key_id))
102✔
92
    }
102✔
93
}
94

95
impl<'a> InnerKeyId<'a> {
96
    /// Creates a new `KeyId` from the given string.
97
    pub fn new(key_id: impl Into<Cow<'a, str>>) -> Self {
1✔
98
        Self(key_id.into())
1✔
99
    }
1✔
100

101
    /// Const function that creates a new `KeyId` from a static str.
102
    pub const fn from_static(key_id: &'static str) -> Self {
1✔
103
        Self(Cow::Borrowed(key_id))
1✔
104
    }
1✔
105
}
106

107
//===================================================================================================
108

109
/// 阿里云 OSS 的签名 secret
110
#[derive(Clone, PartialEq, Eq, Default, Hash)]
478✔
111
pub struct InnerKeySecret<'a>(Cow<'a, str>);
239✔
112

113
/// 静态作用域的 InnerKeySecret
114
pub type KeySecret = InnerKeySecret<'static>;
115

116
impl Display for InnerKeySecret<'_> {
117
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
118
        write!(f, "******secret******")
2✔
119
    }
2✔
120
}
121

122
impl Debug for InnerKeySecret<'_> {
123
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
124
        f.debug_tuple("KeySecret").finish()
2✔
125
    }
2✔
126
}
127

128
impl From<String> for KeySecret {
129
    fn from(s: String) -> Self {
22✔
130
        Self(Cow::Owned(s))
22✔
131
    }
22✔
132
}
133

134
impl<'a: 'b, 'b> From<&'a str> for InnerKeySecret<'b> {
135
    fn from(secret: &'a str) -> Self {
98✔
136
        Self(Cow::Borrowed(secret))
98✔
137
    }
98✔
138
}
139

140
impl<'a> InnerKeySecret<'a> {
141
    /// Creates a new `KeySecret` from the given string.
142
    pub fn new(secret: impl Into<Cow<'a, str>>) -> Self {
3✔
143
        Self(secret.into())
3✔
144
    }
3✔
145

146
    /// Const function that creates a new `KeySecret` from a static str.
147
    pub const fn from_static(secret: &'static str) -> Self {
1✔
148
        Self(Cow::Borrowed(secret))
1✔
149
    }
1✔
150

151
    #[cfg(test)]
152
    pub(crate) fn as_str(&self) -> &str {
8✔
153
        &self.0
8✔
154
    }
8✔
155

156
    /// 加密 String 数据
157
    #[inline]
158
    pub fn encryption_string(
7✔
159
        &self,
160
        string: String,
161
    ) -> Result<String, hmac::digest::crypto_common::InvalidLength> {
162
        self.encryption(string.as_bytes())
7✔
163
    }
7✔
164

165
    /// # 加密数据
166
    /// 这种加密方式可保证秘钥明文只会存在于 `InnerKeySecret` 类型内,不会被读取或复制
167
    pub fn encryption(
81✔
168
        &self,
169
        data: &[u8],
170
    ) -> Result<String, hmac::digest::crypto_common::InvalidLength> {
171
        use base64::engine::general_purpose::STANDARD;
172
        use base64::Engine;
173
        use hmac::{Hmac, Mac};
174
        use sha1::Sha1;
175
        type HmacSha1 = Hmac<Sha1>;
176

177
        let secret = self.0.as_bytes();
81✔
178

179
        let mut mac = HmacSha1::new_from_slice(secret)?;
81✔
180

181
        mac.update(data);
81✔
182

183
        let sha1 = mac.finalize().into_bytes();
81✔
184

185
        Ok(STANDARD.encode(sha1))
81✔
186
    }
81✔
187
}
188

189
//===================================================================================================
190

191
/// # OSS 的可用区
192
/// [aliyun docs](https://help.aliyun.com/document_detail/31837.htm)
193
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
470✔
194
#[non_exhaustive]
195
pub struct EndPoint {
196
    pub(crate) kind: EndPointKind,
231✔
197
    /// default false
198
    pub(crate) is_internal: bool,
235✔
199
}
200

201
impl EndPoint {
202
    /// 杭州
203
    pub const CN_HANGZHOU: Self = Self {
204
        kind: EndPointKind::CnHangzhou,
205
        is_internal: false,
206
    };
207
    /// 杭州
208
    pub const HANGZHOU: Self = Self::CN_HANGZHOU;
209

210
    /// 上海
211
    pub const CN_SHANGHAI: Self = Self {
212
        kind: EndPointKind::CnShanghai,
213
        is_internal: false,
214
    };
215
    /// 上海
216
    pub const SHANGHAI: Self = Self::CN_SHANGHAI;
217

218
    /// 青岛
219
    pub const CN_QINGDAO: Self = Self {
220
        kind: EndPointKind::CnQingdao,
221
        is_internal: false,
222
    };
223
    /// 青岛
224
    pub const QINGDAO: Self = Self::CN_QINGDAO;
225

226
    /// 北京
227
    pub const CN_BEIJING: Self = Self {
228
        kind: EndPointKind::CnBeijing,
229
        is_internal: false,
230
    };
231
    /// 北京
232
    pub const BEIJING: Self = Self::CN_BEIJING;
233

234
    /// 张家口
235
    pub const CN_ZHANGJIAKOU: Self = Self::ZHANGJIAKOU;
236
    /// 张家口
237
    pub const ZHANGJIAKOU: Self = Self {
238
        kind: EndPointKind::CnZhangjiakou,
239
        is_internal: false,
240
    };
241

242
    /// 香港
243
    pub const CN_HONGKONG: Self = Self {
244
        kind: EndPointKind::CnHongkong,
245
        is_internal: false,
246
    };
247
    /// 香港
248
    pub const HONGKONG: Self = Self::CN_HONGKONG;
249

250
    /// 深圳
251
    pub const CN_SHENZHEN: Self = Self {
252
        kind: EndPointKind::CnShenzhen,
253
        is_internal: false,
254
    };
255
    /// 深圳
256
    pub const SHENZHEN: Self = Self::CN_SHENZHEN;
257

258
    /// UsWest1
259
    pub const US_WEST_1: Self = Self {
260
        kind: EndPointKind::UsWest1,
261
        is_internal: false,
262
    };
263

264
    /// UsEast1
265
    pub const US_EAST_1: Self = Self {
266
        kind: EndPointKind::UsEast1,
267
        is_internal: false,
268
    };
269

270
    /// ApSouthEast1
271
    pub const AP_SOUTH_EAST_1: Self = Self {
272
        kind: EndPointKind::ApSouthEast1,
273
        is_internal: false,
274
    };
275
}
276

277
/// # OSS 的可用区种类 enum
278
#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)]
374✔
279
#[non_exhaustive]
280
pub(crate) enum EndPointKind {
281
    /// 杭州可用区
282
    #[default]
283
    CnHangzhou,
134✔
284
    /// 上海可用区
285
    CnShanghai,
286
    /// 青岛可用区
287
    CnQingdao,
288
    /// 北京可用区
289
    CnBeijing,
290
    /// 张家口可用区
291
    CnZhangjiakou, // 张家口 lenght=13
292
    /// 香港
293
    CnHongkong,
294
    /// 深圳
295
    CnShenzhen,
296
    /// 美国西部
297
    UsWest1,
298
    /// 美国东部
299
    UsEast1,
300
    /// 新加坡
301
    ApSouthEast1,
302
    /// 其他可用区 fuzhou,ap-southeast-6 等
303
    Other(Cow<'static, str>),
3✔
304
}
305

306
impl AsRef<str> for EndPoint {
307
    /// ```
308
    /// # use aliyun_oss_client::types::EndPoint;
309
    ///
310
    /// assert_eq!(EndPoint::HANGZHOU.as_ref(), "cn-hangzhou");
311
    /// assert_eq!(EndPoint::SHANGHAI.as_ref(), "cn-shanghai");
312
    /// assert_eq!(EndPoint::QINGDAO.as_ref(), "cn-qingdao");
313
    /// assert_eq!(EndPoint::BEIJING.as_ref(), "cn-beijing");
314
    /// assert_eq!(EndPoint::ZHANGJIAKOU.as_ref(), "cn-zhangjiakou");
315
    /// assert_eq!(EndPoint::HONGKONG.as_ref(), "cn-hongkong");
316
    /// assert_eq!(EndPoint::SHENZHEN.as_ref(), "cn-shenzhen");
317
    /// assert_eq!(EndPoint::US_WEST_1.as_ref(), "us-west-1");
318
    /// assert_eq!(EndPoint::US_EAST_1.as_ref(), "us-east-1");
319
    /// assert_eq!(EndPoint::AP_SOUTH_EAST_1.as_ref(), "ap-southeast-1");
320
    /// ```
321
    fn as_ref(&self) -> &str {
121✔
322
        use EndPointKind::*;
323
        match &self.kind {
121✔
324
            CnHangzhou => HANGZHOU,
11✔
325
            CnShanghai => SHANGHAI,
32✔
326
            CnQingdao => QINGDAO,
63✔
327
            CnBeijing => BEIJING,
4✔
328
            CnZhangjiakou => ZHANGJIAKOU,
1✔
329
            CnHongkong => HONGKONG,
1✔
330
            CnShenzhen => SHENZHEN,
1✔
331
            UsWest1 => US_WEST1,
1✔
332
            UsEast1 => US_EAST1,
1✔
333
            ApSouthEast1 => AP_SOUTH_EAST1,
1✔
334
            Other(str) => str,
10✔
335
        }
336
    }
121✔
337
}
338

339
impl Display for EndPoint {
340
    /// ```
341
    /// # use aliyun_oss_client::types::EndPoint;
342
    /// assert_eq!(format!("{}", EndPoint::HANGZHOU), "cn-hangzhou");
343
    /// ```
344
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1✔
345
        write!(f, "{}", self.as_ref())
1✔
346
    }
1✔
347
}
348

349
impl TryFrom<String> for EndPoint {
350
    type Error = InvalidEndPoint;
351
    /// 字符串转 endpoint
352
    ///
353
    /// 举例
354
    /// ```
355
    /// # use aliyun_oss_client::types::EndPoint;
356
    /// let e: EndPoint = String::from("qingdao").try_into().unwrap();
357
    /// assert_eq!(e, EndPoint::QINGDAO);
358
    /// ```
359
    fn try_from(url: String) -> Result<Self, Self::Error> {
3✔
360
        Self::new(&url)
3✔
361
    }
3✔
362
}
363

364
impl<'a> TryFrom<&'a str> for EndPoint {
365
    type Error = InvalidEndPoint;
366
    /// 字符串字面量转 endpoint
367
    ///
368
    /// 举例
369
    /// ```
370
    /// # use aliyun_oss_client::types::EndPoint;
371
    /// let e: EndPoint = "qingdao".try_into().unwrap();
372
    /// assert_eq!(e, EndPoint::QINGDAO);
373
    /// ```
374
    fn try_from(url: &'a str) -> Result<Self, Self::Error> {
37✔
375
        Self::new(url)
37✔
376
    }
37✔
377
}
378

379
impl FromStr for EndPoint {
380
    type Err = InvalidEndPoint;
381
    fn from_str(url: &str) -> Result<Self, Self::Err> {
59✔
382
        Self::new(url)
59✔
383
    }
59✔
384
}
385

386
impl TryFrom<Url> for EndPoint {
387
    type Error = InvalidEndPoint;
388
    fn try_from(url: Url) -> Result<Self, Self::Error> {
7✔
389
        use url::Host;
390
        let domain = if let Some(Host::Domain(domain)) = url.host() {
7✔
391
            domain
392
        } else {
393
            return Err(InvalidEndPoint::new());
1✔
394
        };
395
        let mut url_pieces = domain.rsplit('.');
6✔
396

397
        match (url_pieces.next(), url_pieces.next()) {
6✔
398
            (Some(COM), Some(ALIYUNCS)) => (),
6✔
399
            _ => return Err(InvalidEndPoint::new()),
2✔
400
        }
401

402
        match url_pieces.next() {
4✔
403
            Some(endpoint) => match EndPoint::from_host_piece(endpoint) {
3✔
404
                Ok(end) => Ok(end),
2✔
405
                _ => Err(InvalidEndPoint::new()),
1✔
406
            },
3✔
407
            _ => Err(InvalidEndPoint::new()),
1✔
408
        }
409
    }
7✔
410
}
411

412
impl<'a> EndPoint {
413
    /// 通过字符串字面值初始化 endpoint
414
    ///
415
    /// 例如
416
    /// ```
417
    /// # use aliyun_oss_client::types::EndPoint;
418
    /// EndPoint::from_static("qingdao");
419
    /// ```
420
    pub fn from_static(url: &'a str) -> Self {
2✔
421
        Self::new(url).unwrap_or_else(|_| panic!("Unknown Endpoint :{}", url))
3✔
422
    }
2✔
423

424
    /// # Safety
425
    /// 用于静态定义其他可用区
426
    pub const unsafe fn from_static2(url: &'static str) -> Self {
1✔
427
        Self {
1✔
428
            kind: EndPointKind::Other(Cow::Borrowed(url)),
1✔
429
            is_internal: false,
430
        }
431
    }
1✔
432

433
    /// 初始化 endpoint enum
434
    /// ```rust
435
    /// # use aliyun_oss_client::types::EndPoint;
436
    /// # use std::borrow::Cow;
437
    /// assert!(matches!(
438
    ///     EndPoint::new("shanghai"),
439
    ///     Ok(EndPoint::SHANGHAI)
440
    /// ));
441
    ///
442
    /// assert!(EndPoint::new("abc-").is_err());
443
    /// assert!(EndPoint::new("-abc").is_err());
444
    /// assert!(EndPoint::new("abc-def234ab").is_ok());
445
    /// assert!(EndPoint::new("abc-def*#$%^ab").is_err());
446
    /// assert!(EndPoint::new("cn-jinan").is_ok());
447
    /// assert!(EndPoint::new("cn-jinan").is_ok());
448
    /// assert!(EndPoint::new("oss-cn-jinan").is_err());
449
    /// ```
450
    pub fn new(url: &'a str) -> Result<Self, InvalidEndPoint> {
183✔
451
        const OSS_STR: &str = "oss";
452
        use EndPointKind::*;
453
        if url.is_empty() {
183✔
454
            return Err(InvalidEndPoint::new());
1✔
455
        }
456
        // 是否是内网
457
        let is_internal = url.ends_with(OSS_INTERNAL);
182✔
458
        let url = if is_internal {
182✔
459
            let len = url.len();
3✔
460
            &url[..len - 9]
3✔
461
        } else {
462
            url
179✔
463
        };
464

465
        let kind = if url.contains(SHANGHAI_L) {
182✔
466
            Ok(CnShanghai)
76✔
467
        } else if url.contains(HANGZHOU_L) {
106✔
468
            Ok(CnHangzhou)
3✔
469
        } else if url.contains(QINGDAO_L) {
103✔
470
            Ok(CnQingdao)
70✔
471
        } else if url.contains(BEIJING_L) {
33✔
472
            Ok(CnBeijing)
1✔
473
        } else if url.contains(ZHANGJIAKOU_L) {
32✔
474
            Ok(CnZhangjiakou)
1✔
475
        } else if url.contains(HONGKONG_L) {
31✔
476
            Ok(CnHongkong)
1✔
477
        } else if url.contains(SHENZHEN_L) {
30✔
478
            Ok(CnShenzhen)
1✔
479
        } else if url.contains(US_WEST1) {
29✔
480
            Ok(UsWest1)
1✔
481
        } else if url.contains(US_EAST1) {
28✔
482
            Ok(UsEast1)
1✔
483
        } else if url.contains(AP_SOUTH_EAST1) {
38✔
484
            Ok(ApSouthEast1)
1✔
485
        } else {
486
            if url.starts_with('-') || url.ends_with('-') || url.starts_with(OSS_STR) {
26✔
487
                return Err(InvalidEndPoint::new());
13✔
488
            }
489

490
            if !url.chars().all(valid_oss_character) {
13✔
491
                return Err(InvalidEndPoint::new());
2✔
492
            }
493

494
            Ok(Other(Cow::Owned(url.to_owned())))
11✔
495
        };
496

497
        kind.map(|kind| Self { kind, is_internal })
334✔
498
    }
183✔
499

500
    /// 从 oss 域名中提取 Endpoint 信息
501
    pub(crate) fn from_host_piece(url: &'a str) -> Result<Self, InvalidEndPoint> {
23✔
502
        if !url.starts_with(OSS_HYPHEN) {
23✔
503
            return Err(InvalidEndPoint::new());
4✔
504
        }
505
        Self::new(&url[4..])
19✔
506
    }
23✔
507

508
    /// use env init Endpoint
509
    pub fn from_env() -> Result<Self, InvalidEndPoint> {
11✔
510
        let endpoint = std::env::var("ALIYUN_ENDPOINT").map_err(|_| InvalidEndPoint::new())?;
12✔
511
        let mut endpoint: EndPoint = endpoint.parse().map_err(|_| InvalidEndPoint::new())?;
12✔
512
        if let Ok(is_internal) = std::env::var("ALIYUN_OSS_INTERNAL") {
8✔
513
            if is_internal == TRUE1
5✔
514
                || is_internal == TRUE2
4✔
515
                || is_internal == TRUE3
3✔
516
                || is_internal == TRUE4
2✔
517
            {
518
                endpoint.set_internal(true);
4✔
519
            }
520
        }
8✔
521

522
        Ok(endpoint)
8✔
523
    }
11✔
524

525
    /// # 调整 API 指向是否为内网
526
    ///
527
    /// 当在 Aliyun ECS 上执行时,设为 true 会更高效,默认是 false
528
    pub fn set_internal(&mut self, is_internal: bool) {
14✔
529
        self.is_internal = is_internal;
14✔
530
    }
14✔
531

532
    /// 返回当前的 endpoint 是否为内网
533
    pub fn is_internal(&self) -> bool {
6✔
534
        self.is_internal
6✔
535
    }
6✔
536

537
    /// 转化成 Url
538
    /// ```
539
    /// # use aliyun_oss_client::types::EndPoint;
540
    /// use reqwest::Url;
541
    /// let mut endpoint = EndPoint::new("shanghai").unwrap();
542
    /// assert_eq!(
543
    ///     endpoint.to_url(),
544
    ///     Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap()
545
    /// );
546
    ///
547
    /// endpoint.set_internal(true);
548
    /// assert_eq!(
549
    ///     endpoint.to_url(),
550
    ///     Url::parse("https://oss-cn-shanghai-internal.aliyuncs.com").unwrap()
551
    /// );
552
    /// ```
553
    pub fn to_url(&self) -> Url {
65✔
554
        let mut url = String::from(OSS_DOMAIN_PREFIX);
65✔
555
        url.push_str(self.as_ref());
65✔
556

557
        // internal
558
        if self.is_internal {
65✔
559
            url.push_str(OSS_INTERNAL);
6✔
560
        }
561

562
        url.push_str(OSS_DOMAIN_MAIN);
65✔
563
        Url::parse(&url).unwrap_or_else(|_| panic!("covert to url failed, endpoint: {}", url))
65✔
564
    }
65✔
565
}
566

567
/// 无效的可用区
568
#[derive(PartialEq, Eq, Hash, Clone)]
4✔
569
#[non_exhaustive]
570
pub struct InvalidEndPoint {
571
    _priv: (),
1✔
572
}
573

574
impl InvalidEndPoint {
575
    pub(crate) fn new() -> InvalidEndPoint {
36✔
576
        InvalidEndPoint { _priv: () }
577
    }
36✔
578
}
579

580
impl Debug for InvalidEndPoint {
581
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
582
        f.debug_struct("InvalidEndPoint").finish()
2✔
583
    }
2✔
584
}
585

586
impl Error for InvalidEndPoint {}
587

588
impl fmt::Display for InvalidEndPoint {
589
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7✔
590
        write!(
7✔
591
            f,
592
            "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix"
593
        )
594
    }
7✔
595
}
596

597
impl PartialEq<&str> for EndPoint {
598
    /// # 相等比较
599
    /// ```
600
    /// # use aliyun_oss_client::types::EndPoint;
601
    /// let e: EndPoint = String::from("qingdao").try_into().unwrap();
602
    /// assert!(e == "cn-qingdao");
603
    /// ```
604
    #[inline]
605
    fn eq(&self, other: &&str) -> bool {
1✔
606
        &self.as_ref() == other
1✔
607
    }
1✔
608
}
609

610
impl PartialEq<EndPoint> for &str {
611
    /// # 相等比较
612
    /// ```
613
    /// # use aliyun_oss_client::types::EndPoint;
614
    /// let e: EndPoint = String::from("qingdao").try_into().unwrap();
615
    /// assert!("cn-qingdao" == e);
616
    /// ```
617
    #[inline]
618
    fn eq(&self, other: &EndPoint) -> bool {
1✔
619
        self == &other.as_ref()
1✔
620
    }
1✔
621
}
622

623
impl PartialEq<Url> for EndPoint {
624
    /// # 相等比较
625
    /// ```
626
    /// # use aliyun_oss_client::types::EndPoint;
627
    /// use reqwest::Url;
628
    /// let endpoint = EndPoint::new("shanghai").unwrap();
629
    /// assert!(endpoint == Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap());
630
    /// ```
631
    #[inline]
632
    fn eq(&self, other: &Url) -> bool {
1✔
633
        &self.to_url() == other
1✔
634
    }
1✔
635
}
636

637
//===================================================================================================
638

639
/// 存储 bucket 名字的类型
640
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
186✔
641
pub struct BucketName(Cow<'static, str>);
93✔
642

643
impl AsRef<str> for BucketName {
644
    fn as_ref(&self) -> &str {
142✔
645
        &self.0
142✔
646
    }
142✔
647
}
648

649
impl Display for BucketName {
650
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
76✔
651
        write!(f, "{}", self.0)
76✔
652
    }
76✔
653
}
654

655
impl Default for BucketName {
656
    #![allow(clippy::unwrap_used)]
657
    fn default() -> BucketName {
141✔
658
        BucketName::new("a").unwrap()
141✔
659
    }
141✔
660
}
661

662
// impl TryInto<HeaderValue> for BucketName {
663
//     type Error = InvalidHeaderValue;
664
//     fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
665
//         HeaderValue::from_str(self.as_ref())
666
//     }
667
// }
668
impl TryFrom<String> for BucketName {
669
    type Error = InvalidBucketName;
670
    /// ```
671
    /// # use aliyun_oss_client::types::BucketName;
672
    /// let b: BucketName = String::from("abc").try_into().unwrap();
673
    /// assert_eq!(b, BucketName::new("abc").unwrap());
674
    /// ```
675
    fn try_from(s: String) -> Result<Self, Self::Error> {
1✔
676
        Self::new(s)
1✔
677
    }
1✔
678
}
679

680
impl<'a> TryFrom<&'a str> for BucketName {
681
    type Error = InvalidBucketName;
682
    /// ```
683
    /// # use aliyun_oss_client::types::BucketName;
684
    /// let b: BucketName = "abc".try_into().unwrap();
685
    /// assert_eq!(b, BucketName::new("abc").unwrap());
686
    /// ```
687
    fn try_from(bucket: &'a str) -> Result<Self, Self::Error> {
61✔
688
        Self::from_static(bucket)
61✔
689
    }
61✔
690
}
691

692
impl FromStr for BucketName {
693
    type Err = InvalidBucketName;
694
    /// ```
695
    /// # use aliyun_oss_client::types::BucketName;
696
    /// let b: BucketName = "abc".parse().unwrap();
697
    /// assert_eq!(b, BucketName::new("abc").unwrap());
698
    /// ```
699
    fn from_str(s: &str) -> Result<Self, Self::Err> {
80✔
700
        Self::from_static(s)
80✔
701
    }
80✔
702
}
703

704
impl<'a> BucketName {
705
    /// Creates a new `BucketName` from the given string.
706
    /// 只允许小写字母、数字、短横线(-),且不能以短横线开头或结尾
707
    ///
708
    /// ```
709
    /// # use aliyun_oss_client::types::BucketName;
710
    ///
711
    /// assert!(BucketName::new("").is_err());
712
    /// assert!(BucketName::new("abc").is_ok());
713
    /// assert!(BucketName::new("abc-").is_err());
714
    /// assert!(BucketName::new("-abc").is_err());
715
    /// assert!(BucketName::new("abc-def234ab").is_ok());
716
    /// assert!(BucketName::new("abc-def*#$%^ab").is_err());
717
    /// ```
718
    pub fn new(bucket: impl Into<Cow<'static, str>>) -> Result<Self, InvalidBucketName> {
167✔
719
        let bucket = bucket.into();
167✔
720

721
        if bucket.is_empty() || bucket.starts_with('-') || bucket.ends_with('-') {
167✔
722
            return Err(InvalidBucketName::new());
3✔
723
        }
724

725
        if !bucket.chars().all(valid_oss_character) {
164✔
726
            return Err(InvalidBucketName::new());
1✔
727
        }
728

729
        Ok(Self(bucket))
163✔
730
    }
167✔
731

732
    /// use env init BucketName
733
    pub fn from_env() -> Result<Self, InvalidBucketName> {
4✔
734
        let string = std::env::var("ALIYUN_BUCKET").map_err(|_| InvalidBucketName::new())?;
5✔
735

736
        string.parse()
3✔
737
    }
4✔
738

739
    /// Const function that creates a new `BucketName` from a static str.
740
    /// ```
741
    /// # use aliyun_oss_client::types::BucketName;
742
    ///
743
    /// assert!(BucketName::from_static("").is_err());
744
    /// assert!(BucketName::from_static("abc").is_ok());
745
    /// assert!(BucketName::from_static("abc-").is_err());
746
    /// assert!(BucketName::from_static("-abc").is_err());
747
    /// assert!(BucketName::from_static("abc-def234ab").is_ok());
748
    /// assert!(BucketName::from_static("abc-def*#$%^ab").is_err());
749
    /// ```
750
    pub fn from_static(bucket: &'a str) -> Result<Self, InvalidBucketName> {
198✔
751
        if bucket.is_empty() || bucket.starts_with('-') || bucket.ends_with('-') {
198✔
752
            return Err(InvalidBucketName::new());
12✔
753
        }
754

755
        if !bucket.chars().all(valid_oss_character) {
186✔
756
            return Err(InvalidBucketName::new());
1✔
757
        }
758

759
        Ok(Self(Cow::Owned(bucket.to_owned())))
185✔
760
    }
198✔
761

762
    /// # Safety
763
    pub const unsafe fn from_static2(bucket: &'static str) -> Self {
3✔
764
        Self(Cow::Borrowed(bucket))
3✔
765
    }
3✔
766
}
767

768
fn valid_oss_character(c: char) -> bool {
1,004✔
769
    match c {
19✔
770
        _ if c.is_ascii_lowercase() => true,
1,004✔
771
        _ if c.is_numeric() => true,
106✔
772
        '-' => true,
15✔
773
        _ => false,
4✔
774
    }
775
}
1,004✔
776

777
impl PartialEq<&str> for BucketName {
778
    /// 相等比较
779
    /// ```
780
    /// # use aliyun_oss_client::types::BucketName;
781
    /// let path = BucketName::new("abc").unwrap();
782
    /// assert!(path == "abc");
783
    /// ```
784
    #[inline]
785
    fn eq(&self, other: &&str) -> bool {
2✔
786
        &self.0 == other
2✔
787
    }
2✔
788
}
789

790
impl PartialEq<BucketName> for &str {
791
    /// 相等比较
792
    /// ```
793
    /// # use aliyun_oss_client::types::BucketName;
794
    /// let path = BucketName::new("abc").unwrap();
795
    /// assert!("abc" == path);
796
    /// ```
797
    #[inline]
798
    fn eq(&self, other: &BucketName) -> bool {
5✔
799
        self == &other.0
5✔
800
    }
5✔
801
}
802

803
/// 无效的 bucket 名称
804
#[derive(PartialEq, Clone)]
4✔
805
#[non_exhaustive]
806
pub struct InvalidBucketName {
807
    _priv: (),
1✔
808
}
809

810
impl InvalidBucketName {
811
    pub(crate) fn new() -> InvalidBucketName {
25✔
812
        InvalidBucketName { _priv: () }
813
    }
25✔
814
}
815

816
impl Debug for InvalidBucketName {
817
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
818
        f.debug_struct("InvalidBucketName").finish()
2✔
819
    }
2✔
820
}
821

822
impl Error for InvalidBucketName {}
823

824
impl fmt::Display for InvalidBucketName {
825
    /// ```
826
    /// # use aliyun_oss_client::types::BucketName;
827
    ///
828
    /// let err = BucketName::from_static("").unwrap_err();
829
    /// assert_eq!(
830
    ///     format!("{}", err),
831
    ///     "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix"
832
    /// );
833
    /// ```
834
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5✔
835
        write!(
5✔
836
            f,
837
            "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix"
838
        )
839
    }
5✔
840
}
841

842
//===================================================================================================
843

844
/// aliyun OSS 的配置 ContentMd5
845
#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)]
140✔
846
pub struct InnerContentMd5<'a>(Cow<'a, str>);
70✔
847
/// 静态作用域的 InnerContentMd5
848
pub type ContentMd5 = InnerContentMd5<'static>;
849

850
impl AsRef<str> for InnerContentMd5<'_> {
851
    fn as_ref(&self) -> &str {
79✔
852
        &self.0
79✔
853
    }
79✔
854
}
855

856
impl Display for InnerContentMd5<'_> {
857
    /// ```
858
    /// # use aliyun_oss_client::types::ContentMd5;
859
    /// let md5 = ContentMd5::new("abc");
860
    /// assert_eq!(format!("{md5}"), "abc");
861
    /// ```
862
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
863
        write!(f, "{}", self.0)
2✔
864
    }
2✔
865
}
866

867
impl TryInto<HeaderValue> for InnerContentMd5<'_> {
868
    type Error = InvalidHeaderValue;
869
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
870
        HeaderValue::from_str(self.as_ref())
1✔
871
    }
1✔
872
}
873

874
impl TryInto<HeaderValue> for &InnerContentMd5<'_> {
875
    type Error = InvalidHeaderValue;
876
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
4✔
877
        HeaderValue::from_str(self.as_ref())
4✔
878
    }
4✔
879
}
880
impl From<String> for ContentMd5 {
881
    /// ```
882
    /// # use aliyun_oss_client::types::ContentMd5;
883
    /// let md5: ContentMd5 = String::from("abc").into();
884
    /// assert_eq!(format!("{md5}"), "abc");
885
    /// ```
886
    fn from(s: String) -> Self {
1✔
887
        Self(Cow::Owned(s))
1✔
888
    }
1✔
889
}
890

891
impl<'a: 'b, 'b> From<&'a str> for InnerContentMd5<'b> {
892
    fn from(value: &'a str) -> Self {
6✔
893
        Self(Cow::Borrowed(value))
6✔
894
    }
6✔
895
}
896

897
impl<'a> InnerContentMd5<'a> {
898
    /// Creates a new `ContentMd5` from the given string.
899
    pub fn new(val: impl Into<Cow<'a, str>>) -> Self {
900
        Self(val.into())
901
    }
902

903
    /// Const function that creates a new `ContentMd5` from a static str.
904
    pub const fn from_static(val: &'static str) -> Self {
1✔
905
        Self(Cow::Borrowed(val))
1✔
906
    }
1✔
907
}
908

909
//===================================================================================================
910

911
/// aliyun OSS 的配置 ContentType
912
#[derive(Clone, Debug, PartialEq, Eq, Default)]
144✔
913
pub struct ContentType(Cow<'static, str>);
72✔
914

915
impl AsRef<str> for ContentType {
916
    fn as_ref(&self) -> &str {
73✔
917
        &self.0
73✔
918
    }
73✔
919
}
920

921
impl Display for ContentType {
922
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
923
        write!(f, "{}", self.0)
2✔
924
    }
2✔
925
}
926

927
impl TryInto<HeaderValue> for ContentType {
928
    type Error = InvalidHeaderValue;
929
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
930
        HeaderValue::from_str(self.as_ref())
1✔
931
    }
1✔
932
}
933
impl TryFrom<HeaderValue> for ContentType {
934
    type Error = ToStrError;
935
    fn try_from(value: HeaderValue) -> Result<Self, Self::Error> {
13✔
936
        Ok(Self(Cow::Owned(value.to_str()?.to_owned())))
13✔
937
    }
13✔
938
}
939
impl From<String> for ContentType {
940
    fn from(s: String) -> Self {
1✔
941
        Self(Cow::Owned(s))
1✔
942
    }
1✔
943
}
944

945
impl ContentType {
946
    /// Creates a new `ContentMd5` from the given string.
947
    pub fn new(val: impl Into<Cow<'static, str>>) -> Self {
948
        Self(val.into())
949
    }
950

951
    /// Const function that creates a new `ContentMd5` from a static str.
952
    pub const fn from_static(val: &'static str) -> Self {
1✔
953
        Self(Cow::Borrowed(val))
1✔
954
    }
1✔
955
}
956

957
//===================================================================================================
958

959
/// 用于计算签名的 Date
960
#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)]
476✔
961
pub struct InnerDate<'a>(Cow<'a, str>);
238✔
962
/// 静态作用域的 InnerDate
963
pub type Date = InnerDate<'static>;
964

965
impl AsRef<str> for InnerDate<'_> {
966
    fn as_ref(&self) -> &str {
151✔
967
        &self.0
151✔
968
    }
151✔
969
}
970

971
impl Display for InnerDate<'_> {
972
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1✔
973
        write!(f, "{}", self.0)
1✔
974
    }
1✔
975
}
976

977
impl TryInto<HeaderValue> for InnerDate<'_> {
978
    type Error = InvalidHeaderValue;
979
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
980
        HeaderValue::from_str(self.as_ref())
1✔
981
    }
1✔
982
}
983

984
impl<Tz: TimeZone> From<DateTime<Tz>> for Date
985
where
986
    Tz::Offset: fmt::Display,
987
{
988
    fn from(d: DateTime<Tz>) -> Self {
74✔
989
        Self(Cow::Owned(d.format("%a, %d %b %Y %T GMT").to_string()))
74✔
990
    }
74✔
991
}
992

993
impl<'a> InnerDate<'a> {
994
    /// # Safety
995
    /// Const function that creates a new `Date` from a static str.
996
    pub const unsafe fn from_static(val: &'static str) -> Self {
4✔
997
        Self(Cow::Borrowed(val))
4✔
998
    }
4✔
999
}
1000

1001
//===================================================================================================
1002

1003
/// 计算方式,参考 [aliyun 文档](https://help.aliyun.com/document_detail/31951.htm)
1004
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
164✔
1005
pub struct InnerCanonicalizedResource<'a>(Cow<'a, str>);
82✔
1006
/// 静态作用域的 InnerCanonicalizedResource
1007
pub type CanonicalizedResource = InnerCanonicalizedResource<'static>;
1008

1009
impl AsRef<str> for InnerCanonicalizedResource<'_> {
1010
    fn as_ref(&self) -> &str {
166✔
1011
        &self.0
166✔
1012
    }
166✔
1013
}
1014

1015
impl Display for InnerCanonicalizedResource<'_> {
1016
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
4✔
1017
        write!(f, "{}", self.0)
4✔
1018
    }
4✔
1019
}
1020

1021
impl TryInto<HeaderValue> for InnerCanonicalizedResource<'_> {
1022
    type Error = InvalidHeaderValue;
1023
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
1024
        HeaderValue::from_str(self.as_ref())
1✔
1025
    }
1✔
1026
}
1027
impl From<String> for CanonicalizedResource {
1028
    fn from(s: String) -> Self {
1✔
1029
        Self(Cow::Owned(s))
1✔
1030
    }
1✔
1031
}
1032

1033
impl<'a: 'b, 'b> From<&'a str> for InnerCanonicalizedResource<'b> {
1034
    fn from(value: &'a str) -> Self {
8✔
1035
        Self(Cow::Borrowed(value))
8✔
1036
    }
8✔
1037
}
1038

1039
impl Default for InnerCanonicalizedResource<'_> {
1040
    fn default() -> Self {
195✔
1041
        InnerCanonicalizedResource(Cow::Owned("/".to_owned()))
195✔
1042
    }
195✔
1043
}
1044

1045
#[cfg(any(feature = "core", feature = "auth"))]
1046
pub(crate) const CONTINUATION_TOKEN: &str = "continuation-token";
1047
#[cfg(any(feature = "core", feature = "auth"))]
1048
pub(crate) const BUCKET_INFO: &str = "bucketInfo";
1049
#[cfg(any(feature = "core", feature = "auth"))]
1050
const QUERY_KEYWORD: [&str; 2] = ["acl", BUCKET_INFO];
1051

1052
impl<'a> InnerCanonicalizedResource<'a> {
1053
    /// Creates a new `CanonicalizedResource` from the given string.
1054
    pub fn new(val: impl Into<Cow<'a, str>>) -> Self {
116✔
1055
        Self(val.into())
116✔
1056
    }
116✔
1057

1058
    /// 只有 endpoint ,而没有 bucket 的时候
1059
    #[inline(always)]
1060
    pub fn from_endpoint() -> Self {
1✔
1061
        Self::default()
1✔
1062
    }
1✔
1063

1064
    /// Const function that creates a new `CanonicalizedResource` from a static str.
1065
    pub const fn from_static(val: &'static str) -> Self {
1✔
1066
        Self(Cow::Borrowed(val))
1✔
1067
    }
1✔
1068

1069
    /// 获取 bucket 的签名参数
1070
    #[cfg(feature = "core")]
1071
    pub fn from_bucket<B: AsRef<BucketName>>(bucket: B, query: Option<&str>) -> Self {
7✔
1072
        match query {
7✔
1073
            Some(q) => {
6✔
1074
                if QUERY_KEYWORD.iter().any(|&str| str == q) {
18✔
1075
                    return Self::new(format!("/{}/?{}", bucket.as_ref().as_ref(), q));
5✔
1076
                }
1077

1078
                Self::new(format!("/{}/", bucket.as_ref().as_ref()))
1✔
1079
            }
1080
            None => Self::default(),
1✔
1081
        }
1082
    }
7✔
1083

1084
    /// 获取 bucket 的签名参数
1085
    #[cfg(feature = "auth")]
1086
    pub fn from_bucket_name(bucket: &BucketName, query: Option<&str>) -> Self {
7✔
1087
        match query {
7✔
1088
            Some(q) => {
6✔
1089
                if QUERY_KEYWORD.iter().any(|&str| str == q) {
18✔
1090
                    return Self::new(format!("/{}/?{}", bucket.as_ref(), q));
5✔
1091
                }
1092

1093
                Self::new(format!("/{}/", bucket.as_ref()))
1✔
1094
            }
1095
            None => Self::default(),
1✔
1096
        }
1097
    }
7✔
1098

1099
    /// 获取 bucket 的签名参数
1100
    /// 带查询条件的
1101
    ///
1102
    /// 如果查询条件中有翻页的话,则忽略掉其他字段
1103
    #[cfg(feature = "core")]
1104
    #[inline]
1105
    pub fn from_bucket_query<B: AsRef<BucketName>>(bucket: B, query: &Query) -> Self {
10✔
1106
        Self::from_bucket_query2(bucket.as_ref(), query)
10✔
1107
    }
10✔
1108

1109
    #[cfg(feature = "core")]
1110
    #[doc(hidden)]
1111
    pub fn from_bucket_query2(bucket: &BucketName, query: &Query) -> Self {
13✔
1112
        match query.get(QueryKey::CONTINUATION_TOKEN) {
13✔
1113
            Some(v) => Self::new(format!(
3✔
1114
                "/{}/?continuation-token={}",
1115
                bucket.as_ref(),
1✔
1116
                v.as_ref()
1✔
1117
            )),
1118
            None => Self::new(format!("/{}/", bucket.as_ref())),
12✔
1119
        }
1120
    }
13✔
1121

1122
    /// 根据 OSS 存储对象(Object)查询签名参数
1123
    #[cfg(feature = "core")]
1124
    pub(crate) fn from_object<Q: IntoQuery, B: AsRef<str>, P: AsRef<str>>(
14✔
1125
        (bucket, path): (B, P),
14✔
1126
        query: Q,
1127
    ) -> Self {
1128
        let query = query.into_query();
14✔
1129
        if query.is_empty() {
14✔
1130
            Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref()))
13✔
1131
        } else {
1132
            Self::new(format!(
3✔
1133
                "/{}/{}?{}",
1134
                bucket.as_ref(),
1✔
1135
                path.as_ref(),
1✔
1136
                query.to_url_query()
1✔
1137
            ))
1138
        }
1139
    }
14✔
1140

1141
    pub(crate) fn from_object_str(bucket: &str, path: &str) -> Self {
41✔
1142
        Self::new(format!("/{}/{}", bucket, path))
41✔
1143
    }
41✔
1144

1145
    #[cfg(feature = "auth")]
1146
    pub(crate) fn from_object_without_query<B: AsRef<str>, P: AsRef<str>>(
1✔
1147
        bucket: B,
1148
        path: P,
1149
    ) -> Self {
1150
        Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref()))
1✔
1151
    }
1✔
1152
}
1153

1154
impl PartialEq<&str> for InnerCanonicalizedResource<'_> {
1155
    /// # 相等比较
1156
    /// ```
1157
    /// # use aliyun_oss_client::types::CanonicalizedResource;
1158
    /// let res = CanonicalizedResource::new("abc");
1159
    /// assert!(res == "abc");
1160
    /// ```
1161
    #[inline]
1162
    fn eq(&self, other: &&str) -> bool {
21✔
1163
        &self.0 == other
21✔
1164
    }
21✔
1165
}
1166

1167
impl PartialEq<InnerCanonicalizedResource<'_>> for &str {
1168
    /// # 相等比较
1169
    /// ```
1170
    /// # use aliyun_oss_client::types::CanonicalizedResource;
1171
    /// let res = CanonicalizedResource::new("abc");
1172
    /// assert!("abc" == res);
1173
    /// ```
1174
    #[inline]
1175
    fn eq(&self, other: &InnerCanonicalizedResource<'_>) -> bool {
1✔
1176
        self == &other.0
1✔
1177
    }
1✔
1178
}
1179

1180
/// 根据 endpoint, bucket, path 获取接口信息
1181
pub fn get_url_resource(
32✔
1182
    endpoint: &EndPoint,
1183
    bucket: &BucketName,
1184
    path: &ObjectPathInner,
1185
) -> (Url, CanonicalizedResource) {
1186
    let mut url = url_from_bucket(endpoint, bucket);
32✔
1187
    url.set_object_path(path);
32✔
1188

1189
    let resource = CanonicalizedResource::from_object_str(bucket.as_ref(), path.as_ref());
32✔
1190

1191
    (url, resource)
32✔
1192
}
32✔
1193

1194
/// 根据 endpoint, bucket, path 获取接口信息
1195
pub fn get_url_resource2<E: AsRef<EndPoint>, B: AsRef<BucketName>>(
32✔
1196
    endpoint: E,
1197
    bucket: B,
1198
    path: &ObjectPathInner,
1199
) -> (Url, CanonicalizedResource) {
1200
    get_url_resource(endpoint.as_ref(), bucket.as_ref(), path)
32✔
1201
}
32✔
1202

1203
pub(crate) fn url_from_bucket(endpoint: &EndPoint, bucket: &BucketName) -> Url {
38✔
1204
    let url = format!(
76✔
1205
        "https://{}.oss-{}.aliyuncs.com",
1206
        bucket.as_ref(),
38✔
1207
        endpoint.as_ref()
38✔
1208
    );
1209
    url.parse().unwrap_or_else(|_| {
38✔
1210
        unreachable!("covert to url failed, bucket: {bucket}, endpoint: {endpoint}")
×
1211
    })
1212
}
38✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc