Skip to main content

ali_oss_rs/
object_common.rs

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///
15/// Represents the access control list (ACL) for an object in Aliyun OSS.
16///
17#[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    /// 表示 Object 未经过压缩或编码
95    #[default]
96    #[cfg_attr(feature = "serde-support", serde(rename = "identity"))]
97    Identity,
98
99    /// 表示 Object 采用 Lempel-Ziv(LZ77) 压缩算法以及 32 位 CRC 校验的编码方式。
100    #[cfg_attr(feature = "serde-support", serde(rename = "gzip"))]
101    Gzip,
102
103    /// 表示 Object 采用 zlib 结构和 deflate 压缩算法的编码方式。
104    #[cfg_attr(feature = "serde-support", serde(rename = "deflate"))]
105    Deflate,
106
107    /// 表示 Object 采用 Lempel-Ziv-Welch(LZW) 压缩算法的编码方式。
108    #[cfg_attr(feature = "serde-support", serde(rename = "compress"))]
109    Compress,
110
111    /// 表示 Object 采用 Brotli 压缩算法的编码方式。
112    #[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    /// Try to create a ContentEncoding from a string.
132    /// Acceptable values are "identity", "gzip", "deflate", "compress", and "br".
133    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    /// See [`try_from(value: &str)`] for more details.
149    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    /// See [`try_from(value: &str)`] for more details.
158    fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
159        Self::try_from(value.as_str())
160    }
161}
162
163/// Options for putting object
164///
165/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/putobject>
166#[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    /// 文件的 mime_type。如果不指定,则从文件名猜测。如果猜测不到,则使用 application/octet-stream
171    /// 如果是直接从字节数组创建 Object 的,则不会猜测这个值,建议显式指定
172    pub mime_type: Option<String>,
173
174    /// 指定该 Object 被下载时网页的缓存行为。取值如下:
175    ///
176    /// - `no-cache`:不可直接使用缓存,而是先到服务端验证 Object 是否已更新。如果 Object 已更新,表明缓存已过期,需从服务端重新下载 Object;如果 Object 未更新,表明缓存未过期,此时将使用本地缓存。
177    /// - `no-store`:所有内容都不会被缓存。
178    /// - `public`:所有内容都将被缓存。
179    /// - `private`:所有内容只在客户端缓存。
180    /// - `max-age=<seconds>`:缓存内容的相对过期时间,单位为秒。此选项仅在 HTTP 1.1 中可用。示例:`max-age=3600`
181    pub cache_control: Option<String>,
182
183    /// 指定Object的展示形式。取值如下:
184    ///
185    /// - `inline`: 直接预览文件内容。
186    /// - `attachment`: 以原文件名的形式下载到浏览器指定路径。
187    /// - `attachment; filename="yourFileName"`: 以自定义文件名的形式下载到浏览器指定路径。 `yourFileName` 用于自定义下载后的文件名称,例如 `example.jpg`。
188    ///
189    /// 注意:
190    ///
191    /// - 如果 Object 名称包含星号(`*`)、正斜线(`/`)等特殊字符时,可能会出现特殊字符转义的情况。例如,下载 `example*.jpg` 到本地时,`example*.jpg` 可能会转义为`example_.jpg`。
192    /// - 如需确保下载名称中包含中文字符的 Object 到本地指定路径后,文件名称不出现乱码的现象,您需要将名称中包含的中文字符进行 URL 编码。例如,将`测试.txt` 从 OSS 下载到本地后,需要保留文件名为 `测试.txt`,需按照 `"attachment;filename="+URLEncoder.encode("测试","UTF-8")+".txt;filename*=UTF-8''"+URLEncoder.encode("测试","UTF-8")+".txt"` 的格式设置 `Content-Disposition`,即 `attachment;filename=%E6%B5%8B%E8%AF%95.txt;filename*=UTF-8''%E6%B5%8B%E8%AF%95.txt`
193    pub content_disposition: Option<String>,
194
195    pub content_encoding: Option<ContentEncoding>,
196
197    /// 上传内容的 MD5 摘要算法结果的 base64 字符串。用于检查消息内容是否与发送时一致。Content-MD5 是由 MD5 算法生成的值。上传了 Content-MD5 请求头后,OSS 会计算消息体的 Content-MD5 并检查一致性。
198    pub content_md5: Option<String>,
199
200    /// 过期时间。例如:`Wed, 08 Jul 2015 16:57:01 GMT`
201    pub expires: Option<String>,
202
203    /// 指定 PutObject 操作时是否覆盖同名 Object。
204    ///
205    /// 当目标 Bucket 处于已开启或已暂停的版本控制状态时,
206    /// `x-oss-forbid-overwrite` 请求 Header 设置无效,即允许覆盖同名 Object。
207    ///
208    /// - 不指定 `x-oss-forbid-overwrite` 或者指定 `x-oss-forbid-overwrite` 为 `false` 时,表示允许覆盖同名 Object。
209    /// - 指定 `x-oss-forbid-overwrite` 为 `true` 时,表示禁止覆盖同名 Object。
210    ///
211    /// **设置 `x-oss-forbid-overwrite` 请求 Header 导致 QPS 处理性能下降,如果您有大量的操作需要使用 `x-oss-forbid-overwrite` 请求 Header(QPS > 1000),请联系技术支持,避免影响您的业务。**
212    pub forbid_overwrite: Option<bool>,
213
214    /// 创建 Object 时,指定服务器端加密方式。
215    /// 指定此选项后,在响应头中会返回此选项,OSS 会对上传的 Object 进行加密编码存储。当下载该 Object 时,响应头中会包含 `x-oss-server-side-encryption`,且该值会被设置成此 Object 的加密算法。
216    pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
217
218    /// 指定Object的加密算法。如果未指定此选项,表明 Object 使用 AES256 加密算法。此选项仅当 `x-oss-server-side-encryption` 为 KMS 时有效。
219    pub server_side_data_encryption: Option<ServerSideEncryptionAlgorithm>,
220
221    /// KMS托管的用户主密钥。此选项仅在 `x-oss-server-side-encryption` 为 KMS 时有效。
222    pub server_side_encryption_key_id: Option<String>,
223
224    /// 如果不指定,则默认采用 Bucket 的 ACL。
225    pub object_acl: Option<ObjectAcl>,
226
227    /// 如果不指定,则默认采用 Bucket 的存储类型。
228    pub storage_class: Option<StorageClass>,
229
230    /// 使用 PutObject 接口时,如果配置以 `x-oss-meta-` 为前缀的参数,则该参数视为元数据,例如 `x-oss-meta-location`。
231    /// 一个 Object 可以有多个类似的参数,但所有的元数据总大小不能超过 8 KB。
232    /// 元数据支持短划线(`-`)、数字、英文字母(`a~z`)。英文字符的大写字母会被转成小写字母,不支持下划线(`_`)在内的其他字符。
233    ///
234    /// **注意:Key 必须是 `x-oss-meta-` 开头的**
235    pub metadata: HashMap<String, String>,
236
237    /// Object 标签
238    ///
239    /// 签合法字符集包括大小写字母、数字、空格和下列符号:`+ - = . _ : /`。
240    pub tags: HashMap<String, String>,
241
242    /// For `put_object` only.
243    pub callback: Option<Callback>,
244
245    /// 额外的 URI 查询参数。
246    ///
247    /// 主要是初始化分片上传的时候,有一个额外参数 `sequential`,
248    /// 但是这个参数在官方文档上都没有说明,所以就放到这里吧,有需要的话就使用
249    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
401/// Options for getting object
402///
403/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobject>
404pub struct GetObjectOptions {
405    // The following fields are header items
406    /// 指定文件传输的范围。
407    ///
408    /// - 如果指定的范围符合规范,返回消息中会包含整个 Object 的大小和此次返回 Object 的范围。例如:`Content-Range: bytes 0~9/44`,表示整个 Object 大小为 `44`,此次返回的范围为 `0~9`。
409    /// - 如果指定的范围不符合规范,则传送整个 Object,并且结果中不包含 `Content-Range`。
410    pub range: Option<String>,
411
412    /// GMT 日期时间字符串,例如:`Fri, 13 Nov 2015 14:47:53 GMT`
413    ///
414    /// 如果指定的时间早于实际修改时间或指定的时间不符合规范,则直接返回 Object,并返回 `200 OK`;
415    /// 如果指定的时间等于或者晚于实际修改时间,则返回 `304 Not Modified`。
416    pub if_modified_since: Option<String>,
417
418    /// GMT 日期时间字符串,例如:`Fri, 13 Nov 2015 14:47:53 GMT`
419    ///
420    /// 如果指定的时间等于或者晚于 Object 实际修改时间,则正常传输 Object,并返回 `200 OK`;
421    /// 如果指定的时间早于实际修改时间,则返回 `412 Precondition Failed`。
422    /// `If-Modified-Since` 和 `If-Unmodified-Since` 可以同时使用。
423    pub if_unmodified_since: Option<String>,
424
425    /// ETag 值
426    ///
427    /// 如果传入的 `ETag` 和 Object 的 `ETag` 匹配,则正常传输 Object,并返回 `200 OK`;
428    /// 如果传入的 `ETag` 和 Object 的 `ETag` 不匹配,则返回 `412 Precondition Failed`。
429    pub if_match: Option<String>,
430
431    /// ETag 值
432    ///
433    /// 如果传入的 `ETag` 值和 `Object` 的 `ETag` 不匹配,则正常传输 Object,并返回 `200 OK`;
434    /// 如果传入的 `ETag` 和 `Object` 的 `ETag` 匹配,则返回 `304 Not Modified`。
435    ///
436    /// `If-Match` 和 `If-None-Match` 可以同时使用。
437    pub if_none_match: Option<String>,
438
439    /// 指定客户端的编码类型。例如:gzip
440    ///
441    /// 如果要对返回内容进行 Gzip 压缩传输,您需要在请求头中以显示方式加入 `Accept-Encoding:gzip`。
442    /// OSS 会根据 Object 的 Content-Type 和 Object 大小(不小于1KB),
443    /// 判断传输过程中是否对数据进行 Gzip 压缩。满足条件时,数据以压缩形式传输,否则,数据以原始形式传输。
444    ///
445    /// 注意:
446    ///
447    /// - 如果采用了 Gzip 压缩且压缩生效,则不会附带 `ETag` 和 `Content-Length` 信息。
448    /// - 目前 OSS 支持对以下 `Content-Type` 类型的数据进行 Gzip 压缩:
449    ///   - text/cache-manifest
450    ///   - text/xml
451    ///   - text/css
452    ///   - text/html
453    ///   - text/plain
454    ///   - application/javascript
455    ///   - application/x-javascript
456    ///   - application/rss+xml
457    ///   - application/json
458    ///   - text/json
459    pub accept_encoding: Option<String>,
460
461    // The following fields are query parameters
462    /// Add `Content-Language` to response header
463    pub response_content_language: Option<String>,
464
465    /// Add `Expires` to response header
466    pub response_expires: Option<String>,
467
468    /// Add `Cache-Control` to response header
469    pub response_cache_control: Option<String>,
470
471    /// Add `Content-Disposition` to response header
472    pub response_content_disposition: Option<String>,
473
474    /// Add `Content-Encoding` to response header
475    pub response_content_encoding: Option<ContentEncoding>,
476
477    /// The version to retreive
478    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/// A "placeholder" struct for adding more fields in the future
599#[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    // check for metadata and tags
617    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    // max file size for putting object is 5GB
665    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 no `content-type` specified, try to guess from file
672    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    // move the body to request
677    request = request.body(request_body);
678
679    if let Some(options) = options {
680        // if `mime_type` is specified, overwrite it's value which guess from file (maybe)
681        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            // custom variable values are not serialized
737            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/// Options for getting object metadata
753///
754/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/getobjectmeta>
755#[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/// Options for heading object
763///
764/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/headobject>
765#[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    /// 已经移除了首尾双引号(`"`)之后的字符串
845    pub etag: String,
846    pub hash_crc64ecma: Option<u64>,
847
848    /// Object 通过生命周期规则转储为冷归档或者深度冷归档存储类型的时间。
849    pub transition_time: Option<String>,
850
851    /// Object 的最后一次访问时间。时间格式为 HTTP 1.1 协议中规定的 GMT 时间。
852    /// 开启访问跟踪时,该字段的值会随着文件被访问的时间持续更新。
853    /// 如果开启后关闭了访问跟踪,该字段的值保留为上一次最后更新的值。
854    /// 示例: `Tue, 30 Mar 2021 06:07:48 GMT`
855    pub last_access_time: Option<String>,
856
857    /// 时间格式为 HTTP 1.1 协议中规定的 GMT 时间。
858    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    /// 对于 `Appendable` 类型的 Object 会返回此 Header,指明下一次请求应当提供的 `position`。
868    pub next_append_position: Option<u64>,
869
870    /// 配置了生命周期规则的Bucket中Object的过期时间。
871    pub expiration: Option<String>,
872
873    /// 如果 Object 存储类型为 `Archive`、`ColdArchive` 或者 `DeepColdArchive`,
874    /// 且您已提交 Restore 请求,则响应头中会以 `x-oss-restore` 返回该 Object 的 Restore 状态,分如下几种情况:
875    ///
876    /// - 如果没有提交 Restore 或者 Restore 已经超时,则不返回该字段。
877    /// - 如果已经提交 Restore,且 Restore 没有完成,则返回的 `x-oss-restore` 值为 `ongoing-request="true"`。
878    /// - 如果已经提交 Restore,且 Restore 已经完成,则返回的 `x-oss-restore` 值为 `ongoing-request="false", expiry-date="Sun, 16 Apr 2017 08:12:33 GMT"`,其中 `expiry-date` 是 Restore 完成后 Object 进入可读状态的过期时间。
879    pub restore: Option<String>,
880
881    /// 当用户通过轻量消息队列 SMQ 创建 OSS 事件通知后,
882    /// 在进行请求 OSS 相关操作时如果有匹配的事件通知规则,
883    /// 则响应中会携带这个 Header,值为经过 Base64 编码 JSON 格式的事件通知结果。
884    pub process_status: Option<String>,
885
886    /// 当 Object 所属的 Bucket 被设置为请求者付费模式,
887    /// 且请求者不是 Bucket 的拥有者时,响应中将携带此 Header,值为 `requester`。
888    pub request_charged: Option<String>,
889
890    /// - 对于 `Normal` 类型的 Object,根据 RFC 1864 标准对消息内容(不包括Header)计算 Md5 值获得 128 比特位数字,对该数字进行 Base64 编码作为一个消息的 Content-Md5 值。
891    /// - `Multipart` 和 `Appendable` 类型的文件不会返回这个 Header。
892    pub content_md5: Option<String>,
893
894    /// 当 Object 所在的 Bucket 配置了 CORS 规则,且请求的 Origin 满足指定的 CORS 规则时会在响应中包含这个 Origin。
895    pub access_control_allow_origin: Option<String>,
896
897    /// 当 Object 所在的 Bucket 配置了 CORS 规则,且请求的 `Access-Control-Request-Method` 满足指定的CORS规则时会在响应中包含允许的 Methods。
898    pub access_control_allow_methods: Option<String>,
899
900    /// 当 Object 所在的 Bucket 配置了 CORS 规则,且请求满足 Bucket 配置的 CORS 规则时会在响应中包含 `MaxAgeSeconds`。
901    pub access_control_allow_max_age: Option<String>,
902
903    /// 当 Object 所在的 Bucket 配置了 CORS 规则,且请求满足指定的 CORS 规则时会在响应中包含这些 Headers。
904    pub access_control_allow_headers: Option<String>,
905
906    /// 表示允许访问客户端 JavaScript 程序的 headers 列表。当 Object 所在的 Bucket 配置了 CORS 规则,且请求满足指定的CORS规则时会在响应中包含 ExposeHeader。
907    pub access_control_expose_headers: Option<String>,
908
909    /// 对象关联的标签个数。仅当用户有读取标签权限时返回。
910    pub tag_count: Option<u32>,
911
912    /// `x-oss-meta-` 开头的用户自定义属性
913    pub metadata: HashMap<String, String>,
914}
915
916impl From<HashMap<String, String>> for ObjectMetadata {
917    /// Consumes the headers map and return ObjectMetadata
918    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                // Not good...
930                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            // CAUTION!! must be the last field to handle because `drain` consumes all the entries left in the map
971            metadata: headers.drain().filter(|(k, _)| k.starts_with("x-oss-meta-")).collect(),
972        }
973    }
974}
975
976/// Options for copying objects
977///
978/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/copyobject>
979#[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    /// 指定 CopyObject 操作时是否覆盖同名目标 Object。
984    /// 当目标 Bucket 处于已开启或已暂停版本控制状态时,`x-oss-forbid-overwrite` 请求 Header 设置无效,即允许覆盖同名Object。
985    ///
986    /// - 未指定 `x-oss-forbid-overwrite` 或者指定 `x-oss-forbid-overwrite` 为 `false` 时,表示允许覆盖同名目标 Object。
987    /// - 指定 `x-oss-forbid-overwrite` 为 `true` 时,表示禁止覆盖同名 Object。
988    ///
989    /// 设置`x-oss-forbid-overwrite` 请求 Header 会导致 QPS 处理性能下降,
990    /// 如果您有大量的操作需要使用 `x-oss-forbid-overwrite` 请求 Header(QPS>1000),请联系技术支持,避免影响您的业务。
991    pub forbid_overwrite: Option<bool>,
992
993    /// 默认复制源 Object 的当前版本。如果需要复制指定的版本,请设置此参数
994    pub source_version_id: Option<String>,
995
996    /// 如果源 Object 的 ETag 值和您提供的 ETag 相等,则执行拷贝操作,并返回 200 OK。
997    pub copy_source_if_match: Option<String>,
998
999    /// 如果源 Object 的 ETag 值和您提供的 ETag 不相等,则执行拷贝操作,并返回 200 OK。
1000    pub copy_source_if_none_match: Option<String>,
1001
1002    /// 如果指定的时间等于或者晚于文件实际修改时间,则正常拷贝文件,并返回 200 OK。
1003    /// e.g. `Mon, 11 May 2020 08:16:23 GMT`
1004    pub copy_source_if_unmodified_since: Option<String>,
1005
1006    /// 如果指定的时间早于文件实际修改时间,则正常拷贝文件,并返回200 OK。
1007    /// e.g. `Mon, 11 May 2020 08:16:23 GMT`
1008    pub copy_source_if_modified_since: Option<String>,
1009
1010    /// 指定如何设置目标 Object 的元数据。
1011    pub metadata_directive: Option<MetadataDirective>,
1012
1013    /// Key 以 `x-oss-meta-` 开头
1014    pub metadata: HashMap<String, String>,
1015
1016    pub server_side_encryption: Option<ServerSideEncryptionAlgorithm>,
1017    pub server_side_encryption_key_id: Option<String>,
1018
1019    /// 指定 OSS 创建目标 Object 时的访问权限。
1020    pub object_acl: Option<ObjectAcl>,
1021
1022    /// 指定 OSS 创建目标 Object 时的存储类型
1023    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/// Options for deleting object
1163#[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
1170/// A "placeholder" struct for adding more fields in the future
1171pub struct DeleteObjectResult;
1172
1173/// A "placeholder" struct for adding more fields in the future
1174pub 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        // validate metadata key and taggings
1210        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/// Put object result enumeration
1383#[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    /// This is response headers from aliyun oss api when you put object with no callback specified
1388    #[cfg_attr(feature = "serde-camelcase", serde(rename = "apiResponse", rename_all = "camelCase"))]
1389    ApiResponse(PutObjectApiResponse),
1390
1391    /// This is your callback response content string when you put object with callback specified.
1392    /// `.0` should be a valid JSON string.
1393    #[cfg_attr(feature = "serde-camelcase", serde(rename = "callbackResponse"))]
1394    CallbackResponse(String),
1395}
1396
1397/// The response headers from aliyun oss put object api
1398#[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    /// 已经移除了首尾双引号(`"`)之后的字符串
1405    pub etag: String,
1406
1407    /// 文件 MD5 值,Base64 编码的字符串
1408    pub content_md5: String,
1409
1410    /// 文件 CRC64 值
1411    pub hash_crc64ecma: u64,
1412
1413    /// 表示文件的版本 ID。仅当您将文件上传至已开启版本控制状态的 Bucket 时,会返回该响应头。
1414    pub version_id: Option<String>,
1415}
1416
1417/// If you put object without callback, parse aliyun oss api headers into `PutObjectResult::WithoutCallback` enum variant
1418impl 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
1430/// Options for appending object
1431pub 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/// Payload while call delete multiple objects in one request
1457///
1458/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects>
1459#[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    /// Object keys to delete
1466    pub objects: Vec<DeleteMultipleObjectsItem>,
1467}
1468
1469impl DeleteMultipleObjectsRequest {
1470    /// Consumes data and generate xml content
1471    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    /// A simpler mode: only keys to delete are specified
1526    FromKeys(&'a [T]),
1527
1528    /// User custom full request
1529    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/// Result of deleting multiple objects ()
1543///
1544/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects>
1545#[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/// 发起回调请求的 `Content-Type`
1639#[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/// The callback while call
1650///
1651/// - `put_object_from_file`
1652/// - `put_object_from_buffer`
1653/// - `put_object_from_base64`
1654/// - `complete_multipart_upload`
1655///
1656/// to create an object, if create object successfully,
1657/// the OSS server will call your server according to this callback config with `POST` request method.
1658///
1659/// Official document: <https://help.aliyun.com/zh/oss/developer-reference/callback>
1660#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1661pub struct Callback {
1662    /// 创建 Object 成功之后,OSS 服务器会 `POST` 到这个 URL。
1663    /// 该 URL 调用后的要求有:
1664    ///
1665    /// - 响应 `HTTP/1.1 200 OK`
1666    /// - 响应头 `Content-Length` 必须是合法的值
1667    /// - 响应体为 JSON 格式
1668    /// - 响应体最大为 3MB
1669    ///
1670    /// 另外:
1671    ///
1672    /// - 支持同时配置最多 5 个 URL,多个 URL 间以分号(;)分隔。OSS 会依次发送请求,直到第一个回调请求成功返回。
1673    /// - 支持 HTTPS 协议地址。
1674    /// - **不支持**填写 IPV6 地址,也**不支持**填写指向 IPV6 地址的域名。
1675    /// - 为了保证正确处理中文等情况,此 URL 需做编码处理,
1676    ///   例如 `https://example.com/中文.php?key=value&中文名称=中文值`
1677    ///   需要编码为 `https://example.com/%E4%B8%AD%E6%96%87.php?key=value&%E4%B8%AD%E6%96%87%E5%90%8D%E7%A7%B0=%E4%B8%AD%E6%96%87%E5%80%BC`。
1678    #[serde(rename = "callbackUrl")]
1679    pub url: String,
1680
1681    /// 发起回调请求时 `Host` 头的值,格式为域名或 IP 地址。仅在设置了 `url` 时有效。
1682    /// 如果没有配置 `host`,则解析 `url` 中的 URL,并将解析的 `Host` 填充到 `Host` 请求头中。
1683    #[serde(rename = "callbackHost", skip_serializing_if = "Option::is_none")]
1684    pub host: Option<String>,
1685
1686    /// 发起回调时请求Body的值,例如 `key=${object}&etag=${etag}&my_var=${x:my_var}`。
1687    ///
1688    /// 支持:
1689    ///
1690    /// - OSS系统参数,要写成 `${var_name}` 格式
1691    ///   - `bucket`: The bucket name
1692    ///   - `object`: The object key
1693    ///   - `etag`: The ETag of the object
1694    ///   - `size`: 以字节为单位的 Object 大小。调用 `complete_multipart_upload` 时,`size` 为整个 Object 的大小
1695    ///   - `mimeType`: 资源类型,例如 jpeg 图片的资源类型为 `image/jpeg`
1696    ///   - `imageInfo.height`: 图片高度。该变量仅适用于图片格式,对于非图片格式,该变量的值为空
1697    ///   - `imageInfo.width`: 图片宽度。该变量仅适用于图片格式,对于非图片格式,该变量的值为空
1698    ///   - `imageInfo.format`: 图片格式,例如 JPG、PNG 等。该变量仅适用于图片格式,对于非图片格式,该变量的值为空
1699    ///   - `crc64`: 与上传文件后返回的 `x-oss-hash-crc64ecma` 头内容一致
1700    ///   - `contentMd5`: 与上传文件后返回的 `Content-MD5` 头内容一致。仅在 `put_object_from_xxx` 时候该变量的值不为空
1701    ///   - `vpcId`: 发起请求的客户端所在的 VpcId。如果不是通过 VPC 发起请求,则该变量的值为空
1702    ///   - `clientIp`: 发起请求的客户端 IP 地址
1703    ///   - `reqId`: 发起请求的 RequestId
1704    ///   - 发起请求的接口名称,例如 `PutObject`、 `PostObject` 等
1705    /// - 自定义参数,其使用格式为 `${x:var_name}`。参数值放到 `custom_variables` 中
1706    /// - 常量(字面量)
1707    #[serde(rename = "callbackBody")]
1708    pub body: String,
1709
1710    /// 客户端发起回调请求时,OSS是否向地址发送服务器名称指示 SNI(Server Name Indication)。
1711    /// 是否发送 SNI 取决于服务器的配置和需求。
1712    /// 对于使用同一个 IP 地址来托管多个 TLS/SSL 证书的服务器的情况,建议选择发送 SNI
1713    #[serde(rename = "callbackSNI", skip_serializing_if = "Option::is_none")]
1714    pub sni: Option<bool>,
1715
1716    /// 发起回调请求时候的 `Content-Type`。默认是:`application/x-www-form-urlencoded`
1717    #[serde(rename = "callbackBodyType", skip_serializing_if = "Option::is_none")]
1718    pub body_type: Option<CallbackBodyType>,
1719
1720    /// 在回调请求体 (`body`) 中携带的数据,如果有自定义参数,请使用此属性容纳自定义参数的值。
1721    /// Key 为自定义变量名,但是不包含 `x:` 前缀。在生成 `body` 的时候会自动增加
1722    /// 注意:这里我使用了 Map 来接受自定义参数,也就是说,这里**不支持**多个同名的自定义参数来表示集合数据类型
1723    #[serde(skip_serializing)]
1724    pub custom_variables: HashMap<String, String>,
1725}
1726
1727/// 回调请求数据枚举值
1728///
1729/// `Oss` 开头的,其中 `.0` 是此参数值对应的参数名。
1730/// 因为大部分时候,传入的参数名是固定的,自定参数的 body 参数名和自定义参数的参数值、常量参数值,一般都是静态字符串,
1731/// 所以这里用了 `&'a str` 的形态。如果 `&'a str` 不满足你的需求,请使用 `CallbackBodyParameter::Literal(String, String)`
1732///
1733/// # Example
1734///
1735/// ```rust
1736/// use ali_oss_rs::object_common::CallbackBodyParameter;
1737///
1738///
1739/// assert_eq!("foo=${bucket}", CallbackBodyParameter::OssBucket("foo").to_body_string());
1740/// assert_eq!("foo=${x:bar}", CallbackBodyParameter::Custom(
1741///     "foo",
1742///     "bar",
1743///     "Are you OK?".to_string()
1744/// ).to_body_string());
1745/// assert_eq!("foo=bar", CallbackBodyParameter::Constant("foo", "bar").to_body_string());
1746/// assert_eq!(
1747///     "foo=${x:bar}",
1748///     CallbackBodyParameter::Literal(
1749///         "foo".to_string(),
1750///         "${x:bar}".to_string()
1751///     ).to_body_string()
1752/// );
1753///
1754/// ```
1755pub 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    /// - `.0` 是在回调中的参数名,
1772    /// - `.1` 是在回调中参数值对应的自定义变量的名字,也就是 `${x:custom_var_name}` 中的 `custom_var_name`。
1773    ///   例如:`CallbackBodyParameter::Custom("foo", "bar", "Are you OK?".to_string())`,
1774    ///   在请求体中就是 `foo=${x:bar}`
1775    /// - `.2` 是在自定义参数表中的自定参数对应的值。例如,上面的例子中,会在 `custom_variables` 中加入一个 `("x:bar", "Are you OK?")` 的条目
1776    ///
1777    /// 这么设计是为了防止在 body 中增加了自定义参数,但是忘记了传递自定义参数的值
1778    Custom(&'a str, &'a str, String),
1779
1780    /// `.0` 是在回调中的参数名,`.1` 是在回调中的参数值,不做解析,在回调的时候原封不动插入给定的字符。
1781    ///
1782    /// 例如:`CallbackBodyParameter::Constant("hello", "world")`,
1783    /// 在请求体中就是 `hello=world`
1784    Constant(&'a str, &'a str),
1785
1786    /// 这是一个兜底的,如果上面 `&'str` 不满足需求,就使用这个。
1787    /// 因为它可以获取所有权。在生成回调请求体的时候,会按照字符串直接拼接。
1788    ///
1789    /// 例如:`CallbackBodyParameter::Literal("foo".to_string(), "${x:bar}")`,
1790    /// 在请求体中就是 `foo=${x:bar}`
1791    Literal(String, String),
1792}
1793
1794impl CallbackBodyParameter<'_> {
1795    /// 转换成 callback body 中的格式
1796    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
1819/// Callback builder, hope it's helpful when you building your callback
1820pub 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) = &param {
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    /// 一般情况下,不需要直接调用这个函数。 `body_parameter` 已经处理了自定义参数的情况了。
1869    /// 除非你使用 `CallbackBodyParameter::Literal`,并且涉及到自定义参数的,就需要使用这个函数把自定义参数值加入进来。
1870    /// `k` 无需携带 `x:` 前缀,在插入的时候自动追加前缀
1871    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/// Job parameters tier for restoring object
1893///
1894/// 冷归档、深度冷归档类型 Object 解冻优先级。取值范围如下:
1895///
1896/// - 冷归档类型 Object
1897///   - 高优先级(Expedited):表示 1 小时内完成解冻。
1898///   - 标准(Standard,默认值):表示 2~5 小时内完成解冻。
1899///   - 批量(Bulk):表示 5~12 小时内完成解冻。
1900/// - 深度冷归档类型 Object
1901///   - 高优先级(Expedited):表示 12 小时内完成解冻。
1902///   - 标准(Standard,默认值):表示48小时内完成解冻。
1903#[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/// Restore object request
1963#[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    /// 设置归档、冷归档以及深度冷归档类型 Object 的解冻天数。
1968    ///
1969    /// - 归档类型 Object 解冻天数的取值范围为 1~7,单位为天
1970    /// - 冷归档以及深度冷归档类型 Object 解冻天数的取值范围为 1~365,单位为天。
1971    pub days: u16,
1972
1973    /// 对于开启版本控制的 Bucket,Object 的各个版本可以对应不同的存储类型。
1974    /// 默认解冻 Object 当前版本,您可以通过指定 `versionId` 的方式来解冻 Object 指定版本。
1975    pub version_id: Option<String>,
1976
1977    /// 冷归档、深度冷归档类型Object解冻优先级. 参见 [`RestoreJobTier`]
1978    pub tier: Option<RestoreJobTier>,
1979}
1980
1981impl RestoreObjectRequest {
1982    /// Consume value and build XML content for requesting
1983    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/// Restore object result
2009#[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}