1use 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}