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

tu6ge / oss-rs / 5863951548

pending completion
5863951548

Pull #24

github

tu6ge
feat(client): Reduce constraints on generic param
Pull Request #24: Branch0.12

39 of 39 new or added lines in 4 files covered. (100.0%)

6180 of 6422 relevant lines covered (96.23%)

9.24 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
use crate::consts::{TRUE1, TRUE2, TRUE3, TRUE4};
22

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

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

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

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

52
const COM: &str = "com";
53
const ALIYUNCS: &str = "aliyuncs";
54

55
/// 阿里云 OSS 的签名 key
56
#[derive(Clone, Debug, PartialEq, Eq, Default)]
550✔
57
pub struct InnerKeyId<'a>(Cow<'a, str>);
275✔
58

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

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

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

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

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

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

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

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

105
//===================================================================================================
106

107
/// 阿里云 OSS 的签名 secret
108
#[derive(Clone, PartialEq, Eq, Default)]
430✔
109
pub struct InnerKeySecret<'a>(Cow<'a, str>);
215✔
110

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

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

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

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

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

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

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

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

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

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

175
        let secret = self.0.as_bytes();
65✔
176

177
        let mut mac = HmacSha1::new_from_slice(secret)?;
65✔
178

179
        mac.update(data);
65✔
180

181
        let sha1 = mac.finalize().into_bytes();
65✔
182

183
        Ok(STANDARD.encode(sha1))
65✔
184
    }
65✔
185
}
186

187
//===================================================================================================
188

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

199
// TODO
200
#[allow(non_upper_case_globals)]
201
impl EndPoint {
202
    /// 杭州
203
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_HANGZHOU")]
204
    pub const CnHangzhou: Self = Self {
205
        kind: EndPointKind::CnHangzhou,
206
        is_internal: false,
207
    };
208
    /// 杭州
209
    pub const CN_HANGZHOU: Self = Self {
210
        kind: EndPointKind::CnHangzhou,
211
        is_internal: false,
212
    };
213

214
    /// 上海
215
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_SHANGHAI")]
216
    pub const CnShanghai: Self = Self {
217
        kind: EndPointKind::CnShanghai,
218
        is_internal: false,
219
    };
220
    /// 上海
221
    pub const CN_SHANGHAI: Self = Self {
222
        kind: EndPointKind::CnShanghai,
223
        is_internal: false,
224
    };
225

226
    /// 青岛
227
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_QINGDAO")]
228
    pub const CnQingdao: Self = Self {
229
        kind: EndPointKind::CnQingdao,
230
        is_internal: false,
231
    };
232
    /// 青岛
233
    pub const CN_QINGDAO: Self = Self {
234
        kind: EndPointKind::CnQingdao,
235
        is_internal: false,
236
    };
237

238
    /// 北京
239
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_BEIJING")]
240
    pub const CnBeijing: Self = Self {
241
        kind: EndPointKind::CnBeijing,
242
        is_internal: false,
243
    };
244
    /// 北京
245
    pub const CN_BEIJING: Self = Self {
246
        kind: EndPointKind::CnBeijing,
247
        is_internal: false,
248
    };
249

250
    /// 张家口
251
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_ZHANGJIAKOU")]
252
    pub const CnZhangjiakou: Self = Self {
253
        kind: EndPointKind::CnZhangjiakou,
254
        is_internal: false,
255
    };
256
    /// 张家口
257
    pub const CN_ZHANGJIAKOU: Self = Self {
258
        kind: EndPointKind::CnZhangjiakou,
259
        is_internal: false,
260
    };
261

262
    /// 香港
263
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_HONGKONG")]
264
    pub const CnHongkong: Self = Self {
265
        kind: EndPointKind::CnHongkong,
266
        is_internal: false,
267
    };
268
    /// 香港
269
    pub const CN_HONGKONG: Self = Self {
270
        kind: EndPointKind::CnHongkong,
271
        is_internal: false,
272
    };
273

274
    /// 深圳
275
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::CN_SHENZHEN")]
276
    pub const CnShenzhen: Self = Self {
277
        kind: EndPointKind::CnShenzhen,
278
        is_internal: false,
279
    };
280
    /// 深圳
281
    pub const CN_SHENZHEN: Self = Self {
282
        kind: EndPointKind::CnShenzhen,
283
        is_internal: false,
284
    };
285

286
    /// UsWest1
287
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::US_WEST_1")]
288
    pub const UsWest1: Self = Self {
289
        kind: EndPointKind::UsWest1,
290
        is_internal: false,
291
    };
292
    /// UsWest1
293
    pub const US_WEST_1: Self = Self {
294
        kind: EndPointKind::UsWest1,
295
        is_internal: false,
296
    };
297

298
    /// UsEast1
299
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::US_EAST_1")]
300
    pub const UsEast1: Self = Self {
301
        kind: EndPointKind::UsEast1,
302
        is_internal: false,
303
    };
304
    /// UsEast1
305
    pub const US_EAST_1: Self = Self {
306
        kind: EndPointKind::UsEast1,
307
        is_internal: false,
308
    };
309

310
    /// ApSouthEast1
311
    #[deprecated(since = "0.13.0", note = "replace with EndPoint::AP_SOUTH_EAST_1")]
312
    pub const ApSouthEast1: Self = Self {
313
        kind: EndPointKind::ApSouthEast1,
314
        is_internal: false,
315
    };
316
    /// ApSouthEast1
317
    pub const AP_SOUTH_EAST_1: Self = Self {
318
        kind: EndPointKind::ApSouthEast1,
319
        is_internal: false,
320
    };
321
}
322

