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