Skip to main content

ali_oss_rs/
multipart_common.rs

1//! Multipart upload types
2
3use std::collections::HashMap;
4
5use base64::{prelude::BASE64_STANDARD, Engine};
6use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
7
8use crate::{
9    common,
10    error::Error,
11    object_common::{build_put_object_request, Callback, PutObjectOptions, PutObjectOptionsBuilder},
12    request::{OssRequest, RequestMethod},
13    util::{sanitize_etag, validate_bucket_name, validate_object_key},
14    RequestBody, Result,
15};
16
17pub type InitiateMultipartUploadOptions = PutObjectOptions;
18pub type InitiateMultipartUploadOptionsBuilder = PutObjectOptionsBuilder;
19
20/// Initiate mutlipart upload result
21#[derive(Debug, Clone, Default)]
22#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
24pub struct InitiateMultipartUploadResult {
25    pub bucket: String,
26    pub key: String,
27    pub upload_id: String,
28}
29
30impl InitiateMultipartUploadResult {
31    pub(crate) fn from_xml(xml: &str) -> Result<Self> {
32        let mut reader = quick_xml::Reader::from_str(xml);
33        let mut tag = String::new();
34        let mut data = Self::default();
35
36        loop {
37            match reader.read_event()? {
38                Event::Eof => break,
39                Event::Start(t) => tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string(),
40                Event::Text(s) => {
41                    let text = s.unescape()?.trim().to_string();
42                    match tag.as_str() {
43                        "Bucket" => data.bucket = text,
44                        "Key" => data.key = text,
45                        "UploadId" => data.upload_id = text,
46                        _ => {}
47                    }
48                }
49                Event::End(_) => tag.clear(),
50                _ => {}
51            }
52        }
53
54        Ok(data)
55    }
56}
57
58/// Request data for upload part
59#[derive(Debug, Clone)]
60#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
61#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
62pub struct UploadPartRequest {
63    /// 每一个上传的 Part 都有一个标识它的号码(partNumber)。
64    ///
65    /// 取值:1~10000
66    ///
67    /// 单个 Part 的大小限制为 100 KB~5 GB。
68    /// MultipartUpload 事件中除最后一个 Part 以外,其他 Part 的大小都要大于或等于 100 KB。
69    /// 因不确定是否为最后一个 Part,
70    /// UploadPart 接口并不会立即校验上传 Part 的大小,只有当 CompleteMultipartUpload 时才会校验。
71    pub part_number: u32,
72
73    /// The upload id returned from InitiateMultipartUpload
74    pub upload_id: String,
75}
76
77impl UploadPartRequest {
78    pub fn new<S>(part_number: u32, upload_id: S) -> Self
79    where
80        S: AsRef<str>,
81    {
82        Self {
83            part_number,
84            upload_id: upload_id.as_ref().to_string(),
85        }
86    }
87}
88
89/// Upload part result.
90#[derive(Debug, Clone)]
91#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
92#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
93pub struct UploadPartResult {
94    pub request_id: String,
95
96    /// Used when call `CompleteMultipartUpload`
97    pub etag: String,
98}
99
100impl From<HashMap<String, String>> for UploadPartResult {
101    fn from(mut headers: HashMap<String, String>) -> Self {
102        Self {
103            request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
104            etag: sanitize_etag(headers.remove("etag").unwrap_or_default()),
105        }
106    }
107}
108
109/// Data required for upload part copy for copying object larger than 1GB
110#[derive(Debug, Clone)]
111#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
112#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
113pub struct UploadPartCopyRequest {
114    /// 每一个上传的 Part 都有一个标识它的号码(partNumber)。
115    ///
116    /// 取值:1~10000
117    ///
118    /// 单个 Part 的大小限制为 100 KB~5 GB。
119    /// MultipartUpload 事件中除最后一个 Part 以外,其他 Part 的大小都要大于或等于 100 KB。
120    /// 因不确定是否为最后一个 Part,
121    /// UploadPart 接口并不会立即校验上传 Part 的大小,只有当 CompleteMultipartUpload 时才会校验。
122    pub part_number: u32,
123
124    /// The upload id returned from InitiateMultipartUpload
125    pub upload_id: String,
126
127    /// The object key **without** bucket part because UploadPartCopy can copy objects in the same bucket only. e.g. `path/to/sub-path/obj_key.zip`.
128    pub source_object_key: String,
129}
130
131impl UploadPartCopyRequest {
132    pub fn new<S1, S2>(part_number: u32, upload_id: S1, source_object_key: S2) -> Self
133    where
134        S1: AsRef<str>,
135        S2: AsRef<str>,
136    {
137        Self {
138            part_number,
139            upload_id: upload_id.as_ref().to_string(),
140            source_object_key: source_object_key.as_ref().to_string(),
141        }
142    }
143}
144
145/// Other options for upload part copy
146#[derive(Debug, Clone, Default)]
147#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
148#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
149pub struct UploadPartCopyOptions {
150    /// 如果源 Object 开启了版本控制,这里可以指明版本,实现从 Object 的指定版本进行拷贝
151    pub source_object_version_id: Option<String>,
152
153    /// 源 Object 的拷贝范围。例如设置 `bytes=0-9`,表示拷贝 `0` 到 `9` 这 `10` 个字节。
154    /// 这个范围是两端闭区间的 `[start, end]`,并且下标从 `0` 开始。也就是最大的取值范围是 `[0, file_length - 1]`
155    ///
156    /// - 不指定该请求头时,表示拷贝整个源 Object。
157    /// - 当指定的范围不符合规范时,则拷贝整个源 Object
158    pub copy_source_range: Option<String>,
159
160    pub copy_source_if_match: Option<String>,
161    pub copy_source_if_none_match: Option<String>,
162    pub copy_source_if_unmodified_since: Option<String>,
163    pub copy_source_if_modified_since: Option<String>,
164}
165
166#[derive(Debug, Default)]
167pub struct UploadPartCopyOptionsBuilder {
168    options: UploadPartCopyOptions,
169}
170
171impl UploadPartCopyOptionsBuilder {
172    pub fn new() -> Self {
173        Self::default()
174    }
175
176    pub fn source_object_version_id<S: Into<String>>(mut self, version_id: S) -> Self {
177        self.options.source_object_version_id = Some(version_id.into());
178        self
179    }
180
181    pub fn copy_source_range<S: Into<String>>(mut self, range: S) -> Self {
182        self.options.copy_source_range = Some(range.into());
183        self
184    }
185
186    pub fn copy_source_if_match<S: Into<String>>(mut self, etag: S) -> Self {
187        self.options.copy_source_if_match = Some(etag.into());
188        self
189    }
190
191    pub fn copy_source_if_none_match<S: Into<String>>(mut self, etag: S) -> Self {
192        self.options.copy_source_if_none_match = Some(etag.into());
193        self
194    }
195
196    pub fn copy_source_if_unmodified_since<S: Into<String>>(mut self, timestamp: S) -> Self {
197        self.options.copy_source_if_unmodified_since = Some(timestamp.into());
198        self
199    }
200
201    pub fn copy_source_if_modified_since<S: Into<String>>(mut self, timestamp: S) -> Self {
202        self.options.copy_source_if_modified_since = Some(timestamp.into());
203        self
204    }
205
206    pub fn build(self) -> UploadPartCopyOptions {
207        self.options
208    }
209}
210
211/// Result for upload part copy
212#[derive(Debug, Clone, Default)]
213#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
214#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
215pub struct UploadPartCopyResult {
216    pub last_modified: String,
217    pub etag: String,
218}
219
220impl UploadPartCopyResult {
221    pub(crate) fn from_xml(xml: &str) -> Result<Self> {
222        let mut reader = quick_xml::Reader::from_str(xml);
223        let mut tag = String::new();
224        let mut data = Self::default();
225
226        loop {
227            match reader.read_event()? {
228                Event::Eof => break,
229                Event::Start(t) => tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string(),
230                Event::Text(text) => {
231                    let s = text.unescape()?.trim().to_string();
232                    match tag.as_str() {
233                        "LastModified" => data.last_modified = s,
234                        "ETag" => data.etag = sanitize_etag(s),
235                        _ => {}
236                    }
237                }
238                Event::End(_) => tag.clear(),
239                _ => {}
240            }
241        }
242
243        Ok(data)
244    }
245}
246
247/// Request data for complete multipart upload
248#[derive(Debug, Clone)]
249#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
250#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
251pub struct CompleteMultipartUploadRequest {
252    pub upload_id: String,
253    /// `.0` is the `part_number` while upload part,
254    /// `.1` is the returned ETag after upload part done successfully.
255    pub parts: Vec<(u32, String)>,
256}
257
258impl CompleteMultipartUploadRequest {
259    /// Consume self and generate XML string for sending request
260    pub(crate) fn into_xml(self) -> Result<String> {
261        let Self { upload_id: _, parts } = self;
262
263        let mut writer = quick_xml::Writer::new(Vec::new());
264
265        writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
266
267        writer.write_event(Event::Start(BytesStart::new("CompleteMultipartUpload")))?;
268
269        for (n, s) in parts.into_iter() {
270            writer.write_event(Event::Start(BytesStart::new("Part")))?;
271
272            writer.write_event(Event::Start(BytesStart::new("PartNumber")))?;
273            writer.write_event(Event::Text(BytesText::new(&n.to_string())))?;
274            writer.write_event(Event::End(BytesEnd::new("PartNumber")))?;
275
276            writer.write_event(Event::Start(BytesStart::new("ETag")))?;
277            let etag = if s.starts_with("\"") { s } else { format!("\"{}", s) };
278
279            let etag = if etag.ends_with("\"") { etag } else { format!("{}\"", etag) };
280
281            writer.write_event(Event::Text(BytesText::new(&etag)))?;
282            writer.write_event(Event::End(BytesEnd::new("ETag")))?;
283
284            writer.write_event(Event::End(BytesEnd::new("Part")))?;
285        }
286
287        writer.write_event(Event::End(BytesEnd::new("CompleteMultipartUpload")))?;
288        Ok(String::from_utf8(writer.into_inner())?)
289    }
290}
291
292/// Options for complete multipart uploads
293#[derive(Debug, Clone, Default)]
294#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
295#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
296pub struct CompleteMultipartUploadOptions {
297    pub callback: Option<Callback>,
298}
299
300/// Complete multipart upload result
301#[derive(Debug, Clone)]
302#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
303pub enum CompleteMultipartUploadResult {
304    /// This is response headers from aliyun oss api when you put object with no callback specified
305    #[cfg_attr(feature = "serde-camelcase", serde(rename = "apiResponse", rename_all = "camelCase"))]
306    ApiResponse(CompleteMultipartUploadApiResponse),
307
308    /// This is your callback response content string when you put object with callback specified.
309    /// `.0` should be a valid JSON string.
310    #[cfg_attr(feature = "serde-camelcase", serde(rename = "callbackResponse"))]
311    CallbackResponse(String),
312}
313
314/// Request data for complete multipart upload
315#[derive(Debug, Clone, Default)]
316#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
317#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
318pub struct CompleteMultipartUploadApiResponse {
319    pub bucket: String,
320    pub key: String,
321    pub etag: String,
322}
323
324impl CompleteMultipartUploadApiResponse {
325    pub(crate) fn from_xml(xml: &str) -> Result<Self> {
326        let mut reader = quick_xml::Reader::from_str(xml);
327        let mut tag = String::new();
328        let mut data = Self::default();
329
330        loop {
331            match reader.read_event()? {
332                Event::Eof => break,
333                Event::Start(t) => tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string(),
334                Event::Text(s) => {
335                    let text = s.unescape()?.trim().to_string();
336                    match tag.as_str() {
337                        "Bucket" => data.bucket = text,
338                        "Key" => data.key = text,
339                        "ETag" => data.etag = sanitize_etag(text),
340                        _ => {}
341                    }
342                }
343                Event::End(_) => tag.clear(),
344                _ => {}
345            }
346        }
347
348        Ok(data)
349    }
350}
351
352/// query options for listing all multipart uploads which is initialized but not completed nor aborted.
353#[derive(Debug, Clone, Default)]
354#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
355#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
356pub struct ListMultipartUploadsOptions {
357    /// 用于对 Object 名称进行分组的字符。所有名称包含指定的前缀且首次出现 delimiter 字符之间的 Object 作为一组元素 CommonPrefixes。
358    pub delimiter: Option<char>,
359
360    /// 限定此次返回 Multipart Upload 事件的最大个数,默认值为 1000。最大值为 1000。
361    pub max_uploads: Option<u32>,
362
363    /// 与 upload-id-marker 参数配合使用,用于指定返回结果的起始位置
364    pub key_marker: Option<String>,
365
366    /// 与 upload-id-marker 参数配合使用,用于指定返回结果的起始位置。
367    ///
368    /// - 如果未设置 upload-id-marker 参数,查询结果中包含所有 Object 名称的字典序大于 key-marker 参数值的 Multipart Upload 事件。
369    /// - 如果设置了 upload-id-marker 参数,查询结果中包含:
370    ///   - 所有 Object 名称的字典序大于 key-marker 参数值的 Multipart Upload 事件。
371    ///   - Object 名称等于 key-marker 参数值,但是 UploadId 比 upload-id-marker 参数值大的 Multipart Upload 事件。
372    pub upload_id_marker: Option<String>,
373
374    /// 限定返回的 Object Key 必须以 prefix 作为前缀。注意使用 prefix 查询时,返回的 Key 中仍会包含 prefix。
375    pub prefix: Option<String>,
376}
377
378#[derive(Debug, Default)]
379pub struct ListMultipartUploadsOptionsBuilder {
380    options: ListMultipartUploadsOptions,
381}
382
383impl ListMultipartUploadsOptionsBuilder {
384    pub fn new() -> Self {
385        Self::default()
386    }
387
388    pub fn delimiter(mut self, delimiter: char) -> Self {
389        self.options.delimiter = Some(delimiter);
390        self
391    }
392
393    pub fn max_uploads(mut self, max_uploads: u32) -> Self {
394        self.options.max_uploads = Some(max_uploads);
395        self
396    }
397
398    pub fn key_marker<S: Into<String>>(mut self, key_marker: S) -> Self {
399        self.options.key_marker = Some(key_marker.into());
400        self
401    }
402
403    pub fn upload_id_marker<S: Into<String>>(mut self, upload_id_marker: S) -> Self {
404        self.options.upload_id_marker = Some(upload_id_marker.into());
405        self
406    }
407
408    pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self {
409        self.options.prefix = Some(prefix.into());
410        self
411    }
412
413    pub fn build(self) -> ListMultipartUploadsOptions {
414        self.options
415    }
416}
417
418#[derive(Debug, Clone, Default)]
419#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
420#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
421pub struct ListMultipartUploadsResultItem {
422    pub key: String,
423    pub upload_id: String,
424
425    /// Multipart Upload 事件初始化的时间。示例:`2012-02-23T04:18:23.000Z`
426    pub initiated: String,
427}
428
429impl ListMultipartUploadsResultItem {
430    pub(crate) fn from_xml_reader(reader: &mut quick_xml::Reader<&[u8]>) -> Result<Self> {
431        let mut tag = String::new();
432        let mut item = Self::default();
433
434        loop {
435            match reader.read_event()? {
436                Event::Eof => break,
437                Event::Start(t) => tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string(),
438                Event::Text(s) => {
439                    let s = s.unescape()?.trim().to_string();
440                    match tag.as_str() {
441                        "Key" => item.key = s,
442                        "UploadId" => item.upload_id = s,
443                        "Initiated" => item.initiated = s,
444                        _ => {}
445                    }
446                }
447                Event::End(t) => {
448                    tag.clear();
449                    if t.local_name().as_ref() == b"Upload" {
450                        break;
451                    }
452                }
453                _ => {}
454            }
455        }
456
457        Ok(item)
458    }
459}
460
461#[derive(Debug, Clone, Default)]
462#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
463#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
464pub struct ListMultipartUploadsResult {
465    pub bucket: String,
466    pub prefix: Option<String>,
467    pub delimiter: Option<char>,
468    pub key_marker: Option<String>,
469    pub upload_id_marker: Option<String>,
470    pub next_key_marker: Option<String>,
471    pub next_upload_id_marker: Option<String>,
472    pub max_uploads: u32,
473    pub is_truncated: bool,
474    pub uploads: Vec<ListMultipartUploadsResultItem>,
475    pub common_prefixes: Vec<String>,
476}
477
478impl ListMultipartUploadsResult {
479    pub(crate) fn from_xml(xml: &str) -> Result<Self> {
480        let mut reader = quick_xml::Reader::from_str(xml);
481        let mut tag = String::new();
482        let mut level = 0;
483
484        let mut ret = Self::default();
485
486        loop {
487            match reader.read_event()? {
488                Event::Eof => break,
489                Event::Start(t) => {
490                    if t.local_name().as_ref() == b"Upload" {
491                        ret.uploads.push(ListMultipartUploadsResultItem::from_xml_reader(&mut reader)?);
492                    } else {
493                        level += 1;
494                        tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string();
495                    }
496                }
497                Event::Text(s) => {
498                    let text = s.unescape()?.trim().to_string();
499                    match tag.as_str() {
500                        "Bucket" => ret.bucket = text,
501                        "KeyMarker" => ret.key_marker = if text.is_empty() { None } else { Some(text) },
502                        "UploadIdMarker" => ret.upload_id_marker = if text.is_empty() { None } else { Some(text) },
503                        "NextKeyMarker" => ret.next_key_marker = if text.is_empty() { None } else { Some(text) },
504                        "NextUploadIdMarker" => ret.next_upload_id_marker = if text.is_empty() { None } else { Some(text) },
505                        "Prefix" if level == 2 => ret.prefix = if text.is_empty() { None } else { Some(text) },
506                        "Prefix" if level == 3 => ret.common_prefixes.push(text),
507                        "Delimiter" => ret.delimiter = text.chars().next(),
508                        "MaxUploads" => ret.max_uploads = text.parse::<u32>().unwrap_or_default(),
509                        "IsTruncated" => ret.is_truncated = text == "true",
510                        _ => {}
511                    }
512                }
513                Event::End(_) => {
514                    tag.clear();
515                    level -= 1;
516                }
517                _ => {}
518            }
519        }
520
521        Ok(ret)
522    }
523}
524
525#[derive(Debug, Clone, Default)]
526#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
527#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
528pub struct ListPartsOptions {
529    /// Maximum parts number in response data. Valida data range: `[1, 1000]`
530    pub max_parts: Option<u32>,
531
532    /// The start part number. only parts which numbers are greater than the give `part_number_marker` will be returned
533    pub part_number_marker: Option<u32>,
534}
535
536#[derive(Debug, Clone, Default)]
537#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
538#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
539pub struct ListPartsResultItem {
540    pub etag: String,
541    pub part_number: u32,
542    pub size: u64,
543    pub last_modified: String,
544}
545
546impl ListPartsResultItem {
547    pub(crate) fn from_xml_reader(reader: &mut quick_xml::Reader<&[u8]>) -> Result<Self> {
548        let mut tag = String::new();
549        let mut data = Self::default();
550
551        loop {
552            match reader.read_event()? {
553                Event::Eof => break,
554                Event::Start(t) => tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string(),
555                Event::Text(text) => {
556                    let s = text.unescape()?.trim().to_string();
557                    match tag.as_str() {
558                        "PartNumber" => data.part_number = s.parse()?,
559                        "Size" => data.size = s.parse()?,
560                        "ETag" => data.etag = sanitize_etag(s),
561                        "LastModified" => data.last_modified = s,
562                        _ => {}
563                    }
564                }
565                Event::End(t) => {
566                    tag.clear();
567                    if t.local_name().as_ref() == b"Part" {
568                        break;
569                    }
570                }
571                _ => {}
572            }
573        }
574
575        Ok(data)
576    }
577}
578
579#[derive(Debug, Clone, Default)]
580#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
581#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
582pub struct ListPartsResult {
583    pub bucket: String,
584    pub key: String,
585    pub upload_id: String,
586    pub max_parts: Option<u32>,
587    pub part_number_marker: Option<u32>,
588    pub next_part_number_marker: Option<u32>,
589    pub is_truncated: bool,
590    pub parts: Vec<ListPartsResultItem>,
591}
592
593impl ListPartsResult {
594    pub(crate) fn from_xml(xml: &str) -> Result<Self> {
595        let mut reader = quick_xml::Reader::from_str(xml);
596        let mut tag = String::new();
597        let mut data = Self::default();
598
599        loop {
600            match reader.read_event()? {
601                Event::Eof => break,
602                Event::Start(t) => {
603                    if t.local_name().as_ref() == b"Part" {
604                        data.parts.push(ListPartsResultItem::from_xml_reader(&mut reader)?);
605                    } else {
606                        tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string();
607                    }
608                }
609                Event::Text(text) => {
610                    let s = text.unescape()?.trim().to_string();
611                    match tag.as_str() {
612                        "Bucket" => data.bucket = s,
613                        "Key" => data.key = s,
614                        "UploadId" => data.upload_id = s,
615                        "MaxParts" => data.max_parts = if s.is_empty() { None } else { Some(s.parse()?) },
616                        "PartNumberMarker" => data.part_number_marker = if s.is_empty() { None } else { Some(s.parse()?) },
617                        "NextPartNumberMarker" => data.next_part_number_marker = if s.is_empty() { None } else { Some(s.parse()?) },
618                        "IsTruncated" => data.is_truncated = s == "true",
619                        _ => {}
620                    }
621                }
622                Event::End(_) => {
623                    tag.clear();
624                }
625                _ => {}
626            }
627        }
628
629        Ok(data)
630    }
631}
632
633pub(crate) fn build_initiate_multipart_uploads_request(
634    bucket_name: &str,
635    object_key: &str,
636    options: &Option<InitiateMultipartUploadOptions>,
637) -> Result<OssRequest> {
638    if !validate_bucket_name(bucket_name) {
639        return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
640    }
641
642    if !validate_object_key(object_key) {
643        return Err(Error::Other(format!("invalid object key: {}", object_key)));
644    }
645
646    let mut request = build_put_object_request(bucket_name, object_key, RequestBody::Empty, options)?;
647
648    request = request
649        .method(RequestMethod::Post)
650        .bucket(bucket_name)
651        .object(object_key)
652        .add_query("uploads", "");
653
654    Ok(request)
655}
656
657pub(crate) fn build_upload_part_request(bucket_name: &str, object_key: &str, body: RequestBody, params: UploadPartRequest) -> Result<OssRequest> {
658    if !validate_bucket_name(bucket_name) {
659        return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
660    }
661
662    if !validate_object_key(object_key) {
663        return Err(Error::Other(format!("invalid object key: {}", object_key)));
664    }
665
666    let UploadPartRequest { part_number, upload_id } = params;
667
668    if !(1..=10000).contains(&part_number) {
669        return Err(Error::Other(format!(
670            "invalid part number: {}. part number should be in range [1, 10000]",
671            part_number
672        )));
673    }
674
675    if upload_id.is_empty() {
676        return Err(Error::Other("invalid upload id. upload id must not be empty".to_string()));
677    }
678
679    let request = OssRequest::new()
680        .method(RequestMethod::Put)
681        .bucket(bucket_name)
682        .object(object_key)
683        .add_query("partNumber", part_number.to_string())
684        .add_query("uploadId", upload_id)
685        .body(body);
686
687    Ok(request)
688}
689
690pub(crate) fn build_upload_part_copy_request(
691    bucket_name: &str,
692    object_key: &str,
693    data: UploadPartCopyRequest,
694    options: &Option<UploadPartCopyOptions>,
695) -> Result<OssRequest> {
696    if !validate_bucket_name(bucket_name) {
697        return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
698    }
699
700    if !validate_object_key(object_key) {
701        return Err(Error::Other(format!("invalid destination object key: {}", object_key)));
702    }
703
704    if !validate_object_key(&data.source_object_key) {
705        return Err(Error::Other(format!("invalid source object key: {}", data.source_object_key)));
706    }
707
708    if !(1..=10000).contains(&data.part_number) {
709        return Err(Error::Other(format!("invalid part number: {}", data.part_number)));
710    }
711
712    let UploadPartCopyRequest {
713        part_number,
714        upload_id,
715        source_object_key,
716    } = data;
717
718    if upload_id.is_empty() {
719        return Err(Error::Other("invalid upload id: must not be empty".to_string()));
720    }
721
722    if !validate_object_key(&source_object_key) {
723        return Err(Error::Other(format!("invalid source object key: {}", source_object_key)));
724    }
725
726    let mut request = OssRequest::new()
727        .method(RequestMethod::Put)
728        .bucket(bucket_name)
729        .object(object_key)
730        .add_query("uploadId", upload_id)
731        .add_query("partNumber", part_number.to_string());
732
733    let mut copy_source = format!("/{}/{}", bucket_name, source_object_key);
734    if let Some(opt) = options {
735        if let Some(v) = &opt.source_object_version_id {
736            copy_source = format!("{}?versionId={}", copy_source, v);
737        }
738    }
739
740    request = request.add_header("x-oss-copy-source", &copy_source);
741
742    if let Some(options) = options {
743        if let Some(s) = &options.copy_source_range {
744            request = request.add_header("x-oss-copy-source-range", s);
745        }
746
747        if let Some(s) = &options.copy_source_if_match {
748            request = request.add_header("x-oss-copy-source-if-match", s);
749        }
750
751        if let Some(s) = &options.copy_source_if_none_match {
752            request = request.add_header("x-oss-copy-source-if-none-match", s);
753        }
754
755        if let Some(s) = &options.copy_source_if_modified_since {
756            request = request.add_header("x-oss-copy-source-if-modified-since", s);
757        }
758
759        if let Some(s) = &options.copy_source_if_unmodified_since {
760            request = request.add_header("x-oss-copy-source-if-unmodified-since", s);
761        }
762    }
763
764    Ok(request)
765}
766
767pub(crate) fn build_complete_multipart_uploads_request(
768    bucket_name: &str,
769    object_key: &str,
770    data: CompleteMultipartUploadRequest,
771    options: &Option<CompleteMultipartUploadOptions>,
772) -> Result<OssRequest> {
773    if !validate_bucket_name(bucket_name) {
774        return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
775    }
776
777    if !validate_object_key(object_key) {
778        return Err(Error::Other(format!("invalid object key: {}", object_key)));
779    }
780
781    if data.upload_id.is_empty() {
782        return Err(Error::Other("upload id must not be empty".to_string()));
783    }
784
785    if data.parts.is_empty() {
786        return Err(Error::Other("multipart uploads items must not be empty".to_string()));
787    }
788
789    let upload_id = data.upload_id.clone();
790
791    let xml = data.into_xml()?;
792
793    let mut request = OssRequest::new()
794        .method(RequestMethod::Post)
795        .bucket(bucket_name)
796        .object(object_key)
797        .add_query("uploadId", &upload_id)
798        .content_type(common::MIME_TYPE_XML)
799        .text_body(xml);
800
801    if let Some(options) = options {
802        if let Some(cb) = &options.callback {
803            // custom variable values are not serialized
804            let callback_json = serde_json::to_string(cb)?;
805            let callback_base64 = BASE64_STANDARD.encode(&callback_json);
806            request = request.add_header("x-oss-callback", callback_base64);
807
808            if !cb.custom_variables.is_empty() {
809                let callback_vars_json = serde_json::to_string(&cb.custom_variables)?;
810                let callback_vars_base64 = BASE64_STANDARD.encode(&callback_vars_json);
811                request = request.add_header("x-oss-callback-var", callback_vars_base64);
812            }
813        }
814    }
815
816    Ok(request)
817}
818
819pub(crate) fn build_list_multipart_uploads_request(bucket_name: &str, options: &Option<ListMultipartUploadsOptions>) -> Result<OssRequest> {
820    if !validate_bucket_name(bucket_name) {
821        return Err(Error::Other("invalid bucket name".to_string()));
822    }
823
824    let mut request = OssRequest::new().method(RequestMethod::Get).bucket(bucket_name).add_query("uploads", "");
825
826    if let Some(options) = options {
827        if let Some(c) = options.delimiter {
828            request = request.add_query("delimiter", c.to_string());
829        }
830
831        if let Some(n) = options.max_uploads {
832            request = request.add_query("max-uploads", n.to_string());
833        }
834
835        if let Some(s) = &options.key_marker {
836            request = request.add_query("key-marker", s);
837        }
838
839        if let Some(s) = &options.upload_id_marker {
840            request = request.add_query("upload-id-marker", s);
841        }
842
843        if let Some(s) = &options.prefix {
844            request = request.add_query("prefix", s);
845        }
846    }
847    Ok(request)
848}
849
850pub(crate) fn build_list_parts_request(bucket_name: &str, object_key: &str, upload_id: &str, options: &Option<ListPartsOptions>) -> Result<OssRequest> {
851    if !validate_bucket_name(bucket_name) {
852        return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
853    }
854
855    if !validate_object_key(object_key) {
856        return Err(Error::Other(format!("invalid object key: {}", object_key)));
857    }
858
859    if upload_id.is_empty() {
860        return Err(Error::Other("upload id must not be empty".to_string()));
861    }
862
863    let mut request = OssRequest::new()
864        .method(RequestMethod::Get)
865        .bucket(bucket_name)
866        .object(object_key)
867        .add_query("uploadId", upload_id);
868
869    if let Some(options) = options {
870        if let Some(n) = options.max_parts {
871            request = request.add_query("max-parts", n.to_string());
872        }
873
874        if let Some(n) = options.part_number_marker {
875            request = request.add_query("part-number-marker", n.to_string());
876        }
877    }
878
879    Ok(request)
880}
881
882#[cfg(test)]
883mod test_multipart_common {
884    use super::ListMultipartUploadsResult;
885
886    #[test]
887    fn test_list_multipart_uploads_result() {
888        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
889        <ListMultipartUploadsResult xmlns="http://doc.oss-cn-hangzhou.aliyuncs.com">
890            <Bucket>oss-example</Bucket>
891            <KeyMarker></KeyMarker>
892            <UploadIdMarker></UploadIdMarker>
893            <NextKeyMarker>oss.avi</NextKeyMarker>
894            <NextUploadIdMarker>0004B99B8E707874FC2D692FA5D77D3F</NextUploadIdMarker>
895            <Delimiter></Delimiter>
896            <Prefix></Prefix>
897            <MaxUploads>1000</MaxUploads>
898            <IsTruncated>false</IsTruncated>
899            <Upload>
900                <Key>multipart.data</Key>
901                <UploadId>0004B999EF518A1FE585B0C9360DC4C8</UploadId>
902                <Initiated>2012-02-23T04:18:23.000Z</Initiated>
903            </Upload>
904            <Upload>
905                <Key>multipart.data</Key>
906                <UploadId>0004B999EF5A239BB9138C6227D6****</UploadId>
907                <Initiated>2012-02-23T04:18:23.000Z</Initiated>
908            </Upload>
909            <Upload>
910                <Key>oss.avi</Key>
911                <UploadId>0004B99B8E707874FC2D692FA5D7****</UploadId>
912                <Initiated>2012-02-23T06:14:27.000Z</Initiated>
913            </Upload>
914            <CommonPrefixes>
915                <Prefix>a/b/</Prefix>
916            </CommonPrefixes>
917        </ListMultipartUploadsResult>"#;
918
919        let data = ListMultipartUploadsResult::from_xml(xml).unwrap();
920        assert_eq!(Some("oss.avi".to_string()), data.next_key_marker);
921        assert_eq!(Some("0004B99B8E707874FC2D692FA5D77D3F".to_string()), data.next_upload_id_marker);
922        assert_eq!(1000, data.max_uploads);
923        assert_eq!(3, data.uploads.len());
924
925        assert_eq!("multipart.data", data.uploads[0].key);
926        assert_eq!("0004B999EF518A1FE585B0C9360DC4C8", &data.uploads[0].upload_id);
927        assert_eq!("2012-02-23T04:18:23.000Z", &data.uploads[0].initiated);
928
929        assert_eq!(1, data.common_prefixes.len());
930        assert_eq!("a/b/", &data.common_prefixes[0]);
931
932        println!("{:#?}", data);
933    }
934}