1use crate::client::{BucketOperations, OSSClientInner};
4use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
5use crate::http::client::{HttpRequest, HttpResponse};
6use crate::types::bucket::BucketName;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(rename = "Style")]
12struct StyleConfig {
13 #[serde(rename = "Content")]
14 content: String,
15}
16
17#[derive(Debug, Clone, Deserialize)]
18#[serde(rename = "Style")]
19struct StyleConfigResp {
20 #[serde(rename = "Name")]
21 name: String,
22 #[serde(rename = "Content")]
23 content: String,
24}
25
26#[derive(Debug, Clone, Deserialize)]
27#[serde(rename = "StyleList")]
28struct StyleListResp {
29 #[serde(rename = "Style", default)]
30 styles: Vec<StyleConfigResp>,
31}
32
33pub struct PutBucketStyleBuilder {
34 client: Arc<OSSClientInner>,
35 bucket: BucketName,
36 name: String,
37 content: String,
38}
39impl PutBucketStyleBuilder {
40 pub(crate) fn new(
41 c: Arc<OSSClientInner>,
42 b: BucketName,
43 n: impl Into<String>,
44 ct: impl Into<String>,
45 ) -> Self {
46 Self {
47 client: c,
48 bucket: b,
49 name: n.into(),
50 content: ct.into(),
51 }
52 }
53 pub async fn send(self) -> Result<PutBucketStyleOutput> {
54 put_style_impl(self.client, self.bucket, self.name, self.content).await
55 }
56}
57#[derive(Debug, Clone)]
58pub struct PutBucketStyleOutput {
59 pub request_id: String,
60}
61
62pub struct GetBucketStyleBuilder {
63 client: Arc<OSSClientInner>,
64 bucket: BucketName,
65 name: String,
66}
67impl GetBucketStyleBuilder {
68 pub(crate) fn new(c: Arc<OSSClientInner>, b: BucketName, n: impl Into<String>) -> Self {
69 Self {
70 client: c,
71 bucket: b,
72 name: n.into(),
73 }
74 }
75 pub async fn send(self) -> Result<GetBucketStyleOutput> {
76 get_style_impl(self.client, self.bucket, self.name).await
77 }
78}
79#[derive(Debug, Clone)]
80pub struct GetBucketStyleOutput {
81 pub name: String,
82 pub content: String,
83}
84
85pub struct ListBucketStyleBuilder {
86 client: Arc<OSSClientInner>,
87 bucket: BucketName,
88}
89impl ListBucketStyleBuilder {
90 pub(crate) fn new(c: Arc<OSSClientInner>, b: BucketName) -> Self {
91 Self {
92 client: c,
93 bucket: b,
94 }
95 }
96 pub async fn send(self) -> Result<ListBucketStyleOutput> {
97 list_style_impl(self.client, self.bucket).await
98 }
99}
100#[derive(Debug, Clone)]
101pub struct ListBucketStyleOutput {
102 pub styles: Vec<StyleEntry>,
103}
104#[derive(Debug, Clone)]
105pub struct StyleEntry {
106 pub name: String,
107 pub content: String,
108}
109
110pub struct DeleteBucketStyleBuilder {
111 client: Arc<OSSClientInner>,
112 bucket: BucketName,
113 name: String,
114}
115impl DeleteBucketStyleBuilder {
116 pub(crate) fn new(c: Arc<OSSClientInner>, b: BucketName, n: impl Into<String>) -> Self {
117 Self {
118 client: c,
119 bucket: b,
120 name: n.into(),
121 }
122 }
123 pub async fn send(self) -> Result<DeleteBucketStyleOutput> {
124 delete_style_impl(self.client, self.bucket, self.name).await
125 }
126}
127#[derive(Debug, Clone)]
128pub struct DeleteBucketStyleOutput {
129 pub request_id: String,
130}
131
132async fn put_style_impl(
133 client: Arc<OSSClientInner>,
134 bucket: BucketName,
135 name: String,
136 content: String,
137) -> Result<PutBucketStyleOutput> {
138 let ep = client.endpoint.clone();
139 let uri = format!(
140 "https://{}.{}?style&styleName={}",
141 bucket.as_str(),
142 ep,
143 name
144 );
145 let qp = vec![("style".into(), String::new()), ("styleName".into(), name)];
146 let xml = crate::util::xml::to_xml(&StyleConfig { content })?;
147 let req = HttpRequest::builder()
148 .method(http::Method::PUT)
149 .uri(&uri)
150 .body(bytes::Bytes::from(xml))
151 .build();
152 let r = client
153 .send_signed(req, Some(&bucket), qp)
154 .await
155 .map_err(|e| terr("PutBucketStyle", &bucket, &ep, e))?;
156 if r.status().is_success() {
157 Ok(PutBucketStyleOutput {
158 request_id: rid(&r),
159 })
160 } else {
161 Err(serr("PutBucketStyle", &bucket, r))
162 }
163}
164
165async fn get_style_impl(
166 client: Arc<OSSClientInner>,
167 bucket: BucketName,
168 name: String,
169) -> Result<GetBucketStyleOutput> {
170 let ep = client.endpoint.clone();
171 let uri = format!(
172 "https://{}.{}?style&styleName={}",
173 bucket.as_str(),
174 ep,
175 name
176 );
177 let qp = vec![("style".into(), String::new()), ("styleName".into(), name)];
178 let req = HttpRequest::builder()
179 .method(http::Method::GET)
180 .uri(&uri)
181 .build();
182 let r = client
183 .send_signed(req, Some(&bucket), qp)
184 .await
185 .map_err(|e| terr("GetBucketStyle", &bucket, &ep, e))?;
186 if r.is_success() {
187 let s: StyleConfigResp = crate::util::xml::from_xml(r.body_as_str().unwrap_or(""))
188 .map_err(|e| OssError {
189 kind: OssErrorKind::DeserializationError,
190 context: Box::new(ErrorContext {
191 operation: Some("parse XML".into()),
192 bucket: Some(bucket.to_string()),
193 ..Default::default()
194 }),
195 source: Some(Box::new(e)),
196 })?;
197 Ok(GetBucketStyleOutput {
198 name: s.name,
199 content: s.content,
200 })
201 } else {
202 Err(serr("GetBucketStyle", &bucket, r))
203 }
204}
205
206async fn list_style_impl(
207 client: Arc<OSSClientInner>,
208 bucket: BucketName,
209) -> Result<ListBucketStyleOutput> {
210 let ep = client.endpoint.clone();
211 let uri = format!("https://{}.{}?style", bucket.as_str(), ep);
212 let qp = vec![("style".into(), String::new())];
213 let req = HttpRequest::builder()
214 .method(http::Method::GET)
215 .uri(&uri)
216 .build();
217 let r = client
218 .send_signed(req, Some(&bucket), qp)
219 .await
220 .map_err(|e| terr("ListBucketStyle", &bucket, &ep, e))?;
221 if r.is_success() {
222 let l: StyleListResp =
223 crate::util::xml::from_xml(r.body_as_str().unwrap_or("")).map_err(|e| OssError {
224 kind: OssErrorKind::DeserializationError,
225 context: Box::new(ErrorContext {
226 operation: Some("parse XML".into()),
227 bucket: Some(bucket.to_string()),
228 ..Default::default()
229 }),
230 source: Some(Box::new(e)),
231 })?;
232 Ok(ListBucketStyleOutput {
233 styles: l
234 .styles
235 .into_iter()
236 .map(|s| StyleEntry {
237 name: s.name,
238 content: s.content,
239 })
240 .collect(),
241 })
242 } else {
243 Err(serr("ListBucketStyle", &bucket, r))
244 }
245}
246
247async fn delete_style_impl(
248 client: Arc<OSSClientInner>,
249 bucket: BucketName,
250 name: String,
251) -> Result<DeleteBucketStyleOutput> {
252 let ep = client.endpoint.clone();
253 let uri = format!(
254 "https://{}.{}?style&styleName={}",
255 bucket.as_str(),
256 ep,
257 name
258 );
259 let qp = vec![("style".into(), String::new()), ("styleName".into(), name)];
260 let req = HttpRequest::builder()
261 .method(http::Method::DELETE)
262 .uri(&uri)
263 .build();
264 let r = client
265 .send_signed(req, Some(&bucket), qp)
266 .await
267 .map_err(|e| terr("DeleteBucketStyle", &bucket, &ep, e))?;
268 if r.status().is_success() {
269 Ok(DeleteBucketStyleOutput {
270 request_id: rid(&r),
271 })
272 } else {
273 Err(serr("DeleteBucketStyle", &bucket, r))
274 }
275}
276
277fn rid(r: &HttpResponse) -> String {
278 r.headers
279 .get("x-oss-request-id")
280 .and_then(|v| v.to_str().ok())
281 .unwrap_or("")
282 .to_string()
283}
284fn terr(op: &str, b: &BucketName, ep: &str, e: OssError) -> OssError {
285 OssError {
286 kind: OssErrorKind::TransportError,
287 context: Box::new(ErrorContext {
288 operation: Some(op.into()),
289 bucket: Some(b.to_string()),
290 endpoint: Some(ep.into()),
291 ..Default::default()
292 }),
293 source: Some(Box::new(e)),
294 }
295}
296fn serr(op: &str, b: &BucketName, r: HttpResponse) -> OssError {
297 OssError {
298 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
299 status_code: r.status().as_u16(),
300 code: String::new(),
301 message: String::new(),
302 request_id: String::new(),
303 host_id: String::new(),
304 resource: Some(b.to_string()),
305 string_to_sign: None,
306 })),
307 context: Box::new(ErrorContext {
308 operation: Some(op.into()),
309 bucket: Some(b.to_string()),
310 ..Default::default()
311 }),
312 source: None,
313 }
314}
315
316impl BucketOperations {
317 pub fn put_style(
318 &self,
319 name: impl Into<String>,
320 content: impl Into<String>,
321 ) -> PutBucketStyleBuilder {
322 PutBucketStyleBuilder::new(
323 self.client_inner().clone(),
324 self.bucket_name().clone(),
325 name,
326 content,
327 )
328 }
329 pub fn get_style(&self, name: impl Into<String>) -> GetBucketStyleBuilder {
330 GetBucketStyleBuilder::new(
331 self.client_inner().clone(),
332 self.bucket_name().clone(),
333 name,
334 )
335 }
336 pub fn list_style(&self) -> ListBucketStyleBuilder {
337 ListBucketStyleBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
338 }
339 pub fn delete_style(&self, name: impl Into<String>) -> DeleteBucketStyleBuilder {
340 DeleteBucketStyleBuilder::new(
341 self.client_inner().clone(),
342 self.bucket_name().clone(),
343 name,
344 )
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 #[test]
352 fn style_xml() {
353 let c = StyleConfig {
354 content: "image/resize,m_fixed,w_100".into(),
355 };
356 let x = crate::util::xml::to_xml(&c).unwrap();
357 assert!(x.contains("<Content>image/resize,m_fixed,w_100</Content>"));
358 }
359}