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

tu6ge / oss-rs / 5829505807

pending completion
5829505807

push

github

tu6ge
Support std IO (#26)

* feat(decode)!: change init object fn

* todo

* feat(error): OssError add more info

when OssError code is SignatureDoesNotMatch ,show expect
 sign string

* feat(io): support write

* feat: blocking support

* feat: blocking read

* feat: 允许读取的数据于目标数组长度不一致

* feat: 分离 Rc 和内部数据

* feat: support Arc Object Content

* feat: 解决多次写入少量数据导致oss错误的问题

当多次写入少量数据,不符合分片的最小数量时,调用 oss 接口会导致报错

* refactor

* feat: 交互 arc 与 rc 的位置

* docs(io)

* docs(io)

* style

* chore: default close blocking

* fix

* style

* feat(io): change seek

* feat(io): change error type

* style

* feat(bucket)!: change base_bucket_info

* test(io)

* test(doc): remove deprecated

* test(io)

* test(io)

* test(io)

* style(io): clippy

* chore: support more derive

* refactor

* docs

1293 of 1293 new or added lines in 19 files covered. (100.0%)

7298 of 7685 relevant lines covered (94.96%)

9.62 hits per line

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

91.01
/src/object/content.rs
1
//! # 读写 object 内容 (实现 std Write/Read)
9✔
2
//!
3
//! ## 写入数据
4
//! ```rust,no_run
5
//! # use aliyun_oss_client::{Client, object::Content};
6
//! # use std::sync::Arc;
7
//! use std::io::Write;
8
//! fn main() -> std::io::Result<()> {
9
//!     dotenv::dotenv().ok();
10
//!     let client = Client::from_env().unwrap();
11
//!
12
//!     let mut object = Content::from_client(Arc::new(client)).path("path1.txt").unwrap();
13
//!     object.write(b"abc")?;
14
//!     object.flush();
15
//!
16
//!     Ok(())
17
//! }
18
//! ```
19
//!
20
//! ## 读取数据
21
//! ```rust,no_run
22
//! # use aliyun_oss_client::{Client, Query};
23
//! use aliyun_oss_client::object::content::List;
24
//! use std::io::Read;
25
//! #[tokio::main]
26
//! async fn main() -> std::io::Result<()> {
27
//!     dotenv::dotenv().ok();
28
//!     let client = Client::from_env().unwrap();
29
//!
30
//!     let list: List = client
31
//!         .get_custom_object(&Query::default())
32
//!         .await
33
//!         .unwrap();
34
//!     let mut vec = list.to_vec();
35
//!     let mut object = &mut vec[0];
36
//!     let mut buffer = [0; 10];
37
//!     object.read(&mut buffer)?;
38
//!
39
//!     println!("{:?}", buffer);
40
//!
41
//!     Ok(())
42
//! }
43
//! ```
44

45
use std::{
46
    error::Error,
47
    fmt::Display,
48
    io::{Read, Result as IoResult, Seek, SeekFrom, Write},
49
    ops::{Deref, DerefMut},
50
    sync::Arc,
51
};
52

53
use futures::executor::block_on;
54
use http::{header::CONTENT_LENGTH, HeaderValue, Method};
55
use url::Url;
56

57
use crate::{
58
    builder::BuilderError,
59
    decode::RefineObject,
60
    file::{AlignBuilder, DEFAULT_CONTENT_TYPE},
61
    types::{
62
        object::{InvalidObjectPath, SetObjectPath},
63
        CanonicalizedResource,
64
    },
65
    Client, ObjectPath,
66
};
67

68
use super::{BuildInItemError, InitObject, Objects};
69

70
#[cfg(feature = "blocking")]
71
pub mod blocking;
72

73
#[cfg(not(test))]
74
use crate::file::Files;
75
#[cfg(test)]
76
use mock::Files;
77

78
#[cfg(test)]
79
mod mock;
80
#[cfg(test)]
81
mod test_suite;
82
#[cfg(all(test, feature = "blocking"))]
83
mod test_suite_block;
84

85
/// # object 内容
86
/// [OSS 分片上传文档](https://help.aliyun.com/zh/oss/user-guide/multipart-upload-12)
87
#[derive(Default)]
28✔
88
pub struct Content {
89
    client: Arc<Client>,
14✔
90
    inner: Inner,
14✔
91
}
92

93
/// # 内部
94
#[derive(Debug, Clone, PartialEq, Eq)]
8✔
95
pub struct Inner {
96
    path: ObjectPath,
4✔
97
    content_size: u64,
4✔
98
    current_pos: u64,
4✔
99
    content_part: Vec<Vec<u8>>,
4✔
100
    content_type: &'static str,
4✔
101
    upload_id: String,
4✔
102
    /// 分片上传返回的 etag
103
    etag_list: Vec<(u16, HeaderValue)>,
4✔
104
    part_size: usize,
4✔
105
}
106

107
impl Write for Content {
108
    // 写入缓冲区
109
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
7✔
110
        self.inner.write(buf)
7✔
111
    }
7✔
112

113
    // 按分片数量选择上传 OSS 的方式
114
    fn flush(&mut self) -> IoResult<()> {
×
115
        let len = self.content_part.len();
×
116

117
        //println!("len: {}", len);
118

119
        if len == 0 {
×
120
            return Ok(());
×
121
        }
122
        if len == 1 {
×
123
            return block_on(self.upload());
×
124
        }
125

126
        block_on(self.upload_multi())
×
127
    }
×
128
}
129

