1use std::collections::HashMap;
4
5use async_trait::async_trait;
6
7use crate::tagging_common::{
8 build_delete_object_tag_request, build_get_object_tag_request, build_put_object_tag_request, parse_tags_from_xml, DeleteObjectTagOptions,
9 GetObjectTagOptions, PutObjectTagOptions,
10};
11use crate::{Client, Result};
12
13#[async_trait]
14pub trait ObjectTagOperations {
15 async fn get_object_tags<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectTagOptions>) -> Result<HashMap<String, String>>
19 where
20 S1: AsRef<str> + Send,
21 S2: AsRef<str> + Send;
22
23 async fn put_object_tags<S1, S2>(&self, bucket_name: S1, object_key: S2, tags: HashMap<String, String>, options: Option<PutObjectTagOptions>) -> Result<()>
27 where
28 S1: AsRef<str> + Send,
29 S2: AsRef<str> + Send;
30
31 async fn delete_object_tags<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<DeleteObjectTagOptions>) -> Result<()>
35 where
36 S1: AsRef<str> + Send,
37 S2: AsRef<str> + Send;
38}
39
40#[async_trait]
41impl ObjectTagOperations for Client {
42 async fn get_object_tags<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<GetObjectTagOptions>) -> Result<HashMap<String, String>>
46 where
47 S1: AsRef<str> + Send,
48 S2: AsRef<str> + Send,
49 {
50 let request = build_get_object_tag_request(bucket_name.as_ref(), object_key.as_ref(), &options)?;
51 let (_, xml) = self.do_request::<String>(request).await?;
52 parse_tags_from_xml(xml)
53 }
54
55 async fn put_object_tags<S1, S2>(&self, bucket_name: S1, object_key: S2, tags: HashMap<String, String>, options: Option<PutObjectTagOptions>) -> Result<()>
59 where
60 S1: AsRef<str> + Send,
61 S2: AsRef<str> + Send,
62 {
63 let request = build_put_object_tag_request(bucket_name.as_ref(), object_key.as_ref(), &tags, &options)?;
64 let _ = self.do_request::<()>(request).await?;
65 Ok(())
66 }
67
68 async fn delete_object_tags<S1, S2>(&self, bucket_name: S1, object_key: S2, options: Option<DeleteObjectTagOptions>) -> Result<()>
72 where
73 S1: AsRef<str> + Send,
74 S2: AsRef<str> + Send,
75 {
76 let request = build_delete_object_tag_request(bucket_name.as_ref(), object_key.as_ref(), &options)?;
77 let _ = self.do_request::<()>(request).await?;
78 Ok(())
79 }
80}
81
82#[cfg(test)]
83mod test_tagging_async {
84 use std::{collections::HashMap, sync::Once};
85
86 use uuid::Uuid;
87
88 use crate::{
89 object::ObjectOperations,
90 object_common::{HeadObjectOptionsBuilder, PutObjectApiResponse, PutObjectOptionsBuilder, PutObjectResult},
91 tagging::ObjectTagOperations,
92 tagging_common::{GetObjectTagOptions, PutObjectTagOptions},
93 Client,
94 };
95
96 static INIT: Once = Once::new();
97
98 fn setup() {
99 INIT.call_once(|| {
100 simple_logger::init_with_level(log::Level::Debug).unwrap();
101 dotenvy::dotenv().unwrap();
102 });
103 }
104
105 #[tokio::test]
106 async fn test_tagging_async() {
107 log::debug!("test object tagging");
108 setup();
109 let client = Client::from_env();
110
111 let bucket_name = "yuanyq-2";
112 let object_key = format!("ali-oss-rs-test/{}.webp", Uuid::new_v4());
113 let file_path = "/home/yuanyq/Pictures/test-8.webp";
114
115 let options = PutObjectOptionsBuilder::new().tag("tag-a", "tag-value-a").build();
116
117 let res = client.put_object_from_file(bucket_name, &object_key, file_path, Some(options)).await;
118 assert!(res.is_ok());
119
120 let ret = res.unwrap();
121
122 if let PutObjectResult::ApiResponse(PutObjectApiResponse {
123 request_id: _,
124 etag: _,
125 content_md5: _,
126 hash_crc64ecma: _,
127 version_id,
128 }) = ret
129 {
130 assert!(version_id.is_some());
131 } else {
132 panic!("response type does not match");
133 }
134
135 log::debug!("create a new version");
136
137 let options = PutObjectOptionsBuilder::new().tag("tag-a", "tag-value-a").build();
138
139 let res = client.put_object_from_file(bucket_name, &object_key, file_path, Some(options)).await;
140 assert!(res.is_ok());
141
142 let ret = res.unwrap();
143
144 let version_id = if let PutObjectResult::ApiResponse(PutObjectApiResponse {
145 request_id: _,
146 etag: _,
147 content_md5: _,
148 hash_crc64ecma: _,
149 version_id,
150 }) = ret
151 {
152 assert!(version_id.is_some());
153 version_id.unwrap()
154 } else {
155 panic!("response type does not match");
156 };
157
158 log::debug!("last version id: {}", version_id);
159
160 let res = client
161 .head_object(bucket_name, &object_key, Some(HeadObjectOptionsBuilder::new().version_id(&version_id).build()))
162 .await;
163
164 let ret = res.unwrap();
165 assert_eq!(Some(1), ret.tag_count);
166
167 let res = client
168 .get_object_tags(
169 bucket_name,
170 &object_key,
171 Some(GetObjectTagOptions {
172 version_id: Some(version_id.clone()),
173 }),
174 )
175 .await;
176 log::debug!("get object tag response: {:#?}", res);
177 assert!(res.is_ok());
178 let ret = res.unwrap();
179 assert!(ret.contains_key("tag-a"));
180 assert_eq!("tag-value-a", ret.get("tag-a").unwrap());
181
182 let new_tags = HashMap::from([
183 ("tag-b".to_string(), "tag-value-b".to_string()),
184 ("tag-c".to_string(), "tag-value-c".to_string()),
185 ]);
186
187 let _ = client
188 .put_object_tags(
189 bucket_name,
190 &object_key,
191 new_tags,
192 Some(PutObjectTagOptions {
193 version_id: Some(version_id.clone()),
194 }),
195 )
196 .await;
197
198 let res = client
199 .get_object_tags(
200 bucket_name,
201 &object_key,
202 Some(GetObjectTagOptions {
203 version_id: Some(version_id.clone()),
204 }),
205 )
206 .await;
207 log::debug!("get object tag response: {:#?}", res);
208 assert!(res.is_ok());
209
210 let ret = res.unwrap();
211 assert!(ret.contains_key("tag-b"));
212 assert_eq!("tag-value-b", ret.get("tag-b").unwrap());
213
214 assert!(ret.contains_key("tag-c"));
215 assert_eq!("tag-value-c", ret.get("tag-c").unwrap());
216
217 let _ = client.delete_object_tags(bucket_name, &object_key, None).await;
218
219 let res = client
220 .get_object_tags(
221 bucket_name,
222 &object_key,
223 Some(GetObjectTagOptions {
224 version_id: Some(version_id.clone()),
225 }),
226 )
227 .await;
228 log::debug!("get object tag response: {:#?}", res);
229 assert!(res.is_ok());
230 let ret = res.unwrap();
231 assert!(ret.is_empty());
232
233 let _ = client.delete_object(bucket_name, &object_key, None).await;
234 }
235}