323
/// # OSS 的可用区种类 enum
324
#[derive(Clone, Debug, PartialEq, Eq, Default)]
281✔
325
#[non_exhaustive]
326
pub(crate) enum EndPointKind {
327
    /// 杭州可用区
328
    #[default]
329
    CnHangzhou,
97✔
330
    /// 上海可用区
331
    CnShanghai,
332
    /// 青岛可用区
333
    CnQingdao,
334
    /// 北京可用区
335
    CnBeijing,
336
    /// 张家口可用区
337
    CnZhangjiakou, // 张家口 lenght=13
338
    /// 香港
339
    CnHongkong,
340
    /// 深圳
341
    CnShenzhen,
342
    /// 美国西部
343
    UsWest1,
344
    /// 美国东部
345
    UsEast1,
346
    /// 新加坡
347
    ApSouthEast1,
348
    /// 其他可用区 fuzhou,ap-southeast-6 等
349
    Other(Cow<'static, str>),
3✔
350
}
351

352
impl AsRef<str> for EndPoint {
353
    /// ```
354
    /// # use aliyun_oss_client::types::EndPoint;
355
    ///
356
    /// assert_eq!(EndPoint::CnHangzhou.as_ref(), "cn-hangzhou");
357
    /// assert_eq!(EndPoint::CnShanghai.as_ref(), "cn-shanghai");
358
    /// assert_eq!(EndPoint::CnQingdao.as_ref(), "cn-qingdao");
359
    /// assert_eq!(EndPoint::CnBeijing.as_ref(), "cn-beijing");
360
    /// assert_eq!(EndPoint::CnZhangjiakou.as_ref(), "cn-zhangjiakou");
361
    /// assert_eq!(EndPoint::CnHongkong.as_ref(), "cn-hongkong");
362
    /// assert_eq!(EndPoint::CnShenzhen.as_ref(), "cn-shenzhen");
363
    /// assert_eq!(EndPoint::UsWest1.as_ref(), "us-west-1");
364
    /// assert_eq!(EndPoint::UsEast1.as_ref(), "us-east-1");
365
    /// assert_eq!(EndPoint::ApSouthEast1.as_ref(), "ap-southeast-1");
366
    /// ```
367
    fn as_ref(&self) -> &str {
107✔
368
        use EndPointKind::*;
369
        match &self.kind {
107✔
370
            CnHangzhou => HANGZHOU,
11✔
371
            CnShanghai => SHANGHAI,
32✔
372
            CnQingdao => QINGDAO,
49✔
373
            CnBeijing => BEIJING,
4✔
374
            CnZhangjiakou => ZHANGJIAKOU,
1✔
375
            CnHongkong => HONGKONG,
1✔
376
            CnShenzhen => SHENZHEN,
1✔
377
            UsWest1 => US_WEST1,
1✔
378
            UsEast1 => US_EAST1,
1✔
379
            ApSouthEast1 => AP_SOUTH_EAST1,
1✔
380
            Other(str) => str,
10✔
381
        }
382
    }
107✔
383
}
384

385
impl Display for EndPoint {
386
    /// ```
387
    /// # use aliyun_oss_client::types::EndPoint;
388
    /// assert_eq!(format!("{}", EndPoint::CnHangzhou), "cn-hangzhou");
389
    /// ```
390
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1✔
391
        write!(f, "{}", self.as_ref())
1✔
392
    }
1✔
393
}
394

395
impl TryFrom<String> for EndPoint {
396
    type Error = InvalidEndPoint;
397
    /// 字符串转 endpoint
398
    ///
399
    /// 举例
400
    /// ```
401
    /// # use aliyun_oss_client::types::EndPoint;
402
    /// let e: EndPoint = String::from("qingdao").try_into().unwrap();
403
    /// assert_eq!(e, EndPoint::CnQingdao);
404
    /// ```
405
    fn try_from(url: String) -> Result<Self, Self::Error> {
3✔
406
        Self::new(&url)
3✔
407
    }
3✔
408
}
409

410
impl<'a> TryFrom<&'a str> for EndPoint {
411
    type Error = InvalidEndPoint;
412
    /// 字符串字面量转 endpoint
413
    ///
414
    /// 举例
415
    /// ```
416
    /// # use aliyun_oss_client::types::EndPoint;
417
    /// let e: EndPoint = "qingdao".try_into().unwrap();
418
    /// assert_eq!(e, EndPoint::CnQingdao);
419
    /// ```
420
    fn try_from(url: &'a str) -> Result<Self, Self::Error> {
38✔
421
        Self::new(url)
38✔
422
    }
38✔
423
}
424

425
impl FromStr for EndPoint {
426
    type Err = InvalidEndPoint;
427
    fn from_str(url: &str) -> Result<Self, Self::Err> {
61✔
428
        Self::new(url)
61✔
429
    }
61✔
430
}
431

432
impl TryFrom<Url> for EndPoint {
433
    type Error = InvalidEndPoint;
434
    fn try_from(url: Url) -> Result<Self, Self::Error> {
7✔
435
        use url::Host;
436
        let domain = if let Some(Host::Domain(domain)) = url.host() {
7✔
437
            domain
438
        } else {
439
            return Err(InvalidEndPoint { _priv: () });
1✔
440
        };
441
        let mut url_pieces = domain.rsplit('.');
6✔
442

443
        match (url_pieces.next(), url_pieces.next()) {
6✔
444
            (Some(COM), Some(ALIYUNCS)) => (),
6✔
445
            _ => return Err(InvalidEndPoint { _priv: () }),
2✔
446
        }
447

448
        match url_pieces.next() {
4✔
449
            Some(endpoint) => match EndPoint::from_host_piece(endpoint) {
3✔
450
                Ok(end) => Ok(end),
2✔
451
                _ => Err(InvalidEndPoint { _priv: () }),
1✔
452
            },
3✔
453
            _ => Err(InvalidEndPoint { _priv: () }),
1✔
454
        }
455
    }
7✔
456
}
457

