1use std::{collections::HashMap, fmt::Display};
2
3use base64::prelude::{Engine, BASE64_STANDARD};
4use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
5
6use crate::{
7 common::{self, build_tag_string, MetadataDirective, ObjectType, ServerSideEncryptionAlgorithm, StorageClass, TagDirective, MIME_TYPE_XML},
8 error::Error,
9 request::{OssRequest, RequestMethod},
10 util::{sanitize_etag, validate_bucket_name, validate_meta_key, validate_object_key, validate_tag_key, validate_tag_value},
11 RequestBody, Result,
12};
13
14#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
18#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
19pub enum ObjectAcl {
20 #[default]
21 Default,
22
23 #[cfg_attr(feature = "serde-support", serde(rename = "public-read-write"))]
24 PublicReadWrite,
25
26 #[cfg_attr(feature = "serde-support", serde(rename = "public-read"))]
27 PublicRead,
28
29 #[cfg_attr(feature = "serde-support", serde(rename = "private"))]
30 Private,
31}
32
33impl ObjectAcl {
34 pub fn as_str(&self) -> &str {
35 match self {
36 ObjectAcl::PublicReadWrite => "public-read-write",
37 ObjectAcl::PublicRead => "public-read",
38 ObjectAcl::Private => "private",
39 ObjectAcl::Default => "default",
40 }
41 }
42}
43
44impl Display for ObjectAcl {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 ObjectAcl::PublicReadWrite => write!(f, "public-read-write"),
48 ObjectAcl::PublicRead => write!(f, "public-read"),
49 ObjectAcl::Private => write!(f, "private"),
50 ObjectAcl::Default => write!(f, "default"),
51 }
52 }
53}
54
55impl AsRef<str> for ObjectAcl {
56 fn as_ref(&self) -> &str {
57 self.as_str()
58 }
59}
60
61impl TryFrom<&str> for ObjectAcl {
62 type Error = crate::error::Error;
63
64 fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
65 match s {
66 "public-read-write" => Ok(ObjectAcl::PublicReadWrite),
67 "public-read" => Ok(ObjectAcl::PublicRead),
68 "private" => Ok(ObjectAcl::Private),
69 "default" | "" => Ok(ObjectAcl::Default),
70 _ => Err(Error::Other(format!("Invalid ACL value: {}", s))),
71 }
72 }
73}
74
75impl TryFrom<String> for ObjectAcl {
76 type Error = crate::error::Error;
77
78 fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
79 Self::try_from(s.as_str())
80 }
81}
82
83impl TryFrom<&String> for ObjectAcl {
84 type Error = crate::error::Error;
85
86 fn try_from(s: &String) -> std::result::Result<Self, Self::Error> {
87 Self::try_from(s.as_str())
88 }
89}
90
91#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
92#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
93pub enum ContentEncoding {
94 #[default]
96 #[cfg_attr(feature = "serde-support", serde(rename = "identity"))]
97 Identity,
98
99 #[cfg_attr(feature = "serde-support", serde(rename = "gzip"))]
101 Gzip,
102
103 #[cfg_attr(feature = "serde-support", serde(rename = "deflate"))]
105 Deflate,
106
107 #[cfg_attr(feature = "serde-support", serde(rename = "compress"))]
109 Compress,
110
111 #[cfg_attr(feature = "serde-support", serde(rename = "br"))]
113 Brotli,
114}
115
116impl ContentEncoding {
117 pub fn as_str(&self) -> &str {
118 match self {
119 ContentEncoding::Identity => "identity",
120 ContentEncoding::Gzip => "gzip",
121 ContentEncoding::Deflate => "deflate",
122 ContentEncoding::Compress => "compress",
123 ContentEncoding::Brotli => "br",
124 }
125 }
126}
127
128impl TryFrom<&str> for ContentEncoding {
129 type Error = Error;
130
131 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
134 match value.to_lowercase().as_str() {
135 "identity" => Ok(ContentEncoding::Identity),
136 "gzip" => Ok(ContentEncoding::Gzip),
137 "deflate" => Ok(ContentEncoding::Deflate),
138 "compress" => Ok(ContentEncoding::Compress),
139 "br" => Ok(ContentEncoding::Brotli),
140 _ => Err(Error::Other(format!("invalid content encoding: {}", value))),
141 }
142 }
143}
144
145impl TryFrom<&String> for ContentEncoding {
146 type Error = Error;
147
148 fn try_from(value: &String) -> std::result::Result<Self, Self::Error> {
150 Self::try_from(value.as_str())
151 }
152}
153
154impl TryFrom<String> for ContentEncoding {
155 type Error = Error;
156
157 fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
159 Self::try_from(value.as_str())
160 }
161}
162
163#[derive(Debug, Clone, Default)]
167#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
168#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
169pub struct PutObjectOptions {
170 pub mime_type: Option<String>,
173
174 pub cache_control: Option<String>,
182
183 pub content_disposition: Option<String>,
194
195 pub content_encoding: Option<ContentEncoding>,
196
197 pub content_md5: Option<String>,
199
200 pub expires: Option<String>,
202
203 pub forbid_overwrite: Option<bool>,
213
214 pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
217
218 pub server_side_data_encryption: Option<ServerSideEncryptionAlgorithm>,
220
221 pub server_side_encryption_key_id: Option<String>,
223
224 pub object_acl: Option<ObjectAcl>,
226
227 pub storage_class: Option<StorageClass>,
229
230 pub metadata: HashMap<String, String>,
236
237 pub tags: HashMap<String, String>,
241
242 pub callback: Option<Callback>,
244
245 pub parameters: HashMap<String, String>,
250}
251
252pub struct PutObjectOptionsBuilder {
253 mime_type: Option<String>,
254 cache_control: Option<String>,
255 content_disposition: Option<String>,
256 content_encoding: Option<ContentEncoding>,
257 content_md5: Option<String>,
258 expires: Option<String>,
259 forbid_overwrite: Option<bool>,
260 server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
261 server_side_data_encryption: Option<ServerSideEncryptionAlgorithm>,
262 server_side_encryption_key_id: Option<String>,
263 object_acl: Option<ObjectAcl>,
264 storage_class: Option<StorageClass>,
265 metadata: HashMap<String, String>,
266 tags: HashMap<String, String>,
267 callback: Option<Callback>,
268 parameters: HashMap<String, String>,
269}
270
271impl PutObjectOptionsBuilder {
272 pub fn new() -> Self {
273 Self {
274 mime_type: None,
275 cache_control: None,
276 content_disposition: None,
277 content_encoding: None,
278 content_md5: None,
279 expires: None,
280 forbid_overwrite: None,
281 server_side_encryption: None,
282 server_side_data_encryption: None,
283 server_side_encryption_key_id: None,
284 object_acl: None,
285 storage_class: None,
286 metadata: HashMap::new(),
287 tags: HashMap::new(),
288 callback: None,
289 parameters: HashMap::new(),
290 }
291 }
292
293 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
294 self.mime_type = Some(mime_type.into());
295 self
296 }
297
298 pub fn cache_control(mut self, cache_control: impl Into<String>) -> Self {
299 self.cache_control = Some(cache_control.into());
300 self
301 }
302
303 pub fn content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
304 self.content_disposition = Some(content_disposition.into());
305 self
306 }
307
308 pub fn content_encoding(mut self, content_encoding: ContentEncoding) -> Self {
309 self.content_encoding = Some(content_encoding);
310 self
311 }
312
313 pub fn content_md5(mut self, content_md5: impl Into<String>) -> Self {
314 self.content_md5 = Some(content_md5.into());
315 self
316 }
317
318 pub fn expires(mut self, expires: impl Into<String>) -> Self {
319 self.expires = Some(expires.into());
320 self
321 }
322
323 pub fn forbid_overwrite(mut self, forbid_overwrite: bool) -> Self {
324 self.forbid_overwrite = Some(forbid_overwrite);
325 self
326 }
327
328 pub fn server_side_encryption(mut self, algorithm: ServerSideEncryptionAlgorithm) -> Self {
329 self.server_side_encryption = Some(algorithm);
330 self
331 }
332
333 pub fn server_side_data_encryption(mut self, algorithm: ServerSideEncryptionAlgorithm) -> Self {
334 self.server_side_data_encryption = Some(algorithm);
335 self
336 }
337
338 pub fn server_side_encryption_key_id(mut self, key_id: impl Into<String>) -> Self {
339 self.server_side_encryption_key_id = Some(key_id.into());
340 self
341 }
342
343 pub fn object_acl(mut self, acl: ObjectAcl) -> Self {
344 self.object_acl = Some(acl);
345 self
346 }
347
348 pub fn storage_class(mut self, storage_class: StorageClass) -> Self {
349 self.storage_class = Some(storage_class);
350 self
351 }
352
353 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
354 self.metadata.insert(key.into(), value.into());
355 self
356 }
357
358 pub fn tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
359 self.tags.insert(key.into(), value.into());
360 self
361 }
362
363 pub fn callback(mut self, cb: Callback) -> Self {
364 self.callback = Some(cb);
365 self
366 }
367
368 pub fn parameter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
369 self.parameters.insert(key.into(), value.into());
370 self
371 }
372
373 pub fn build(self) -> PutObjectOptions {
374 PutObjectOptions {
375 mime_type: self.mime_type,
376 cache_control: self.cache_control,
377 content_disposition: self.content_disposition,
378 content_encoding: self.content_encoding,
379 content_md5: self.content_md5,
380 expires: self.expires,
381 forbid_overwrite: self.forbid_overwrite,
382 server_side_encryption: self.server_side_encryption,
383 server_side_data_encryption: self.server_side_data_encryption,
384 server_side_encryption_key_id: self.server_side_encryption_key_id,
385 object_acl: self.object_acl,
386 storage_class: self.storage_class,
387 metadata: self.metadata,
388 tags: self.tags,
389 callback: self.callback,
390 parameters: self.parameters,
391 }
392 }
393}
394
395impl Default for PutObjectOptionsBuilder {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401pub struct GetObjectOptions {
405 pub range: Option<String>,
411
412 pub if_modified_since: Option<String>,
417
418 pub if_unmodified_since: Option<String>,
424
425 pub if_match: Option<String>,
430
431 pub if_none_match: Option<String>,
438
439 pub accept_encoding: Option<String>,
460
461 pub response_content_language: Option<String>,
464
465 pub response_expires: Option<String>,
467
468 pub response_cache_control: Option<String>,
470
471 pub response_content_disposition: Option<String>,
473
474 pub response_content_encoding: Option<ContentEncoding>,
476
477 pub version_id: Option<String>,
479}
480
481pub struct GetObjectOptionsBuilder {
482 range: Option<String>,
483 if_modified_since: Option<String>,
484 if_unmodified_since: Option<String>,
485 if_match: Option<String>,
486 if_non_match: Option<String>,
487 accept_encoding: Option<String>,
488 response_content_language: Option<String>,
489 response_expires: Option<String>,
490 response_cache_control: Option<String>,
491 response_content_disposition: Option<String>,
492 response_content_encoding: Option<ContentEncoding>,
493 version_id: Option<String>,
494}
495
496impl GetObjectOptionsBuilder {
497 pub fn new() -> Self {
498 Self {
499 range: None,
500 if_modified_since: None,
501 if_unmodified_since: None,
502 if_match: None,
503 if_non_match: None,
504 accept_encoding: None,
505 response_content_language: None,
506 response_expires: None,
507 response_cache_control: None,
508 response_content_disposition: None,
509 response_content_encoding: None,
510 version_id: None,
511 }
512 }
513
514 pub fn range(mut self, range: impl Into<String>) -> Self {
515 self.range = Some(range.into());
516 self
517 }
518
519 pub fn if_modified_since(mut self, if_modified_since: impl Into<String>) -> Self {
520 self.if_modified_since = Some(if_modified_since.into());
521 self
522 }
523
524 pub fn if_unmodified_since(mut self, if_unmodified_since: impl Into<String>) -> Self {
525 self.if_unmodified_since = Some(if_unmodified_since.into());
526 self
527 }
528
529 pub fn if_match(mut self, if_match: impl Into<String>) -> Self {
530 self.if_match = Some(if_match.into());
531 self
532 }
533
534 pub fn if_non_match(mut self, if_non_match: impl Into<String>) -> Self {
535 self.if_non_match = Some(if_non_match.into());
536 self
537 }
538
539 pub fn accept_encoding(mut self, accept_encoding: impl Into<String>) -> Self {
540 self.accept_encoding = Some(accept_encoding.into());
541 self
542 }
543
544 pub fn response_content_language(mut self, content_language: impl Into<String>) -> Self {
545 self.response_content_language = Some(content_language.into());
546 self
547 }
548
549 pub fn response_expires(mut self, expires: impl Into<String>) -> Self {
550 self.response_expires = Some(expires.into());
551 self
552 }
553
554 pub fn response_cache_control(mut self, cache_control: impl Into<String>) -> Self {
555 self.response_cache_control = Some(cache_control.into());
556 self
557 }
558
559 pub fn response_content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
560 self.response_content_disposition = Some(content_disposition.into());
561 self
562 }
563
564 pub fn response_content_encoding(mut self, content_encoding: ContentEncoding) -> Self {
565 self.response_content_encoding = Some(content_encoding);
566 self
567 }
568
569 pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
570 self.version_id = Some(version_id.into());
571 self
572 }
573
574 pub fn build(self) -> GetObjectOptions {
575 GetObjectOptions {
576 range: self.range,
577 if_modified_since: self.if_modified_since,
578 if_unmodified_since: self.if_unmodified_since,
579 if_match: self.if_match,
580 if_none_match: self.if_non_match,
581 accept_encoding: self.accept_encoding,
582 response_content_language: self.response_content_language,
583 response_expires: self.response_expires,
584 response_cache_control: self.response_cache_control,
585 response_content_disposition: self.response_content_disposition,
586 response_content_encoding: self.response_content_encoding,
587 version_id: self.version_id,
588 }
589 }
590}
591
592impl Default for GetObjectOptionsBuilder {
593 fn default() -> Self {
594 Self::new()
595 }
596}
597
598#[derive(Debug)]
600pub struct GetObjectResult;
601
602pub(crate) fn build_put_object_request(
603 bucket_name: &str,
604 object_key: &str,
605 request_body: RequestBody,
606 options: &Option<PutObjectOptions>,
607) -> Result<OssRequest> {
608 if !validate_bucket_name(bucket_name) {
609 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
610 }
611
612 if !validate_object_key(object_key) {
613 return Err(Error::Other(format!("invalid object key: {}", object_key)));
614 }
615
616 if let Some(options) = &options {
618 for (k, v) in &options.metadata {
619 if k.is_empty() || !validate_meta_key(k) || v.is_empty() {
620 return Err(Error::Other(format!("invalid meta data: \"{}: {}\". the key must starts with `x-oss-meta-`, and only `[0-9a-z\\-]` are allowed; the key and value must not be empty", k, v)));
621 }
622 }
623
624 for (k, v) in &options.tags {
625 if k.is_empty() || !validate_tag_key(k) || (!v.is_empty() && !validate_tag_value(v)) {
626 return Err(Error::Other(format!(
627 "invalid tagging data: \"{}={}\". only `[0-9a-zA-Z\\+\\-=\\.:/]` and space character are allowed",
628 k, v
629 )));
630 }
631 }
632 }
633
634 let mut request = OssRequest::new().method(RequestMethod::Put).bucket(bucket_name).object(object_key);
635
636 if let Some(options) = &options {
637 for (k, v) in &options.parameters {
638 request = request.add_query(k.clone(), v.clone());
639 }
640 }
641
642 let content_length = match &request_body {
643 RequestBody::Empty => 0u64,
644 RequestBody::Text(s) => s.len() as u64,
645 RequestBody::Bytes(bytes) => bytes.len() as u64,
646 RequestBody::File(file_path, range) => {
647 if let Some(r) = range {
648 r.end - r.start
649 } else {
650 if !file_path.exists() || !file_path.is_file() {
651 return Err(Error::Other(format!(
652 "{} does not exist or is not a regular file",
653 file_path.as_os_str().to_str().unwrap_or("UNKNOWN")
654 )));
655 }
656
657 let file_meta = std::fs::metadata(file_path)?;
658
659 file_meta.len()
660 }
661 }
662 };
663
664 if content_length > 5_368_709_120 {
666 return Err(Error::Other(format!("length {} exceeds limitation. max allowed is 5GB", content_length)));
667 }
668
669 request = request.content_length(content_length);
670
671 if let RequestBody::File(file_path, _) = &request_body {
673 request = request.content_type(mime_guess::from_path(file_path).first_or_octet_stream().as_ref());
674 }
675
676 request = request.body(request_body);
678
679 if let Some(options) = options {
680 if let Some(s) = &options.mime_type {
682 request = request.add_header("content-type", s);
683 }
684
685 if let Some(s) = &options.cache_control {
686 request = request.add_header("cache-control", s);
687 }
688
689 if let Some(s) = &options.content_disposition {
690 request = request.add_header("content-disposition", s);
691 }
692
693 if let Some(enc) = &options.content_encoding {
694 request = request.add_header("content-encoding", enc.as_str());
695 }
696
697 if let Some(s) = &options.expires {
698 request = request.add_header("expires", s);
699 }
700
701 if let Some(b) = &options.forbid_overwrite {
702 if *b {
703 request = request.add_header("x-oss-forbid-overwrite", "true");
704 }
705 }
706
707 if let Some(a) = &options.server_side_encryption {
708 request = request.add_header("x-oss-server-side-encryption", a.as_str());
709 }
710
711 if let Some(a) = &options.server_side_data_encryption {
712 request = request.add_header("x-oss-server-side-data-encryption", a.as_str());
713 }
714
715 if let Some(s) = &options.server_side_encryption_key_id {
716 request = request.add_header("x-oss-server-side-encryption-key-id", s);
717 }
718
719 if let Some(acl) = &options.object_acl {
720 request = request.add_header("x-oss-object-acl", acl.as_str());
721 }
722
723 if let Some(store) = &options.storage_class {
724 request = request.add_header("x-oss-storage-class", store.as_str());
725 }
726
727 for (k, v) in &options.metadata {
728 request = request.add_header(k, v);
729 }
730
731 if !options.tags.is_empty() {
732 request = request.add_header("x-oss-tagging", build_tag_string(&options.tags));
733 }
734
735 if let Some(cb) = &options.callback {
736 let callback_json = serde_json::to_string(cb)?;
738 let callback_base64 = BASE64_STANDARD.encode(&callback_json);
739 request = request.add_header("x-oss-callback", callback_base64);
740
741 if !cb.custom_variables.is_empty() {
742 let callback_vars_json = serde_json::to_string(&cb.custom_variables)?;
743 let callback_vars_base64 = BASE64_STANDARD.encode(&callback_vars_json);
744 request = request.add_header("x-oss-callback-var", callback_vars_base64);
745 }
746 }
747 }
748
749 Ok(request)
750}
751
752#[derive(Debug, Clone, Default)]
756#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
757#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
758pub struct GetObjectMetadataOptions {
759 pub version_id: Option<String>,
760}
761
762#[derive(Debug, Clone, Default)]
766#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
767#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
768pub struct HeadObjectOptions {
769 pub version_id: Option<String>,
770 pub if_modified_since: Option<String>,
771 pub if_unmodified_since: Option<String>,
772 pub if_match: Option<String>,
773 pub if_none_match: Option<String>,
774}
775
776pub struct HeadObjectOptionsBuilder {
777 version_id: Option<String>,
778 if_modified_since: Option<String>,
779 if_unmodified_since: Option<String>,
780 if_match: Option<String>,
781 if_none_match: Option<String>,
782}
783
784impl HeadObjectOptionsBuilder {
785 pub fn new() -> Self {
786 Self {
787 version_id: None,
788 if_modified_since: None,
789 if_unmodified_since: None,
790 if_match: None,
791 if_none_match: None,
792 }
793 }
794
795 pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
796 self.version_id = Some(version_id.into());
797 self
798 }
799
800 pub fn if_modified_since(mut self, if_modified_since: impl Into<String>) -> Self {
801 self.if_modified_since = Some(if_modified_since.into());
802 self
803 }
804
805 pub fn if_unmodified_since(mut self, if_unmodified_since: impl Into<String>) -> Self {
806 self.if_unmodified_since = Some(if_unmodified_since.into());
807 self
808 }
809
810 pub fn if_match(mut self, if_match: impl Into<String>) -> Self {
811 self.if_match = Some(if_match.into());
812 self
813 }
814
815 pub fn if_none_match(mut self, if_none_match: impl Into<String>) -> Self {
816 self.if_none_match = Some(if_none_match.into());
817 self
818 }
819
820 pub fn build(self) -> HeadObjectOptions {
821 HeadObjectOptions {
822 version_id: self.version_id,
823 if_modified_since: self.if_modified_since,
824 if_unmodified_since: self.if_unmodified_since,
825 if_match: self.if_match,
826 if_none_match: self.if_none_match,
827 }
828 }
829}
830
831impl Default for HeadObjectOptionsBuilder {
832 fn default() -> Self {
833 Self::new()
834 }
835}
836
837#[derive(Debug, Clone, Default)]
838#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
839#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
840pub struct ObjectMetadata {
841 pub request_id: String,
842 pub content_length: u64,
843
844 pub etag: String,
846 pub hash_crc64ecma: Option<u64>,
847
848 pub transition_time: Option<String>,
850
851 pub last_access_time: Option<String>,
856
857 pub last_modified: Option<String>,
859
860 pub version_id: Option<String>,
861
862 pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
863 pub server_side_encryption_key_id: Option<String>,
864 pub storage_class: Option<StorageClass>,
865 pub object_type: Option<ObjectType>,
866
867 pub next_append_position: Option<u64>,
869
870 pub expiration: Option<String>,
872
873 pub restore: Option<String>,
880
881 pub process_status: Option<String>,
885
886 pub request_charged: Option<String>,
889
890 pub content_md5: Option<String>,
893
894 pub access_control_allow_origin: Option<String>,
896
897 pub access_control_allow_methods: Option<String>,
899
900 pub access_control_allow_max_age: Option<String>,
902
903 pub access_control_allow_headers: Option<String>,
905
906 pub access_control_expose_headers: Option<String>,
908
909 pub tag_count: Option<u32>,
911
912 pub metadata: HashMap<String, String>,
914}
915
916impl From<HashMap<String, String>> for ObjectMetadata {
917 fn from(mut headers: HashMap<String, String>) -> Self {
919 Self {
920 request_id: headers.remove("x-oss-request-id").unwrap_or("".to_string()),
921 content_length: headers.remove("content-length").unwrap_or("0".to_string()).parse().unwrap_or(0),
922 etag: sanitize_etag(headers.remove("etag").unwrap_or_default()),
923 hash_crc64ecma: headers.remove("x-oss-hash-crc64ecma").map(|s| s.parse::<u64>().unwrap_or(0)),
924 transition_time: headers.remove("x-oss-transition-time"),
925 last_access_time: headers.remove("x-oss-last-access-time"),
926 last_modified: headers.remove("last-modified"),
927 version_id: headers.remove("x-oss-version-id"),
928 server_side_encryption: if let Some(s) = headers.remove("x-oss-server-side-encryption") {
929 if let Ok(v) = s.try_into() {
931 Some(v)
932 } else {
933 None
934 }
935 } else {
936 None
937 },
938 server_side_encryption_key_id: headers.remove("x-oss-server-side-encryption-key-id"),
939 storage_class: if let Some(s) = headers.remove("x-oss-storage-class") {
940 if let Ok(v) = s.try_into() {
941 Some(v)
942 } else {
943 None
944 }
945 } else {
946 None
947 },
948 object_type: if let Some(s) = headers.remove("x-oss-object-type") {
949 if let Ok(v) = s.try_into() {
950 Some(v)
951 } else {
952 None
953 }
954 } else {
955 None
956 },
957 next_append_position: headers.remove("x-oss-next-append-position").map(|s| s.parse().unwrap_or(0)),
958 expiration: headers.remove("x-oss-expiration"),
959 restore: headers.remove("x-oss-restore"),
960 process_status: headers.remove("x-oss-process-status"),
961 request_charged: headers.remove("x-oss-request-charged"),
962 content_md5: headers.remove("content-md5"),
963 access_control_allow_origin: headers.remove("access-control-allow-origin"),
964 access_control_allow_methods: headers.remove("access-control-allow-methods"),
965 access_control_allow_headers: headers.remove("access-control-allow-headers"),
966 access_control_allow_max_age: headers.remove("access-control-max-age"),
967 access_control_expose_headers: headers.remove("access-control-expose-headers"),
968 tag_count: headers.remove("x-oss-tagging-count").map(|s| s.parse().unwrap_or(0)),
969
970 metadata: headers.drain().filter(|(k, _)| k.starts_with("x-oss-meta-")).collect(),
972 }
973 }
974}
975
976#[derive(Debug, Clone, Default)]
980#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
981#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
982pub struct CopyObjectOptions {
983 pub forbid_overwrite: Option<bool>,
992
993 pub source_version_id: Option<String>,
995
996 pub copy_source_if_match: Option<String>,
998
999 pub copy_source_if_none_match: Option<String>,
1001
1002 pub copy_source_if_unmodified_since: Option<String>,
1005
1006 pub copy_source_if_modified_since: Option<String>,
1009
1010 pub metadata_directive: Option<MetadataDirective>,
1012
1013 pub metadata: HashMap<String, String>,
1015
1016 pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
1017 pub server_side_encryption_key_id: Option<String>,
1018
1019 pub object_acl: Option<ObjectAcl>,
1021
1022 pub storage_class: Option<StorageClass>,
1024
1025 pub tags: HashMap<String, String>,
1026 pub tag_directive: Option<TagDirective>,
1027}
1028
1029pub struct CopyObjectOptionsBuilder {
1030 forbid_overwrite: Option<bool>,
1031 source_version_id: Option<String>,
1032 copy_source_if_match: Option<String>,
1033 copy_source_if_none_match: Option<String>,
1034 copy_source_if_unmodified_since: Option<String>,
1035 copy_source_if_modified_since: Option<String>,
1036 metadata_directive: Option<MetadataDirective>,
1037 metadata: HashMap<String, String>,
1038 server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
1039 server_side_encryption_key_id: Option<String>,
1040 object_acl: Option<ObjectAcl>,
1041 storage_class: Option<StorageClass>,
1042 tags: HashMap<String, String>,
1043 tag_directive: Option<TagDirective>,
1044}
1045
1046impl CopyObjectOptionsBuilder {
1047 pub fn new() -> Self {
1048 Self {
1049 forbid_overwrite: None,
1050 source_version_id: None,
1051 copy_source_if_match: None,
1052 copy_source_if_none_match: None,
1053 copy_source_if_unmodified_since: None,
1054 copy_source_if_modified_since: None,
1055 metadata_directive: None,
1056 metadata: HashMap::new(),
1057 server_side_encryption: None,
1058 server_side_encryption_key_id: None,
1059 object_acl: None,
1060 storage_class: None,
1061 tags: HashMap::new(),
1062 tag_directive: None,
1063 }
1064 }
1065
1066 pub fn forbid_overwrite(mut self, forbid_overwrite: bool) -> Self {
1067 self.forbid_overwrite = Some(forbid_overwrite);
1068 self
1069 }
1070
1071 pub fn source_version_id(mut self, version_id: impl Into<String>) -> Self {
1072 self.source_version_id = Some(version_id.into());
1073 self
1074 }
1075
1076 pub fn copy_source_if_match(mut self, copy_source_if_match: impl Into<String>) -> Self {
1077 self.copy_source_if_match = Some(copy_source_if_match.into());
1078 self
1079 }
1080
1081 pub fn copy_source_if_none_match(mut self, copy_source_if_none_match: impl Into<String>) -> Self {
1082 self.copy_source_if_none_match = Some(copy_source_if_none_match.into());
1083 self
1084 }
1085
1086 pub fn copy_source_if_unmodified_since(mut self, copy_source_if_unmodified_since: impl Into<String>) -> Self {
1087 self.copy_source_if_unmodified_since = Some(copy_source_if_unmodified_since.into());
1088 self
1089 }
1090
1091 pub fn copy_source_if_modified_since(mut self, copy_source_if_modified_since: impl Into<String>) -> Self {
1092 self.copy_source_if_modified_since = Some(copy_source_if_modified_since.into());
1093 self
1094 }
1095
1096 pub fn metadata_directive(mut self, metadata_directive: MetadataDirective) -> Self {
1097 self.metadata_directive = Some(metadata_directive);
1098 self
1099 }
1100
1101 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1102 self.metadata.insert(key.into(), value.into());
1103 self
1104 }
1105
1106 pub fn server_side_encryption(mut self, algorithm: ServerSideEncryptionAlgorithm) -> Self {
1107 self.server_side_encryption = Some(algorithm);
1108 self
1109 }
1110
1111 pub fn server_side_encryption_key_id(mut self, key_id: impl Into<String>) -> Self {
1112 self.server_side_encryption_key_id = Some(key_id.into());
1113 self
1114 }
1115
1116 pub fn object_acl(mut self, acl: ObjectAcl) -> Self {
1117 self.object_acl = Some(acl);
1118 self
1119 }
1120
1121 pub fn storage_class(mut self, storage_class: StorageClass) -> Self {
1122 self.storage_class = Some(storage_class);
1123 self
1124 }
1125
1126 pub fn tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1127 self.tags.insert(key.into(), value.into());
1128 self
1129 }
1130
1131 pub fn tag_directive(mut self, tag_directive: TagDirective) -> Self {
1132 self.tag_directive = Some(tag_directive);
1133 self
1134 }
1135
1136 pub fn build(self) -> CopyObjectOptions {
1137 CopyObjectOptions {
1138 forbid_overwrite: self.forbid_overwrite,
1139 source_version_id: self.source_version_id,
1140 copy_source_if_match: self.copy_source_if_match,
1141 copy_source_if_none_match: self.copy_source_if_none_match,
1142 copy_source_if_unmodified_since: self.copy_source_if_unmodified_since,
1143 copy_source_if_modified_since: self.copy_source_if_modified_since,
1144 metadata_directive: self.metadata_directive,
1145 metadata: self.metadata,
1146 server_side_encryption: self.server_side_encryption,
1147 server_side_encryption_key_id: self.server_side_encryption_key_id,
1148 object_acl: self.object_acl,
1149 storage_class: self.storage_class,
1150 tags: self.tags,
1151 tag_directive: self.tag_directive,
1152 }
1153 }
1154}
1155
1156impl Default for CopyObjectOptionsBuilder {
1157 fn default() -> Self {
1158 Self::new()
1159 }
1160}
1161
1162#[derive(Debug, Clone, Default)]
1164#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1165#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1166pub struct DeleteObjectOptions {
1167 pub version_id: Option<String>,
1168}
1169
1170pub struct DeleteObjectResult;
1172
1173pub struct CopyObjectResult;
1175
1176pub(crate) fn build_copy_object_request(
1177 source_bucket_name: &str,
1178 source_object_key: &str,
1179 dest_bucket_name: &str,
1180 dest_object_key: &str,
1181 options: &Option<CopyObjectOptions>,
1182) -> Result<OssRequest> {
1183 if !validate_bucket_name(source_bucket_name) {
1184 return Err(Error::Other(format!("invalid source bucket name: {}", source_bucket_name)));
1185 }
1186
1187 if !validate_object_key(source_object_key) {
1188 return Err(Error::Other(format!("invalid source object key: {}", source_object_key)));
1189 }
1190
1191 if !validate_bucket_name(dest_bucket_name) {
1192 return Err(Error::Other(format!("invalid destination bucket name: {}", dest_bucket_name)));
1193 }
1194
1195 if !validate_object_key(dest_object_key) {
1196 return Err(Error::Other(format!("invalid destination object key: {}", dest_object_key)));
1197 }
1198
1199 let mut request = OssRequest::new()
1200 .method(RequestMethod::Put)
1201 .bucket(dest_bucket_name)
1202 .object(dest_object_key)
1203 .add_header(
1204 "x-oss-copy-source",
1205 format!("/{}/{}", urlencoding::encode(source_bucket_name), urlencoding::encode(source_object_key)),
1206 );
1207
1208 if let Some(options) = options {
1209 for (k, _) in options.metadata.iter() {
1211 if !validate_meta_key(k) {
1212 return Err(Error::Other(format!("invalid metadata key: {}", k)));
1213 }
1214 }
1215
1216 for (k, v) in options.tags.iter() {
1217 if !validate_tag_key(k) || !validate_tag_value(v) {
1218 return Err(Error::Other(format!("invalid tagging data: {}={}", k, v)));
1219 }
1220 }
1221
1222 if let Some(s) = &options.source_version_id {
1223 request = request.add_query("versionId", s);
1224 }
1225
1226 if let Some(b) = options.forbid_overwrite {
1227 request = request.add_header("x-oss-forbid-overwrite", b.to_string())
1228 }
1229
1230 if let Some(s) = &options.copy_source_if_match {
1231 request = request.add_header("x-oss-copy-source-if-match", s);
1232 }
1233
1234 if let Some(s) = &options.copy_source_if_none_match {
1235 request = request.add_header("x-oss-copy-source-if-none-match", s);
1236 }
1237
1238 if let Some(s) = &options.copy_source_if_modified_since {
1239 request = request.add_header("x-oss-copy-source-if-modified-since", s);
1240 }
1241
1242 if let Some(s) = &options.copy_source_if_unmodified_since {
1243 request = request.add_header("x-oss-copy-source-if-unmodified-since", s);
1244 }
1245
1246 if let Some(md) = options.metadata_directive {
1247 request = request.add_header("x-oss-metadata-directive", md);
1248 }
1249
1250 if let Some(a) = &options.server_side_encryption {
1251 request = request.add_header("x-oss-server-side-encryption", a);
1252 }
1253
1254 if let Some(s) = &options.server_side_encryption_key_id {
1255 request = request.add_header("x-oss-server-side-encryption-key-id", s);
1256 }
1257
1258 if let Some(acl) = options.object_acl {
1259 request = request.add_header("x-oss-object-acl", acl);
1260 }
1261
1262 if let Some(sc) = options.storage_class {
1263 request = request.add_header("x-oss-storage-class", sc);
1264 }
1265
1266 if let Some(td) = options.tag_directive {
1267 request = request.add_header("x-oss-tag-directive", td);
1268 }
1269
1270 if !options.tags.is_empty() {
1271 request = request.add_header("x-oss-tagging", build_tag_string(&options.tags));
1272 }
1273
1274 for (key, value) in options.metadata.iter() {
1275 request = request.add_header(key, value);
1276 }
1277 }
1278
1279 Ok(request)
1280}
1281
1282pub(crate) fn build_get_object_request(bucket_name: &str, object_key: &str, options: &Option<GetObjectOptions>) -> Result<OssRequest> {
1283 if !validate_bucket_name(bucket_name) {
1284 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
1285 }
1286
1287 if !validate_object_key(object_key) {
1288 return Err(Error::Other(format!("invalid object key: {}", object_key)));
1289 }
1290
1291 let mut request = OssRequest::new().method(RequestMethod::Get).bucket(bucket_name).object(object_key);
1292
1293 if let Some(options) = options {
1294 if let Some(s) = &options.range {
1295 request = request.add_header("range", s);
1296 }
1297
1298 if let Some(s) = &options.if_modified_since {
1299 request = request.add_header("if-modified-since", s);
1300 }
1301
1302 if let Some(s) = &options.if_unmodified_since {
1303 request = request.add_header("if-unmodified-since", s);
1304 }
1305
1306 if let Some(s) = &options.if_match {
1307 request = request.add_header("if-match", s);
1308 }
1309
1310 if let Some(s) = &options.if_none_match {
1311 request = request.add_header("if-none-match", s);
1312 }
1313
1314 if let Some(s) = &options.accept_encoding {
1315 request = request.add_header("accept-encoding", s);
1316 }
1317
1318 if let Some(s) = &options.response_content_language {
1319 request = request.add_query("response-content-language", s);
1320 }
1321
1322 if let Some(s) = &options.response_expires {
1323 request = request.add_query("response-expires", s);
1324 }
1325
1326 if let Some(s) = &options.response_cache_control {
1327 request = request.add_query("response-cache-control", s);
1328 }
1329
1330 if let Some(s) = &options.response_content_disposition {
1331 request = request.add_query("response-content-disposition", s);
1332 }
1333
1334 if let Some(ce) = options.response_content_encoding {
1335 request = request.add_query("response-content-encoding", ce.as_str());
1336 }
1337
1338 if let Some(s) = &options.version_id {
1339 request = request.add_query("versionId", s);
1340 }
1341 }
1342
1343 Ok(request)
1344}
1345
1346pub(crate) fn build_head_object_request(bucket_name: &str, object_key: &str, options: &Option<HeadObjectOptions>) -> Result<OssRequest> {
1347 if !validate_bucket_name(bucket_name) {
1348 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
1349 }
1350
1351 if !validate_object_key(object_key) {
1352 return Err(Error::Other(format!("invalid object key: {}", object_key)));
1353 }
1354
1355 let mut request = OssRequest::new().method(RequestMethod::Head).bucket(bucket_name).object(object_key);
1356
1357 if let Some(options) = options {
1358 if let Some(s) = &options.if_modified_since {
1359 request = request.add_header("if-modified-since", s);
1360 }
1361
1362 if let Some(s) = &options.if_unmodified_since {
1363 request = request.add_header("if-unmodified-since", s);
1364 }
1365
1366 if let Some(s) = &options.if_match {
1367 request = request.add_header("if-match", s);
1368 }
1369
1370 if let Some(s) = &options.if_none_match {
1371 request = request.add_header("if-none-match", s);
1372 }
1373
1374 if let Some(s) = &options.version_id {
1375 request = request.add_query("versionId", s);
1376 }
1377 }
1378
1379 Ok(request)
1380}
1381
1382#[derive(Debug, Clone)]
1384#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1385#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1386pub enum PutObjectResult {
1387 #[cfg_attr(feature = "serde-camelcase", serde(rename = "apiResponse", rename_all = "camelCase"))]
1389 ApiResponse(PutObjectApiResponse),
1390
1391 #[cfg_attr(feature = "serde-camelcase", serde(rename = "callbackResponse"))]
1394 CallbackResponse(String),
1395}
1396
1397#[derive(Debug, Clone)]
1399#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1400#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1401pub struct PutObjectApiResponse {
1402 pub request_id: String,
1403
1404 pub etag: String,
1406
1407 pub content_md5: String,
1409
1410 pub hash_crc64ecma: u64,
1412
1413 pub version_id: Option<String>,
1415}
1416
1417impl From<HashMap<String, String>> for PutObjectApiResponse {
1419 fn from(mut headers: HashMap<String, String>) -> Self {
1420 Self {
1421 request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
1422 etag: sanitize_etag(headers.remove("etag").unwrap_or_default()),
1423 content_md5: headers.remove("content-md5").unwrap_or_default(),
1424 hash_crc64ecma: headers.remove("x-oss-hash-crc64ecma").unwrap_or("0".to_string()).parse().unwrap_or(0),
1425 version_id: headers.remove("x-oss-version-id"),
1426 }
1427 }
1428}
1429
1430pub type AppendObjectOptions = PutObjectOptions;
1432pub type AppendObjectOptionsBuilder = PutObjectOptionsBuilder;
1433
1434pub struct AppendObjectResult {
1435 pub request_id: String,
1436 pub next_append_position: u64,
1437}
1438
1439impl From<HashMap<String, String>> for AppendObjectResult {
1440 fn from(mut headers: HashMap<String, String>) -> Self {
1441 Self {
1442 request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
1443 next_append_position: headers.remove("x-oss-next-append-position").unwrap_or("0".to_string()).parse().unwrap_or(0),
1444 }
1445 }
1446}
1447
1448#[derive(Debug, Clone, Default)]
1449#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1450#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1451pub struct DeleteMultipleObjectsItem {
1452 pub key: String,
1453 pub version_id: Option<String>,
1454}
1455
1456#[derive(Debug, Clone, Default)]
1460#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1461#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1462pub struct DeleteMultipleObjectsRequest {
1463 pub quiet: Option<bool>,
1464
1465 pub objects: Vec<DeleteMultipleObjectsItem>,
1467}
1468
1469impl DeleteMultipleObjectsRequest {
1470 pub(crate) fn into_xml(self) -> Result<String> {
1472 let mut writer = quick_xml::Writer::new(Vec::new());
1473 writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
1474
1475 writer.write_event(Event::Start(BytesStart::new("Delete")))?;
1476
1477 if let Some(b) = self.quiet {
1478 writer.write_event(Event::Start(BytesStart::new("Quiet")))?;
1479 writer.write_event(Event::Text(BytesText::new(&b.to_string())))?;
1480 writer.write_event(Event::End(BytesEnd::new("Quiet")))?;
1481 }
1482
1483 for item in self.objects {
1484 writer.write_event(Event::Start(BytesStart::new("Object")))?;
1485
1486 writer.write_event(Event::Start(BytesStart::new("Key")))?;
1487 writer.write_event(Event::Text(BytesText::new(&item.key)))?;
1488 writer.write_event(Event::End(BytesEnd::new("Key")))?;
1489
1490 if let Some(s) = item.version_id {
1491 writer.write_event(Event::Start(BytesStart::new("VersionId")))?;
1492 writer.write_event(Event::Text(BytesText::new(&s)))?;
1493 writer.write_event(Event::End(BytesEnd::new("VersionId")))?;
1494 }
1495
1496 writer.write_event(Event::End(BytesEnd::new("Object")))?;
1497 }
1498
1499 writer.write_event(Event::End(BytesEnd::new("Delete")))?;
1500
1501 Ok(String::from_utf8(writer.into_inner())?)
1502 }
1503}
1504
1505impl<T> From<&[T]> for DeleteMultipleObjectsRequest
1506where
1507 T: AsRef<str>,
1508{
1509 fn from(object_keys: &[T]) -> Self {
1510 Self {
1511 objects: object_keys
1512 .iter()
1513 .map(|s| DeleteMultipleObjectsItem {
1514 key: s.as_ref().to_string(),
1515 ..Default::default()
1516 })
1517 .collect::<Vec<_>>(),
1518 ..Default::default()
1519 }
1520 }
1521}
1522
1523#[derive(Debug)]
1524pub enum DeleteMultipleObjectsConfig<'a, T: AsRef<str> + 'a> {
1525 FromKeys(&'a [T]),
1527
1528 FullRequest(DeleteMultipleObjectsRequest),
1530}
1531
1532#[derive(Debug, Clone, Default)]
1533#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1534#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1535pub struct DeleteMultipleObjectsResultItem {
1536 pub key: String,
1537 pub version_id: Option<String>,
1538 pub delete_marker: Option<String>,
1539 pub delete_marker_version_id: Option<String>,
1540}
1541
1542#[derive(Debug, Clone, Default)]
1546#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1547#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1548pub struct DeleteMultipleObjectsResult {
1549 pub items: Vec<DeleteMultipleObjectsResultItem>,
1550}
1551
1552impl DeleteMultipleObjectsResult {
1553 pub fn from_xml(xml_content: &str) -> Result<Self> {
1554 let mut reader = quick_xml::Reader::from_str(xml_content);
1555 let mut tag = String::new();
1556 let mut items = Vec::new();
1557
1558 let mut current_item = DeleteMultipleObjectsResultItem::default();
1559
1560 loop {
1561 match reader.read_event()? {
1562 Event::Eof => break,
1563 Event::Start(t) => {
1564 tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string();
1565 if tag == "Deleted" {
1566 current_item = DeleteMultipleObjectsResultItem::default();
1567 }
1568 }
1569
1570 Event::Text(e) => {
1571 let s = e.unescape()?.trim().to_string();
1572 match tag.as_str() {
1573 "Key" => current_item.key = s,
1574 "VersionId" => current_item.version_id = Some(s),
1575 "DeleteMarker" => current_item.delete_marker = Some(s),
1576 "DeleteMarkerVersionId" => current_item.delete_marker_version_id = Some(s),
1577 _ => {}
1578 }
1579 }
1580
1581 Event::End(t) => {
1582 if t.local_name().as_ref() == b"Deleted" {
1583 items.push(current_item.clone());
1584 }
1585 tag.clear();
1586 }
1587
1588 _ => {}
1589 }
1590 }
1591
1592 Ok(Self { items })
1593 }
1594}
1595
1596pub(crate) fn build_delete_multiple_objects_request<S>(bucket_name: &str, config: DeleteMultipleObjectsConfig<S>) -> Result<OssRequest>
1597where
1598 S: AsRef<str>,
1599{
1600 if !validate_bucket_name(bucket_name) {
1601 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
1602 }
1603
1604 let mut request = OssRequest::new()
1605 .method(RequestMethod::Post)
1606 .bucket(bucket_name)
1607 .add_query("delete", "")
1608 .content_type(MIME_TYPE_XML);
1609
1610 let items_len = match &config {
1611 DeleteMultipleObjectsConfig::FromKeys(items) => items.len(),
1612 DeleteMultipleObjectsConfig::FullRequest(cfg) => cfg.objects.len(),
1613 };
1614
1615 if items_len > common::DELETE_MULTIPLE_OBJECTS_LIMIT {
1616 return Err(Error::Other(format!(
1617 "{} exceeds the items count limits while deleting multiple objects",
1618 items_len
1619 )));
1620 }
1621
1622 let payload = match config {
1623 DeleteMultipleObjectsConfig::FromKeys(items) => DeleteMultipleObjectsRequest::from(items),
1624 DeleteMultipleObjectsConfig::FullRequest(delete_multiple_objects_request) => delete_multiple_objects_request,
1625 };
1626
1627 let xml_content = payload.into_xml()?;
1628 let content_md5 = BASE64_STANDARD.encode(*md5::compute(xml_content.as_bytes()));
1629
1630 request = request
1631 .content_length(xml_content.len() as u64)
1632 .add_header("content-md5", content_md5)
1633 .text_body(xml_content);
1634
1635 Ok(request)
1636}
1637
1638#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1640pub enum CallbackBodyType {
1641 #[default]
1642 #[serde(rename = "application/x-www-form-urlencoded")]
1643 FormUrlEncoded,
1644
1645 #[serde(rename = "application/json")]
1646 Json,
1647}
1648
1649#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1661pub struct Callback {
1662 #[serde(rename = "callbackUrl")]
1679 pub url: String,
1680
1681 #[serde(rename = "callbackHost", skip_serializing_if = "Option::is_none")]
1684 pub host: Option<String>,
1685
1686 #[serde(rename = "callbackBody")]
1708 pub body: String,
1709
1710 #[serde(rename = "callbackSNI", skip_serializing_if = "Option::is_none")]
1714 pub sni: Option<bool>,
1715
1716 #[serde(rename = "callbackBodyType", skip_serializing_if = "Option::is_none")]
1718 pub body_type: Option<CallbackBodyType>,
1719
1720 #[serde(skip_serializing)]
1724 pub custom_variables: HashMap<String, String>,
1725}
1726
1727pub enum CallbackBodyParameter<'a> {
1756 OssBucket(&'a str),
1757 OssObject(&'a str),
1758 OssETag(&'a str),
1759 OssSize(&'a str),
1760 OssMimeType(&'a str),
1761 OssImageHeight(&'a str),
1762 OssImageWidth(&'a str),
1763 OssImageFormat(&'a str),
1764 OssCrc64(&'a str),
1765 OssContentMd5(&'a str),
1766 OssVpcId(&'a str),
1767 OssClientIp(&'a str),
1768 OssRequestId(&'a str),
1769 OssOperation(&'a str),
1770
1771 Custom(&'a str, &'a str, String),
1779
1780 Constant(&'a str, &'a str),
1785
1786 Literal(String, String),
1792}
1793
1794impl CallbackBodyParameter<'_> {
1795 pub fn to_body_string(&self) -> String {
1797 match self {
1798 CallbackBodyParameter::OssBucket(k) => format!("{}=${{bucket}}", k),
1799 CallbackBodyParameter::OssObject(k) => format!("{}=${{object}}", k),
1800 CallbackBodyParameter::OssETag(k) => format!("{}=${{etag}}", k),
1801 CallbackBodyParameter::OssSize(k) => format!("{}=${{size}}", k),
1802 CallbackBodyParameter::OssMimeType(k) => format!("{}=${{mimeType}}", k),
1803 CallbackBodyParameter::OssImageHeight(k) => format!("{}=${{imageInfo.height}}", k),
1804 CallbackBodyParameter::OssImageWidth(k) => format!("{}=${{imageInfo.width}}", k),
1805 CallbackBodyParameter::OssImageFormat(k) => format!("{}=${{imageInfo.format}}", k),
1806 CallbackBodyParameter::OssCrc64(k) => format!("{}=${{crc64}}", k),
1807 CallbackBodyParameter::OssContentMd5(k) => format!("{}=${{contentMd5}}", k),
1808 CallbackBodyParameter::OssVpcId(k) => format!("{}=${{vpcId}}", k),
1809 CallbackBodyParameter::OssClientIp(k) => format!("{}=${{clientIp}}", k),
1810 CallbackBodyParameter::OssRequestId(k) => format!("{}=${{reqId}}", k),
1811 CallbackBodyParameter::OssOperation(k) => format!("{}=${{operation}}", k),
1812 CallbackBodyParameter::Custom(k, v, _) => format!("{}=${{x:{}}}", k, v),
1813 CallbackBodyParameter::Constant(k, v) => format!("{}={}", k, v),
1814 CallbackBodyParameter::Literal(k, v) => format!("{}={}", k, v),
1815 }
1816 }
1817}
1818
1819pub struct CallbackBuilder<'a> {
1821 url: String,
1822 host: Option<String>,
1823 sni: Option<bool>,
1824 body_type: Option<CallbackBodyType>,
1825 body_parameters: Vec<CallbackBodyParameter<'a>>,
1826 custom_variables: HashMap<String, String>,
1827}
1828
1829impl<'a> CallbackBuilder<'a> {
1830 pub fn new(url: impl Into<String>) -> Self {
1831 Self {
1832 url: url.into(),
1833 host: None,
1834 sni: None,
1835 body_type: None,
1836 body_parameters: vec![],
1837 custom_variables: HashMap::new(),
1838 }
1839 }
1840
1841 pub fn host(mut self, host: impl Into<String>) -> Self {
1842 self.host = Some(host.into());
1843 self
1844 }
1845
1846 pub fn sni(mut self, sni: bool) -> Self {
1847 self.sni = Some(sni);
1848 self
1849 }
1850
1851 pub fn body_type(mut self, body_type: CallbackBodyType) -> Self {
1852 self.body_type = Some(body_type);
1853 self
1854 }
1855
1856 pub fn body_parameter(mut self, param: CallbackBodyParameter<'a>) -> Self {
1857 if let CallbackBodyParameter::Custom(_, custom_var_name, custom_var_value) = ¶m {
1858 self.custom_variables.insert(
1859 format!("x:{}", custom_var_name.strip_prefix("x:").unwrap_or(custom_var_name)),
1860 custom_var_value.clone(),
1861 );
1862 }
1863
1864 self.body_parameters.push(param);
1865 self
1866 }
1867
1868 pub fn custom_variable(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
1872 let key: String = k.into();
1873 self.custom_variables
1874 .insert(format!("x:{}", key.strip_prefix("x:").unwrap_or(key.as_str())), v.into());
1875 self
1876 }
1877
1878 pub fn build(self) -> Callback {
1879 let body_string = self.body_parameters.into_iter().map(|bp| bp.to_body_string()).collect::<Vec<_>>().join("&");
1880
1881 Callback {
1882 url: self.url,
1883 host: self.host,
1884 body: body_string,
1885 sni: self.sni,
1886 body_type: self.body_type,
1887 custom_variables: self.custom_variables,
1888 }
1889 }
1890}
1891
1892#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
1904#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1905pub enum RestoreJobTier {
1906 #[cfg_attr(feature = "serde-support", serde(rename = "Standard"))]
1907 #[default]
1908 Standard,
1909
1910 #[cfg_attr(feature = "serde-support", serde(rename = "Expedited"))]
1911 Expedited,
1912
1913 #[cfg_attr(feature = "serde-support", serde(rename = "Bulk"))]
1914 Bulk,
1915}
1916
1917impl RestoreJobTier {
1918 pub fn as_str(&self) -> &str {
1919 match self {
1920 RestoreJobTier::Standard => "Standard",
1921 RestoreJobTier::Expedited => "Expedited",
1922 RestoreJobTier::Bulk => "Bulk",
1923 }
1924 }
1925}
1926
1927impl AsRef<str> for RestoreJobTier {
1928 fn as_ref(&self) -> &str {
1929 self.as_str()
1930 }
1931}
1932
1933impl TryFrom<&str> for RestoreJobTier {
1934 type Error = Error;
1935
1936 fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
1937 match s {
1938 "Standard" => Ok(Self::Standard),
1939 "Expedited" => Ok(Self::Expedited),
1940 "Bulk" => Ok(Self::Bulk),
1941 _ => Err(Error::Other(format!("invalid job tier: {}", s))),
1942 }
1943 }
1944}
1945
1946impl TryFrom<&String> for RestoreJobTier {
1947 type Error = Error;
1948
1949 fn try_from(s: &String) -> std::result::Result<Self, Self::Error> {
1950 Self::try_from(s.as_str())
1951 }
1952}
1953
1954impl TryFrom<String> for RestoreJobTier {
1955 type Error = Error;
1956
1957 fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
1958 Self::try_from(s.as_str())
1959 }
1960}
1961
1962#[derive(Debug, Clone, Default)]
1964#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1965#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1966pub struct RestoreObjectRequest {
1967 pub days: u16,
1972
1973 pub version_id: Option<String>,
1976
1977 pub tier: Option<RestoreJobTier>,
1979}
1980
1981impl RestoreObjectRequest {
1982 pub(crate) fn into_xml(self) -> Result<String> {
1984 let mut writer = quick_xml::Writer::new(Vec::new());
1985
1986 writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
1987
1988 writer.write_event(Event::Start(BytesStart::new("RestoreRequest")))?;
1989
1990 writer.write_event(Event::Start(BytesStart::new("Days")))?;
1991 writer.write_event(Event::Text(BytesText::new(self.days.to_string().as_str())))?;
1992 writer.write_event(Event::End(BytesEnd::new("Days")))?;
1993
1994 if let Some(t) = self.tier {
1995 writer.write_event(Event::Start(BytesStart::new("JobParameters")))?;
1996 writer.write_event(Event::Start(BytesStart::new("Tier")))?;
1997 writer.write_event(Event::Text(BytesText::new(t.as_str())))?;
1998 writer.write_event(Event::End(BytesEnd::new("Tier")))?;
1999 writer.write_event(Event::End(BytesEnd::new("JobParameters")))?;
2000 }
2001
2002 writer.write_event(Event::End(BytesEnd::new("RestoreRequest")))?;
2003
2004 Ok(String::from_utf8(writer.into_inner())?)
2005 }
2006}
2007
2008#[derive(Debug, Clone)]
2010#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
2011#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
2012pub struct RestoreObjectResult {
2013 pub request_id: String,
2014 pub object_restore_priority: Option<String>,
2015 pub version_id: Option<String>,
2016}
2017
2018impl From<HashMap<String, String>> for RestoreObjectResult {
2019 fn from(mut headers: HashMap<String, String>) -> Self {
2020 Self {
2021 request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
2022 object_restore_priority: headers.remove("x-oss-object-restore-priority"),
2023 version_id: headers.remove("x-oss-version-id"),
2024 }
2025 }
2026}
2027
2028pub(crate) fn build_restore_object_request(bucket_name: &str, object_key: &str, config: RestoreObjectRequest) -> Result<OssRequest> {
2029 if !validate_bucket_name(bucket_name) {
2030 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
2031 }
2032
2033 if !validate_object_key(object_key) {
2034 return Err(Error::Other(format!("invalid object key: {}", object_key)));
2035 }
2036
2037 let mut request = OssRequest::new()
2038 .method(RequestMethod::Post)
2039 .bucket(bucket_name)
2040 .object(object_key)
2041 .add_query("restore", "");
2042
2043 if let Some(s) = &config.version_id {
2044 request = request.add_query("versionId", s);
2045 }
2046
2047 let xml = config.into_xml()?;
2048 request = request.content_type("application/xml").content_length(xml.len() as u64).text_body(xml);
2049
2050 Ok(request)
2051}
2052
2053#[cfg(test)]
2054mod test_object_common {
2055 use crate::object_common::CallbackBodyParameter;
2056
2057 #[cfg(feature = "serde-support")]
2058 use super::PutObjectResult;
2059
2060 #[test]
2061 fn test_callback_body_parameter() {
2062 assert_eq!("foo=${bucket}", CallbackBodyParameter::OssBucket("foo").to_body_string());
2063 assert_eq!(
2064 "foo=${x:bar}",
2065 CallbackBodyParameter::Custom("foo", "bar", "Are you OK?".to_string()).to_body_string()
2066 );
2067
2068 assert_eq!("foo=bar", CallbackBodyParameter::Constant("foo", "bar").to_body_string());
2069 assert_eq!(
2070 "foo=${x:bar}",
2071 CallbackBodyParameter::Literal("foo".to_string(), "${x:bar}".to_string()).to_body_string()
2072 );
2073 }
2074
2075 #[test]
2076 #[cfg(feature = "serde-support")]
2077 fn test_put_object_result_serde() {
2078 use crate::object_common::PutObjectApiResponse;
2079
2080 let ret = PutObjectResult::ApiResponse(PutObjectApiResponse {
2081 request_id: "abc".to_string(),
2082 etag: "1232".to_string(),
2083 content_md5: "abcsdf".to_string(),
2084 hash_crc64ecma: 1232344,
2085 version_id: None,
2086 });
2087
2088 let s = serde_json::to_string(&ret).unwrap();
2089 println!("{}", s);
2090 }
2091}