130
impl Read for Content {
131
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
4✔
132
        let len = buf.len();
4✔
133
        if len as u64 > Inner::MAX_SIZE {
4✔
134
            return Err(ContentError::new(ContentErrorKind::OverflowMaxSize).into());
1✔
135
        }
136

137
        let end = self.current_pos + len as u64;
3✔
138
        let vec = block_on(
3✔
139
            self.client
6✔
140
                .get_object(self.path.clone(), self.current_pos..end - 1),
3✔
141
        )?;
×
142

143
        let len = vec.len().min(buf.len());
3✔
144
        buf[..len].copy_from_slice(&vec[..len]);
3✔
145

146
        Ok(len)
3✔
147
    }
4✔
148
}
149

150
impl Seek for Content {
151
    fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
×
152
        self.inner.seek(pos)
×
153
    }
×
154
}
155

156
impl Deref for Content {
157
    type Target = Inner;
158
    fn deref(&self) -> &Self::Target {
69✔
159
        &self.inner
160
    }
69✔
161
}
162

163
impl DerefMut for Content {
164
    fn deref_mut(&mut self) -> &mut Inner {
61✔
165
        &mut self.inner
166
    }
61✔
167
}
168

169
/// 带内容的 object 列表
170
pub type List = Objects<Content>;
171

172
impl InitObject<Content> for List {
173
    fn init_object(&mut self) -> Option<Content> {
1✔
174
        Some(Content {
1✔
175
            client: self.client(),
1✔
176
            ..Default::default()
1✔
177
        })
178
    }
1✔
179
}
180

181
impl Content {
182
    /// 从 client 创建
183
    pub fn from_client(client: Arc<Client>) -> Content {
9✔
184
        Content {
9✔
185
            client,
186
            ..Default::default()
9✔
187
        }
188
    }
9✔
189
    /// 设置 ObjectPath
190
    pub fn path<P>(mut self, path: P) -> Result<Self, InvalidObjectPath>
9✔
191
    where
192
        P: TryInto<ObjectPath>,
193
        P::Error: Into<InvalidObjectPath>,
194
    {
195
        self.path = path.try_into().map_err(Into::into)?;
9✔
196
        self.content_type_with_path();
9✔
197
        Ok(self)
9✔
198
    }
9✔
199

200
    fn part_canonicalized(&self, query: &str) -> (Url, CanonicalizedResource) {
9✔
201
        let mut url = self.client.get_bucket_url();
9✔
202
        url.set_object_path(&self.path);
9✔
203
        url.set_query(Some(query));
9✔
204

205
        let bucket = self.client.get_bucket_name();
9✔
206
        (
9✔
207
            url,
9✔
208
            CanonicalizedResource::new(format!("/{}/{}?{}", bucket, self.path.as_ref(), query)),
9✔
209
        )
210
    }
9✔
211
    async fn upload(&mut self) -> IoResult<()> {
2✔
212
        let content = self.content_part.pop().expect("content_part len is not 1");
1✔
213
        self.client
4✔
214
            .put_content_base(content, self.content_type, self.path.clone())
1✔
215
            .await
1✔
216
            .map(|_| ())
1✔
217
            .map_err(Into::into)
218
    }
2✔
219

220
    async fn upload_multi(&mut self) -> IoResult<()> {
3✔
221
        self.init_multi().await?;
1✔
222

223
        let mut i = 1;
1✔
224
        let mut size: u64 = 0;
1✔
225
        self.content_part.reverse();
1✔
226
        while let Some(item) = self.content_part.pop() {
3✔
227
            size += item.len() as u64;
2✔
228
            self.upload_part(i, item).await?;
2✔
229
            i += 1;
2✔
230
        }
231

232
        self.complete_multi().await?;
1✔
233
        self.content_size = size;
1✔
234
        Ok(())
1✔
235
    }
2✔
236

237
    /// 初始化批量上传
238
    async fn init_multi(&mut self) -> Result<(), ContentError> {
4✔
239
        const UPLOADS: &str = "uploads";
240

241
        let (url, resource) = self.part_canonicalized(UPLOADS);
2✔
242
        let xml = self
12✔
243
            .client
244
            .builder(Method::POST, url, resource)?
2✔
245
            .send_adjust_error()
246
            .await?
2✔
247
            .text()
248
            .await?;
2✔
249

250
        self.parse_upload_id(&xml)
2✔
251
    }
4✔
252
    /// 上传分块
253
    async fn upload_part(&mut self, index: u16, buf: Vec<u8>) -> Result<(), ContentError> {
18✔
254
        const ETAG: &str = "ETag";
255

256
        if self.upload_id.is_empty() {
6✔
257
            return Err(ContentError::new(ContentErrorKind::NoFoundUploadId));
1✔
258
        }
259

260
        if self.etag_list.len() >= Inner::MAX_PARTS_COUNT as usize {
5✔
261
            return Err(ContentError::new(ContentErrorKind::OverflowMaxPartsCount));
1✔
262
        }
263
        if buf.len() > Inner::PART_SIZE_MAX {
4✔
264
            return Err(ContentError::new(ContentErrorKind::OverflowPartSize));
1✔
265
        }
266

267
        let (url, resource) =
3✔
268
            self.part_canonicalized(&format!("partNumber={}&uploadId={}", index, self.upload_id));
6✔
269

270
        let content_length = buf.len().to_string();
3✔
271
        let headers = vec![(
6✔
272
            CONTENT_LENGTH,
3✔
273
            HeaderValue::from_str(&content_length)
3✔
274
                .expect("content length must be a valid header value"),
275
        )];
276

277
        let resp = self
12✔
278
            .client
279
            .builder_with_header(Method::PUT, url, resource, headers)?
3✔
280
            .body(buf)
3✔
281
            .send_adjust_error()
282
            .await?;
3✔
283

284
        let etag = resp
6✔
285
            .headers()
286
            .get(ETAG)
287
            .ok_or(ContentError::new(ContentErrorKind::NoFoundEtag))?;
3✔
288

289
        //println!("etag: {:?}", etag);
290

291
        // 59A2A10DD1686F679EE885FC1EBA5183
292
        //let etag = &(etag.to_str().unwrap())[1..33];
293

294
        self.etag_list.push((index, etag.to_owned()));
3✔
295

296
        Ok(())
3✔
297
    }
12✔
298
    /// 完成分块上传
299
    async fn complete_multi(&mut self) -> Result<(), ContentError> {
10✔
300
        if self.upload_id.is_empty() {
4✔
301
            return Err(ContentError::new(ContentErrorKind::NoFoundUploadId));
2✔
302
        }
303

304
        let xml = self.etag_list_xml()?;
2✔
305

306
        let (url, resource) = self.part_canonicalized(&format!("uploadId={}", self.upload_id));
2✔
307

308
        let content_length = xml.len().to_string();
2✔
309
        let headers = vec![(
4✔
310
            CONTENT_LENGTH,
2✔
311
            HeaderValue::from_str(&content_length)
2✔
312
                .expect("content length must be a valid header value"),
313
        )];
314

315
        let _resp = self
8✔
316
            .client
317
            .builder_with_header(Method::POST, url, resource, headers)?
2✔
318
            .body(xml)
2✔
319
            .send_adjust_error()
320
            .await?;
2✔
321

322
        self.etag_list.clear();
2✔
323
        self.upload_id = String::default();
2✔
324

325
        Ok(())
2✔
326
    }
8✔
327
    /// 取消分块上传
328
    pub async fn abort_multi(&mut self) -> Result<(), ContentError> {
2✔
329
        if self.upload_id.is_empty() {
1✔
330
            return Err(ContentError::new(ContentErrorKind::NoFoundUploadId));
×
331
        }
332
        let query = format!("uploadId={}", self.upload_id);
1✔
333

334
        let (url, resource) = self.part_canonicalized(&query);
1✔
335
        let _resp = self
4✔
336
            .client
337
            .builder(Method::DELETE, url, resource)?
1✔
338
            .send_adjust_error()
339
            .await?;
1✔
340

341
        //println!("resp: {:?}", resp);
342
        self.etag_list.clear();
1✔
343
        self.upload_id = String::default();
1✔
344

345
        Ok(())
1✔
346
    }
2✔
347
}
348