458
impl<'a> EndPoint {
459
    /// 通过字符串字面值初始化 endpoint
460
    ///
461
    /// 例如
462
    /// ```
463
    /// # use aliyun_oss_client::types::EndPoint;
464
    /// EndPoint::from_static("qingdao");
465
    /// ```
466
    pub fn from_static(url: &'a str) -> Self {
2✔
467
        Self::new(url).unwrap_or_else(|_| panic!("Unknown Endpoint :{}", url))
3✔
468
    }
2✔
469

470
    /// # Safety
471
    /// 用于静态定义其他可用区
472
    pub const unsafe fn from_static2(url: &'static str) -> Self {
1✔
473
        Self {
1✔
474
            kind: EndPointKind::Other(Cow::Borrowed(url)),
1✔
475
            is_internal: false,
476
        }
477
    }
1✔
478

479
    /// 初始化 endpoint enum
480
    /// ```rust
481
    /// # use aliyun_oss_client::types::EndPoint;
482
    /// # use std::borrow::Cow;
483
    /// assert!(matches!(
484
    ///     EndPoint::new("shanghai"),
485
    ///     Ok(EndPoint::CnShanghai)
486
    /// ));
487
    ///
488
    /// assert!(EndPoint::new("abc-").is_err());
489
    /// assert!(EndPoint::new("-abc").is_err());
490
    /// assert!(EndPoint::new("abc-def234ab").is_ok());
491
    /// assert!(EndPoint::new("abc-def*#$%^ab").is_err());
492
    /// assert!(EndPoint::new("cn-jinan").is_ok());
493
    /// assert!(EndPoint::new("cn-jinan").is_ok());
494
    /// assert!(EndPoint::new("oss-cn-jinan").is_err());
495
    /// ```
496
    pub fn new(url: &'a str) -> Result<Self, InvalidEndPoint> {
186✔
497
        const OSS_STR: &str = "oss";
498
        use EndPointKind::*;
499
        if url.is_empty() {
186✔
500
            return Err(InvalidEndPoint { _priv: () });
1✔
501
        }
502
        // 是否是内网
503
        let is_internal = url.ends_with(OSS_INTERNAL);
185✔
504
        let url = if is_internal {
185✔
505
            let len = url.len();
3✔
506
            &url[..len - 9]
3✔
507
        } else {
508
            url
182✔
509
        };
510

511
        let kind = if url.contains(SHANGHAI_L) {
185✔
512
            Ok(CnShanghai)
76✔
513
        } else if url.contains(HANGZHOU_L) {
109✔
514
            Ok(CnHangzhou)
3✔
515
        } else if url.contains(QINGDAO_L) {
106✔
516
            Ok(CnQingdao)
73✔
517
        } else if url.contains(BEIJING_L) {
33✔
518
            Ok(CnBeijing)
1✔
519
        } else if url.contains(ZHANGJIAKOU_L) {
32✔
520
            Ok(CnZhangjiakou)
1✔
521
        } else if url.contains(HONGKONG_L) {
31✔
522
            Ok(CnHongkong)
1✔
523
        } else if url.contains(SHENZHEN_L) {
30✔
524
            Ok(CnShenzhen)
1✔
525
        } else if url.contains(US_WEST1) {
29✔
526
            Ok(UsWest1)
1✔
527
        } else if url.contains(US_EAST1) {
28✔
528
            Ok(UsEast1)
1✔
529
        } else if url.contains(AP_SOUTH_EAST1) {
38✔
530
            Ok(ApSouthEast1)
1✔
531
        } else {
532
            if url.starts_with('-') || url.ends_with('-') || url.starts_with(OSS_STR) {
26✔
533
                return Err(InvalidEndPoint { _priv: () });
13✔
534
            }
535

536
            if !url.chars().all(valid_oss_character) {
13✔
537
                return Err(InvalidEndPoint { _priv: () });
2✔
538
            }
539

540
            Ok(Other(Cow::Owned(url.to_owned())))
11✔
541
        };
542

543
        kind.map(|kind| Self { kind, is_internal })
340✔
544
    }
186✔
545

546
    /// 从 oss 域名中提取 Endpoint 信息
547
    pub(crate) fn from_host_piece(url: &'a str) -> Result<Self, InvalidEndPoint> {
23✔
548
        if !url.starts_with(OSS_HYPHEN) {
23✔
549
            return Err(InvalidEndPoint { _priv: () });
4✔
550
        }
551
        Self::new(&url[4..])
19✔
552
    }
23✔
553

554
    /// use env init Endpoint
555
    pub fn from_env() -> Result<Self, InvalidEndPoint> {
11✔
556
        let endpoint =
11✔
557
            std::env::var("ALIYUN_ENDPOINT").map_err(|_| InvalidEndPoint { _priv: () })?;
12✔
558
        let mut endpoint: EndPoint = endpoint
10✔
559
            .parse()
560
            .map_err(|_| InvalidEndPoint { _priv: () })?;
2✔
561
        if let Ok(is_internal) = std::env::var("ALIYUN_OSS_INTERNAL") {
8✔
562
            if is_internal == TRUE1
5✔
563
                || is_internal == TRUE2
4✔
564
                || is_internal == TRUE3
3✔
565
                || is_internal == TRUE4
2✔
566
            {
567
                endpoint.set_internal(true);
4✔
568
            }
569
        }
8✔
570

571
        Ok(endpoint)
8✔
572
    }
