Skip to main content

aliyun_oss/operations/
bucket_policy.rs

1//! Bucket policy (RAM) operations.
2
3use 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}