Skip to main content

aliyun_oss/operations/
bucket_cname.rs

1//! CNAME domain binding operations.
2
3use 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 = "CnameConfiguration")]
12struct CnameConfig {
13    #[serde(rename = "Domain")]
14    domain: String,
15}
16
17#[derive(Debug, Clone, Deserialize)]
18#[serde(rename = "ListCnameResult")]
19struct ListCnameResult {
20    #[serde(rename = "Cname", default)]
21    cnames: Vec<CnameInfo>,
22}
23#[derive(Debug, Clone, Deserialize)]
24struct CnameInfo {
25    #[serde(rename = "Domain")]
26    domain: String,
27    #[serde(rename = "Status", default)]
28    status: String,
29}
30
31pub struct PutBucketCnameBuilder {
32    client: Arc<OSSClientInner>,
33    bucket: BucketName,
34    domain: String,
35}
36impl PutBucketCnameBuilder {
37    pub(crate) fn new(c: Arc<OSSClientInner>, b: BucketName, d: impl Into<String>) -> Self {
38        Self {
39            client: c,
40            bucket: b,
41            domain: d.into(),
42        }
43    }
44    pub async fn send(self) -> Result<PutBucketCnameOutput> {
45        put_cname_impl(self.client, self.bucket, self.domain).await
46    }
47}
48#[derive(Debug, Clone)]
49pub struct PutBucketCnameOutput {
50    pub request_id: String,
51}
52
53pub struct ListBucketCnameBuilder {
54    client: Arc<OSSClientInner>,
55    bucket: BucketName,
56}
57impl ListBucketCnameBuilder {
58    pub(crate) fn new(c: Arc<OSSClientInner>, b: BucketName) -> Self {
59        Self {
60            client: c,
61            bucket: b,
62        }
63    }
64    pub async fn send(self) -> Result<ListBucketCnameOutput> {
65        list_cname_impl(self.client, self.bucket).await
66    }
67}
68#[derive(Debug, Clone)]
69pub struct ListBucketCnameOutput {
70    pub cnames: Vec<CnameEntry>,
71}
72#[derive(Debug, Clone)]
73pub struct CnameEntry {
74    pub domain: String,
75    pub status: String,
76}
77
78pub struct DeleteBucketCnameBuilder {
79    client: Arc<OSSClientInner>,
80    bucket: BucketName,
81    domain: String,
82}
83impl DeleteBucketCnameBuilder {
84    pub(crate) fn new(c: Arc<OSSClientInner>, b: BucketName, d: impl Into<String>) -> Self {
85        Self {
86            client: c,
87            bucket: b,
88            domain: d.into(),
89        }
90    }
91    pub async fn send(self) -> Result<DeleteBucketCnameOutput> {
92        delete_cname_impl(self.client, self.bucket, self.domain).await
93    }
94}
95#[derive(Debug, Clone)]
96pub struct DeleteBucketCnameOutput {
97    pub request_id: String,
98}
99
100async fn put_cname_impl(
101    client: Arc<OSSClientInner>,
102    bucket: BucketName,
103    domain: String,
104) -> Result<PutBucketCnameOutput> {
105    let ep = client.endpoint.clone();
106    let uri = format!("https://{}.{}?cname", bucket.as_str(), ep);
107    let qp = vec![("cname".into(), String::new())];
108    let xml = crate::util::xml::to_xml(&CnameConfig { domain })?;
109    let req = HttpRequest::builder()
110        .method(http::Method::PUT)
111        .uri(&uri)
112        .body(bytes::Bytes::from(xml))
113        .build();
114    let r = client
115        .send_signed(req, Some(&bucket), qp)
116        .await
117        .map_err(|e| err("PutBucketCname", &bucket, &ep, e))?;
118    if r.status().is_success() {
119        Ok(PutBucketCnameOutput {
120            request_id: rid(&r),
121        })
122    } else {
123        Err(service_err("PutBucketCname", &bucket, r))
124    }
125}
126
127async fn list_cname_impl(
128    client: Arc<OSSClientInner>,
129    bucket: BucketName,
130) -> Result<ListBucketCnameOutput> {
131    let ep = client.endpoint.clone();
132    let uri = format!("https://{}.{}?cname", bucket.as_str(), ep);
133    let qp = vec![("cname".into(), String::new())];
134    let req = HttpRequest::builder()
135        .method(http::Method::GET)
136        .uri(&uri)
137        .build();
138    let r = client
139        .send_signed(req, Some(&bucket), qp)
140        .await
141        .map_err(|e| err("ListBucketCname", &bucket, &ep, e))?;
142    if r.is_success() {
143        let result: ListCnameResult = crate::util::xml::from_xml(r.body_as_str().unwrap_or(""))
144            .map_err(|e| OssError {
145                kind: OssErrorKind::DeserializationError,
146                context: Box::new(ErrorContext {
147                    operation: Some("parse XML".into()),
148                    bucket: Some(bucket.to_string()),
149                    ..Default::default()
150                }),
151                source: Some(Box::new(e)),
152            })?;
153        Ok(ListBucketCnameOutput {
154            cnames: result
155                .cnames
156                .into_iter()
157                .map(|c| CnameEntry {
158                    domain: c.domain,
159                    status: c.status,
160                })
161                .collect(),
162        })
163    } else {
164        Err(service_err("ListBucketCname", &bucket, r))
165    }
166}
167
168async fn delete_cname_impl(
169    client: Arc<OSSClientInner>,
170    bucket: BucketName,
171    domain: String,
172) -> Result<DeleteBucketCnameOutput> {
173    let ep = client.endpoint.clone();
174    let uri = format!("https://{}.{}?cname&comp=delete", bucket.as_str(), ep);
175    let qp = vec![
176        ("cname".into(), String::new()),
177        ("comp".into(), "delete".into()),
178    ];
179    let xml = crate::util::xml::to_xml(&CnameConfig { domain })?;
180    let req = HttpRequest::builder()
181        .method(http::Method::POST)
182        .uri(&uri)
183        .body(bytes::Bytes::from(xml))
184        .build();
185    let r = client
186        .send_signed(req, Some(&bucket), qp)
187        .await
188        .map_err(|e| err("DeleteBucketCname", &bucket, &ep, e))?;
189    if r.status().is_success() {
190        Ok(DeleteBucketCnameOutput {
191            request_id: rid(&r),
192        })
193    } else {
194        Err(service_err("DeleteBucketCname", &bucket, r))
195    }
196}
197
198fn rid(r: &HttpResponse) -> String {
199    r.headers
200        .get("x-oss-request-id")
201        .and_then(|v| v.to_str().ok())
202        .unwrap_or("")
203        .to_string()
204}
205fn err(op: &str, b: &BucketName, ep: &str, e: OssError) -> OssError {
206    OssError {
207        kind: OssErrorKind::TransportError,
208        context: Box::new(ErrorContext {
209            operation: Some(op.into()),
210            bucket: Some(b.to_string()),
211            endpoint: Some(ep.into()),
212            ..Default::default()
213        }),
214        source: Some(Box::new(e)),
215    }
216}
217fn service_err(op: &str, b: &BucketName, r: HttpResponse) -> OssError {
218    OssError {
219        kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
220            status_code: r.status().as_u16(),
221            code: String::new(),
222            message: String::new(),
223            request_id: String::new(),
224            host_id: String::new(),
225            resource: Some(b.to_string()),
226            string_to_sign: None,
227        })),
228        context: Box::new(ErrorContext {
229            operation: Some(op.into()),
230            bucket: Some(b.to_string()),
231            ..Default::default()
232        }),
233        source: None,
234    }
235}
236
237impl BucketOperations {
238    pub fn put_cname(&self, domain: impl Into<String>) -> PutBucketCnameBuilder {
239        PutBucketCnameBuilder::new(
240            self.client_inner().clone(),
241            self.bucket_name().clone(),
242            domain,
243        )
244    }
245    pub fn list_cname(&self) -> ListBucketCnameBuilder {
246        ListBucketCnameBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
247    }
248    pub fn delete_cname(&self, domain: impl Into<String>) -> DeleteBucketCnameBuilder {
249        DeleteBucketCnameBuilder::new(
250            self.client_inner().clone(),
251            self.bucket_name().clone(),
252            domain,
253        )
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::client::OSSClientInner;
261    use crate::config::credentials::Credentials;
262    use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
263    use crate::types::region::Region;
264    use std::sync::Mutex;
265    struct Rc {
266        r: Arc<Mutex<Vec<HttpRequest>>>,
267    }
268    #[async_trait::async_trait]
269    impl HttpClient for Rc {
270        async fn send(&self, req: HttpRequest) -> crate::error::Result<HttpResponse> {
271            self.r.lock().unwrap().push(req);
272            let mut h = http::HeaderMap::new();
273            h.insert("x-oss-request-id", http::HeaderValue::from_static("rid"));
274            Ok(HttpResponse {
275                status: http::StatusCode::OK,
276                headers: h,
277                body: bytes::Bytes::new(),
278            })
279        }
280    }
281    fn ci() -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
282        let rq = Arc::new(Mutex::new(Vec::new()));
283        let h = Arc::new(Rc { r: rq.clone() });
284        let cr = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
285            Credentials::builder()
286                .access_key_id("ak")
287                .access_key_secret("sk")
288                .build()
289                .unwrap(),
290        ));
291        (
292            Arc::new(OSSClientInner {
293                http: h,
294                credentials: cr,
295                signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
296                region: Region::CnHangzhou,
297                endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
298            }),
299            rq,
300        )
301    }
302    #[test]
303    fn cname_xml() {
304        let c = CnameConfig {
305            domain: "cdn.example.com".into(),
306        };
307        let x = crate::util::xml::to_xml(&c).unwrap();
308        assert!(x.contains("<Domain>cdn.example.com</Domain>"));
309    }
310    #[tokio::test]
311    async fn put_cname_sends_request() {
312        let (i, r) = ci();
313        PutBucketCnameBuilder::new(
314            i,
315            BucketName::new("test-bucket").unwrap(),
316            "cdn.example.com",
317        )
318        .send()
319        .await
320        .unwrap();
321        assert_eq!(r.lock().unwrap()[0].method, http::Method::PUT);
322    }
323}