toolcraft_s3_kit/
bucket_client.rs1use std::sync::Arc;
2
3use bytes::Bytes;
4use toolcraft_utils::{presign_get_object, presign_put_object, sign_request};
5
6use crate::client::S3Client;
7use crate::error::Result;
8use crate::util::{check_status, parse_object_list, url_encode, ObjectInfo};
9
10#[derive(Clone)]
20pub struct BucketClient {
21 inner: Arc<S3Client>,
22 bucket: String,
23}
24
25impl BucketClient {
28 pub fn new(client: Arc<S3Client>, bucket: impl Into<String>) -> Self {
29 Self { inner: client, bucket: bucket.into() }
30 }
31}
32
33impl BucketClient {
36 pub async fn list_objects(&self, prefix: Option<&str>) -> Result<Vec<ObjectInfo>> {
37 let c = &self.inner;
38 let path = format!("/{}", self.bucket);
39 let query = match prefix {
40 Some(p) => format!("list-type=2&prefix={}", url_encode(p)),
41 None => "list-type=2".to_string(),
42 };
43 let auth = sign_request(
44 "GET",
45 &c.access_key,
46 &c.secret_key,
47 &c.host(),
48 &path,
49 &query,
50 Some(&c.region),
51 );
52
53 let resp = c
54 .http
55 .get(format!("{}?{}", c.url(&path), query))
56 .header("host", c.host())
57 .header("x-amz-date", &auth.x_amz_date)
58 .header("x-amz-content-sha256", &auth.x_amz_content_sha256)
59 .header("authorization", &auth.authorization)
60 .send()
61 .await?;
62
63 let xml = check_status(resp).await?.text().await?;
64 parse_object_list(&xml)
65 }
66
67 pub async fn upload_file(
68 &self,
69 key: &str,
70 data: Bytes,
71 content_type: Option<&str>,
72 ) -> Result<()> {
73 let c = &self.inner;
74 let url = presign_put_object(
75 &c.access_key,
76 &c.secret_key,
77 &self.bucket,
78 key,
79 Some(&c.region),
80 c.base_url.as_str(),
81 None,
82 );
83
84 let mut req = c.http.put(&url).body(data);
85 if let Some(ct) = content_type {
86 req = req.header("content-type", ct);
87 }
88 check_status(req.send().await?).await.map(|_| ())
89 }
90
91 pub async fn download_object(&self, key: &str) -> Result<Bytes> {
92 let c = &self.inner;
93 let url = presign_get_object(
94 &c.access_key,
95 &c.secret_key,
96 &self.bucket,
97 key,
98 Some(&c.region),
99 c.base_url.as_str(),
100 None,
101 );
102
103 let resp = check_status(c.http.get(&url).send().await?).await?;
104 Ok(resp.bytes().await?)
105 }
106
107 pub async fn delete_object(&self, key: &str) -> Result<()> {
108 let c = &self.inner;
109 let path = format!("/{}/{}", self.bucket, key.trim_start_matches('/'));
110 let auth = sign_request(
111 "DELETE",
112 &c.access_key,
113 &c.secret_key,
114 &c.host(),
115 &path,
116 "",
117 Some(&c.region),
118 );
119
120 let resp = c
121 .http
122 .delete(c.url(&path))
123 .header("host", c.host())
124 .header("x-amz-date", &auth.x_amz_date)
125 .header("x-amz-content-sha256", &auth.x_amz_content_sha256)
126 .header("authorization", &auth.authorization)
127 .send()
128 .await?;
129
130 check_status(resp).await.map(|_| ())
131 }
132
133 pub fn presign_upload(&self, key: &str, expires_secs: Option<u64>) -> String {
135 let c = &self.inner;
136 presign_put_object(
137 &c.access_key,
138 &c.secret_key,
139 &self.bucket,
140 key,
141 Some(&c.region),
142 c.base_url.as_str(),
143 expires_secs,
144 )
145 }
146}