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>,
240
241 pub callback: Option<Callback>,
243}
244
245pub struct PutObjectOptionsBuilder {
246 mime_type: Option<String>,
247 cache_control: Option<String>,
248 content_disposition: Option<String>,
249 content_encoding: Option<ContentEncoding>,
250 content_md5: Option<String>,
251 expires: Option<String>,
252 forbid_overwrite: Option<bool>,
253 server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
254 server_side_data_encryption: Option<ServerSideEncryptionAlgorithm>,
255 server_side_encryption_key_id: Option<String>,
256 object_acl: Option<ObjectAcl>,
257 storage_class: Option<StorageClass>,
258 metadata: HashMap<String, String>,
259 tags: HashMap<String, String>,
260 callback: Option<Callback>,
261}
262
263impl PutObjectOptionsBuilder {
264 pub fn new() -> Self {
265 Self {
266 mime_type: None,
267 cache_control: None,
268 content_disposition: None,
269 content_encoding: None,
270 content_md5: None,
271 expires: None,
272 forbid_overwrite: None,
273 server_side_encryption: None,
274 server_side_data_encryption: None,
275 server_side_encryption_key_id: None,
276 object_acl: None,
277 storage_class: None,
278 metadata: HashMap::new(),
279 tags: HashMap::new(),
280 callback: None,
281 }
282 }
283
284 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
285 self.mime_type = Some(mime_type.into());
286 self
287 }
288
289 pub fn cache_control(mut self, cache_control: impl Into<String>) -> Self {
290 self.cache_control = Some(cache_control.into());
291 self
292 }
293
294 pub fn content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
295 self.content_disposition = Some(content_disposition.into());
296 self
297 }
298
299 pub fn content_encoding(mut self, content_encoding: ContentEncoding) -> Self {
300 self.content_encoding = Some(content_encoding);
301 self
302 }
303
304 pub fn content_md5(mut self, content_md5: impl Into<String>) -> Self {
305 self.content_md5 = Some(content_md5.into());
306 self
307 }
308
309 pub fn expires(mut self, expires: impl Into<String>) -> Self {
310 self.expires = Some(expires.into());
311 self
312 }
313
314 pub fn forbid_overwrite(mut self, forbid_overwrite: bool) -> Self {
315 self.forbid_overwrite = Some(forbid_overwrite);
316 self
317 }
318
319 pub fn server_side_encryption(mut self, algorithm: ServerSideEncryptionAlgorithm) -> Self {
320 self.server_side_encryption = Some(algorithm);
321 self
322 }
323
324 pub fn server_side_data_encryption(mut self, algorithm: ServerSideEncryptionAlgorithm) -> Self {
325 self.server_side_data_encryption = Some(algorithm);
326 self
327 }
328
329 pub fn server_side_encryption_key_id(mut self, key_id: impl Into<String>) -> Self {
330 self.server_side_encryption_key_id = Some(key_id.into());
331 self
332 }
333
334 pub fn object_acl(mut self, acl: ObjectAcl) -> Self {
335 self.object_acl = Some(acl);
336 self
337 }
338
339 pub fn storage_class(mut self, storage_class: StorageClass) -> Self {
340 self.storage_class = Some(storage_class);
341 self
342 }
343
344 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
345 self.metadata.insert(key.into(), value.into());
346 self
347 }
348
349 pub fn tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
350 self.tags.insert(key.into(), value.into());
351 self
352 }
353
354 pub fn callback(mut self, cb: Callback) -> Self {
355 self.callback = Some(cb);
356 self
357 }
358
359 pub fn build(self) -> PutObjectOptions {
360 PutObjectOptions {
361 mime_type: self.mime_type,
362 cache_control: self.cache_control,
363 content_disposition: self.content_disposition,
364 content_encoding: self.content_encoding,
365 content_md5: self.content_md5,
366 expires: self.expires,
367 forbid_overwrite: self.forbid_overwrite,
368 server_side_encryption: self.server_side_encryption,
369 server_side_data_encryption: self.server_side_data_encryption,
370 server_side_encryption_key_id: self.server_side_encryption_key_id,
371 object_acl: self.object_acl,
372 storage_class: self.storage_class,
373 metadata: self.metadata,
374 tags: self.tags,
375 callback: self.callback,
376 }
377 }
378}
379
380impl Default for PutObjectOptionsBuilder {
381 fn default() -> Self {
382 Self::new()
383 }
384}
385
386pub struct GetObjectOptions {
390 pub range: Option<String>,
396
397 pub if_modified_since: Option<String>,
402
403 pub if_unmodified_since: Option<String>,
409
410 pub if_match: Option<String>,
415
416 pub if_none_match: Option<String>,
423
424 pub accept_encoding: Option<String>,
445
446 pub response_content_language: Option<String>,
449
450 pub response_expires: Option<String>,
452
453 pub response_cache_control: Option<String>,
455
456 pub response_content_disposition: Option<String>,
458
459 pub response_content_encoding: Option<ContentEncoding>,
461
462 pub version_id: Option<String>,
464}
465
466pub struct GetObjectOptionsBuilder {
467 range: Option<String>,
468 if_modified_since: Option<String>,
469 if_unmodified_since: Option<String>,
470 if_match: Option<String>,
471 if_non_match: Option<String>,
472 accept_encoding: Option<String>,
473 response_content_language: Option<String>,
474 response_expires: Option<String>,
475 response_cache_control: Option<String>,
476 response_content_disposition: Option<String>,
477 response_content_encoding: Option<ContentEncoding>,
478 version_id: Option<String>,
479}
480
481impl GetObjectOptionsBuilder {
482 pub fn new() -> Self {
483 Self {
484 range: None,
485 if_modified_since: None,
486 if_unmodified_since: None,
487 if_match: None,
488 if_non_match: None,
489 accept_encoding: None,
490 response_content_language: None,
491 response_expires: None,
492 response_cache_control: None,
493 response_content_disposition: None,
494 response_content_encoding: None,
495 version_id: None,
496 }
497 }
498
499 pub fn range(mut self, range: impl Into<String>) -> Self {
500 self.range = Some(range.into());
501 self
502 }
503
504 pub fn if_modified_since(mut self, if_modified_since: impl Into<String>) -> Self {
505 self.if_modified_since = Some(if_modified_since.into());
506 self
507 }
508
509 pub fn if_unmodified_since(mut self, if_unmodified_since: impl Into<String>) -> Self {
510 self.if_unmodified_since = Some(if_unmodified_since.into());
511 self
512 }
513
514 pub fn if_match(mut self, if_match: impl Into<String>) -> Self {
515 self.if_match = Some(if_match.into());
516 self
517 }
518
519 pub fn if_non_match(mut self, if_non_match: impl Into<String>) -> Self {
520 self.if_non_match = Some(if_non_match.into());
521 self
522 }
523
524 pub fn accept_encoding(mut self, accept_encoding: impl Into<String>) -> Self {
525 self.accept_encoding = Some(accept_encoding.into());
526 self
527 }
528
529 pub fn response_content_language(mut self, content_language: impl Into<String>) -> Self {
530 self.response_content_language = Some(content_language.into());
531 self
532 }
533
534 pub fn response_expires(mut self, expires: impl Into<String>) -> Self {
535 self.response_expires = Some(expires.into());
536 self
537 }
538
539 pub fn response_cache_control(mut self, cache_control: impl Into<String>) -> Self {
540 self.response_cache_control = Some(cache_control.into());
541 self
542 }
543
544 pub fn response_content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
545 self.response_content_disposition = Some(content_disposition.into());
546 self
547 }
548
549 pub fn response_content_encoding(mut self, content_encoding: ContentEncoding) -> Self {
550 self.response_content_encoding = Some(content_encoding);
551 self
552 }
553
554 pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
555 self.version_id = Some(version_id.into());
556 self
557 }
558
559 pub fn build(self) -> GetObjectOptions {
560 GetObjectOptions {
561 range: self.range,
562 if_modified_since: self.if_modified_since,
563 if_unmodified_since: self.if_unmodified_since,
564 if_match: self.if_match,
565 if_none_match: self.if_non_match,
566 accept_encoding: self.accept_encoding,
567 response_content_language: self.response_content_language,
568 response_expires: self.response_expires,
569 response_cache_control: self.response_cache_control,
570 response_content_disposition: self.response_content_disposition,
571 response_content_encoding: self.response_content_encoding,
572 version_id: self.version_id,
573 }
574 }
575}
576
577impl Default for GetObjectOptionsBuilder {
578 fn default() -> Self {
579 Self::new()
580 }
581}
582
583#[derive(Debug)]
585pub struct GetObjectResult;
586
587pub(crate) fn build_put_object_request(
588 bucket_name: &str,
589 object_key: &str,
590 request_body: RequestBody,
591 options: &Option<PutObjectOptions>,
592) -> Result<OssRequest> {
593 if !validate_bucket_name(bucket_name) {
594 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
595 }
596
597 if !validate_object_key(object_key) {
598 return Err(Error::Other(format!("invalid object key: {}", object_key)));
599 }
600
601 if let Some(options) = &options {
603 for (k, v) in &options.metadata {
604 if k.is_empty() || !validate_meta_key(k) || v.is_empty() {
605 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)));
606 }
607 }
608
609 for (k, v) in &options.tags {
610 if k.is_empty() || !validate_tag_key(k) || (!v.is_empty() && !validate_tag_value(v)) {
611 return Err(Error::Other(format!(
612 "invalid tagging data: \"{}={}\". only `[0-9a-zA-Z\\+\\-=\\.:/]` and space character are allowed",
613 k, v
614 )));
615 }
616 }
617 }
618
619 let mut request = OssRequest::new().method(RequestMethod::Put).bucket(bucket_name).object(object_key);
620
621 let content_length = match &request_body {
622 RequestBody::Empty => 0u64,
623 RequestBody::Text(s) => s.len() as u64,
624 RequestBody::Bytes(bytes) => bytes.len() as u64,
625 RequestBody::File(file_path, range) => {
626 if let Some(r) = range {
627 r.end - r.start
628 } else {
629 if !file_path.exists() || !file_path.is_file() {
630 return Err(Error::Other(format!(
631 "{} does not exist or is not a regular file",
632 file_path.as_os_str().to_str().unwrap_or("UNKNOWN")
633 )));
634 }
635
636 let file_meta = std::fs::metadata(file_path)?;
637
638 file_meta.len()
639 }
640 }
641 };
642
643 if content_length > 5_368_709_120 {
645 return Err(Error::Other(format!("length {} exceeds limitation. max allowed is 5GB", content_length)));
646 }
647
648 request = request.content_length(content_length);
649
650 if let RequestBody::File(file_path, _) = &request_body {
652 request = request.content_type(mime_guess::from_path(file_path).first_or_octet_stream().as_ref());
653 }
654
655 request = request.body(request_body);
657
658 if let Some(options) = options {
659 if let Some(s) = &options.mime_type {
661 request = request.add_header("content-type", s);
662 }
663
664 if let Some(s) = &options.cache_control {
665 request = request.add_header("cache-control", s);
666 }
667
668 if let Some(s) = &options.content_disposition {
669 request = request.add_header("content-disposition", s);
670 }
671
672 if let Some(enc) = &options.content_encoding {
673 request = request.add_header("content-encoding", enc.as_str());
674 }
675
676 if let Some(s) = &options.expires {
677 request = request.add_header("expires", s);
678 }
679
680 if let Some(b) = &options.forbid_overwrite {
681 if *b {
682 request = request.add_header("x-oss-forbid-overwrite", "true");
683 }
684 }
685
686 if let Some(a) = &options.server_side_encryption {
687 request = request.add_header("x-oss-server-side-encryption", a.as_str());
688 }
689
690 if let Some(a) = &options.server_side_data_encryption {
691 request = request.add_header("x-oss-server-side-data-encryption", a.as_str());
692 }
693
694 if let Some(s) = &options.server_side_encryption_key_id {
695 request = request.add_header("x-oss-server-side-encryption-key-id", s);
696 }
697
698 if let Some(acl) = &options.object_acl {
699 request = request.add_header("x-oss-object-acl", acl.as_str());
700 }
701
702 if let Some(store) = &options.storage_class {
703 request = request.add_header("x-oss-storage-class", store.as_str());
704 }
705
706 for (k, v) in &options.metadata {
707 request = request.add_header(k, v);
708 }
709
710 if !options.tags.is_empty() {
711 request = request.add_header("x-oss-tagging", build_tag_string(&options.tags));
712 }
713
714 if let Some(cb) = &options.callback {
715 let callback_json = serde_json::to_string(cb)?;
717 let callback_base64 = BASE64_STANDARD.encode(&callback_json);
718 request = request.add_header("x-oss-callback", callback_base64);
719
720 if !cb.custom_variables.is_empty() {
721 let callback_vars_json = serde_json::to_string(&cb.custom_variables)?;
722 let callback_vars_base64 = BASE64_STANDARD.encode(&callback_vars_json);
723 request = request.add_header("x-oss-callback-var", callback_vars_base64);
724 }
725 }
726 }
727
728 Ok(request)
729}
730
731#[derive(Debug, Clone, Default)]
735#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
736#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
737pub struct GetObjectMetadataOptions {
738 pub version_id: Option<String>,
739}
740
741#[derive(Debug, Clone, Default)]
745#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
746#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
747pub struct HeadObjectOptions {
748 pub version_id: Option<String>,
749 pub if_modified_since: Option<String>,
750 pub if_unmodified_since: Option<String>,
751 pub if_match: Option<String>,
752 pub if_none_match: Option<String>,
753}
754
755pub struct HeadObjectOptionsBuilder {
756 version_id: Option<String>,
757 if_modified_since: Option<String>,
758 if_unmodified_since: Option<String>,
759 if_match: Option<String>,
760 if_none_match: Option<String>,
761}
762
763impl HeadObjectOptionsBuilder {
764 pub fn new() -> Self {
765 Self {
766 version_id: None,
767 if_modified_since: None,
768 if_unmodified_since: None,
769 if_match: None,
770 if_none_match: None,
771 }
772 }
773
774 pub fn version_id(mut self, version_id: impl Into<String>) -> Self {
775 self.version_id = Some(version_id.into());
776 self
777 }
778
779 pub fn if_modified_since(mut self, if_modified_since: impl Into<String>) -> Self {
780 self.if_modified_since = Some(if_modified_since.into());
781 self
782 }
783
784 pub fn if_unmodified_since(mut self, if_unmodified_since: impl Into<String>) -> Self {
785 self.if_unmodified_since = Some(if_unmodified_since.into());
786 self
787 }
788
789 pub fn if_match(mut self, if_match: impl Into<String>) -> Self {
790 self.if_match = Some(if_match.into());
791 self
792 }
793
794 pub fn if_none_match(mut self, if_none_match: impl Into<String>) -> Self {
795 self.if_none_match = Some(if_none_match.into());
796 self
797 }
798
799 pub fn build(self) -> HeadObjectOptions {
800 HeadObjectOptions {
801 version_id: self.version_id,
802 if_modified_since: self.if_modified_since,
803 if_unmodified_since: self.if_unmodified_since,
804 if_match: self.if_match,
805 if_none_match: self.if_none_match,
806 }
807 }
808}
809
810impl Default for HeadObjectOptionsBuilder {
811 fn default() -> Self {
812 Self::new()
813 }
814}
815
816#[derive(Debug, Clone, Default)]
817#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
818#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
819pub struct ObjectMetadata {
820 pub request_id: String,
821 pub content_length: u64,
822
823 pub etag: String,
825 pub hash_crc64ecma: Option<u64>,
826
827 pub transition_time: Option<String>,
829
830 pub last_access_time: Option<String>,
835
836 pub last_modified: Option<String>,
838
839 pub version_id: Option<String>,
840
841 pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
842 pub server_side_encryption_key_id: Option<String>,
843 pub storage_class: Option<StorageClass>,
844 pub object_type: Option<ObjectType>,
845
846 pub next_append_position: Option<u64>,
848
849 pub expiration: Option<String>,
851
852 pub restore: Option<String>,
859
860 pub process_status: Option<String>,
864
865 pub request_charged: Option<String>,
868
869 pub content_md5: Option<String>,
872
873 pub access_control_allow_origin: Option<String>,
875
876 pub access_control_allow_methods: Option<String>,
878
879 pub access_control_allow_max_age: Option<String>,
881
882 pub access_control_allow_headers: Option<String>,
884
885 pub access_control_expose_headers: Option<String>,
887
888 pub tag_count: Option<u32>,
890
891 pub metadata: HashMap<String, String>,
893}
894
895impl From<HashMap<String, String>> for ObjectMetadata {
896 fn from(mut headers: HashMap<String, String>) -> Self {
898 Self {
899 request_id: headers.remove("x-oss-request-id").unwrap_or("".to_string()),
900 content_length: headers.remove("content-length").unwrap_or("0".to_string()).parse().unwrap_or(0),
901 etag: sanitize_etag(headers.remove("etag").unwrap_or_default()),
902 hash_crc64ecma: headers.remove("x-oss-hash-crc64ecma").map(|s| s.parse::<u64>().unwrap_or(0)),
903 transition_time: headers.remove("x-oss-transition-time"),
904 last_access_time: headers.remove("x-oss-last-access-time"),
905 last_modified: headers.remove("last-modified"),
906 version_id: headers.remove("x-oss-version-id"),
907 server_side_encryption: if let Some(s) = headers.remove("x-oss-server-side-encryption") {
908 if let Ok(v) = s.try_into() {
910 Some(v)
911 } else {
912 None
913 }
914 } else {
915 None
916 },
917 server_side_encryption_key_id: headers.remove("x-oss-server-side-encryption-key-id"),
918 storage_class: if let Some(s) = headers.remove("x-oss-storage-class") {
919 if let Ok(v) = s.try_into() {
920 Some(v)
921 } else {
922 None
923 }
924 } else {
925 None
926 },
927 object_type: if let Some(s) = headers.remove("x-oss-object-type") {
928 if let Ok(v) = s.try_into() {
929 Some(v)
930 } else {
931 None
932 }
933 } else {
934 None
935 },
936 next_append_position: headers.remove("x-oss-next-append-position").map(|s| s.parse().unwrap_or(0)),
937 expiration: headers.remove("x-oss-expiration"),
938 restore: headers.remove("x-oss-restore"),
939 process_status: headers.remove("x-oss-process-status"),
940 request_charged: headers.remove("x-oss-request-charged"),
941 content_md5: headers.remove("content-md5"),
942 access_control_allow_origin: headers.remove("access-control-allow-origin"),
943 access_control_allow_methods: headers.remove("access-control-allow-methods"),
944 access_control_allow_headers: headers.remove("access-control-allow-headers"),
945 access_control_allow_max_age: headers.remove("access-control-max-age"),
946 access_control_expose_headers: headers.remove("access-control-expose-headers"),
947 tag_count: headers.remove("x-oss-tagging-count").map(|s| s.parse().unwrap_or(0)),
948
949 metadata: headers.drain().filter(|(k, _)| k.starts_with("x-oss-meta-")).collect(),
951 }
952 }
953}
954
955#[derive(Debug, Clone, Default)]
959#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
960#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
961pub struct CopyObjectOptions {
962 pub forbid_overwrite: Option<bool>,
971
972 pub source_version_id: Option<String>,
974
975 pub copy_source_if_match: Option<String>,
977
978 pub copy_source_if_none_match: Option<String>,
980
981 pub copy_source_if_unmodified_since: Option<String>,
984
985 pub copy_source_if_modified_since: Option<String>,
988
989 pub metadata_directive: Option<MetadataDirective>,
991
992 pub metadata: HashMap<String, String>,
994
995 pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
996 pub server_side_encryption_key_id: Option<String>,
997
998 pub object_acl: Option<ObjectAcl>,
1000
1001 pub storage_class: Option<StorageClass>,
1003
1004 pub tags: HashMap<String, String>,
1005 pub tag_directive: Option<TagDirective>,
1006}
1007
1008pub struct CopyObjectOptionsBuilder {
1009 forbid_overwrite: Option<bool>,
1010 source_version_id: Option<String>,
1011 copy_source_if_match: Option<String>,
1012 copy_source_if_none_match: Option<String>,
1013 copy_source_if_unmodified_since: Option<String>,
1014 copy_source_if_modified_since: Option<String>,
1015 metadata_directive: Option<MetadataDirective>,
1016 metadata: HashMap<String, String>,
1017 server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
1018 server_side_encryption_key_id: Option<String>,
1019 object_acl: Option<ObjectAcl>,
1020 storage_class: Option<StorageClass>,
1021 tags: HashMap<String, String>,
1022 tag_directive: Option<TagDirective>,
1023}
1024
1025impl CopyObjectOptionsBuilder {
1026 pub fn new() -> Self {
1027 Self {
1028 forbid_overwrite: None,
1029 source_version_id: None,
1030 copy_source_if_match: None,
1031 copy_source_if_none_match: None,
1032 copy_source_if_unmodified_since: None,
1033 copy_source_if_modified_since: None,
1034 metadata_directive: None,
1035 metadata: HashMap::new(),
1036 server_side_encryption: None,
1037 server_side_encryption_key_id: None,
1038 object_acl: None,
1039 storage_class: None,
1040 tags: HashMap::new(),
1041 tag_directive: None,
1042 }
1043 }
1044
1045 pub fn forbid_overwrite(mut self, forbid_overwrite: bool) -> Self {
1046 self.forbid_overwrite = Some(forbid_overwrite);
1047 self
1048 }
1049
1050 pub fn source_version_id(mut self, version_id: impl Into<String>) -> Self {
1051 self.source_version_id = Some(version_id.into());
1052 self
1053 }
1054
1055 pub fn copy_source_if_match(mut self, copy_source_if_match: impl Into<String>) -> Self {
1056 self.copy_source_if_match = Some(copy_source_if_match.into());
1057 self
1058 }
1059
1060 pub fn copy_source_if_none_match(mut self, copy_source_if_none_match: impl Into<String>) -> Self {
1061 self.copy_source_if_none_match = Some(copy_source_if_none_match.into());
1062 self
1063 }
1064
1065 pub fn copy_source_if_unmodified_since(mut self, copy_source_if_unmodified_since: impl Into<String>) -> Self {
1066 self.copy_source_if_unmodified_since = Some(copy_source_if_unmodified_since.into());
1067 self
1068 }
1069
1070 pub fn copy_source_if_modified_since(mut self, copy_source_if_modified_since: impl Into<String>) -> Self {
1071 self.copy_source_if_modified_since = Some(copy_source_if_modified_since.into());
1072 self
1073 }
1074
1075 pub fn metadata_directive(mut self, metadata_directive: MetadataDirective) -> Self {
1076 self.metadata_directive = Some(metadata_directive);
1077 self
1078 }
1079
1080 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1081 self.metadata.insert(key.into(), value.into());
1082 self
1083 }
1084
1085 pub fn server_side_encryption(mut self, algorithm: ServerSideEncryptionAlgorithm) -> Self {
1086 self.server_side_encryption = Some(algorithm);
1087 self
1088 }
1089
1090 pub fn server_side_encryption_key_id(mut self, key_id: impl Into<String>) -> Self {
1091 self.server_side_encryption_key_id = Some(key_id.into());
1092 self
1093 }
1094
1095 pub fn object_acl(mut self, acl: ObjectAcl) -> Self {
1096 self.object_acl = Some(acl);
1097 self
1098 }
1099
1100 pub fn storage_class(mut self, storage_class: StorageClass) -> Self {
1101 self.storage_class = Some(storage_class);
1102 self
1103 }
1104
1105 pub fn tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1106 self.tags.insert(key.into(), value.into());
1107 self
1108 }
1109
1110 pub fn tag_directive(mut self, tag_directive: TagDirective) -> Self {
1111 self.tag_directive = Some(tag_directive);
1112 self
1113 }
1114
1115 pub fn build(self) -> CopyObjectOptions {
1116 CopyObjectOptions {
1117 forbid_overwrite: self.forbid_overwrite,
1118 source_version_id: self.source_version_id,
1119 copy_source_if_match: self.copy_source_if_match,
1120 copy_source_if_none_match: self.copy_source_if_none_match,
1121 copy_source_if_unmodified_since: self.copy_source_if_unmodified_since,
1122 copy_source_if_modified_since: self.copy_source_if_modified_since,
1123 metadata_directive: self.metadata_directive,
1124 metadata: self.metadata,
1125 server_side_encryption: self.server_side_encryption,
1126 server_side_encryption_key_id: self.server_side_encryption_key_id,
1127 object_acl: self.object_acl,
1128 storage_class: self.storage_class,
1129 tags: self.tags,
1130 tag_directive: self.tag_directive,
1131 }
1132 }
1133}
1134
1135impl Default for CopyObjectOptionsBuilder {
1136 fn default() -> Self {
1137 Self::new()
1138 }
1139}
1140
1141#[derive(Debug, Clone, Default)]
1143#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1144#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1145pub struct DeleteObjectOptions {
1146 pub version_id: Option<String>,
1147}
1148
1149pub struct DeleteObjectResult;
1151
1152pub struct CopyObjectResult;
1154
1155pub(crate) fn build_copy_object_request(
1156 source_bucket_name: &str,
1157 source_object_key: &str,
1158 dest_bucket_name: &str,
1159 dest_object_key: &str,
1160 options: &Option<CopyObjectOptions>,
1161) -> Result<OssRequest> {
1162 if !validate_bucket_name(source_bucket_name) {
1163 return Err(Error::Other(format!("invalid source bucket name: {}", source_bucket_name)));
1164 }
1165
1166 if !validate_object_key(source_object_key) {
1167 return Err(Error::Other(format!("invalid source object key: {}", source_object_key)));
1168 }
1169
1170 if !validate_bucket_name(dest_bucket_name) {
1171 return Err(Error::Other(format!("invalid destination bucket name: {}", dest_bucket_name)));
1172 }
1173
1174 if !validate_object_key(dest_object_key) {
1175 return Err(Error::Other(format!("invalid destination object key: {}", dest_object_key)));
1176 }
1177
1178 let mut request = OssRequest::new()
1179 .method(RequestMethod::Put)
1180 .bucket(dest_bucket_name)
1181 .object(dest_object_key)
1182 .add_header(
1183 "x-oss-copy-source",
1184 format!("/{}/{}", urlencoding::encode(source_bucket_name), urlencoding::encode(source_object_key)),
1185 );
1186
1187 if let Some(options) = options {
1188 for (k, _) in options.metadata.iter() {
1190 if !validate_meta_key(k) {
1191 return Err(Error::Other(format!("invalid metadata key: {}", k)));
1192 }
1193 }
1194
1195 for (k, v) in options.tags.iter() {
1196 if !validate_tag_key(k) || !validate_tag_value(v) {
1197 return Err(Error::Other(format!("invalid tagging data: {}={}", k, v)));
1198 }
1199 }
1200
1201 if let Some(s) = &options.source_version_id {
1202 request = request.add_query("versionId", s);
1203 }
1204
1205 if let Some(b) = options.forbid_overwrite {
1206 request = request.add_header("x-oss-forbid-overwrite", b.to_string())
1207 }
1208
1209 if let Some(s) = &options.copy_source_if_match {
1210 request = request.add_header("x-oss-copy-source-if-match", s);
1211 }
1212
1213 if let Some(s) = &options.copy_source_if_none_match {
1214 request = request.add_header("x-oss-copy-source-if-none-match", s);
1215 }
1216
1217 if let Some(s) = &options.copy_source_if_modified_since {
1218 request = request.add_header("x-oss-copy-source-if-modified-since", s);
1219 }
1220
1221 if let Some(s) = &options.copy_source_if_unmodified_since {
1222 request = request.add_header("x-oss-copy-source-if-unmodified-since", s);
1223 }
1224
1225 if let Some(md) = options.metadata_directive {
1226 request = request.add_header("x-oss-metadata-directive", md);
1227 }
1228
1229 if let Some(a) = &options.server_side_encryption {
1230 request = request.add_header("x-oss-server-side-encryption", a);
1231 }
1232
1233 if let Some(s) = &options.server_side_encryption_key_id {
1234 request = request.add_header("x-oss-server-side-encryption-key-id", s);
1235 }
1236
1237 if let Some(acl) = options.object_acl {
1238 request = request.add_header("x-oss-object-acl", acl);
1239 }
1240
1241 if let Some(sc) = options.storage_class {
1242 request = request.add_header("x-oss-storage-class", sc);
1243 }
1244
1245 if let Some(td) = options.tag_directive {
1246 request = request.add_header("x-oss-tag-directive", td);
1247 }
1248
1249 if !options.tags.is_empty() {
1250 request = request.add_header("x-oss-tagging", build_tag_string(&options.tags));
1251 }
1252
1253 for (key, value) in options.metadata.iter() {
1254 request = request.add_header(key, value);
1255 }
1256 }
1257
1258 Ok(request)
1259}
1260
1261pub(crate) fn build_get_object_request(bucket_name: &str, object_key: &str, options: &Option<GetObjectOptions>) -> Result<OssRequest> {
1262 if !validate_bucket_name(bucket_name) {
1263 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
1264 }
1265
1266 if !validate_object_key(object_key) {
1267 return Err(Error::Other(format!("invalid object key: {}", object_key)));
1268 }
1269
1270 let mut request = OssRequest::new().method(RequestMethod::Get).bucket(bucket_name).object(object_key);
1271
1272 if let Some(options) = options {
1273 if let Some(s) = &options.range {
1274 request = request.add_header("range", s);
1275 }
1276
1277 if let Some(s) = &options.if_modified_since {
1278 request = request.add_header("if-modified-since", s);
1279 }
1280
1281 if let Some(s) = &options.if_unmodified_since {
1282 request = request.add_header("if-unmodified-since", s);
1283 }
1284
1285 if let Some(s) = &options.if_match {
1286 request = request.add_header("if-match", s);
1287 }
1288
1289 if let Some(s) = &options.if_none_match {
1290 request = request.add_header("if-none-match", s);
1291 }
1292
1293 if let Some(s) = &options.accept_encoding {
1294 request = request.add_header("accept-encoding", s);
1295 }
1296
1297 if let Some(s) = &options.response_content_language {
1298 request = request.add_query("response-content-language", s);
1299 }
1300
1301 if let Some(s) = &options.response_expires {
1302 request = request.add_query("response-expires", s);
1303 }
1304
1305 if let Some(s) = &options.response_cache_control {
1306 request = request.add_query("response-cache-control", s);
1307 }
1308
1309 if let Some(s) = &options.response_content_disposition {
1310 request = request.add_query("response-content-disposition", s);
1311 }
1312
1313 if let Some(ce) = options.response_content_encoding {
1314 request = request.add_query("response-content-encoding", ce.as_str());
1315 }
1316
1317 if let Some(s) = &options.version_id {
1318 request = request.add_query("versionId", s);
1319 }
1320 }
1321
1322 Ok(request)
1323}
1324
1325pub(crate) fn build_head_object_request(bucket_name: &str, object_key: &str, options: &Option<HeadObjectOptions>) -> Result<OssRequest> {
1326 if !validate_bucket_name(bucket_name) {
1327 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
1328 }
1329
1330 if !validate_object_key(object_key) {
1331 return Err(Error::Other(format!("invalid object key: {}", object_key)));
1332 }
1333
1334 let mut request = OssRequest::new().method(RequestMethod::Head).bucket(bucket_name).object(object_key);
1335
1336 if let Some(options) = options {
1337 if let Some(s) = &options.if_modified_since {
1338 request = request.add_header("if-modified-since", s);
1339 }
1340
1341 if let Some(s) = &options.if_unmodified_since {
1342 request = request.add_header("if-unmodified-since", s);
1343 }
1344
1345 if let Some(s) = &options.if_match {
1346 request = request.add_header("if-match", s);
1347 }
1348
1349 if let Some(s) = &options.if_none_match {
1350 request = request.add_header("if-none-match", s);
1351 }
1352
1353 if let Some(s) = &options.version_id {
1354 request = request.add_query("versionId", s);
1355 }
1356 }
1357
1358 Ok(request)
1359}
1360
1361#[derive(Debug, Clone)]
1363#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1364#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1365pub enum PutObjectResult {
1366 #[cfg_attr(feature = "serde-camelcase", serde(rename = "apiResponse", rename_all = "camelCase"))]
1368 ApiResponse(PutObjectApiResponse),
1369
1370 #[cfg_attr(feature = "serde-camelcase", serde(rename = "callbackResponse"))]
1373 CallbackResponse(String),
1374}
1375
1376#[derive(Debug, Clone)]
1378#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1379#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1380pub struct PutObjectApiResponse {
1381 pub request_id: String,
1382
1383 pub etag: String,
1385
1386 pub content_md5: String,
1388
1389 pub hash_crc64ecma: u64,
1391
1392 pub version_id: Option<String>,
1394}
1395
1396impl From<HashMap<String, String>> for PutObjectApiResponse {
1398 fn from(mut headers: HashMap<String, String>) -> Self {
1399 Self {
1400 request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
1401 etag: sanitize_etag(headers.remove("etag").unwrap_or_default()),
1402 content_md5: headers.remove("content-md5").unwrap_or_default(),
1403 hash_crc64ecma: headers.remove("x-oss-hash-crc64ecma").unwrap_or("0".to_string()).parse().unwrap_or(0),
1404 version_id: headers.remove("x-oss-version-id"),
1405 }
1406 }
1407}
1408
1409pub type AppendObjectOptions = PutObjectOptions;
1411pub type AppendObjectOptionsBuilder = PutObjectOptionsBuilder;
1412
1413pub struct AppendObjectResult {
1414 pub request_id: String,
1415 pub next_append_position: u64,
1416}
1417
1418impl From<HashMap<String, String>> for AppendObjectResult {
1419 fn from(mut headers: HashMap<String, String>) -> Self {
1420 Self {
1421 request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
1422 next_append_position: headers.remove("x-oss-next-append-position").unwrap_or("0".to_string()).parse().unwrap_or(0),
1423 }
1424 }
1425}
1426
1427#[derive(Debug, Clone, Default)]
1428#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1429#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1430pub struct DeleteMultipleObjectsItem {
1431 pub key: String,
1432 pub version_id: Option<String>,
1433}
1434
1435#[derive(Debug, Clone, Default)]
1439#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1440#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1441pub struct DeleteMultipleObjectsRequest {
1442 pub quiet: Option<bool>,
1443
1444 pub objects: Vec<DeleteMultipleObjectsItem>,
1446}
1447
1448impl DeleteMultipleObjectsRequest {
1449 pub(crate) fn into_xml(self) -> Result<String> {
1451 let mut writer = quick_xml::Writer::new(Vec::new());
1452 writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
1453
1454 writer.write_event(Event::Start(BytesStart::new("Delete")))?;
1455
1456 if let Some(b) = self.quiet {
1457 writer.write_event(Event::Start(BytesStart::new("Quiet")))?;
1458 writer.write_event(Event::Text(BytesText::new(&b.to_string())))?;
1459 writer.write_event(Event::End(BytesEnd::new("Quiet")))?;
1460 }
1461
1462 for item in self.objects {
1463 writer.write_event(Event::Start(BytesStart::new("Object")))?;
1464
1465 writer.write_event(Event::Start(BytesStart::new("Key")))?;
1466 writer.write_event(Event::Text(BytesText::new(&item.key)))?;
1467 writer.write_event(Event::End(BytesEnd::new("Key")))?;
1468
1469 if let Some(s) = item.version_id {
1470 writer.write_event(Event::Start(BytesStart::new("VersionId")))?;
1471 writer.write_event(Event::Text(BytesText::new(&s)))?;
1472 writer.write_event(Event::End(BytesEnd::new("VersionId")))?;
1473 }
1474
1475 writer.write_event(Event::End(BytesEnd::new("Object")))?;
1476 }
1477
1478 writer.write_event(Event::End(BytesEnd::new("Delete")))?;
1479
1480 Ok(String::from_utf8(writer.into_inner())?)
1481 }
1482}
1483
1484impl<T> From<&[T]> for DeleteMultipleObjectsRequest
1485where
1486 T: AsRef<str>,
1487{
1488 fn from(object_keys: &[T]) -> Self {
1489 Self {
1490 objects: object_keys
1491 .iter()
1492 .map(|s| DeleteMultipleObjectsItem {
1493 key: s.as_ref().to_string(),
1494 ..Default::default()
1495 })
1496 .collect::<Vec<_>>(),
1497 ..Default::default()
1498 }
1499 }
1500}
1501
1502#[derive(Debug)]
1503pub enum DeleteMultipleObjectsConfig<'a, T: AsRef<str> + 'a> {
1504 FromKeys(&'a [T]),
1506
1507 FullRequest(DeleteMultipleObjectsRequest),
1509}
1510
1511#[derive(Debug, Clone, Default)]
1512#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1513#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1514pub struct DeleteMultipleObjectsResultItem {
1515 pub key: String,
1516 pub version_id: Option<String>,
1517 pub delete_marker: Option<String>,
1518 pub delete_marker_version_id: Option<String>,
1519}
1520
1521#[derive(Debug, Clone, Default)]
1525#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1526#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1527pub struct DeleteMultipleObjectsResult {
1528 pub items: Vec<DeleteMultipleObjectsResultItem>,
1529}
1530
1531impl DeleteMultipleObjectsResult {
1532 pub fn from_xml(xml_content: &str) -> Result<Self> {
1533 let mut reader = quick_xml::Reader::from_str(xml_content);
1534 let mut tag = String::new();
1535 let mut items = Vec::new();
1536
1537 let mut current_item = DeleteMultipleObjectsResultItem::default();
1538
1539 loop {
1540 match reader.read_event()? {
1541 Event::Eof => break,
1542 Event::Start(t) => {
1543 tag = String::from_utf8_lossy(t.local_name().as_ref()).to_string();
1544 if tag == "Deleted" {
1545 current_item = DeleteMultipleObjectsResultItem::default();
1546 }
1547 }
1548
1549 Event::Text(e) => {
1550 let s = e.unescape()?.trim().to_string();
1551 match tag.as_str() {
1552 "Key" => current_item.key = s,
1553 "VersionId" => current_item.version_id = Some(s),
1554 "DeleteMarker" => current_item.delete_marker = Some(s),
1555 "DeleteMarkerVersionId" => current_item.delete_marker_version_id = Some(s),
1556 _ => {}
1557 }
1558 }
1559
1560 Event::End(t) => {
1561 if t.local_name().as_ref() == b"Deleted" {
1562 items.push(current_item.clone());
1563 }
1564 tag.clear();
1565 }
1566
1567 _ => {}
1568 }
1569 }
1570
1571 Ok(Self { items })
1572 }
1573}
1574
1575pub(crate) fn build_delete_multiple_objects_request<S>(bucket_name: &str, config: DeleteMultipleObjectsConfig<S>) -> Result<OssRequest>
1576where
1577 S: AsRef<str>,
1578{
1579 if !validate_bucket_name(bucket_name) {
1580 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
1581 }
1582
1583 let mut request = OssRequest::new()
1584 .method(RequestMethod::Post)
1585 .bucket(bucket_name)
1586 .add_query("delete", "")
1587 .content_type(MIME_TYPE_XML);
1588
1589 let items_len = match &config {
1590 DeleteMultipleObjectsConfig::FromKeys(items) => items.len(),
1591 DeleteMultipleObjectsConfig::FullRequest(cfg) => cfg.objects.len(),
1592 };
1593
1594 if items_len > common::DELETE_MULTIPLE_OBJECTS_LIMIT {
1595 return Err(Error::Other(format!(
1596 "{} exceeds the items count limits while deleting multiple objects",
1597 items_len
1598 )));
1599 }
1600
1601 let payload = match config {
1602 DeleteMultipleObjectsConfig::FromKeys(items) => DeleteMultipleObjectsRequest::from(items),
1603 DeleteMultipleObjectsConfig::FullRequest(delete_multiple_objects_request) => delete_multiple_objects_request,
1604 };
1605
1606 let xml_content = payload.into_xml()?;
1607 let content_md5 = BASE64_STANDARD.encode(*md5::compute(xml_content.as_bytes()));
1608
1609 request = request
1610 .content_length(xml_content.len() as u64)
1611 .add_header("content-md5", content_md5)
1612 .text_body(xml_content);
1613
1614 Ok(request)
1615}
1616
1617#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1619pub enum CallbackBodyType {
1620 #[default]
1621 #[serde(rename = "application/x-www-form-urlencoded")]
1622 FormUrlEncoded,
1623
1624 #[serde(rename = "application/json")]
1625 Json,
1626}
1627
1628#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1640pub struct Callback {
1641 #[serde(rename = "callbackUrl")]
1658 pub url: String,
1659
1660 #[serde(rename = "callbackHost", skip_serializing_if = "Option::is_none")]
1663 pub host: Option<String>,
1664
1665 #[serde(rename = "callbackBody")]
1687 pub body: String,
1688
1689 #[serde(rename = "callbackSNI", skip_serializing_if = "Option::is_none")]
1693 pub sni: Option<bool>,
1694
1695 #[serde(rename = "callbackBodyType", skip_serializing_if = "Option::is_none")]
1697 pub body_type: Option<CallbackBodyType>,
1698
1699 #[serde(skip_serializing)]
1703 pub custom_variables: HashMap<String, String>,
1704}
1705
1706pub enum CallbackBodyParameter<'a> {
1735 OssBucket(&'a str),
1736 OssObject(&'a str),
1737 OssETag(&'a str),
1738 OssSize(&'a str),
1739 OssMimeType(&'a str),
1740 OssImageHeight(&'a str),
1741 OssImageWidth(&'a str),
1742 OssImageFormat(&'a str),
1743 OssCrc64(&'a str),
1744 OssContentMd5(&'a str),
1745 OssVpcId(&'a str),
1746 OssClientIp(&'a str),
1747 OssRequestId(&'a str),
1748 OssOperation(&'a str),
1749
1750 Custom(&'a str, &'a str, String),
1758
1759 Constant(&'a str, &'a str),
1764
1765 Literal(String, String),
1771}
1772
1773impl CallbackBodyParameter<'_> {
1774 pub fn to_body_string(&self) -> String {
1776 match self {
1777 CallbackBodyParameter::OssBucket(k) => format!("{}=${{bucket}}", k),
1778 CallbackBodyParameter::OssObject(k) => format!("{}=${{object}}", k),
1779 CallbackBodyParameter::OssETag(k) => format!("{}=${{etag}}", k),
1780 CallbackBodyParameter::OssSize(k) => format!("{}=${{size}}", k),
1781 CallbackBodyParameter::OssMimeType(k) => format!("{}=${{mimeType}}", k),
1782 CallbackBodyParameter::OssImageHeight(k) => format!("{}=${{imageInfo.height}}", k),
1783 CallbackBodyParameter::OssImageWidth(k) => format!("{}=${{imageInfo.width}}", k),
1784 CallbackBodyParameter::OssImageFormat(k) => format!("{}=${{imageInfo.format}}", k),
1785 CallbackBodyParameter::OssCrc64(k) => format!("{}=${{crc64}}", k),
1786 CallbackBodyParameter::OssContentMd5(k) => format!("{}=${{contentMd5}}", k),
1787 CallbackBodyParameter::OssVpcId(k) => format!("{}=${{vpcId}}", k),
1788 CallbackBodyParameter::OssClientIp(k) => format!("{}=${{clientIp}}", k),
1789 CallbackBodyParameter::OssRequestId(k) => format!("{}=${{reqId}}", k),
1790 CallbackBodyParameter::OssOperation(k) => format!("{}=${{operation}}", k),
1791 CallbackBodyParameter::Custom(k, v, _) => format!("{}=${{x:{}}}", k, v),
1792 CallbackBodyParameter::Constant(k, v) => format!("{}={}", k, v),
1793 CallbackBodyParameter::Literal(k, v) => format!("{}={}", k, v),
1794 }
1795 }
1796}
1797
1798pub struct CallbackBuilder<'a> {
1800 url: String,
1801 host: Option<String>,
1802 sni: Option<bool>,
1803 body_type: Option<CallbackBodyType>,
1804 body_parameters: Vec<CallbackBodyParameter<'a>>,
1805 custom_variables: HashMap<String, String>,
1806}
1807
1808impl<'a> CallbackBuilder<'a> {
1809 pub fn new(url: impl Into<String>) -> Self {
1810 Self {
1811 url: url.into(),
1812 host: None,
1813 sni: None,
1814 body_type: None,
1815 body_parameters: vec![],
1816 custom_variables: HashMap::new(),
1817 }
1818 }
1819
1820 pub fn host(mut self, host: impl Into<String>) -> Self {
1821 self.host = Some(host.into());
1822 self
1823 }
1824
1825 pub fn sni(mut self, sni: bool) -> Self {
1826 self.sni = Some(sni);
1827 self
1828 }
1829
1830 pub fn body_type(mut self, body_type: CallbackBodyType) -> Self {
1831 self.body_type = Some(body_type);
1832 self
1833 }
1834
1835 pub fn body_parameter(mut self, param: CallbackBodyParameter<'a>) -> Self {
1836 if let CallbackBodyParameter::Custom(_, custom_var_name, custom_var_value) = ¶m {
1837 self.custom_variables.insert(
1838 format!("x:{}", custom_var_name.strip_prefix("x:").unwrap_or(custom_var_name)),
1839 custom_var_value.clone(),
1840 );
1841 }
1842
1843 self.body_parameters.push(param);
1844 self
1845 }
1846
1847 pub fn custom_variable(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
1851 let key: String = k.into();
1852 self.custom_variables
1853 .insert(format!("x:{}", key.strip_prefix("x:").unwrap_or(key.as_str())), v.into());
1854 self
1855 }
1856
1857 pub fn build(self) -> Callback {
1858 let body_string = self.body_parameters.into_iter().map(|bp| bp.to_body_string()).collect::<Vec<_>>().join("&");
1859
1860 Callback {
1861 url: self.url,
1862 host: self.host,
1863 body: body_string,
1864 sni: self.sni,
1865 body_type: self.body_type,
1866 custom_variables: self.custom_variables,
1867 }
1868 }
1869}
1870
1871#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
1883#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1884pub enum RestoreJobTier {
1885 #[cfg_attr(feature = "serde-support", serde(rename = "Standard"))]
1886 #[default]
1887 Standard,
1888
1889 #[cfg_attr(feature = "serde-support", serde(rename = "Expedited"))]
1890 Expedited,
1891
1892 #[cfg_attr(feature = "serde-support", serde(rename = "Bulk"))]
1893 Bulk,
1894}
1895
1896impl RestoreJobTier {
1897 pub fn as_str(&self) -> &str {
1898 match self {
1899 RestoreJobTier::Standard => "Standard",
1900 RestoreJobTier::Expedited => "Expedited",
1901 RestoreJobTier::Bulk => "Bulk",
1902 }
1903 }
1904}
1905
1906impl AsRef<str> for RestoreJobTier {
1907 fn as_ref(&self) -> &str {
1908 self.as_str()
1909 }
1910}
1911
1912impl TryFrom<&str> for RestoreJobTier {
1913 type Error = Error;
1914
1915 fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
1916 match s {
1917 "Standard" => Ok(Self::Standard),
1918 "Expedited" => Ok(Self::Expedited),
1919 "Bulk" => Ok(Self::Bulk),
1920 _ => Err(Error::Other(format!("invalid job tier: {}", s))),
1921 }
1922 }
1923}
1924
1925impl TryFrom<&String> for RestoreJobTier {
1926 type Error = Error;
1927
1928 fn try_from(s: &String) -> std::result::Result<Self, Self::Error> {
1929 Self::try_from(s.as_str())
1930 }
1931}
1932
1933impl TryFrom<String> for RestoreJobTier {
1934 type Error = Error;
1935
1936 fn try_from(s: String) -> std::result::Result<Self, Self::Error> {
1937 Self::try_from(s.as_str())
1938 }
1939}
1940
1941#[derive(Debug, Clone, Default)]
1943#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1944#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1945pub struct RestoreObjectRequest {
1946 pub days: u16,
1951
1952 pub version_id: Option<String>,
1955
1956 pub tier: Option<RestoreJobTier>,
1958}
1959
1960impl RestoreObjectRequest {
1961 pub(crate) fn into_xml(self) -> Result<String> {
1963 let mut writer = quick_xml::Writer::new(Vec::new());
1964
1965 writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
1966
1967 writer.write_event(Event::Start(BytesStart::new("RestoreRequest")))?;
1968
1969 writer.write_event(Event::Start(BytesStart::new("Days")))?;
1970 writer.write_event(Event::Text(BytesText::new(self.days.to_string().as_str())))?;
1971 writer.write_event(Event::End(BytesEnd::new("Days")))?;
1972
1973 if let Some(t) = self.tier {
1974 writer.write_event(Event::Start(BytesStart::new("JobParameters")))?;
1975 writer.write_event(Event::Start(BytesStart::new("Tier")))?;
1976 writer.write_event(Event::Text(BytesText::new(t.as_str())))?;
1977 writer.write_event(Event::End(BytesEnd::new("Tier")))?;
1978 writer.write_event(Event::End(BytesEnd::new("JobParameters")))?;
1979 }
1980
1981 writer.write_event(Event::End(BytesEnd::new("RestoreRequest")))?;
1982
1983 Ok(String::from_utf8(writer.into_inner())?)
1984 }
1985}
1986
1987#[derive(Debug, Clone)]
1989#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
1990#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
1991pub struct RestoreObjectResult {
1992 pub request_id: String,
1993 pub object_restore_priority: Option<String>,
1994 pub version_id: Option<String>,
1995}
1996
1997impl From<HashMap<String, String>> for RestoreObjectResult {
1998 fn from(mut headers: HashMap<String, String>) -> Self {
1999 Self {
2000 request_id: headers.remove("x-oss-request-id").unwrap_or_default(),
2001 object_restore_priority: headers.remove("x-oss-object-restore-priority"),
2002 version_id: headers.remove("x-oss-version-id"),
2003 }
2004 }
2005}
2006
2007pub(crate) fn build_restore_object_request(bucket_name: &str, object_key: &str, config: RestoreObjectRequest) -> Result<OssRequest> {
2008 if !validate_bucket_name(bucket_name) {
2009 return Err(Error::Other(format!("invalid bucket name: {}", bucket_name)));
2010 }
2011
2012 if !validate_object_key(object_key) {
2013 return Err(Error::Other(format!("invalid object key: {}", object_key)));
2014 }
2015
2016 let mut request = OssRequest::new()
2017 .method(RequestMethod::Post)
2018 .bucket(bucket_name)
2019 .object(object_key)
2020 .add_query("restore", "");
2021
2022 if let Some(s) = &config.version_id {
2023 request = request.add_query("versionId", s);
2024 }
2025
2026 let xml = config.into_xml()?;
2027 request = request.content_type("application/xml").content_length(xml.len() as u64).text_body(xml);
2028
2029 Ok(request)
2030}
2031
2032#[cfg(test)]
2033mod test_object_common {
2034 use crate::object_common::CallbackBodyParameter;
2035
2036 #[cfg(feature = "serde-support")]
2037 use super::PutObjectResult;
2038
2039 #[test]
2040 fn test_callback_body_parameter() {
2041 assert_eq!("foo=${bucket}", CallbackBodyParameter::OssBucket("foo").to_body_string());
2042 assert_eq!(
2043 "foo=${x:bar}",
2044 CallbackBodyParameter::Custom("foo", "bar", "Are you OK?".to_string()).to_body_string()
2045 );
2046
2047 assert_eq!("foo=bar", CallbackBodyParameter::Constant("foo", "bar").to_body_string());
2048 assert_eq!(
2049 "foo=${x:bar}",
2050 CallbackBodyParameter::Literal("foo".to_string(), "${x:bar}".to_string()).to_body_string()
2051 );
2052 }
2053
2054 #[test]
2055 #[cfg(feature = "serde-support")]
2056 fn test_put_object_result_serde() {
2057 use crate::object_common::PutObjectApiResponse;
2058
2059 let ret = PutObjectResult::ApiResponse(PutObjectApiResponse {
2060 request_id: "abc".to_string(),
2061 etag: "1232".to_string(),
2062 content_md5: "abcsdf".to_string(),
2063 hash_crc64ecma: 1232344,
2064 version_id: None,
2065 });
2066
2067 let s = serde_json::to_string(&ret).unwrap();
2068 println!("{}", s);
2069 }
2070}