1use crate::client::{BucketOperations, OSSClientInner};
4use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
5use crate::http::client::HttpRequest;
6use crate::types::bucket::BucketName;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(rename = "RedundancyTransitionConfiguration")]
12struct RedundancyTransitionConfig {
13 #[serde(rename = "TaskId")]
14 task_id: String,
15 #[serde(rename = "Status", skip_serializing_if = "Option::is_none")]
16 status: Option<String>,
17}
18
19#[derive(Debug, Clone, Deserialize)]
20#[serde(rename = "RedundancyTransitionConfiguration")]
21struct RedundancyTransitionConfigResp {
22 #[serde(rename = "TaskId")]
23 task_id: String,
24 #[serde(rename = "Status", default)]
25 status: String,
26}
27
28pub struct PutBucketRedundancyBuilder {
29 client: Arc<OSSClientInner>,
30 bucket: BucketName,
31 task_id: String,
32}
33impl PutBucketRedundancyBuilder {
34 pub(crate) fn new(
35 client: Arc<OSSClientInner>,
36 bucket: BucketName,
37 task_id: impl Into<String>,
38 ) -> Self {
39 Self {
40 client,
41 bucket,
42 task_id: task_id.into(),
43 }
44 }
45 pub async fn send(self) -> Result<PutBucketRedundancyOutput> {
46 let ep = self.client.endpoint.clone();
47 let uri = format!(
48 "https://{}.{}?redundancyTransition",
49 self.bucket.as_str(),
50 ep
51 );
52 let qp = vec![("redundancyTransition".into(), String::new())];
53 let cfg = RedundancyTransitionConfig {
54 task_id: self.task_id,
55 status: None,
56 };
57 let xml = crate::util::xml::to_xml(&cfg)?;
58 let req = HttpRequest::builder()
59 .method(http::Method::PUT)
60 .uri(&uri)
61 .body(bytes::Bytes::from(xml))
62 .build();
63 let r = self
64 .client
65 .send_signed(req, Some(&self.bucket), qp)
66 .await
67 .map_err(|e| OssError {
68 kind: OssErrorKind::TransportError,
69 context: Box::new(ErrorContext {
70 operation: Some("PutBucketRedundancy".into()),
71 bucket: Some(self.bucket.to_string()),
72 endpoint: Some(ep),
73 ..Default::default()
74 }),
75 source: Some(Box::new(e)),
76 })?;
77 if r.status().is_success() {
78 Ok(PutBucketRedundancyOutput {
79 request_id: r
80 .headers
81 .get("x-oss-request-id")
82 .and_then(|v| v.to_str().ok())
83 .unwrap_or("")
84 .to_string(),
85 })
86 } else {
87 Err(OssError {
88 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
89 status_code: r.status().as_u16(),
90 code: String::new(),
91 message: String::new(),
92 request_id: String::new(),
93 host_id: String::new(),
94 resource: Some(self.bucket.to_string()),
95 string_to_sign: None,
96 })),
97 context: Box::new(ErrorContext {
98 operation: Some("PutBucketRedundancy".into()),
99 bucket: Some(self.bucket.to_string()),
100 ..Default::default()
101 }),
102 source: None,
103 })
104 }
105 }
106}
107#[derive(Debug, Clone)]
108pub struct PutBucketRedundancyOutput {
109 pub request_id: String,
110}
111
112pub struct GetBucketRedundancyBuilder {
113 client: Arc<OSSClientInner>,
114 bucket: BucketName,
115}
116impl GetBucketRedundancyBuilder {
117 pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
118 Self { client, bucket }
119 }
120 pub async fn send(self) -> Result<GetBucketRedundancyOutput> {
121 let ep = self.client.endpoint.clone();
122 let uri = format!(
123 "https://{}.{}?redundancyTransition",
124 self.bucket.as_str(),
125 ep
126 );
127 let qp = vec![("redundancyTransition".into(), String::new())];
128 let req = HttpRequest::builder()
129 .method(http::Method::GET)
130 .uri(&uri)
131 .build();
132 let r = self
133 .client
134 .send_signed(req, Some(&self.bucket), qp)
135 .await
136 .map_err(|e| OssError {
137 kind: OssErrorKind::TransportError,
138 context: Box::new(ErrorContext {
139 operation: Some("GetBucketRedundancy".into()),
140 bucket: Some(self.bucket.to_string()),
141 endpoint: Some(ep),
142 ..Default::default()
143 }),
144 source: Some(Box::new(e)),
145 })?;
146 if r.is_success() {
147 let c: RedundancyTransitionConfigResp =
148 crate::util::xml::from_xml(r.body_as_str().unwrap_or("")).map_err(|e| {
149 OssError {
150 kind: OssErrorKind::DeserializationError,
151 context: Box::new(ErrorContext {
152 operation: Some("parse XML".into()),
153 bucket: Some(self.bucket.to_string()),
154 ..Default::default()
155 }),
156 source: Some(Box::new(e)),
157 }
158 })?;
159 Ok(GetBucketRedundancyOutput {
160 task_id: c.task_id,
161 status: c.status,
162 })
163 } else {
164 Err(OssError {
165 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
166 status_code: r.status().as_u16(),
167 code: String::new(),
168 message: String::new(),
169 request_id: String::new(),
170 host_id: String::new(),
171 resource: Some(self.bucket.to_string()),
172 string_to_sign: None,
173 })),
174 context: Box::new(ErrorContext {
175 operation: Some("GetBucketRedundancy".into()),
176 bucket: Some(self.bucket.to_string()),
177 ..Default::default()
178 }),
179 source: None,
180 })
181 }
182 }
183}
184#[derive(Debug, Clone)]
185pub struct GetBucketRedundancyOutput {
186 pub task_id: String,
187 pub status: String,
188}
189
190pub struct DeleteBucketRedundancyBuilder {
191 client: Arc<OSSClientInner>,
192 bucket: BucketName,
193 task_id: String,
194}
195impl DeleteBucketRedundancyBuilder {
196 pub(crate) fn new(
197 client: Arc<OSSClientInner>,
198 bucket: BucketName,
199 task_id: impl Into<String>,
200 ) -> Self {
201 Self {
202 client,
203 bucket,
204 task_id: task_id.into(),
205 }
206 }
207 pub async fn send(self) -> Result<DeleteBucketRedundancyOutput> {
208 let ep = self.client.endpoint.clone();
209 let uri = format!(
210 "https://{}.{}?redundancyTransition&taskId={}",
211 self.bucket.as_str(),
212 ep,
213 self.task_id
214 );
215 let qp = vec![
216 ("redundancyTransition".into(), String::new()),
217 ("taskId".into(), self.task_id),
218 ];
219 let req = HttpRequest::builder()
220 .method(http::Method::DELETE)
221 .uri(&uri)
222 .build();
223 let r = self
224 .client
225 .send_signed(req, Some(&self.bucket), qp)
226 .await
227 .map_err(|e| OssError {
228 kind: OssErrorKind::TransportError,
229 context: Box::new(ErrorContext {
230 operation: Some("DeleteBucketRedundancy".into()),
231 bucket: Some(self.bucket.to_string()),
232 endpoint: Some(ep),
233 ..Default::default()
234 }),
235 source: Some(Box::new(e)),
236 })?;
237 if r.status().is_success() {
238 Ok(DeleteBucketRedundancyOutput {
239 request_id: r
240 .headers
241 .get("x-oss-request-id")
242 .and_then(|v| v.to_str().ok())
243 .unwrap_or("")
244 .to_string(),
245 })
246 } else {
247 Err(OssError {
248 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
249 status_code: r.status().as_u16(),
250 code: String::new(),
251 message: String::new(),
252 request_id: String::new(),
253 host_id: String::new(),
254 resource: Some(self.bucket.to_string()),
255 string_to_sign: None,
256 })),
257 context: Box::new(ErrorContext {
258 operation: Some("DeleteBucketRedundancy".into()),
259 bucket: Some(self.bucket.to_string()),
260 ..Default::default()
261 }),
262 source: None,
263 })
264 }
265 }
266}
267#[derive(Debug, Clone)]
268pub struct DeleteBucketRedundancyOutput {
269 pub request_id: String,
270}
271
272impl BucketOperations {
273 pub fn put_redundancy_transition(
274 &self,
275 task_id: impl Into<String>,
276 ) -> PutBucketRedundancyBuilder {
277 PutBucketRedundancyBuilder::new(
278 self.client_inner().clone(),
279 self.bucket_name().clone(),
280 task_id,
281 )
282 }
283 pub fn get_redundancy_transition(&self) -> GetBucketRedundancyBuilder {
284 GetBucketRedundancyBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
285 }
286 pub fn delete_redundancy_transition(
287 &self,
288 task_id: impl Into<String>,
289 ) -> DeleteBucketRedundancyBuilder {
290 DeleteBucketRedundancyBuilder::new(
291 self.client_inner().clone(),
292 self.bucket_name().clone(),
293 task_id,
294 )
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::client::OSSClientInner;
302 use crate::config::credentials::Credentials;
303 use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
304 use crate::types::region::Region;
305 use std::sync::Mutex;
306
307 struct Rc {
308 r: Arc<Mutex<Vec<HttpRequest>>>,
309 }
310 #[async_trait::async_trait]
311 impl HttpClient for Rc {
312 async fn send(&self, req: HttpRequest) -> crate::error::Result<HttpResponse> {
313 self.r.lock().unwrap().push(req);
314 let mut h = http::HeaderMap::new();
315 h.insert("x-oss-request-id", http::HeaderValue::from_static("rid"));
316 Ok(HttpResponse {
317 status: http::StatusCode::OK,
318 headers: h,
319 body: bytes::Bytes::new(),
320 })
321 }
322 }
323 fn ci() -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
324 let rq = Arc::new(Mutex::new(Vec::new()));
325 let h = Arc::new(Rc { r: rq.clone() });
326 let cr = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
327 Credentials::builder()
328 .access_key_id("ak")
329 .access_key_secret("sk")
330 .build()
331 .unwrap(),
332 ));
333 (
334 Arc::new(OSSClientInner {
335 http: h,
336 credentials: cr,
337 signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
338 region: Region::CnHangzhou,
339 endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
340 }),
341 rq,
342 )
343 }
344
345 #[test]
346 fn redundancy_xml() {
347 let c = RedundancyTransitionConfig {
348 task_id: "task-xxx".into(),
349 status: None,
350 };
351 let x = crate::util::xml::to_xml(&c).unwrap();
352 assert!(x.contains("<TaskId>task-xxx</TaskId>"));
353 }
354 #[tokio::test]
355 async fn delete_sends_request() {
356 let (i, r) = ci();
357 DeleteBucketRedundancyBuilder::new(i, BucketName::new("test-bucket").unwrap(), "task-1")
358 .send()
359 .await
360 .unwrap();
361 assert_eq!(r.lock().unwrap()[0].method, http::Method::DELETE);
362 }
363}