349
impl Seek for Inner {
350
    fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
4✔
351
        use std::io::{Error, ErrorKind};
352
        let n = match pos {
4✔
353
            SeekFrom::Start(p) => p,
1✔
354
            SeekFrom::End(p) => {
2✔
355
                if self.content_size == 0 {
2✔
356
                    return Err(Error::new(ErrorKind::InvalidData, "content size is 0"));
1✔
357
                }
358
                // self.content_size - p
359
                i64::try_from(self.content_size)
2✔
360
                    .and_then(|v| u64::try_from(v - p))
2✔
361
                    .map_err(|e| Error::new(ErrorKind::InvalidData, e))?
×
362
            }
1✔
363
            // self.current_pos + n
364
            SeekFrom::Current(n) => i64::try_from(self.current_pos)
2✔
365
                .and_then(|v| u64::try_from(v + n))
2✔
366
                .map_err(|e| Error::new(ErrorKind::InvalidData, e))?,
1✔
367
        };
368
        self.current_pos = n;
3✔
369
        Ok(n)
3✔
370
    }
4✔
371
}
372

373
// impl Drop for Content {
374
//     fn drop(&mut self) {
375
//         if self.upload_id.is_empty() == false {
376
//             self.abort_multi();
377
//         }
378
//     }
379
// }
380

381
mod private {
382
    use super::Content;
383

384
    pub trait Sealed {}
385

386
    impl Sealed for Content {}
387

388
    #[cfg(feature = "blocking")]
389
    impl Sealed for super::blocking::Content {}
390
}
391

392
impl<T: DerefMut<Target = Inner> + private::Sealed> RefineObject<BuildInItemError> for T {
393
    #[inline]
394
    fn set_key(&mut self, key: &str) -> Result<(), BuildInItemError> {
395
        self.path = key.parse().map_err(|e| BuildInItemError::new(e, key))?;
396
        self.content_type_with_path();
397
        Ok(())
398
    }
399
    /// 提取 size
400
    fn set_size(&mut self, size: &str) -> Result<(), BuildInItemError> {
401
        if let Ok(size) = size.parse() {
402
            self.content_size = size;
403
        }
404
        Ok(())
405
    }
406
}
407

408
impl Default for Inner {
409
    fn default() -> Self {
40✔
410
        Self {
40✔
411
            path: ObjectPath::default(),
40✔
412
            content_size: u64::default(),
40✔
413
            current_pos: 0,
414
            content_part: Vec::default(),
40✔
415
            content_type: Self::DEFAULT_CONTENT_TYPE,
416
            upload_id: String::default(),
40✔
417
            etag_list: Vec::default(),
40✔
418
            part_size: 200 * 1024 * 1024, // 200M
419
        }
420
    }
40✔
421
}
422

