1use 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#[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#[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 pub part_number: u32,
72
73 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#[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 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#[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 pub part_number: u32,
123
124 pub upload_id: String,
126
127 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#[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 pub source_object_version_id: Option<String>,
152
153 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#[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#[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 pub parts: Vec<(u32, String)>,
256}
257
258impl CompleteMultipartUploadRequest {
259 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#[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#[derive(Debug, Clone)]
302#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
303pub enum CompleteMultipartUploadResult {
304 #[cfg_attr(feature = "serde-camelcase", serde(rename = "apiResponse", rename_all = "camelCase"))]
306 ApiResponse(CompleteMultipartUploadApiResponse),
307
308 #[cfg_attr(feature = "serde-camelcase", serde(rename = "callbackResponse"))]
311 CallbackResponse(String),
312}
313
314#[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#[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 pub delimiter: Option<char>,
359
360 pub max_uploads: Option<u32>,
362
363 pub key_marker: Option<String>,
365
366 pub upload_id_marker: Option<String>,
373
374 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 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 pub max_parts: Option<u32>,
531
532 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", ©_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 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}