1use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::sync::Arc;
21use std::time::Duration;
22
23use bytes::Bytes;
24use constants::X_OSS_META_PREFIX;
25use http::HeaderMap;
26use http::HeaderName;
27use http::HeaderValue;
28use http::Request;
29use http::Response;
30use http::header::CACHE_CONTROL;
31use http::header::CONTENT_DISPOSITION;
32use http::header::CONTENT_LENGTH;
33use http::header::CONTENT_TYPE;
34use http::header::IF_MATCH;
35use http::header::IF_MODIFIED_SINCE;
36use http::header::IF_NONE_MATCH;
37use http::header::IF_UNMODIFIED_SINCE;
38use http::header::RANGE;
39use reqsign::AliyunCredential;
40use reqsign::AliyunLoader;
41use reqsign::AliyunOssSigner;
42use serde::Deserialize;
43use serde::Serialize;
44
45use crate::raw::*;
46use crate::services::oss::core::constants::X_OSS_FORBID_OVERWRITE;
47use crate::*;
48
49pub mod constants {
50    pub const X_OSS_SERVER_SIDE_ENCRYPTION: &str = "x-oss-server-side-encryption";
51
52    pub const X_OSS_SERVER_SIDE_ENCRYPTION_KEY_ID: &str = "x-oss-server-side-encryption-key-id";
53
54    pub const X_OSS_FORBID_OVERWRITE: &str = "x-oss-forbid-overwrite";
55
56    pub const X_OSS_VERSION_ID: &str = "x-oss-version-id";
57
58    pub const RESPONSE_CONTENT_DISPOSITION: &str = "response-content-disposition";
59
60    pub const OSS_QUERY_VERSION_ID: &str = "versionId";
61
62    pub const X_OSS_META_PREFIX: &str = "x-oss-meta-";
63}
64
65pub struct OssCore {
66    pub info: Arc<AccessorInfo>,
67
68    pub root: String,
69    pub bucket: String,
70    pub host: String,
74    pub endpoint: String,
75    pub presign_endpoint: String,
76    pub allow_anonymous: bool,
77
78    pub server_side_encryption: Option<HeaderValue>,
79    pub server_side_encryption_key_id: Option<HeaderValue>,
80
81    pub loader: AliyunLoader,
82    pub signer: AliyunOssSigner,
83}
84
85impl Debug for OssCore {
86    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87        f.debug_struct("Backend")
88            .field("root", &self.root)
89            .field("bucket", &self.bucket)
90            .field("endpoint", &self.endpoint)
91            .field("host", &self.host)
92            .finish_non_exhaustive()
93    }
94}
95
96impl OssCore {
97    async fn load_credential(&self) -> Result<Option<AliyunCredential>> {
98        let cred = self
99            .loader
100            .load()
101            .await
102            .map_err(new_request_credential_error)?;
103
104        if let Some(cred) = cred {
105            Ok(Some(cred))
106        } else if self.allow_anonymous {
107            Ok(None)
109        } else {
110            Err(Error::new(
112                ErrorKind::PermissionDenied,
113                "no valid credential found, please check configuration or try again",
114            )
115            .set_temporary())
116        }
117    }
118
119    pub async fn sign<T>(&self, req: &mut Request<T>) -> Result<()> {
120        let cred = if let Some(cred) = self.load_credential().await? {
121            cred
122        } else {
123            return Ok(());
124        };
125
126        self.signer.sign(req, &cred).map_err(new_request_sign_error)
127    }
128
129    pub async fn sign_query<T>(&self, req: &mut Request<T>, duration: Duration) -> Result<()> {
130        let cred = if let Some(cred) = self.load_credential().await? {
131            cred
132        } else {
133            return Ok(());
134        };
135
136        self.signer
137            .sign_query(req, duration, &cred)
138            .map_err(new_request_sign_error)
139    }
140
141    #[inline]
142    pub async fn send(&self, req: Request<Buffer>) -> Result<Response<Buffer>> {
143        self.info.http_client().send(req).await
144    }
145
146    pub fn insert_sse_headers(&self, mut req: http::request::Builder) -> http::request::Builder {
150        if let Some(v) = &self.server_side_encryption {
151            let mut v = v.clone();
152            v.set_sensitive(true);
153
154            req = req.header(
155                HeaderName::from_static(constants::X_OSS_SERVER_SIDE_ENCRYPTION),
156                v,
157            )
158        }
159        if let Some(v) = &self.server_side_encryption_key_id {
160            let mut v = v.clone();
161            v.set_sensitive(true);
162
163            req = req.header(
164                HeaderName::from_static(constants::X_OSS_SERVER_SIDE_ENCRYPTION_KEY_ID),
165                v,
166            )
167        }
168        req
169    }
170
171    fn insert_metadata_headers(
172        &self,
173        mut req: http::request::Builder,
174        size: Option<u64>,
175        args: &OpWrite,
176    ) -> Result<http::request::Builder> {
177        req = req.header(CONTENT_LENGTH, size.unwrap_or_default());
178
179        if let Some(mime) = args.content_type() {
180            req = req.header(CONTENT_TYPE, mime);
181        }
182
183        if let Some(pos) = args.content_disposition() {
184            req = req.header(CONTENT_DISPOSITION, pos);
185        }
186
187        if let Some(cache_control) = args.cache_control() {
188            req = req.header(CACHE_CONTROL, cache_control);
189        }
190
191        if args.if_not_exists() {
202            req = req.header(X_OSS_FORBID_OVERWRITE, "true");
203        }
204
205        if let Some(user_metadata) = args.user_metadata() {
206            for (key, value) in user_metadata {
207                if !self.check_user_metadata_key(key) {
209                    return Err(Error::new(
210                        ErrorKind::Unsupported,
211                        "the format of the user metadata key is invalid, please refer the document",
212                    ));
213                }
214                req = req.header(format!("{X_OSS_META_PREFIX}{key}"), value)
215            }
216        }
217
218        Ok(req)
219    }
220
221    fn check_user_metadata_key(&self, key: &str) -> bool {
224        key.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
225    }
226
227    pub fn parse_metadata(&self, path: &str, headers: &HeaderMap) -> Result<Metadata> {
238        let mut m = parse_into_metadata(path, headers)?;
239        let user_meta = parse_prefixed_headers(headers, X_OSS_META_PREFIX);
240        if !user_meta.is_empty() {
241            m = m.with_user_metadata(user_meta);
242        }
243
244        Ok(m)
245    }
246}
247
248impl OssCore {
249    #[allow(clippy::too_many_arguments)]
250    pub fn oss_put_object_request(
251        &self,
252        path: &str,
253        size: Option<u64>,
254        args: &OpWrite,
255        body: Buffer,
256        is_presign: bool,
257    ) -> Result<Request<Buffer>> {
258        let p = build_abs_path(&self.root, path);
259        let endpoint = self.get_endpoint(is_presign);
260        let url = format!("{}/{}", endpoint, percent_encode_path(&p));
261
262        let mut req = Request::put(&url);
263
264        req = self.insert_metadata_headers(req, size, args)?;
265
266        req = self.insert_sse_headers(req);
268
269        let req = req.extension(Operation::Write);
270
271        let req = req.body(body).map_err(new_request_build_error)?;
272        Ok(req)
273    }
274
275    pub fn oss_append_object_request(
276        &self,
277        path: &str,
278        position: u64,
279        size: u64,
280        args: &OpWrite,
281        body: Buffer,
282    ) -> Result<Request<Buffer>> {
283        let p = build_abs_path(&self.root, path);
284        let endpoint = self.get_endpoint(false);
285        let url = format!(
286            "{}/{}?append&position={}",
287            endpoint,
288            percent_encode_path(&p),
289            position
290        );
291
292        let mut req = Request::post(&url);
293
294        req = self.insert_metadata_headers(req, Some(size), args)?;
295
296        req = self.insert_sse_headers(req);
298
299        let req = req.extension(Operation::Write);
300
301        let req = req.body(body).map_err(new_request_build_error)?;
302        Ok(req)
303    }
304
305    pub fn oss_get_object_request(
306        &self,
307        path: &str,
308        is_presign: bool,
309        args: &OpRead,
310    ) -> Result<Request<Buffer>> {
311        let p = build_abs_path(&self.root, path);
312        let endpoint = self.get_endpoint(is_presign);
313        let range = args.range();
314        let mut url = format!("{}/{}", endpoint, percent_encode_path(&p));
315
316        let mut query_args = Vec::new();
318        if let Some(override_content_disposition) = args.override_content_disposition() {
319            query_args.push(format!(
320                "{}={}",
321                constants::RESPONSE_CONTENT_DISPOSITION,
322                percent_encode_path(override_content_disposition)
323            ))
324        }
325        if let Some(version) = args.version() {
326            query_args.push(format!(
327                "{}={}",
328                constants::OSS_QUERY_VERSION_ID,
329                percent_encode_path(version)
330            ))
331        }
332
333        if !query_args.is_empty() {
334            url.push_str(&format!("?{}", query_args.join("&")));
335        }
336
337        let mut req = Request::get(&url);
338
339        if !range.is_full() {
340            req = req.header(RANGE, range.to_header());
341            req = req.header("x-oss-range-behavior", "standard");
344        }
345
346        if let Some(if_match) = args.if_match() {
347            req = req.header(IF_MATCH, if_match)
348        }
349        if let Some(if_none_match) = args.if_none_match() {
350            req = req.header(IF_NONE_MATCH, if_none_match);
351        }
352
353        if let Some(if_modified_since) = args.if_modified_since() {
354            req = req.header(IF_MODIFIED_SINCE, if_modified_since.format_http_date());
355        }
356
357        if let Some(if_unmodified_since) = args.if_unmodified_since() {
358            req = req.header(IF_UNMODIFIED_SINCE, if_unmodified_since.format_http_date());
359        }
360
361        let req = req.extension(Operation::Read);
362
363        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
364
365        Ok(req)
366    }
367
368    fn oss_delete_object_request(&self, path: &str, args: &OpDelete) -> Result<Request<Buffer>> {
369        let p = build_abs_path(&self.root, path);
370        let endpoint = self.get_endpoint(false);
371        let mut url = format!("{}/{}", endpoint, percent_encode_path(&p));
372
373        let mut query_args = Vec::new();
374
375        if let Some(version) = args.version() {
376            query_args.push(format!(
377                "{}={}",
378                constants::OSS_QUERY_VERSION_ID,
379                percent_encode_path(version)
380            ))
381        }
382
383        if !query_args.is_empty() {
384            url.push_str(&format!("?{}", query_args.join("&")));
385        }
386
387        let req = Request::delete(&url);
388
389        let req = req.extension(Operation::Delete);
390
391        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
392
393        Ok(req)
394    }
395
396    pub fn oss_head_object_request(
397        &self,
398        path: &str,
399        is_presign: bool,
400        args: &OpStat,
401    ) -> Result<Request<Buffer>> {
402        let p = build_abs_path(&self.root, path);
403        let endpoint = self.get_endpoint(is_presign);
404        let mut url = format!("{}/{}", endpoint, percent_encode_path(&p));
405
406        let mut query_args = Vec::new();
407
408        if let Some(version) = args.version() {
409            query_args.push(format!(
410                "{}={}",
411                constants::OSS_QUERY_VERSION_ID,
412                percent_encode_path(version)
413            ))
414        }
415
416        if !query_args.is_empty() {
417            url.push_str(&format!("?{}", query_args.join("&")));
418        }
419
420        let mut req = Request::head(&url);
421        if let Some(if_match) = args.if_match() {
422            req = req.header(IF_MATCH, if_match)
423        }
424        if let Some(if_none_match) = args.if_none_match() {
425            req = req.header(IF_NONE_MATCH, if_none_match);
426        }
427
428        let req = req.extension(Operation::Stat);
429
430        let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
431
432        Ok(req)
433    }
434
435    pub fn oss_list_object_request(
436        &self,
437        path: &str,
438        token: &str,
439        delimiter: &str,
440        limit: Option<usize>,
441        start_after: Option<String>,
442    ) -> Result<Request<Buffer>> {
443        let p = build_abs_path(&self.root, path);
444
445        let endpoint = self.get_endpoint(false);
446        let mut url = QueryPairsWriter::new(endpoint);
447        url = url.push("list-type", "2");
448        if !delimiter.is_empty() {
449            url = url.push("delimiter", delimiter);
450        }
451        if !p.is_empty() {
453            url = url.push("prefix", &percent_encode_path(&p));
454        }
455
456        if let Some(limit) = limit {
458            url = url.push("max-keys", &limit.to_string());
459        }
460
461        if !token.is_empty() {
463            url = url.push("continuation-token", &percent_encode_path(token));
464        }
465
466        if let Some(start_after) = start_after {
468            let start_after = build_abs_path(&self.root, &start_after);
469            url = url.push("start-after", &percent_encode_path(&start_after));
470        }
471
472        let req = Request::get(url.finish())
473            .extension(Operation::List)
474            .body(Buffer::new())
475            .map_err(new_request_build_error)?;
476        Ok(req)
477    }
478
479    pub async fn oss_get_object(&self, path: &str, args: &OpRead) -> Result<Response<HttpBody>> {
480        let mut req = self.oss_get_object_request(path, false, args)?;
481        self.sign(&mut req).await?;
482        self.info.http_client().fetch(req).await
483    }
484
485    pub async fn oss_head_object(&self, path: &str, args: &OpStat) -> Result<Response<Buffer>> {
486        let mut req = self.oss_head_object_request(path, false, args)?;
487
488        self.sign(&mut req).await?;
489        self.send(req).await
490    }
491
492    pub async fn oss_copy_object(&self, from: &str, to: &str) -> Result<Response<Buffer>> {
493        let source = build_abs_path(&self.root, from);
494        let target = build_abs_path(&self.root, to);
495
496        let url = format!(
497            "{}/{}",
498            self.get_endpoint(false),
499            percent_encode_path(&target)
500        );
501        let source = format!("/{}/{}", self.bucket, percent_encode_path(&source));
502
503        let mut req = Request::put(&url);
504
505        req = self.insert_sse_headers(req);
506
507        req = req.header("x-oss-copy-source", source);
508
509        let req = req.extension(Operation::Copy);
510
511        let mut req = req.body(Buffer::new()).map_err(new_request_build_error)?;
512
513        self.sign(&mut req).await?;
514        self.send(req).await
515    }
516
517    pub async fn oss_list_object(
518        &self,
519        path: &str,
520        token: &str,
521        delimiter: &str,
522        limit: Option<usize>,
523        start_after: Option<String>,
524    ) -> Result<Response<Buffer>> {
525        let mut req = self.oss_list_object_request(path, token, delimiter, limit, start_after)?;
526
527        self.sign(&mut req).await?;
528        self.send(req).await
529    }
530
531    pub async fn oss_list_object_versions(
532        &self,
533        prefix: &str,
534        delimiter: &str,
535        limit: Option<usize>,
536        key_marker: &str,
537        version_id_marker: &str,
538    ) -> Result<Response<Buffer>> {
539        let p = build_abs_path(&self.root, prefix);
540
541        let mut url = QueryPairsWriter::new(&self.endpoint);
542        url = url.push("versions", "");
543
544        if !p.is_empty() {
545            url = url.push("prefix", &percent_encode_path(p.as_str()));
546        }
547        if !delimiter.is_empty() {
548            url = url.push("delimiter", delimiter);
549        }
550
551        if let Some(limit) = limit {
552            url = url.push("max-keys", &limit.to_string());
553        }
554        if !key_marker.is_empty() {
555            url = url.push("key-marker", &percent_encode_path(key_marker));
556        }
557        if !version_id_marker.is_empty() {
558            url = url.push("version-id-marker", &percent_encode_path(version_id_marker));
559        }
560
561        let mut req = Request::get(url.finish())
562            .extension(Operation::List)
563            .body(Buffer::new())
564            .map_err(new_request_build_error)?;
565
566        self.sign(&mut req).await?;
567
568        self.send(req).await
569    }
570
571    pub async fn oss_delete_object(&self, path: &str, args: &OpDelete) -> Result<Response<Buffer>> {
572        let mut req = self.oss_delete_object_request(path, args)?;
573        self.sign(&mut req).await?;
574        self.send(req).await
575    }
576
577    pub async fn oss_delete_objects(
578        &self,
579        paths: Vec<(String, OpDelete)>,
580    ) -> Result<Response<Buffer>> {
581        let url = format!("{}/?delete", self.endpoint);
582
583        let req = Request::post(&url);
584
585        let content = quick_xml::se::to_string(&DeleteObjectsRequest {
586            object: paths
587                .into_iter()
588                .map(|(path, op)| DeleteObjectsRequestObject {
589                    key: build_abs_path(&self.root, &path),
590                    version_id: op.version().map(|v| v.to_owned()),
591                })
592                .collect(),
593        })
594        .map_err(new_xml_serialize_error)?;
595
596        let req = req.header(CONTENT_LENGTH, content.len());
598        let req = req.header(CONTENT_TYPE, "application/xml");
600        let req = req.header("CONTENT-MD5", format_content_md5(content.as_bytes()));
602
603        let req = req.extension(Operation::Delete);
604
605        let mut req = req
606            .body(Buffer::from(Bytes::from(content)))
607            .map_err(new_request_build_error)?;
608
609        self.sign(&mut req).await?;
610
611        self.send(req).await
612    }
613
614    fn get_endpoint(&self, is_presign: bool) -> &str {
615        if is_presign {
616            &self.presign_endpoint
617        } else {
618            &self.endpoint
619        }
620    }
621
622    pub async fn oss_initiate_upload(
623        &self,
624        path: &str,
625        content_type: Option<&str>,
626        content_disposition: Option<&str>,
627        cache_control: Option<&str>,
628        is_presign: bool,
629    ) -> Result<Response<Buffer>> {
630        let path = build_abs_path(&self.root, path);
631        let endpoint = self.get_endpoint(is_presign);
632        let url = format!("{}/{}?uploads", endpoint, percent_encode_path(&path));
633        let mut req = Request::post(&url);
634        if let Some(mime) = content_type {
635            req = req.header(CONTENT_TYPE, mime);
636        }
637        if let Some(disposition) = content_disposition {
638            req = req.header(CONTENT_DISPOSITION, disposition);
639        }
640        if let Some(cache_control) = cache_control {
641            req = req.header(CACHE_CONTROL, cache_control);
642        }
643        req = self.insert_sse_headers(req);
644
645        let req = req.extension(Operation::Write);
646
647        let mut req = req.body(Buffer::new()).map_err(new_request_build_error)?;
648        self.sign(&mut req).await?;
649        self.send(req).await
650    }
651
652    pub async fn oss_upload_part_request(
654        &self,
655        path: &str,
656        upload_id: &str,
657        part_number: usize,
658        is_presign: bool,
659        size: u64,
660        body: Buffer,
661    ) -> Result<Response<Buffer>> {
662        let p = build_abs_path(&self.root, path);
663        let endpoint = self.get_endpoint(is_presign);
664
665        let url = format!(
666            "{}/{}?partNumber={}&uploadId={}",
667            endpoint,
668            percent_encode_path(&p),
669            part_number,
670            percent_encode_path(upload_id)
671        );
672
673        let mut req = Request::put(&url);
674        req = req.header(CONTENT_LENGTH, size);
675
676        let req = req.extension(Operation::Write);
677
678        let mut req = req.body(body).map_err(new_request_build_error)?;
679        self.sign(&mut req).await?;
680        self.send(req).await
681    }
682
683    pub async fn oss_complete_multipart_upload_request(
684        &self,
685        path: &str,
686        upload_id: &str,
687        is_presign: bool,
688        parts: Vec<MultipartUploadPart>,
689    ) -> Result<Response<Buffer>> {
690        let p = build_abs_path(&self.root, path);
691        let endpoint = self.get_endpoint(is_presign);
692        let url = format!(
693            "{}/{}?uploadId={}",
694            endpoint,
695            percent_encode_path(&p),
696            percent_encode_path(upload_id)
697        );
698
699        let req = Request::post(&url);
700
701        let content = quick_xml::se::to_string(&CompleteMultipartUploadRequest {
702            part: parts.to_vec(),
703        })
704        .map_err(new_xml_serialize_error)?;
705        let req = req.header(CONTENT_LENGTH, content.len());
707        let req = req.header(CONTENT_TYPE, "application/xml");
709
710        let req = req.extension(Operation::Write);
711
712        let mut req = req
713            .body(Buffer::from(Bytes::from(content)))
714            .map_err(new_request_build_error)?;
715
716        self.sign(&mut req).await?;
717        self.send(req).await
718    }
719
720    pub async fn oss_abort_multipart_upload(
723        &self,
724        path: &str,
725        upload_id: &str,
726    ) -> Result<Response<Buffer>> {
727        let p = build_abs_path(&self.root, path);
728
729        let url = format!(
730            "{}/{}?uploadId={}",
731            self.endpoint,
732            percent_encode_path(&p),
733            percent_encode_path(upload_id)
734        );
735
736        let mut req = Request::delete(&url)
737            .extension(Operation::Write)
738            .body(Buffer::new())
739            .map_err(new_request_build_error)?;
740        self.sign(&mut req).await?;
741        self.send(req).await
742    }
743}
744
745#[derive(Default, Debug, Serialize)]
747#[serde(default, rename = "Delete", rename_all = "PascalCase")]
748pub struct DeleteObjectsRequest {
749    pub object: Vec<DeleteObjectsRequestObject>,
750}
751
752#[derive(Default, Debug, Serialize)]
753#[serde(rename_all = "PascalCase")]
754pub struct DeleteObjectsRequestObject {
755    pub key: String,
756    #[serde(skip_serializing_if = "Option::is_none")]
757    pub version_id: Option<String>,
758}
759
760#[derive(Default, Debug, Deserialize)]
762#[serde(default, rename = "DeleteResult", rename_all = "PascalCase")]
763pub struct DeleteObjectsResult {
764    pub deleted: Vec<DeleteObjectsResultDeleted>,
765}
766
767#[derive(Default, Debug, Deserialize)]
768#[serde(rename_all = "PascalCase")]
769pub struct DeleteObjectsResultDeleted {
770    pub key: String,
771    pub version_id: Option<String>,
772}
773
774#[derive(Default, Debug, Deserialize)]
775#[serde(rename_all = "PascalCase")]
776pub struct InitiateMultipartUploadResult {
777    #[cfg(test)]
778    pub bucket: String,
779    #[cfg(test)]
780    pub key: String,
781    pub upload_id: String,
782}
783
784#[derive(Clone, Default, Debug, Serialize)]
785#[serde(default, rename_all = "PascalCase")]
786pub struct MultipartUploadPart {
787    #[serde(rename = "PartNumber")]
788    pub part_number: usize,
789    #[serde(rename = "ETag")]
790    pub etag: String,
791}
792
793#[derive(Default, Debug, Serialize)]
794#[serde(default, rename = "CompleteMultipartUpload", rename_all = "PascalCase")]
795pub struct CompleteMultipartUploadRequest {
796    pub part: Vec<MultipartUploadPart>,
797}
798
799#[derive(Default, Debug, Deserialize)]
800#[serde(default, rename_all = "PascalCase")]
801pub struct ListObjectsOutput {
802    pub prefix: String,
803    pub max_keys: u64,
804    pub encoding_type: String,
805    pub is_truncated: bool,
806    pub common_prefixes: Vec<CommonPrefix>,
807    pub contents: Vec<ListObjectsOutputContent>,
808    pub key_count: u64,
809
810    pub next_continuation_token: Option<String>,
811}
812
813#[derive(Default, Debug, Deserialize, PartialEq, Eq)]
814#[serde(default, rename_all = "PascalCase")]
815pub struct ListObjectsOutputContent {
816    pub key: String,
817    pub last_modified: String,
818    #[serde(rename = "ETag")]
819    pub etag: String,
820    pub size: u64,
821}
822
823#[derive(Default, Debug, Deserialize)]
824#[serde(default, rename_all = "PascalCase")]
825pub struct CommonPrefix {
826    pub prefix: String,
827}
828
829#[derive(Default, Debug, Eq, PartialEq, Deserialize)]
830#[serde(rename_all = "PascalCase")]
831pub struct OutputCommonPrefix {
832    pub prefix: String,
833}
834
835#[derive(Default, Debug, Deserialize)]
837#[serde(default, rename_all = "PascalCase")]
838pub struct ListObjectVersionsOutput {
839    pub is_truncated: Option<bool>,
840    pub next_key_marker: Option<String>,
841    pub next_version_id_marker: Option<String>,
842    pub common_prefixes: Vec<OutputCommonPrefix>,
843    pub version: Vec<ListObjectVersionsOutputVersion>,
844    pub delete_marker: Vec<ListObjectVersionsOutputDeleteMarker>,
845}
846
847#[derive(Default, Debug, Eq, PartialEq, Deserialize)]
848#[serde(rename_all = "PascalCase")]
849pub struct ListObjectVersionsOutputVersion {
850    pub key: String,
851    pub version_id: String,
852    pub is_latest: bool,
853    pub size: u64,
854    pub last_modified: String,
855    #[serde(rename = "ETag")]
856    pub etag: Option<String>,
857}
858
859#[derive(Default, Debug, Eq, PartialEq, Deserialize)]
860#[serde(rename_all = "PascalCase")]
861pub struct ListObjectVersionsOutputDeleteMarker {
862    pub key: String,
863    pub version_id: String,
864    pub is_latest: bool,
865    pub last_modified: String,
866}
867
868#[cfg(test)]
869mod tests {
870    use bytes::Buf;
871    use bytes::Bytes;
872
873    use super::*;
874
875    #[test]
877    fn test_serialize_delete_objects_request() {
878        let req = DeleteObjectsRequest {
879            object: vec![
880                DeleteObjectsRequestObject {
881                    key: "multipart.data".to_string(),
882                    version_id: None,
883                },
884                DeleteObjectsRequestObject {
885                    key: "test.jpg".to_string(),
886                    version_id: None,
887                },
888                DeleteObjectsRequestObject {
889                    key: "demo.jpg".to_string(),
890                    version_id: None,
891                },
892            ],
893        };
894
895        let actual = quick_xml::se::to_string(&req).expect("must succeed");
896
897        pretty_assertions::assert_eq!(
898            actual,
899            r#"<Delete>
900  <Object>
901    <Key>multipart.data</Key>
902  </Object>
903  <Object>
904    <Key>test.jpg</Key>
905  </Object>
906  <Object>
907    <Key>demo.jpg</Key>
908  </Object>
909</Delete>"#
910                .replace([' ', '\n'], "")
912        )
913    }
914
915    #[test]
917    fn test_deserialize_delete_objects_result() {
918        let bs = Bytes::from(
919            r#"<?xml version="1.0" encoding="UTF-8"?>
920<DeleteResult xmlns="http://doc.oss-cn-hangzhou.aliyuncs.com">
921    <Deleted>
922       <Key>multipart.data</Key>
923    </Deleted>
924    <Deleted>
925       <Key>test.jpg</Key>
926    </Deleted>
927    <Deleted>
928       <Key>demo.jpg</Key>
929    </Deleted>
930</DeleteResult>"#,
931        );
932
933        let out: DeleteObjectsResult =
934            quick_xml::de::from_reader(bs.reader()).expect("must success");
935
936        assert_eq!(out.deleted.len(), 3);
937        assert_eq!(out.deleted[0].key, "multipart.data");
938        assert_eq!(out.deleted[1].key, "test.jpg");
939        assert_eq!(out.deleted[2].key, "demo.jpg");
940    }
941
942    #[test]
943    fn test_deserialize_initiate_multipart_upload_response() {
944        let bs = Bytes::from(
945            r#"<?xml version="1.0" encoding="UTF-8"?>
946<InitiateMultipartUploadResult xmlns="http://doc.oss-cn-hangzhou.aliyuncs.com">
947    <Bucket>oss-example</Bucket>
948    <Key>multipart.data</Key>
949    <UploadId>0004B9894A22E5B1888A1E29F823****</UploadId>
950</InitiateMultipartUploadResult>"#,
951        );
952        let out: InitiateMultipartUploadResult =
953            quick_xml::de::from_reader(bs.reader()).expect("must success");
954
955        assert_eq!("0004B9894A22E5B1888A1E29F823****", out.upload_id);
956        assert_eq!("multipart.data", out.key);
957        assert_eq!("oss-example", out.bucket);
958    }
959
960    #[test]
961    fn test_serialize_complete_multipart_upload_request() {
962        let req = CompleteMultipartUploadRequest {
963            part: vec![
964                MultipartUploadPart {
965                    part_number: 1,
966                    etag: "\"3349DC700140D7F86A0784842780****\"".to_string(),
967                },
968                MultipartUploadPart {
969                    part_number: 5,
970                    etag: "\"8EFDA8BE206636A695359836FE0A****\"".to_string(),
971                },
972                MultipartUploadPart {
973                    part_number: 8,
974                    etag: "\"8C315065167132444177411FDA14****\"".to_string(),
975                },
976            ],
977        };
978
979        let mut serialized = String::new();
981        let mut serializer = quick_xml::se::Serializer::new(&mut serialized);
982        serializer.indent(' ', 4);
983        req.serialize(serializer).unwrap();
984        pretty_assertions::assert_eq!(
985            serialized,
986            r#"<CompleteMultipartUpload>
987    <Part>
988        <PartNumber>1</PartNumber>
989        <ETag>"3349DC700140D7F86A0784842780****"</ETag>
990    </Part>
991    <Part>
992        <PartNumber>5</PartNumber>
993        <ETag>"8EFDA8BE206636A695359836FE0A****"</ETag>
994    </Part>
995    <Part>
996        <PartNumber>8</PartNumber>
997        <ETag>"8C315065167132444177411FDA14****"</ETag>
998    </Part>
999</CompleteMultipartUpload>"#
1000        )
1001    }
1002
1003    #[test]
1004    fn test_parse_list_output() {
1005        let bs = bytes::Bytes::from(
1006            r#"<?xml version="1.0" encoding="UTF-8"?>
1007<ListBucketResult xmlns="https://doc.oss-cn-hangzhou.aliyuncs.com">
1008    <Name>examplebucket</Name>
1009    <Prefix></Prefix>
1010    <StartAfter>b</StartAfter>
1011    <MaxKeys>3</MaxKeys>
1012    <EncodingType>url</EncodingType>
1013    <IsTruncated>true</IsTruncated>
1014    <NextContinuationToken>CgJiYw--</NextContinuationToken>
1015    <Contents>
1016        <Key>b/c</Key>
1017        <LastModified>2020-05-18T05:45:54.000Z</LastModified>
1018        <ETag>"35A27C2B9EAEEB6F48FD7FB5861D****"</ETag>
1019        <Size>25</Size>
1020        <StorageClass>STANDARD</StorageClass>
1021        <Owner>
1022            <ID>1686240967192623</ID>
1023            <DisplayName>1686240967192623</DisplayName>
1024        </Owner>
1025    </Contents>
1026    <Contents>
1027        <Key>ba</Key>
1028        <LastModified>2020-05-18T11:17:58.000Z</LastModified>
1029        <ETag>"35A27C2B9EAEEB6F48FD7FB5861D****"</ETag>
1030        <Size>25</Size>
1031        <StorageClass>STANDARD</StorageClass>
1032        <Owner>
1033            <ID>1686240967192623</ID>
1034            <DisplayName>1686240967192623</DisplayName>
1035        </Owner>
1036    </Contents>
1037    <Contents>
1038        <Key>bc</Key>
1039        <LastModified>2020-05-18T05:45:59.000Z</LastModified>
1040        <ETag>"35A27C2B9EAEEB6F48FD7FB5861D****"</ETag>
1041        <Size>25</Size>
1042        <StorageClass>STANDARD</StorageClass>
1043        <Owner>
1044            <ID>1686240967192623</ID>
1045            <DisplayName>1686240967192623</DisplayName>
1046        </Owner>
1047    </Contents>
1048    <KeyCount>3</KeyCount>
1049</ListBucketResult>"#,
1050        );
1051
1052        let out: ListObjectsOutput = quick_xml::de::from_reader(bs.reader()).expect("must_success");
1053
1054        assert!(out.is_truncated);
1055        assert_eq!(out.next_continuation_token, Some("CgJiYw--".to_string()));
1056        assert!(out.common_prefixes.is_empty());
1057
1058        assert_eq!(
1059            out.contents,
1060            vec![
1061                ListObjectsOutputContent {
1062                    key: "b/c".to_string(),
1063                    last_modified: "2020-05-18T05:45:54.000Z".to_string(),
1064                    etag: "\"35A27C2B9EAEEB6F48FD7FB5861D****\"".to_string(),
1065                    size: 25,
1066                },
1067                ListObjectsOutputContent {
1068                    key: "ba".to_string(),
1069                    last_modified: "2020-05-18T11:17:58.000Z".to_string(),
1070                    etag: "\"35A27C2B9EAEEB6F48FD7FB5861D****\"".to_string(),
1071                    size: 25,
1072                },
1073                ListObjectsOutputContent {
1074                    key: "bc".to_string(),
1075                    last_modified: "2020-05-18T05:45:59.000Z".to_string(),
1076                    etag: "\"35A27C2B9EAEEB6F48FD7FB5861D****\"".to_string(),
1077                    size: 25,
1078                }
1079            ]
1080        )
1081    }
1082}