rusty_oss/actions/multipart_upload/
upload.rs1use std::time::Duration;
2
3use time::OffsetDateTime;
4use url::Url;
5
6use crate::actions::Method;
7use crate::actions::OSSAction;
8use crate::signing::sign;
9use crate::sorting_iter::SortingIterator;
10use crate::{Bucket, Credentials, Map};
11
12#[derive(Debug, Clone)]
30pub struct UploadPart<'a> {
31 bucket: &'a Bucket,
32 credentials: Option<&'a Credentials>,
33 object: &'a str,
34
35 part_number: u16,
36 upload_id: &'a str,
37
38 query: Map<'a>,
39 headers: Map<'a>,
40}
41
42impl<'a> UploadPart<'a> {
43 #[inline]
44 pub fn new(
45 bucket: &'a Bucket,
46 credentials: Option<&'a Credentials>,
47 object: &'a str,
48 part_number: u16,
49 upload_id: &'a str,
50 ) -> Self {
51 Self {
52 bucket,
53 credentials,
54 object,
55
56 part_number,
57 upload_id,
58
59 query: Map::new(),
60 headers: Map::new(),
61 }
62 }
63}
64
65impl<'a> OSSAction<'a> for UploadPart<'a> {
66 const METHOD: Method = Method::Put;
67
68 fn query_mut(&mut self) -> &mut Map<'a> {
69 &mut self.query
70 }
71
72 fn headers_mut(&mut self) -> &mut Map<'a> {
73 &mut self.headers
74 }
75
76 fn sign_with_time(&self, expires_in: Duration, time: &OffsetDateTime) -> Url {
77 let url = self.bucket.object_url(self.object).unwrap();
78
79 let part_number = self.part_number.to_string();
80 let query = [
81 ("partNumber", part_number.as_str()),
82 ("uploadId", self.upload_id),
83 ];
84
85 match self.credentials {
86 Some(credentials) => sign(
87 time,
88 Method::Put,
89 url,
90 credentials.key(),
91 credentials.secret(),
92 credentials.token(),
93 self.bucket.region(),
94 expires_in.as_secs(),
95 SortingIterator::new(query.iter().copied(), self.query.iter()),
96 self.headers.iter(),
97 ),
98 None => crate::signing::util::add_query_params(url, query.iter().copied()),
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use time::OffsetDateTime;
106
107 use pretty_assertions::assert_eq;
108
109 use super::*;
110 use crate::{Bucket, Credentials, UrlStyle};
111
112 #[test]
113 fn oss_example() {
114 let date = OffsetDateTime::from_unix_timestamp(1369353600).unwrap();
116 let expires_in = Duration::from_secs(86400);
117
118 let endpoint = "https://oss-cn-hangzhou.aliyuncs.com".parse().unwrap();
119 let bucket = Bucket::new(
120 endpoint,
121 UrlStyle::VirtualHost,
122 "examplebucket",
123 "cn-hangzhou",
124 )
125 .unwrap();
126 let credentials = Credentials::new(
127 "access_key_id",
128 "access_key_secret",
129 );
130
131 let action = UploadPart::new(&bucket, Some(&credentials), "test.txt", 1, "abcd");
132
133 let url = action.sign_with_time(expires_in, &date);
134 let expected = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/test.txt?partNumber=1&uploadId=abcd&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=7242888285b04f4e637c0a46a058bc90e0dc71929e0fa6efe9fd44f02ece082d";
135
136 assert_eq!(expected, url.as_str());
137 }
138
139 #[test]
140 fn anonymous_custom_query() {
141 let expires_in = Duration::from_secs(86400);
142
143 let endpoint = "https://oss-cn-hangzhou.aliyuncs.com".parse().unwrap();
144 let bucket = Bucket::new(
145 endpoint,
146 UrlStyle::VirtualHost,
147 "examplebucket",
148 "cn-hangzhou",
149 )
150 .unwrap();
151
152 let action = UploadPart::new(&bucket, None, "test.txt", 1, "abcd");
153 let url = action.sign(expires_in);
154 let expected = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/test.txt?partNumber=1&uploadId=abcd";
155
156 assert_eq!(expected, url.as_str());
157 }
158}