11✔
573

574
    /// # 调整 API 指向是否为内网
575
    ///
576
    /// 当在 Aliyun ECS 上执行时,设为 true 会更高效,默认是 false
577
    pub fn set_internal(&mut self, is_internal: bool) {
15✔
578
        self.is_internal = is_internal;
15✔
579
    }
15✔
580

581
    /// 转化成 Url
582
    /// ```
583
    /// # use aliyun_oss_client::types::EndPoint;
584
    /// use reqwest::Url;
585
    /// let mut endpoint = EndPoint::new("shanghai").unwrap();
586
    /// assert_eq!(
587
    ///     endpoint.to_url(),
588
    ///     Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap()
589
    /// );
590
    ///
591
    /// endpoint.set_internal(true);
592
    /// assert_eq!(
593
    ///     endpoint.to_url(),
594
    ///     Url::parse("https://oss-cn-shanghai-internal.aliyuncs.com").unwrap()
595
    /// );
596
    /// ```
597
    pub fn to_url(&self) -> Url {
51✔
598
        let mut url = String::from(OSS_DOMAIN_PREFIX);
51✔
599
        url.push_str(self.as_ref());
51✔
600

601
        // internal
602
        if self.is_internal {
51✔
603
            url.push_str(OSS_INTERNAL);
9✔
604
        }
605

606
        url.push_str(OSS_DOMAIN_MAIN);
51✔
607
        Url::parse(&url).unwrap_or_else(|_| panic!("covert to url failed, endpoint: {}", url))
51✔
608
    }
51✔
609
}
610

611
/// 无效的可用区
612
#[derive(PartialEq, Eq, Hash)]
2✔
613
#[non_exhaustive]
614
pub struct InvalidEndPoint {
615
    pub(crate) _priv: (),
1✔
616
}
617

618
impl Debug for InvalidEndPoint {
619
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
620
        f.debug_struct("InvalidEndPoint").finish()
2✔
621
    }
2✔
622
}
623

624
impl Error for InvalidEndPoint {}
625

626
impl fmt::Display for InvalidEndPoint {
627
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7✔
628
        write!(
7✔
629
            f,
630
            "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix"
631
        )
632
    }
7✔
633
}
634

635
impl PartialEq<&str> for EndPoint {
636
    /// # 相等比较
637
    /// ```
638
    /// # use aliyun_oss_client::types::EndPoint;
639
    /// let e: EndPoint = String::from("qingdao").try_into().unwrap();
640
    /// assert!(e == "cn-qingdao");
641
    /// ```
642
    #[inline]
643
    fn eq(&self, other: &&str) -> bool {
1✔
644
        &self.as_ref() == other
1✔
645
    }
1✔
646
}
647

648
impl PartialEq<EndPoint> for &str {
649
    /// # 相等比较
650
    /// ```
651
    /// # use aliyun_oss_client::types::EndPoint;
652
    /// let e: EndPoint = String::from("qingdao").try_into().unwrap();
653
    /// assert!("cn-qingdao" == e);
654
    /// ```
655
    #[inline]
656
    fn eq(&self, other: &EndPoint) -> bool {
1✔
657
        self == &other.as_ref()
1✔
658
    }
1✔
659
}
660

661
impl PartialEq<Url> for EndPoint {
662
    /// # 相等比较
663
    /// ```
664
    /// # use aliyun_oss_client::types::EndPoint;
665
    /// use reqwest::Url;
666
    /// let endpoint = EndPoint::new("shanghai").unwrap();
667
    /// assert!(endpoint == Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap());
668
    /// ```
669
    #[inline]
670
    fn eq(&self, other: &Url) -> bool {
1✔
671
        &self.to_url() == other
1✔
672
    }
1✔
673
}
674

675
//===================================================================================================
676

677
/// 存储 bucket 名字的类型
678
#[derive(Clone, Debug, PartialEq, Eq)]
136✔
679
pub struct BucketName(Cow<'static, str>);
68✔
680

681
impl AsRef<str> for BucketName {
682
    fn as_ref(&self) -> &str {
142✔
683
        &self.0
142✔
684
    }
142✔
685
}
686

687
impl Display for BucketName {
688
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44✔
689
        write!(f, "{}", self.0)
44✔
690
    }
44✔
691
}
692

693
impl Default for BucketName {
694
    fn default() -> BucketName {
104✔
695
        #[allow(clippy::unwrap_used)]
696
        BucketName::new("a").unwrap()
104✔
697
    }
104✔
698
}
699

700
// impl TryInto<HeaderValue> for BucketName {
701
//     type Error = InvalidHeaderValue;
702
//     fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
703
//         HeaderValue::from_str(self.as_ref())
704
//     }
705
// }
706
impl TryFrom<String> for BucketName {
707
    type Error = InvalidBucketName;
708
    /// ```
709
    /// # use aliyun_oss_client::types::BucketName;
710
    /// let b: BucketName = String::from("abc").try_into().unwrap();
711
    /// assert_eq!(b, BucketName::new("abc").unwrap());
712
    /// ```
713
    fn try_from(s: String) -> Result<Self, Self::Error> {
1✔
714
        Self::new(s)
1✔
715
    }
1✔
716
}
717

