Skip to main content

aliyun_oss/operations/
bucket_request_payment.rs

1//! Requester-pays configuration operations.
2
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7use crate::client::{BucketOperations, OSSClientInner};
8use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
9use crate::http::client::HttpRequest;
10use crate::types::bucket::BucketName;
11
12#[derive(Debug, Clone, Serialize)]
13#[serde(rename = "RequestPaymentConfiguration")]
14struct RequestPaymentConfiguration {
15    #[serde(rename = "Payer")]
16    payer: String,
17}
18
19#[derive(Debug, Clone, Deserialize)]
20#[serde(rename = "RequestPaymentConfiguration")]
21struct RequestPaymentConfigurationResponse {
22    #[serde(rename = "Payer")]
23    payer: String,
24}
25
26pub struct PutBucketRequestPaymentBuilder {
27    client: Arc<OSSClientInner>,
28    bucket: BucketName,
29    payer: String,
30}
31
32impl PutBucketRequestPaymentBuilder {
33    pub(crate) fn new(
34        client: Arc<OSSClientInner>,
35        bucket: BucketName,
36        payer: impl Into<String>,
37    ) -> Self {
38        Self {
39            client,
40            bucket,
41            payer: payer.into(),
42        }
43    }
44
45    pub async fn send(self) -> Result<PutBucketRequestPaymentOutput> {
46        let endpoint = self.client.endpoint.clone();
47        let uri = format!(
48            "https://{}.{}?requestPayment",
49            self.bucket.as_str(),
50            endpoint
51        );
52        let query_params: Vec<(String, String)> = vec![("requestPayment".into(), String::new())];
53
54        let config = RequestPaymentConfiguration { payer: self.payer };
55        let body_xml = crate::util::xml::to_xml(&config)?;
56
57        let request = HttpRequest::builder()
58            .method(http::Method::PUT)
59            .uri(&uri)
60            .body(bytes::Bytes::from(body_xml))
61            .build();
62
63        let response = self
64            .client
65            .send_signed(request, Some(&self.bucket), query_params)
66            .await
67            .map_err(|e| OssError {
68                kind: OssErrorKind::TransportError,
69                context: Box::new(ErrorContext {
70                    operation: Some("PutBucketRequestPayment".into()),
71                    bucket: Some(self.bucket.to_string()),
72                    endpoint: Some(endpoint),
73                    ..Default::default()
74                }),
75                source: Some(Box::new(e)),
76            })?;
77
78        if response.status().is_success() {
79            Ok(PutBucketRequestPaymentOutput {
80                request_id: response
81                    .headers
82                    .get("x-oss-request-id")
83                    .and_then(|v| v.to_str().ok())
84                    .unwrap_or("")
85                    .to_string(),
86            })
87        } else {
88            Err(OssError {
89                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
90                    status_code: response.status().as_u16(),
91                    code: String::new(),
92                    message: String::new(),
93                    request_id: String::new(),
94                    host_id: String::new(),
95                    resource: Some(self.bucket.to_string()),
96                    string_to_sign: None,
97                })),
98                context: Box::new(ErrorContext {
99                    operation: Some("PutBucketRequestPayment".into()),
100                    bucket: Some(self.bucket.to_string()),
101                    ..Default::default()
102                }),
103                source: None,
104            })
105        }
106    }
107}
108
109#[derive(Debug, Clone)]
110pub struct PutBucketRequestPaymentOutput {
111    pub request_id: String,
112}
113
114pub struct GetBucketRequestPaymentBuilder {
115    client: Arc<OSSClientInner>,
116    bucket: BucketName,
117}
118
119impl GetBucketRequestPaymentBuilder {
120    pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
121        Self { client, bucket }
122    }
123
124    pub async fn send(self) -> Result<GetBucketRequestPaymentOutput> {
125        let endpoint = self.client.endpoint.clone();
126        let uri = format!(
127            "https://{}.{}?requestPayment",
128            self.bucket.as_str(),
129            endpoint
130        );
131        let query_params: Vec<(String, String)> = vec![("requestPayment".into(), String::new())];
132
133        let request = HttpRequest::builder()
134            .method(http::Method::GET)
135            .uri(&uri)
136            .build();
137
138        let response = self
139            .client
140            .send_signed(request, Some(&self.bucket), query_params)
141            .await
142            .map_err(|e| OssError {
143                kind: OssErrorKind::TransportError,
144                context: Box::new(ErrorContext {
145                    operation: Some("GetBucketRequestPayment".into()),
146                    bucket: Some(self.bucket.to_string()),
147                    endpoint: Some(endpoint),
148                    ..Default::default()
149                }),
150                source: Some(Box::new(e)),
151            })?;
152
153        if response.is_success() {
154            let body_str = response.body_as_str().unwrap_or("");
155            let config: RequestPaymentConfigurationResponse = crate::util::xml::from_xml(body_str)
156                .map_err(|e| OssError {
157                    kind: OssErrorKind::DeserializationError,
158                    context: Box::new(ErrorContext {
159                        operation: Some("GetBucketRequestPayment: parse XML".into()),
160                        bucket: Some(self.bucket.to_string()),
161                        ..Default::default()
162                    }),
163                    source: Some(Box::new(e)),
164                })?;
165
166            Ok(GetBucketRequestPaymentOutput {
167                payer: config.payer,
168            })
169        } else {
170            Err(OssError {
171                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
172                    status_code: response.status().as_u16(),
173                    code: String::new(),
174                    message: String::new(),
175                    request_id: String::new(),
176                    host_id: String::new(),
177                    resource: Some(self.bucket.to_string()),
178                    string_to_sign: None,
179                })),
180                context: Box::new(ErrorContext {
181                    operation: Some("GetBucketRequestPayment".into()),
182                    bucket: Some(self.bucket.to_string()),
183                    ..Default::default()
184                }),
185                source: None,
186            })
187        }
188    }
189}
190
191#[derive(Debug, Clone)]
192pub struct GetBucketRequestPaymentOutput {
193    pub payer: String,
194}
195
196impl BucketOperations {
197    pub fn put_request_payment(&self, payer: impl Into<String>) -> PutBucketRequestPaymentBuilder {
198        PutBucketRequestPaymentBuilder::new(
199            self.client_inner().clone(),
200            self.bucket_name().clone(),
201            payer,
202        )
203    }
204
205    pub fn get_request_payment(&self) -> GetBucketRequestPaymentBuilder {
206        GetBucketRequestPaymentBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use std::sync::Mutex;
213
214    use crate::client::OSSClientInner;
215    use crate::config::credentials::Credentials;
216    use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
217    use crate::types::region::Region;
218
219    use super::*;
220
221    struct RecordingHttpClient {
222        requests: Arc<Mutex<Vec<HttpRequest>>>,
223        status_code: http::StatusCode,
224        response_body: bytes::Bytes,
225    }
226
227    #[async_trait::async_trait]
228    impl HttpClient for RecordingHttpClient {
229        async fn send(&self, request: HttpRequest) -> crate::error::Result<HttpResponse> {
230            self.requests.lock().unwrap().push(request);
231            let mut headers = http::HeaderMap::new();
232            headers.insert("x-oss-request-id", http::HeaderValue::from_static("rid-rp"));
233            Ok(HttpResponse {
234                status: self.status_code,
235                headers,
236                body: self.response_body.clone(),
237            })
238        }
239    }
240
241    fn create_test_inner(
242        body: bytes::Bytes,
243    ) -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
244        let requests = Arc::new(Mutex::new(Vec::new()));
245        let http = Arc::new(RecordingHttpClient {
246            requests: requests.clone(),
247            status_code: http::StatusCode::OK,
248            response_body: body,
249        });
250        let credentials = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
251            Credentials::builder()
252                .access_key_id("test-ak")
253                .access_key_secret("test-sk")
254                .build()
255                .unwrap(),
256        ));
257        let inner = Arc::new(OSSClientInner {
258            http,
259            credentials,
260            signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
261            region: Region::CnHangzhou,
262            endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
263        });
264        (inner, requests)
265    }
266
267    #[test]
268    fn request_payment_xml() {
269        let config = RequestPaymentConfiguration {
270            payer: "BucketOwner".into(),
271        };
272        let xml = crate::util::xml::to_xml(&config).unwrap();
273        assert!(xml.contains("<Payer>BucketOwner</Payer>"));
274    }
275
276    #[tokio::test]
277    async fn get_request_payment_parses_response() {
278        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
279<RequestPaymentConfiguration><Payer>Requester</Payer></RequestPaymentConfiguration>"#;
280        let (inner, _) = create_test_inner(bytes::Bytes::from(xml));
281        let builder =
282            GetBucketRequestPaymentBuilder::new(inner, BucketName::new("test-bucket").unwrap());
283        let output = builder.send().await.unwrap();
284        assert_eq!(output.payer, "Requester");
285    }
286}