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