Skip to main content

aliyun_oss/operations/
service.rs

1//! Service-level operations (list buckets).
2
3use std::sync::Arc;
4
5use crate::client::OSSClient;
6use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
7use crate::http::client::HttpRequest;
8
9pub struct ListBucketsBuilder {
10    client: Arc<crate::client::OSSClientInner>,
11}
12
13impl ListBucketsBuilder {
14    pub(crate) fn new(client: Arc<crate::client::OSSClientInner>) -> Self {
15        Self { client }
16    }
17
18    pub async fn send(self) -> Result<crate::types::response::ListBucketsOutput> {
19        let endpoint = self.client.endpoint.clone();
20        let uri = format!("https://{}", endpoint);
21
22        let request = HttpRequest::builder()
23            .method(http::Method::GET)
24            .uri(&uri)
25            .build();
26
27        let response = self
28            .client
29            .send_signed(request, None, Vec::new())
30            .await
31            .map_err(|e| OssError {
32                kind: OssErrorKind::TransportError,
33                context: Box::new(ErrorContext {
34                    operation: Some("ListBuckets".into()),
35                    endpoint: Some(endpoint),
36                    ..Default::default()
37                }),
38                source: Some(Box::new(e)),
39            })?;
40
41        if response.is_success() {
42            let body_str = response.body_as_str().unwrap_or("");
43            Ok(crate::util::xml::from_xml(body_str)?)
44        } else {
45            Err(OssError {
46                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
47                    status_code: response.status().as_u16(),
48                    code: String::new(),
49                    message: String::new(),
50                    request_id: String::new(),
51                    host_id: String::new(),
52                    resource: None,
53                    string_to_sign: None,
54                })),
55                context: Box::new(ErrorContext {
56                    operation: Some("ListBuckets".into()),
57                    ..Default::default()
58                }),
59                source: None,
60            })
61        }
62    }
63}
64
65impl OSSClient {
66    pub fn list_buckets(&self) -> ListBucketsBuilder {
67        ListBucketsBuilder::new(self.inner().clone())
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use std::str::FromStr;
74    use std::sync::Mutex;
75
76    use crate::client::OSSClientInner;
77    use crate::config::credentials::Credentials;
78    use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
79    use crate::types::region::Region;
80
81    use super::*;
82
83    struct RecordingHttpClient {
84        requests: Arc<Mutex<Vec<HttpRequest>>>,
85    }
86
87    #[async_trait::async_trait]
88    impl HttpClient for RecordingHttpClient {
89        async fn send(&self, request: HttpRequest) -> crate::error::Result<HttpResponse> {
90            self.requests.lock().unwrap().push(request);
91            let mut headers = http::HeaderMap::new();
92            headers.insert(
93                "x-oss-request-id",
94                http::HeaderValue::from_static("rid-svc"),
95            );
96            Ok(HttpResponse {
97                status: http::StatusCode::OK,
98                headers,
99                body: bytes::Bytes::from(
100                    r#"<?xml version="1.0" encoding="UTF-8"?><ListAllMyBucketsResult><Owner><ID>owner-id</ID></Owner><Buckets></Buckets></ListAllMyBucketsResult>"#,
101                ),
102            })
103        }
104    }
105
106    fn create_test_inner() -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
107        let requests = Arc::new(Mutex::new(Vec::new()));
108        let http = Arc::new(RecordingHttpClient {
109            requests: requests.clone(),
110        });
111        let credentials = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
112            Credentials::builder()
113                .access_key_id("test-ak")
114                .access_key_secret("test-sk")
115                .build()
116                .unwrap(),
117        ));
118        let inner = Arc::new(OSSClientInner {
119            http,
120            credentials,
121            signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
122            region: Region::CnHangzhou,
123            endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
124        });
125        (inner, requests)
126    }
127
128    #[tokio::test]
129    async fn list_buckets_sends_get_request() {
130        let (inner, requests) = create_test_inner();
131        let builder = ListBucketsBuilder::new(inner);
132
133        builder.send().await.unwrap();
134
135        let captured = requests.lock().unwrap();
136        assert_eq!(captured[0].method, http::Method::GET);
137    }
138
139    #[tokio::test]
140    #[ignore = "requires valid OSS credentials"]
141    async fn e2e_list_buckets() {
142        let ak = std::env::var("OSS_ACCESS_KEY_ID").expect("OSS_ACCESS_KEY_ID not set");
143        let sk = std::env::var("OSS_ACCESS_KEY_SECRET").expect("OSS_ACCESS_KEY_SECRET not set");
144        let region_str = std::env::var("OSS_REGION").unwrap_or_else(|_| "cn-wulanchabu".into());
145
146        let region = Region::from_str(&region_str).unwrap_or_else(|_| Region::Custom {
147            endpoint: format!("oss-{}.aliyuncs.com", region_str),
148            region_id: region_str.clone(),
149        });
150
151        let client = crate::client::OSSClient::builder()
152            .region(region)
153            .credentials(ak, sk)
154            .build()
155            .unwrap();
156
157        let output = client.list_buckets().send().await.unwrap();
158        assert!(!output.owner.id.is_empty());
159        eprintln!(
160            "ListBuckets: {} buckets, owner={}",
161            output.buckets.bucket.len(),
162            output.owner.id
163        );
164    }
165}