718
impl<'a> TryFrom<&'a str> for BucketName {
719
    type Error = InvalidBucketName;
720
    /// ```
721
    /// # use aliyun_oss_client::types::BucketName;
722
    /// let b: BucketName = "abc".try_into().unwrap();
723
    /// assert_eq!(b, BucketName::new("abc").unwrap());
724
    /// ```
725
    fn try_from(bucket: &'a str) -> Result<Self, Self::Error> {
40✔
726
        Self::from_static(bucket)
40✔
727
    }
40✔
728
}
729

730
impl FromStr for BucketName {
731
    type Err = InvalidBucketName;
732
    /// ```
733
    /// # use aliyun_oss_client::types::BucketName;
734
    /// let b: BucketName = "abc".parse().unwrap();
735
    /// assert_eq!(b, BucketName::new("abc").unwrap());
736
    /// ```
737
    fn from_str(s: &str) -> Result<Self, Self::Err> {
80✔
738
        Self::from_static(s)
80✔
739
    }
80✔
740
}
741

742
impl<'a> BucketName {
743
    /// Creates a new `BucketName` from the given string.
744
    /// 只允许小写字母、数字、短横线(-),且不能以短横线开头或结尾
745
    ///
746
    /// ```
747
    /// # use aliyun_oss_client::types::BucketName;
748
    ///
749
    /// assert!(BucketName::new("").is_err());
750
    /// assert!(BucketName::new("abc").is_ok());
751
    /// assert!(BucketName::new("abc-").is_err());
752
    /// assert!(BucketName::new("-abc").is_err());
753
    /// assert!(BucketName::new("abc-def234ab").is_ok());
754
    /// assert!(BucketName::new("abc-def*#$%^ab").is_err());
755
    /// ```
756
    pub fn new(bucket: impl Into<Cow<'static, str>>) -> Result<Self, InvalidBucketName> {
130✔
757
        let bucket = bucket.into();
130✔
758

759
        if bucket.is_empty() || bucket.starts_with('-') || bucket.ends_with('-') {
130✔
760
            return Err(InvalidBucketName { _priv: () });
3✔
761
        }
762

763
        if !bucket.chars().all(valid_oss_character) {
127✔
764
            return Err(InvalidBucketName { _priv: () });
1✔
765
        }
766

767
        Ok(Self(bucket))
126✔
768
    }
130✔
769

770
    /// use env init BucketName
771
    pub fn from_env() -> Result<Self, InvalidBucketName> {
4✔
772
        let string = std::env::var("ALIYUN_BUCKET").map_err(|_| InvalidBucketName { _priv: () })?;
5✔
773

774
        string.parse()
3✔
775
    }
4✔
776

777
    /// Const function that creates a new `BucketName` from a static str.
778
    /// ```
779
    /// # use aliyun_oss_client::types::BucketName;
780
    ///
781
    /// assert!(BucketName::from_static("").is_err());
782
    /// assert!(BucketName::from_static("abc").is_ok());
783
    /// assert!(BucketName::from_static("abc-").is_err());
784
    /// assert!(BucketName::from_static("-abc").is_err());
785
    /// assert!(BucketName::from_static("abc-def234ab").is_ok());
786
    /// assert!(BucketName::from_static("abc-def*#$%^ab").is_err());
787
    /// ```
788
    pub fn from_static(bucket: &'a str) -> Result<Self, InvalidBucketName> {
177✔
789
        if bucket.is_empty() || bucket.starts_with('-') || bucket.ends_with('-') {
177✔
790
            return Err(InvalidBucketName { _priv: () });
12✔
791
        }
792

793
        if !bucket.chars().all(valid_oss_character) {
165✔
794
            return Err(InvalidBucketName { _priv: () });
1✔
795
        }
796

797
        Ok(Self(Cow::Owned(bucket.to_owned())))
164✔
798
    }
177✔
799

800
    /// # Safety
801
    pub const unsafe fn from_static2(bucket: &'static str) -> Self {
3✔
802
        Self(Cow::Borrowed(bucket))
3✔
803
    }
3✔
804
}
805

806
fn valid_oss_character(c: char) -> bool {
905✔
807
    match c {
19✔
808
        _ if c.is_ascii_lowercase() => true,
905✔
809
        _ if c.is_numeric() => true,
107✔
810
        '-' => true,
15✔
811
        _ => false,
4✔
812
    }
813
}
905✔
814

815
impl PartialEq<&str> for BucketName {
816
    /// 相等比较
817
    /// ```
818
    /// # use aliyun_oss_client::types::BucketName;
819
    /// let path = BucketName::new("abc").unwrap();
820
    /// assert!(path == "abc");
821
    /// ```
822
    #[inline]
823
    fn eq(&self, other: &&str) -> bool {
2✔
824
        &self.0 == other
2✔
825
    }
2✔
826
}
827

828
impl PartialEq<BucketName> for &str {
829
    /// 相等比较
830
    /// ```
831
    /// # use aliyun_oss_client::types::BucketName;
832
    /// let path = BucketName::new("abc").unwrap();
833
    /// assert!("abc" == path);
834
    /// ```
835
    #[inline]
836
    fn eq(&self, other: &BucketName) -> bool {
5✔
837
        self == &other.0
5✔
838
    }
5✔
839
}
840

841
/// 无效的 bucket 名称
842
#[derive(PartialEq)]
2✔
843
#[non_exhaustive]
844
pub struct InvalidBucketName {
845
    pub(crate) _priv: (),
1✔
846
}
847