423
fn get_content_type(filename: &str) -> &'static str {
41✔
424
    match filename.rsplit('.').next() {
41✔
425
        Some(str) => match str.to_lowercase().as_str() {
41✔
426
            "jpg" => "image/jpeg",
41✔
427
            "pdf" => "application/pdf",
40✔
428
            "png" => "image/png",
39✔
429
            "gif" => "image/gif",
38✔
430
            "bmp" => "image/bmp",
37✔
431
            "zip" => "application/zip",
36✔
432
            "tar" => "application/x-tar",
35✔
433
            "gz" => "application/gzip",
34✔
434
            "txt" => "text/plain",
33✔
435
            "mp3" => "audio/mpeg",
13✔
436
            "wav" => "audio/wave",
12✔
437
            "mp4" => "video/mp4",
11✔
438
            "mov" => "video/quicktime",
10✔
439
            "avi" => "video/x-msvideo",
9✔
440
            "wmv" => "video/x-ms-wmv",
8✔
441
            "html" => "text/html",
7✔
442
            "js" => "application/javascript",
5✔
443
            "css" => "text/css",
4✔
444
            "php" => "application/x-httpd-php",
3✔
445
            _ => DEFAULT_CONTENT_TYPE,
2✔
446
        },
41✔
447
        None => DEFAULT_CONTENT_TYPE,
×
448
    }
449
}
41✔
450

451
impl Inner {
452
    const DEFAULT_CONTENT_TYPE: &str = DEFAULT_CONTENT_TYPE;
453

454
    /// 最大存储容量 48.8 TB, 49664 = 1024 * 48.5
455
    #[cfg(not(test))]
456
    const MAX_SIZE: u64 = 1024 * 1024 * 1024 * 49_664;
457
    /// 最大 part 数量
458
    #[cfg(not(test))]
459
    const MAX_PARTS_COUNT: u16 = 10000;
460
    /// 单个 part 的最小尺寸 100K
461
    #[cfg(not(test))]
462
    const PART_SIZE_MIN: usize = 102400;
463
    /// 单个 part 的最大尺寸 5G
464
    #[cfg(not(test))]
465
    const PART_SIZE_MAX: usize = 1024 * 1024 * 1024 * 5;
466

467
    /// 最大存储容量 48.8 TB, 49664 = 1024 * 48.5
468
    #[cfg(test)]
469
    const MAX_SIZE: u64 = 200;
470
    /// 最大 part 数量
471
    #[cfg(test)]
472
    const MAX_PARTS_COUNT: u16 = 10;
473
    /// 单个 part 的最小尺寸 100K
474
    #[cfg(test)]
475
    const PART_SIZE_MIN: usize = 10;
476
    /// 单个 part 的最大尺寸 5G
477
    #[cfg(test)]
478
    const PART_SIZE_MAX: usize = 20;
479

480
    /// 设置 ObjectPath
481
    pub fn path<P>(mut self, path: P) -> Result<Self, InvalidObjectPath>
1✔
482
    where
483
        P: TryInto<ObjectPath>,
484
        P::Error: Into<InvalidObjectPath>,
485
    {
486
        self.path = path.try_into().map_err(Into::into)?;
1✔
487
        self.content_type_with_path();
1✔
488
        Ok(self)
1✔
489
    }
1✔
490

491
    fn content_type_with_path(&mut self) {
20✔
492
        self.content_type = get_content_type(self.path.as_ref());
20✔
493
    }
20✔
494

495
    /// 设置 content_type
496
    pub fn content_type(&mut self, content_type: &'static str) {
1✔
497
        self.content_type = content_type;
1✔
498
    }
1✔
499

500
    // 写入缓冲区
501
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
7✔
502
        let part_total = self.content_part.len();
7✔
503
        if part_total >= Inner::MAX_PARTS_COUNT as usize {
7✔
504
            return Err(ContentError::new(ContentErrorKind::OverflowMaxPartsCount).into());
×
505
        }
506

507
        let part_size = self.part_size;
7✔
508
        if let Some(part) = self.content_part.last_mut() {
7✔
509
            let part_len = part.len();
3✔
510
            if part_len < part_size {
3✔
511
                let mid = part_size - part_len;
2✔
512
                let left = &buf[..mid.min(buf.len())];
2✔
513

514
                part.append(&mut left.to_vec());
2✔
515

516
                return Ok(left.len());
2✔
517
            }
518
        }
519

520
        let con = &buf[..buf.len().min(self.part_size)];
5✔
521
        self.content_part.push({
10✔
522
            let mut vec = Vec::with_capacity(self.part_size);
5✔
523
            vec.extend(con);
5✔
524
            vec
5✔
525
        });
526

527
        Ok(con.len())
5✔
528
    }
7✔
529

530
    /// 设置分块的尺寸
531
    pub fn part_size(&mut self, size: usize) -> Result<(), ContentError> {
3✔
532
        if (Self::PART_SIZE_MIN..=Self::PART_SIZE_MAX).contains(&size) {
3✔
533
            self.part_size = size;
1✔
534
            Ok(())
1✔
535
        } else {
536
            Err(ContentError::new(ContentErrorKind::OverflowPartSize))
2✔
537
        }
538
    }
3✔
539
    fn parse_upload_id(&mut self, xml: &str) -> Result<(), ContentError> {
6✔
540
        if let (Some(start), Some(end)) = (xml.find("<UploadId>"), xml.find("</UploadId>")) {
6✔
541
            self.upload_id = (&xml[start + 10..end]).to_owned();
5✔
542
            Ok(())
5✔
543
        } else {
544
            Err(ContentError::new(ContentErrorKind::NoFoundUploadId))
1✔
545
        }
546
    }
