rusty_oss/actions/multipart_upload/
create.rs

1use std::iter;
2use std::time::Duration;
3
4use serde::Deserialize;
5use time::OffsetDateTime;
6use url::Url;
7
8use crate::actions::Method;
9use crate::actions::OSSAction;
10use crate::signing::sign;
11use crate::sorting_iter::SortingIterator;
12use crate::{Bucket, Credentials, Map};
13
14/// Create a multipart upload.
15///
16/// A few advantages of multipart uploads are:
17///
18/// * being able to be resume without having to start back from the beginning
19/// * parallelize the uploads across multiple threads
20///
21/// Find out more about `CreateMultipartUpload` from the [OSS API Reference][api]
22///
23/// [api]: https://help.aliyun.com/zh/oss/developer-reference/initiatemultipartupload
24#[derive(Debug, Clone)]
25pub struct CreateMultipartUpload<'a> {
26    bucket: &'a Bucket,
27    credentials: Option<&'a Credentials>,
28    object: &'a str,
29
30    query: Map<'a>,
31    headers: Map<'a>,
32}
33
34#[derive(Debug, Clone)]
35pub struct CreateMultipartUploadResponse(InnerCreateMultipartUploadResponse);
36
37#[derive(Debug, Clone, Deserialize)]
38struct InnerCreateMultipartUploadResponse {
39    #[serde(rename = "UploadId")]
40    upload_id: String,
41}
42
43impl<'a> CreateMultipartUpload<'a> {
44    #[inline]
45    pub fn new(bucket: &'a Bucket, credentials: Option<&'a Credentials>, object: &'a str) -> Self {
46        Self {
47            bucket,
48            credentials,
49            object,
50
51            query: Map::new(),
52            headers: Map::new(),
53        }
54    }
55
56    pub fn parse_response(s: &str) -> Result<CreateMultipartUploadResponse, quick_xml::DeError> {
57        let parsed = quick_xml::de::from_str(s)?;
58        Ok(CreateMultipartUploadResponse(parsed))
59    }
60}
61
62impl CreateMultipartUploadResponse {
63    pub fn upload_id(&self) -> &str {
64        &self.0.upload_id
65    }
66}
67
68impl<'a> OSSAction<'a> for CreateMultipartUpload<'a> {
69    const METHOD: Method = Method::Post;
70
71    fn query_mut(&mut self) -> &mut Map<'a> {
72        &mut self.query
73    }
74
75    fn headers_mut(&mut self) -> &mut Map<'a> {
76        &mut self.headers
77    }
78
79    fn sign_with_time(&self, expires_in: Duration, time: &OffsetDateTime) -> Url {
80        let url = self.bucket.object_url(self.object).unwrap();
81        let query = iter::once(("uploads", "1"));
82
83        match self.credentials {
84            Some(credentials) => sign(
85                time,
86                Method::Post,
87                url,
88                credentials.key(),
89                credentials.secret(),
90                credentials.token(),
91                self.bucket.region(),
92                expires_in.as_secs(),
93                SortingIterator::new(query, self.query.iter()),
94                self.headers.iter(),
95            ),
96            None => crate::signing::util::add_query_params(url, query),
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use time::OffsetDateTime;
104
105    use pretty_assertions::assert_eq;
106
107    use super::*;
108    use crate::{Bucket, Credentials, UrlStyle};
109
110    #[test]
111    fn oss_example() {
112        // Fri, 24 May 2013 00:00:00 GMT
113        let date = OffsetDateTime::from_unix_timestamp(1369353600).unwrap();
114        let expires_in = Duration::from_secs(86400);
115
116        let endpoint = "https://oss-cn-hangzhou.aliyuncs.com".parse().unwrap();
117        let bucket = Bucket::new(
118            endpoint,
119            UrlStyle::VirtualHost,
120            "examplebucket",
121            "cn-hangzhou",
122        )
123        .unwrap();
124        let credentials = Credentials::new(
125            "access_key_id",
126            "access_key_secret",
127        );
128
129        let action = CreateMultipartUpload::new(&bucket, Some(&credentials), "test.txt");
130
131        let url = action.sign_with_time(expires_in, &date);
132        let expected = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/test.txt?uploads=1&x-oss-additional-headers=host&x-oss-credential=access_key_id%2F20130524%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-date=20130524T000000Z&x-oss-expires=86400&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-signature=9c59d37715619a96d2d8145c20c2b9223daf3cda923196862e14711b633a94ac";
133
134        assert_eq!(expected, url.as_str());
135    }
136
137    #[test]
138    fn anonymous_custom_query() {
139        let expires_in = Duration::from_secs(86400);
140
141        let endpoint = "https://oss-cn-hangzhou.aliyuncs.com".parse().unwrap();
142        let bucket = Bucket::new(
143            endpoint,
144            UrlStyle::VirtualHost,
145            "examplebucket",
146            "cn-hangzhou",
147        )
148        .unwrap();
149
150        let action = CreateMultipartUpload::new(&bucket, None, "test.txt");
151        let url = action.sign(expires_in);
152        let expected = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/test.txt?uploads=1";
153
154        assert_eq!(expected, url.as_str());
155    }
156}