848
impl Debug for InvalidBucketName {
849
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
850
        f.debug_struct("InvalidBucketName").finish()
2✔
851
    }
2✔
852
}
853

854
impl Error for InvalidBucketName {}
855

856
impl fmt::Display for InvalidBucketName {
857
    /// ```
858
    /// # use aliyun_oss_client::types::BucketName;
859
    ///
860
    /// let err = BucketName::from_static("").unwrap_err();
861
    /// assert_eq!(
862
    ///     format!("{}", err),
863
    ///     "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix"
864
    /// );
865
    /// ```
866
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5✔
867
        write!(
5✔
868
            f,
869
            "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix"
870
        )
871
    }
5✔
872
}
873

874
//===================================================================================================
875

876
/// aliyun OSS 的配置 ContentMd5
877
#[derive(Clone, Debug, PartialEq, Eq, Default)]
132✔
878
pub struct InnerContentMd5<'a>(Cow<'a, str>);
66✔
879
/// 静态作用域的 InnerContentMd5
880
pub type ContentMd5 = InnerContentMd5<'static>;
881

882
impl AsRef<str> for InnerContentMd5<'_> {
883
    fn as_ref(&self) -> &str {
65✔
884
        &self.0
65✔
885
    }
65✔
886
}
887

888
impl Display for InnerContentMd5<'_> {
889
    /// ```
890
    /// # use aliyun_oss_client::types::ContentMd5;
891
    /// let md5 = ContentMd5::new("abc");
892
    /// assert_eq!(format!("{md5}"), "abc");
893
    /// ```
894
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
895
        write!(f, "{}", self.0)
2✔
896
    }
2✔
897
}
898

899
impl TryInto<HeaderValue> for InnerContentMd5<'_> {
900
    type Error = InvalidHeaderValue;
901
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
902
        HeaderValue::from_str(self.as_ref())
1✔
903
    }
1✔
904
}
905

906
impl TryInto<HeaderValue> for &InnerContentMd5<'_> {
907
    type Error = InvalidHeaderValue;
908
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
4✔
909
        HeaderValue::from_str(self.as_ref())
4✔
910
    }
4✔
911
}
912
impl From<String> for ContentMd5 {
913
    /// ```
914
    /// # use aliyun_oss_client::types::ContentMd5;
915
    /// let md5: ContentMd5 = String::from("abc").into();
916
    /// assert_eq!(format!("{md5}"), "abc");
917
    /// ```
918
    fn from(s: String) -> Self {
1✔
919
        Self(Cow::Owned(s))
1✔
920
    }
1✔
921
}
922

923
impl<'a: 'b, 'b> From<&'a str> for InnerContentMd5<'b> {
924
    fn from(value: &'a str) -> Self {
6✔
925
        Self(Cow::Borrowed(value))
6✔
926
    }
6✔
927
}
928

929
impl<'a> InnerContentMd5<'a> {
930
    /// Creates a new `ContentMd5` from the given string.
931
    pub fn new(val: impl Into<Cow<'a, str>>) -> Self {
1✔
932
        Self(val.into())
1✔
933
    }
1✔
934

935
    /// Const function that creates a new `ContentMd5` from a static str.
936
    pub const fn from_static(val: &'static str) -> Self {
1✔
937
        Self(Cow::Borrowed(val))
1✔
938
    }
1✔
939
}
940

941
//===================================================================================================
942

943
/// aliyun OSS 的配置 ContentType
944
#[derive(Clone, Debug, PartialEq, Eq, Default)]
132✔
945
pub struct ContentType(Cow<'static, str>);
66✔
946

947
impl AsRef<str> for ContentType {
948
    fn as_ref(&self) -> &str {
60✔
949
        &self.0
60✔
950
    }
60✔
951
}
952

953
impl Display for ContentType {
954
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
955
        write!(f, "{}", self.0)
2✔
956
    }
2✔
957
}
958

959
impl TryInto<HeaderValue> for ContentType {
960
    type Error = InvalidHeaderValue;
961
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
962
        HeaderValue::from_str(self.as_ref())
1✔
963
    }
1✔
964
}
965
impl TryFrom<HeaderValue> for ContentType {
966
    type Error = ToStrError;
967
    fn try_from(value: HeaderValue) -> Result<Self, Self::Error> {
15✔
968
        Ok(Self(Cow::Owned(value.to_str()?.to_owned())))
15✔
969
    }
15✔
970
}
971
impl From<String> for ContentType {
972
    fn from(s: String) -> Self {
1✔
973
        Self(Cow::Owned(s))
1✔
974
    }
1✔
975
}
976

977
impl ContentType {
978
    /// Creates a new `ContentMd5` from the given string.
979
    pub fn new(val: impl Into<Cow<'static, str>>) -> Self {
1✔
980
        Self(val.into())
1✔
981
    }
1✔
982

983
    /// Const function that creates a new `ContentMd5` from a static str.
984
    pub const fn from_static(val: &'static str) -> Self {
1✔
985
        Self(Cow::Borrowed(val))
1✔
986
    }
1✔
987
}
988

989
//===================================================================================================
990

991
/// 用于计算签名的 Date
992
#[derive(Clone, Debug, PartialEq, Eq, Default)]
314✔
993
pub struct InnerDate<'a>(Cow<'a, str>);
157✔
994
/// 静态作用域的 InnerDate
995
pub type Date = InnerDate<'static>;
996