6✔
547
    fn etag_list_xml(&self) -> Result<String, ContentError> {
6✔
548
        if self.etag_list.is_empty() {
6✔
549
            return Err(ContentError::new(ContentErrorKind::EtagListEmpty));
1✔
550
        }
551
        let mut list = String::new();
5✔
552
        for (index, etag) in self.etag_list.iter() {
15✔
553
            list.push_str(&format!(
20✔
554
                "<Part><PartNumber>{}</PartNumber><ETag>{}</ETag></Part>",
555
                index,
556
                etag.to_str().expect("etag covert str failed")
10✔
557
            ));
10✔
558
        }
559

560
        Ok(format!(
5✔
561
            "<CompleteMultipartUpload>{}</CompleteMultipartUpload>",
562
            list
563
        ))
564
    }
6✔
565

566
    /// 清空缓冲区
567
    pub fn part_clear(&mut self) {
4✔
568
        self.content_part.clear();
4✔
569
    }
4✔
570
}
571

572
impl From<Client> for Content {
573
    fn from(value: Client) -> Self {
×
574
        Content {
×
575
            client: Arc::new(value),
×
576
            ..Default::default()
×
577
        }
578
    }
×
579
}
580

581
/// object Content 的错误信息
582
#[derive(Debug)]
×
583
#[non_exhaustive]
584
pub struct ContentError {
585
    kind: ContentErrorKind,
×
586
}
587

588
/// object Content 的错误信息
589
#[derive(Debug)]
×
590
#[non_exhaustive]
591
enum ContentErrorKind {
592
    /// not found upload id
593
    NoFoundUploadId,
594

595
    /// builder request failed
596
    Builder(BuilderError),
×
597

598
    /// not found etag
599
    NoFoundEtag,
600

601
    /// overflow max parts count
602
    OverflowMaxPartsCount,
603

604
    /// etag list is empty
605
    EtagListEmpty,
606

607
    /// part size must be between 100k and 5G
608
    OverflowPartSize,
609

610
    /// max size must be lt 48.8TB
611
    OverflowMaxSize,
612
}
613

614
impl ContentError {
615
    fn new(kind: ContentErrorKind) -> Self {
22✔
616
        Self { kind }
22✔
617
    }
22✔
618
}
619

620
impl Display for ContentError {
621
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13✔
622
        self.kind.fmt(f)
13✔
623
    }
13✔
624
}
625

626
impl Error for ContentError {
627
    fn source(&self) -> Option<&(dyn Error + 'static)> {
×
628
        self.kind.source()
×
629
    }
×
630
}
631

632
impl From<BuilderError> for ContentError {
633
    fn from(value: BuilderError) -> Self {
×
634
        Self {
×
635
            kind: ContentErrorKind::Builder(value),
×
636
        }
637
    }
×
638
}
639
impl From<reqwest::Error> for ContentError {
640
    fn from(value: reqwest::Error) -> Self {
×
641
        Self {
×
642
            kind: ContentErrorKind::Builder(BuilderError::from_reqwest(value)),
×
643
        }
644
    }
×
645
}
646
impl From<ContentError> for std::io::Error {
647
    fn from(ContentError { kind }: ContentError) -> Self {
2✔
648
        use std::io::ErrorKind::*;
649
        use ContentErrorKind::*;
650
        match kind {
2✔
651
            Builder(e) => e.into(),
×
652
            NoFoundUploadId => Self::new(NotFound, kind),
×
653
            NoFoundEtag => Self::new(NotFound, kind),
×
654
            OverflowMaxPartsCount => Self::new(InvalidInput, kind),
×
655
            EtagListEmpty => Self::new(NotFound, kind),
×
656
            OverflowPartSize => Self::new(Unsupported, kind),
×
657
            OverflowMaxSize => Self::new(Unsupported, kind),
2✔
658
        }
659
    }
2✔
660
}
661

662
impl Display for ContentErrorKind {
663
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15✔
664
        match *self {
15✔
665
            Self::NoFoundUploadId => "not found upload id".fmt(f),
6✔
666
            Self::Builder(_) => "builder request failed".fmt(f),
×
667
            Self::NoFoundEtag => "not found etag".fmt(f),
×
668
            Self::OverflowMaxPartsCount => "overflow max parts count".fmt(f),
2✔
669
            Self::EtagListEmpty => "etag list is empty".fmt(f),
1✔
670
            Self::OverflowPartSize => "part size must be between 100k and 5G".fmt(f),
4✔
671
            Self::OverflowMaxSize => "max size must be lt 48.8TB".fmt(f),
2✔
672
        }
673
    }
15✔
674
}
675

676
impl Error for ContentErrorKind {
677
    fn source(&self) -> Option<&(dyn Error + 'static)> {
×
678
        match self {
×
679
            Self::Builder(e) => Some(e),
×
680
            Self::NoFoundUploadId
681
            | Self::NoFoundEtag
682
            | Self::OverflowMaxPartsCount
683
            | Self::EtagListEmpty
684
            | Self::OverflowPartSize
685
            | Self::OverflowMaxSize => None,
×
686
        }
687
    }
×
688
}
689

