rusty_oss/actions/multipart_upload/
create.rs1use 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#[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 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}