1use std::sync::Arc;
4
5use serde::Serialize;
6
7use crate::client::{BucketOperations, OSSClientInner};
8use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
9use crate::http::client::HttpRequest;
10use crate::types::bucket::BucketName;
11use crate::types::object::ObjectKey;
12use crate::util::uri::oss_endpoint_url;
13
14#[derive(Debug, Clone, Serialize)]
15#[serde(rename = "RestoreRequest")]
16struct RestoreRequest {
17 #[serde(rename = "Days")]
18 days: i32,
19 #[serde(rename = "Tier", skip_serializing_if = "Option::is_none")]
20 tier: Option<String>,
21}
22
23pub struct RestoreObjectBuilder {
24 client: Arc<OSSClientInner>,
25 bucket: BucketName,
26 key: ObjectKey,
27 days: i32,
28 tier: Option<String>,
29}
30
31impl RestoreObjectBuilder {
32 pub(crate) fn new(
33 client: Arc<OSSClientInner>,
34 bucket: BucketName,
35 key: ObjectKey,
36 days: i32,
37 ) -> Self {
38 Self {
39 client,
40 bucket,
41 key,
42 days,
43 tier: None,
44 }
45 }
46
47 pub fn tier(mut self, t: impl Into<String>) -> Self {
48 self.tier = Some(t.into());
49 self
50 }
51
52 pub async fn send(self) -> Result<RestoreObjectOutput> {
53 let endpoint = self.client.endpoint.clone();
54 let uri = oss_endpoint_url(
55 &endpoint,
56 Some(self.bucket.as_str()),
57 Some(self.key.as_str()),
58 );
59 let full_uri = format!("{}?restore", uri);
60 let query_params: Vec<(String, String)> = vec![("restore".into(), String::new())];
61
62 let config = RestoreRequest {
63 days: self.days,
64 tier: self.tier,
65 };
66 let body_xml = crate::util::xml::to_xml(&config)?;
67
68 let request = HttpRequest::builder()
69 .method(http::Method::POST)
70 .uri(&full_uri)
71 .body(bytes::Bytes::from(body_xml))
72 .build();
73
74 let response = self
75 .client
76 .send_signed(request, Some(&self.bucket), query_params)
77 .await
78 .map_err(|e| OssError {
79 kind: OssErrorKind::TransportError,
80 context: Box::new(ErrorContext {
81 operation: Some("RestoreObject".into()),
82 bucket: Some(self.bucket.to_string()),
83 object_key: Some(self.key.to_string()),
84 endpoint: Some(endpoint),
85 ..Default::default()
86 }),
87 source: Some(Box::new(e)),
88 })?;
89
90 if response.status().is_success() {
91 Ok(RestoreObjectOutput {
92 request_id: response
93 .headers
94 .get("x-oss-request-id")
95 .and_then(|v| v.to_str().ok())
96 .unwrap_or("")
97 .to_string(),
98 })
99 } else {
100 Err(OssError {
101 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
102 status_code: response.status().as_u16(),
103 code: String::new(),
104 message: String::new(),
105 request_id: String::new(),
106 host_id: String::new(),
107 resource: Some(self.key.to_string()),
108 string_to_sign: None,
109 })),
110 context: Box::new(ErrorContext {
111 operation: Some("RestoreObject".into()),
112 bucket: Some(self.bucket.to_string()),
113 object_key: Some(self.key.to_string()),
114 ..Default::default()
115 }),
116 source: None,
117 })
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
123pub struct RestoreObjectOutput {
124 pub request_id: String,
125}
126
127impl BucketOperations {
128 pub fn restore_object(
129 &self,
130 key: impl Into<String>,
131 days: i32,
132 ) -> Result<RestoreObjectBuilder> {
133 Ok(RestoreObjectBuilder::new(
134 self.client_inner().clone(),
135 self.bucket_name().clone(),
136 ObjectKey::new(key.into())?,
137 days,
138 ))
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::client::OSSClientInner;
146 use crate::config::credentials::Credentials;
147 use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
148 use crate::types::region::Region;
149 use std::sync::Mutex;
150
151 struct Rec {
152 requests: Arc<Mutex<Vec<HttpRequest>>>,
153 }
154 #[async_trait::async_trait]
155 impl HttpClient for Rec {
156 async fn send(&self, request: HttpRequest) -> crate::error::Result<HttpResponse> {
157 self.requests.lock().unwrap().push(request);
158 let mut h = http::HeaderMap::new();
159 h.insert("x-oss-request-id", http::HeaderValue::from_static("rid"));
160 Ok(HttpResponse {
161 status: http::StatusCode::OK,
162 headers: h,
163 body: bytes::Bytes::new(),
164 })
165 }
166 }
167 fn c() -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
168 let r = Arc::new(Mutex::new(Vec::new()));
169 let h = Arc::new(Rec {
170 requests: r.clone(),
171 });
172 let cr = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
173 Credentials::builder()
174 .access_key_id("ak")
175 .access_key_secret("sk")
176 .build()
177 .unwrap(),
178 ));
179 (
180 Arc::new(OSSClientInner {
181 http: h,
182 credentials: cr,
183 signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
184 region: Region::CnHangzhou,
185 endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
186 }),
187 r,
188 )
189 }
190
191 #[test]
192 fn restore_xml_generation() {
193 let r = RestoreRequest {
194 days: 3,
195 tier: Some("Standard".into()),
196 };
197 let x = crate::util::xml::to_xml(&r).unwrap();
198 assert!(x.contains("<Days>3</Days>"));
199 assert!(x.contains("<Tier>Standard</Tier>"));
200 }
201
202 #[tokio::test]
203 async fn restore_sends_post() {
204 let (i, rq) = c();
205 RestoreObjectBuilder::new(
206 i,
207 BucketName::new("test-bucket").unwrap(),
208 ObjectKey::new("k").unwrap(),
209 3,
210 )
211 .send()
212 .await
213 .unwrap();
214 assert_eq!(rq.lock().unwrap()[0].method, http::Method::POST);
215 }
216}