997
impl AsRef<str> for InnerDate<'_> {
998
    fn as_ref(&self) -> &str {
120✔
999
        &self.0
120✔
1000
    }
120✔
1001
}
1002

1003
impl Display for InnerDate<'_> {
1004
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1✔
1005
        write!(f, "{}", self.0)
1✔
1006
    }
1✔
1007
}
1008

1009
impl TryInto<HeaderValue> for InnerDate<'_> {
1010
    type Error = InvalidHeaderValue;
1011
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
1012
        HeaderValue::from_str(self.as_ref())
1✔
1013
    }
1✔
1014
}
1015

1016
impl<Tz: TimeZone> From<DateTime<Tz>> for Date
1017
where
1018
    Tz::Offset: fmt::Display,
1019
{
1020
    fn from(d: DateTime<Tz>) -> Self {
58✔
1021
        Self(Cow::Owned(d.format("%a, %d %b %Y %T GMT").to_string()))
58✔
1022
    }
58✔
1023
}
1024

1025
impl<'a> InnerDate<'a> {
1026
    /// # Safety
1027
    /// Const function that creates a new `Date` from a static str.
1028
    pub const unsafe fn from_static(val: &'static str) -> Self {
5✔
1029
        Self(Cow::Borrowed(val))
5✔
1030
    }
5✔
1031
}
1032

1033
//===================================================================================================
1034

1035
/// 计算方式,参考 [aliyun 文档](https://help.aliyun.com/document_detail/31951.htm)
1036
#[derive(Clone, Debug, PartialEq, Eq)]
102✔
1037
pub struct InnerCanonicalizedResource<'a>(Cow<'a, str>);
51✔
1038
/// 静态作用域的 InnerCanonicalizedResource
1039
pub type CanonicalizedResource = InnerCanonicalizedResource<'static>;
1040

1041
impl AsRef<str> for InnerCanonicalizedResource<'_> {
1042
    fn as_ref(&self) -> &str {
135✔
1043
        &self.0
135✔
1044
    }
135✔
1045
}
1046

1047
impl Display for InnerCanonicalizedResource<'_> {
1048
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2✔
1049
        write!(f, "{}", self.0)
2✔
1050
    }
2✔
1051
}
1052

1053
impl TryInto<HeaderValue> for InnerCanonicalizedResource<'_> {
1054
    type Error = InvalidHeaderValue;
1055
    fn try_into(self) -> Result<HeaderValue, InvalidHeaderValue> {
1✔
1056
        HeaderValue::from_str(self.as_ref())
1✔
1057
    }
1✔
1058
}
1059
impl From<String> for CanonicalizedResource {
1060
    fn from(s: String) -> Self {
1✔
1061
        Self(Cow::Owned(s))
1✔
1062
    }
1✔
1063
}
1064

1065
impl<'a: 'b, 'b> From<&'a str> for InnerCanonicalizedResource<'b> {
1066
    fn from(value: &'a str) -> Self {
8✔
1067
        Self(Cow::Borrowed(value))
8✔
1068
    }
8✔
1069
}
1070

1071
impl Default for InnerCanonicalizedResource<'_> {
1072
    fn default() -> Self {
145✔
1073
        InnerCanonicalizedResource(Cow::Owned("/".to_owned()))
145✔
1074
    }
145✔
1075
}
1076

1077
#[cfg(any(feature = "core", feature = "auth"))]
1078
pub(crate) const CONTINUATION_TOKEN: &str = "continuation-token";
1079
#[cfg(any(feature = "core", feature = "auth"))]
1080
pub(crate) const BUCKET_INFO: &str = "bucketInfo";
1081
#[cfg(any(feature = "core", feature = "auth"))]
1082
const QUERY_KEYWORD: [&str; 2] = ["acl", BUCKET_INFO];
1083