690
#[cfg(test)]
691
mod tests {
692
    use std::{
693
        io::{Read, Seek, Write},
694
        ops::Deref,
695
        sync::Arc,
696
    };
697

698
    use super::{
699
        get_content_type,
700
        test_suite::{AbortMulti, CompleteMulti, InitMulti, UploadMulti, UploadPart},
701
        Content, Inner, List,
702
    };
703

704
    use crate::{decode::RefineObject, object::InitObject, Client, ObjectPath};
705

706
    #[test]
707
    fn default() {
2✔
708
        let inner = Inner::default();
1✔
709

710
        assert_eq!(inner.path, ObjectPath::default());
1✔
711
        assert_eq!(inner.content_size, 0);
1✔
712
        assert_eq!(inner.current_pos, 0);
1✔
713
        assert_eq!(inner.content_part.len(), 0);
1✔
714
        assert_eq!(inner.content_type, "application/octet-stream");
1✔
715
        assert_eq!(inner.upload_id, "");
1✔
716
        assert_eq!(inner.etag_list.len(), 0);
1✔
717
        assert_eq!(inner.part_size, 200 * 1024 * 1024);
1✔
718
    }
2✔
719

720
    #[test]
721
    fn read() {
2✔
722
        let client = Client::test_init();
1✔
723
        let mut con = Content::from_client(Arc::new(client))
1✔
724
            .path("aaa.txt")
725
            .unwrap();
726

727
        let mut buf = [0u8; 201];
1✔
728
        let err = con.read(&mut buf).unwrap_err();
1✔
729
        assert_eq!(err.to_string(), "max size must be lt 48.8TB");
1✔
730

731
        let mut buf = [0u8; 10];
1✔
732
        let len = con.read(&mut buf).unwrap();
1✔
733
        assert_eq!(buf, [1u8, 2, 3, 4, 5, 0, 0, 0, 0, 0]);
1✔
734
        assert_eq!(len, 5);
1✔
735

736
        let mut buf = [0u8; 3];
1✔
737
        let len = con.read(&mut buf).unwrap();
1✔
738
        assert_eq!(buf, [1u8, 2, 3]);
1✔
739
        assert_eq!(len, 3);
1✔
740

741
        con.current_pos = 10;
1✔
742
        let mut buf = [0u8; 3];
1✔
743
        let len = con.read(&mut buf).unwrap();
1✔
744
        assert_eq!(buf, [1u8, 2, 3]);
1✔
745
        assert_eq!(len, 3);
1✔
746
    }
2✔
747

748
    #[test]
749
    fn init_object() {
2✔
750
        let mut list = List::default();
1✔
751
        let client = Client::test_init();
1✔
752
        list.set_client(Arc::new(client.clone()));
1✔
753

754
        let con = list.init_object().unwrap();
1✔
755

756
        assert_eq!(con.client.bucket, client.bucket);
1✔
757
        assert_eq!(con.inner, Inner::default());
1✔
758
    }
2✔
759

760
    #[test]
761
    fn from_client() {
2✔
762
        let client = Client::test_init();
1✔
763

764
        let con = Content::from_client(Arc::new(client.clone()));
1✔
765

766
        assert_eq!(con.client.bucket, client.bucket);
1✔
767
        assert_eq!(con.inner, Inner::default());
1✔
768
    }
2✔
769

770
    #[test]
771
    fn path() {
2✔
772
        let con = Content::default().path("aaa.txt").unwrap();
1✔
773
        assert_eq!(con.path, "aaa.txt");
1✔
774
    }
2✔
775

776
    #[test]
777
    fn part_canonicalized() {
2✔
778
        let client = Client::test_init();
1✔
779
        let con = Content::from_client(Arc::new(client))
1✔
780
            .path("aaa.txt")
781
            .unwrap();
782

783
        let (url, can) = con.part_canonicalized("first=1&second=2");
1✔
784
        assert_eq!(
1✔
785
            url.as_str(),
1✔
786
            "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?first=1&second=2"
787
        );
788
        assert_eq!(can.to_string(), "/bar/aaa.txt?first=1&second=2");
1✔
789
    }
2✔
790

791
    #[tokio::test]
3✔
792
    async fn upload() {
2✔
793
        let client = Client::test_init();
1✔
794
        let mut con = Content::from_client(Arc::new(client))
1✔
795
            .path("aaa.txt")
796
            .unwrap();
797
        con.content_part.push(b"bbb".to_vec());
1✔
798
        con.upload().await.unwrap();
2✔
799
    }
3✔
800

801
    #[tokio::test]
3✔
802
    async fn init_multi() {
2✔
803
        let client = Client::test_init().middleware(Arc::new(InitMulti {}));
1✔
804
        let mut con = Content::from_client(Arc::new(client))
1✔
805
            .path("aaa.txt")
806
            .unwrap();
807

808
        con.init_multi().await.unwrap();
1✔
809

810
        assert_eq!(con.upload_id, "foo_upload_id");
2✔
811
    }
3✔
812

813
    #[tokio::test]
3✔
814
    async fn upload_part() {
2✔
815
        let client = Client::test_init().middleware(Arc::new(UploadPart {}));
1✔
816
        let mut con = Content::from_client(Arc::new(client))
1✔
817
            .path("aaa.txt")
818
            .unwrap();
819

820
        let err = con.upload_part(1, b"bbb".to_vec()).await.unwrap_err();
1✔
821
        assert_eq!(err.to_string(), "not found upload id");
1✔
822

823
        con.upload_id = "foo_upload_id".to_string();
1✔
824
        for _i in 0..10 {
11✔
825
            con.etag_list.push((1, "a".parse().unwrap()));
10✔
826
        }
827
        let err = con.upload_part(1, b"bbb".to_vec()).await.unwrap_err();
1✔
828
        assert_eq!(err.to_string(), "overflow max parts count");
1✔
829
        con.etag_list.clear();
1✔
830

831
        let err = con
4✔
832
            .upload_part(1, b"012345678901234567890".to_vec())
1✔
833
            .await
1✔
834
            .unwrap_err();
835
        assert_eq!(err.to_string(), "part size must be between 100k and 5G");
1✔
836

837
        con.upload_part(2, b"bbb".to_vec()).await.unwrap();
1✔
838
        let (index, value) = con.etag_list.pop().unwrap();
1✔
839
        assert_eq!(index, 2);
1✔
840
        assert_eq!(value.to_str().unwrap(), "foo_etag");
2✔
841
    }
3✔
842

843
    #[tokio::test]
3✔
844
    async fn complete_multi() {
2✔
845
        let client = Client::test_init().middleware(Arc::new(CompleteMulti {}));
1✔
846
        let mut con = Content::from_client(Arc::new(client))
1✔
847
            .path("aaa.txt")
848
            .unwrap();
849
        let err = con.complete_multi().await.unwrap_err();
1✔
850
        assert_eq!(err.to_string(), "not found upload id");
1✔
851

852
        con.upload_id = "foo_upload_id".to_string();
1✔
853
        con.etag_list.push((1, "aaa".parse().unwrap()));
1✔
854
        con.etag_list.push((2, "bbb".parse().unwrap()));
1✔
855
        con.complete_multi().await.unwrap();
1✔
856
        assert!(con.etag_list.is_empty());
1✔
857
        assert!(con.upload_id.is_empty());
2✔
858
    }
3✔
859

860
    #[tokio::test]
3✔
861
    async fn upload_multi() {
2✔
862
        let client = Client::test_init().middleware(Arc::new(UploadMulti {}));
1✔
863
        let mut con = Content::from_client(Arc::new(client))
1✔
864
            .path("aaa.txt")
865
            .unwrap();
866

867
        con.content_part.push(b"aaa".to_vec());
1✔
868
        con.content_part.push(b"bbb".to_vec());
1✔
869

870
        con.upload_multi().await.unwrap();
1✔
871

872
        assert_eq!(con.content_size, 6);
2✔
873
    }
3✔
874

875
    #[tokio::test]
3✔
876
    async fn abort_multi() {
2✔
877
        let client = Client::test_init().middleware(Arc::new(AbortMulti {}));
1✔
878
        let mut con = Content::from_client(Arc::new(client))
1✔
879
            .path("aaa.txt")
880
            .unwrap();
881
        let err = con.complete_multi().await.unwrap_err();
1✔
882
        assert_eq!(err.to_string(), "not found upload id");
1✔
883

884
        con.upload_id = "foo_upload_id".to_string();
1✔
885
        con.etag_list.push((1, "aaa".parse().unwrap()));
1✔
886
        con.abort_multi().await.unwrap();
1✔
887
        assert!(con.etag_list.is_empty());
1✔
888
        assert!(con.upload_id.is_empty());
2✔
889
    }
3✔
890

891
    #[test]
892
    fn seek() {
2✔
893
        let mut inner = Inner::default();
1✔
894
        let pos = inner.seek(std::io::SeekFrom::Start(5)).unwrap();
1✔
895
        assert_eq!(pos, 5);
1✔
896
        assert_eq!(inner.current_pos, 5);
1✔
897

898
        let err = inner.seek(std::io::SeekFrom::End(10)).unwrap_err();
1✔
899
        assert_eq!(err.to_string(), "content size is 0");
1✔
900

901
        inner.content_size = 11;
1✔
902
        let pos = inner.seek(std::io::SeekFrom::End(5)).unwrap();
1✔
903
        assert_eq!(pos, 6);
1✔
904
        inner.current_pos = 6;
1✔
905

906
        let pos = inner.seek(std::io::SeekFrom::Current(-1)).unwrap();
1✔
907
        assert_eq!(pos, 5);
1✔
908
        inner.current_pos = 5;
1✔
909
    }
2✔
910

911
    #[test]
912
    fn test_get_content_type() {
2✔
913
        assert_eq!(get_content_type("aaa.jpg"), "image/jpeg");
1✔
914
        assert_eq!(get_content_type("aaa.pdf"), "application/pdf");
1✔
915
        assert_eq!(get_content_type("aaa.png"), "image/png");
1✔
916
        assert_eq!(get_content_type("aaa.gif"), "image/gif");
1✔
917
        assert_eq!(get_content_type("aaa.bmp"), "image/bmp");
1✔
918
        assert_eq!(get_content_type("aaa.zip"), "application/zip");
1✔
919
        assert_eq!(get_content_type("aaa.tar"), "application/x-tar");
1✔
920
        assert_eq!(get_content_type("aaa.gz"), "application/gzip");
1✔
921
        assert_eq!(get_content_type("aaa.txt"), "text/plain");
1✔
922
        assert_eq!(get_content_type("aaa.mp3"), "audio/mpeg");
1✔
923
        assert_eq!(get_content_type("aaa.wav"), "audio/wave");
1✔
924
        assert_eq!(get_content_type("aaa.mp4"), "video/mp4");
1✔
925
        assert_eq!(get_content_type("aaa.mov"), "video/quicktime");
1✔
926
        assert_eq!(get_content_type("aaa.avi"), "video/x-msvideo");
1✔
927
        assert_eq!(get_content_type("aaa.wmv"), "video/x-ms-wmv");
1✔
928
        assert_eq!(get_content_type("aaa.html"), "text/html");
1✔
929
        assert_eq!(get_content_type("aaa.js"), "application/javascript");
1✔
930
        assert_eq!(get_content_type("aaa.css"), "text/css");
1✔
931
        assert_eq!(get_content_type("aaa.php"), "application/x-httpd-php");
1✔
932
        assert_eq!(get_content_type("aaa.doc"), "application/octet-stream");
1✔
933
        assert_eq!(get_content_type("file"), "application/octet-stream");
1✔
934
    }
2✔
935

936
    #[test]
937
    fn test_path() {
2✔
938
        let mut inner = Inner::default();
1✔
939
        inner = inner.path("bbb.txt").unwrap();
1✔
940
        assert_eq!(inner.path.as_ref(), "bbb.txt");
1✔
941
        assert_eq!(inner.content_type, "text/plain");
1✔
942
    }
2✔
943

944
    #[test]
945
    fn content_type_with_path() {
2✔
946
        let mut inner = Inner::default();
1✔
947
        inner.path = "ccc.html".parse().unwrap();
1✔
948
        inner.content_type_with_path();
1✔
949
        assert_eq!(inner.content_type, "text/html");
1✔
950
    }
2✔
951

952
    #[test]
953
    fn test_content_type() {
2✔
954
        let mut inner = Inner::default();
1✔
955
        inner.content_type("bar");
1✔
956
        assert_eq!(inner.content_type, "bar");
1✔
957
    }
2✔
958

959
    #[test]
960
    fn part_size() {
2✔
961
        let mut inner = Inner::default();
1✔
962
        let err = inner.part_size(5).unwrap_err();
1✔
963
        assert_eq!(err.to_string(), "part size must be between 100k and 5G");
1✔
964

965
        let err = inner.part_size(21).unwrap_err();
1✔
966
        assert_eq!(err.to_string(), "part size must be between 100k and 5G");
1✔
967

968
        inner.part_size(11).unwrap();
1✔
969
        assert_eq!(inner.part_size, 11);
1✔
970
    }
2✔
971

972
    #[test]
973
    fn test_parse_upload_id() {
2✔
974
        let mut content = Inner::default();
1✔
975

976
        let xml = r#"<InitiateMultipartUploadResult>
1✔
977
        <Bucket>bucket_name</Bucket>
978
        <Key>aaa</Key>
979
        <UploadId>AC3251A13464411D8691F271CE33A300</UploadId>
980
      </InitiateMultipartUploadResult>"#;
981
        content.parse_upload_id(xml).unwrap();
1✔
982

983
        assert_eq!(content.upload_id, "AC3251A13464411D8691F271CE33A300");
1✔
984

985
        content.parse_upload_id("abc").unwrap_err();
1✔
986
    }
2✔
987

988
    #[test]
989
    fn etag_list_xml() {
2✔
990
        let mut inner = Inner::default();
1✔
991

992
        let err = inner.etag_list_xml().unwrap_err();
1✔
993
        assert_eq!(err.to_string(), "etag list is empty");
1✔
994

995
        inner.etag_list.push((1, "aaa".parse().unwrap()));
1✔
996
        inner.etag_list.push((2, "bbb".parse().unwrap()));
1✔
997

998
        let xml = inner.etag_list_xml().unwrap();
1✔
999
        assert_eq!(xml, "<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>aaa</ETag></Part><Part><PartNumber>2</PartNumber><ETag>bbb</ETag></Part></CompleteMultipartUpload>");
1✔
1000
    }
2✔
1001

1002
    #[test]
1003
    fn part_clear() {
2✔
1004
        let mut inner = Inner::default();
1✔
1005
        assert_eq!(inner.content_part.len(), 0);
1✔
1006

1007
        inner.content_part.push(vec![1u8, 2, 3]);
1✔
1008
        inner.content_part.push(vec![1u8, 2, 3]);
1✔
1009
        inner.part_clear();
1✔
1010
        assert_eq!(inner.content_part.len(), 0);
1✔
1011
    }
2✔
1012

1013
    #[test]
1014
    fn assert_impl() {
2✔
1015
        fn impl_fn<T: RefineObject<E>, E: std::error::Error + 'static>(_: T) {}
1✔
1016

1017
        impl_fn(Content::default());
1✔
1018

1019
        fn impl_deref<T: Deref<Target = Inner>>(_: T) {}
1✔
1020

1021
        impl_deref(Content::default());
1✔
1022
    }
2✔
1023

1024
    #[test]
1025
    fn test_write() {
2✔
1026
        let mut content = Content::default();
1✔
1027
        content.part_size = 5;
1✔
1028
        content.write_all(b"abcdefg").unwrap();
1✔
1029

1030
        assert!(content.content_part.len() == 2);
1✔
1031
        assert_eq!(content.content_part[0], b"abcde");
1✔
1032
        assert_eq!(content.content_part[1], b"fg");
1✔
1033

1034
        content.part_clear();
1✔
1035

1036
        content.write(b"mn").unwrap();
1✔
1037
        assert!(content.content_part.len() == 1);
1✔
1038
        assert_eq!(content.content_part[0], b"mn");
1✔
1039

1040
        content.part_clear();
1✔
1041

1042
        let len = content.write(b"efghijklmn").unwrap();
1✔
1043
        assert!(content.content_part.len() == 1);
1✔
1044
        assert_eq!(content.content_part[0], b"efghi");
1✔
1045
        assert_eq!(len, 5);
1✔
1046

1047
        content.part_clear();
1✔
1048

1049
        content.write(b"o").unwrap();
1✔
1050
        content.write(b"p").unwrap();
1✔
1051
        content.write(b"q").unwrap();
1✔
1052
        assert!(content.content_part.len() == 1);
1✔
1053
        assert_eq!(content.content_part[0], b"opq");
1✔
1054
    }
2✔
1055
}
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