1084
impl<'a> InnerCanonicalizedResource<'a> {
1085
    /// Creates a new `CanonicalizedResource` from the given string.
1086
    pub fn new(val: impl Into<Cow<'a, str>>) -> Self {
99✔
1087
        Self(val.into())
99✔
1088
    }
99✔
1089

1090
    /// 只有 endpoint ,而没有 bucket 的时候
1091
    #[inline(always)]
1092
    pub fn from_endpoint() -> Self {
1✔
1093
        Self::default()
1✔
1094
    }
1✔
1095

1096
    /// Const function that creates a new `CanonicalizedResource` from a static str.
1097
    pub const fn from_static(val: &'static str) -> Self {
1✔
1098
        Self(Cow::Borrowed(val))
1✔
1099
    }
1✔
1100

1101
    /// 获取 bucket 的签名参数
1102
    #[cfg(feature = "core")]
1103
    pub fn from_bucket<B: AsRef<BucketName>>(bucket: B, query: Option<&str>) -> Self {
7✔
1104
        match query {
7✔
1105
            Some(q) => {
6✔
1106
                if QUERY_KEYWORD.iter().any(|&str| str == q) {
18✔
1107
                    return Self::new(format!("/{}/?{}", bucket.as_ref().as_ref(), q));
5✔
1108
                }
1109

1110
                Self::new(format!("/{}/", bucket.as_ref().as_ref()))
1✔
1111
            }
1112
            None => Self::default(),
1✔
1113
        }
1114
    }
7✔
1115

1116
    /// 获取 bucket 的签名参数
1117
    #[cfg(feature = "auth")]
1118
    pub fn from_bucket_name(bucket: &BucketName, query: Option<&str>) -> Self {
7✔
1119
        match query {
7✔
1120
            Some(q) => {
6✔
1121
                if QUERY_KEYWORD.iter().any(|&str| str == q) {
18✔
1122
                    return Self::new(format!("/{}/?{}", bucket.as_ref(), q));
5✔
1123
                }
1124

1125
                Self::new(format!("/{}/", bucket.as_ref()))
1✔
1126
            }
1127
            None => Self::default(),
1✔
1128
        }
1129
    }
7✔
1130

1131
    /// 获取 bucket 的签名参数
1132
    /// 带查询条件的
1133
    ///
1134
    /// 如果查询条件中有翻页的话,则忽略掉其他字段
1135
    #[cfg(feature = "core")]
1136
    #[inline]
1137
    pub fn from_bucket_query<B: AsRef<BucketName>>(bucket: B, query: &Query) -> Self {
10✔
1138
        Self::from_bucket_query2(bucket.as_ref(), query)
10✔
1139
    }
10✔
1140

1141
    #[cfg(feature = "core")]
1142
    #[doc(hidden)]
1143
    pub fn from_bucket_query2(bucket: &BucketName, query: &Query) -> Self {
13✔
1144
        match query.get(QueryKey::CONTINUATION_TOKEN) {
13✔
1145
            Some(v) => Self::new(format!(
3✔
1146
                "/{}/?continuation-token={}",
1147
                bucket.as_ref(),
1✔
1148
                v.as_ref()
1✔
1149
            )),
1150
            None => Self::new(format!("/{}/", bucket.as_ref())),
12✔
1151
        }
1152
    }
13✔
1153

1154
    /// 根据 OSS 存储对象(Object)查询签名参数
1155
    #[cfg(feature = "core")]
1156
    pub(crate) fn from_object<
14✔
1157
        Q: IntoIterator<Item = (QueryKey, QueryValue)>,
1158
        B: AsRef<str>,
1159
        P: AsRef<str>,
1160
    >(
1161
        (bucket, path): (B, P),
14✔
1162
        query: Q,
1163
    ) -> Self {
1164
        let query = Query::from_iter(query);
14✔
1165
        if query.is_empty() {
14✔
1166
            Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref()))
13✔
1167
        } else {
1168
            Self::new(format!(
3✔
1169
                "/{}/{}?{}",
1170
                bucket.as_ref(),
1✔
1171
                path.as_ref(),
1✔
1172
                query.to_url_query()
1✔
1173
            ))
1174
        }
1175
    }
14✔
1176

1177
    pub(crate) fn from_object_str(bucket: &str, path: &str) -> Self {
41✔
1178
        Self::new(format!("/{}/{}", bucket, path))
41✔
1179
    }
41✔
1180

1181
    #[cfg(feature = "auth")]
1182
    pub(crate) fn from_object_without_query<B: AsRef<str>, P: AsRef<str>>(
1✔
1183
        bucket: B,
1184
        path: P,
1185
    ) -> Self {
1186
        Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref()))
1✔
1187
    }
1✔
1188
}
1189

1190
impl PartialEq<&str> for InnerCanonicalizedResource<'_> {
1191
    /// # 相等比较
1192
    /// ```
1193
    /// # use aliyun_oss_client::types::CanonicalizedResource;
1194
    /// let res = CanonicalizedResource::new("abc");
1195
    /// assert!(res == "abc");
1196
    /// ```
1197
    #[inline]
1198
    fn eq(&self, other: &&str) -> bool {
21✔
1199
        &self.0 == other
21✔
1200
    }
21✔
1201
}
1202

1203
impl PartialEq<InnerCanonicalizedResource<'_>> for &str {
1204
    /// # 相等比较
1205
    /// ```
1206
    /// # use aliyun_oss_client::types::CanonicalizedResource;
1207
    /// let res = CanonicalizedResource::new("abc");
1208
    /// assert!("abc" == res);
1209
    /// ```
1210
    #[inline]
1211
    fn eq(&self, other: &InnerCanonicalizedResource<'_>) -> bool {
1✔
1212
        self == &other.0
1✔
1213
    }
1✔
1214
}
1215

1216
/// 根据 endpoint, bucket, path 获取接口信息
1217
pub fn get_url_resource(
32✔
1218
    endpoint: &EndPoint,
1219
    bucket: &BucketName,
1220
    path: &ObjectPathInner,
1221
) -> (Url, CanonicalizedResource) {
1222
    let mut url = url_from_bucket(endpoint, bucket);
32✔
1223
    url.set_object_path(path);
32✔
1224

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

1227
    (url, resource)
32✔
1228
}
32✔
1229

1230
/// 根据 endpoint, bucket, path 获取接口信息
1231
pub fn get_url_resource2<E: AsRef<EndPoint>, B: AsRef<BucketName>>(
32✔
1232
    endpoint: E,
1233
    bucket: B,
1234
    path: &ObjectPathInner,
1235
) -> (Url, CanonicalizedResource) {
1236
    get_url_resource(endpoint.as_ref(), bucket.as_ref(), path)
32✔
1237
}
32✔
1238

1239
pub(crate) fn url_from_bucket(endpoint: &EndPoint, bucket: &BucketName) -> Url {
38✔
1240
    let url = format!(
76✔
1241
        "https://{}.oss-{}.aliyuncs.com",
1242
        bucket.as_ref(),
38✔
1243
        endpoint.as_ref()
38✔
1244
    );
1245
    url.parse().unwrap_or_else(|_| {
38✔
1246
        unreachable!("covert to url failed, bucket: {bucket}, endpoint: {endpoint}")
×
1247
    })
1248
}
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