1use std::collections::HashMap;
2
3use http::{
4 header::{HeaderName, IntoHeaderName},
5 HeaderMap,
6};
7
8use crate::{
9 datatype::{
10 FromXml, InitiateMultipartUploadResult, ObjectLockConfiguration, RetentionMode, Tagging,
11 ToXml,
12 },
13 error::Result,
14 sse::{Sse, SseCustomerKey},
15 time::UtcTime,
16 utils::urlencode,
17};
18
19use super::QueryMap;
20
21#[derive(Debug, Clone)]
30pub struct BucketArgs {
31 pub(crate) name: String,
32 pub(crate) region: Option<String>,
33 pub(crate) expected_bucket_owner: Option<String>,
34 pub(crate) extra_headers: Option<HeaderMap>,
35}
36
37impl BucketArgs {
38 pub fn new<S: Into<String>>(bucket_name: S) -> Self {
39 Self {
40 name: bucket_name.into(),
41 region: None,
42 expected_bucket_owner: None,
43 extra_headers: None,
44 }
45 }
46
47 pub fn region(mut self, region: Option<String>) -> Self {
49 self.region = region;
50 self
51 }
52
53 pub fn expected_bucket_owner(mut self, expected_bucket_owner: Option<String>) -> Self {
55 self.expected_bucket_owner = expected_bucket_owner;
56 self
57 }
58
59 pub fn extra_headers(mut self, extra_headers: Option<HeaderMap>) -> Self {
61 self.extra_headers = extra_headers;
62 self
63 }
64}
65
66impl<S> From<S> for BucketArgs
67where
68 S: Into<String>,
69{
70 fn from(s: S) -> Self {
71 Self::new(s)
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct CopySource {
78 bucket_name: String,
79 object_name: String,
80 region: Option<String>,
81 offset: usize,
82 length: usize,
83 version_id: Option<String>,
84 metadata_replace: bool,
85 ssec: Option<HeaderMap>,
86 match_etag: Option<String>,
87 not_match_etag: Option<String>,
88 modified_since: Option<String>,
89 unmodified_since: Option<String>,
90}
91
92impl CopySource {
93 pub fn new<T1: Into<String>, T2: Into<String>>(bucket_name: T1, object_name: T2) -> Self {
94 Self {
95 bucket_name: bucket_name.into(),
96 object_name: object_name.into(),
97 region: None,
98 version_id: None,
99 metadata_replace: false,
100 ssec: None,
101 match_etag: None,
102 not_match_etag: None,
103 modified_since: None,
104 unmodified_since: None,
105 offset: 0,
106 length: 0,
107 }
108 }
109
110 pub fn region(mut self, region: Option<String>) -> Self {
112 self.region = region;
113 self
114 }
115
116 pub fn range(mut self, offset: usize, length: usize) -> Self {
120 self.offset = offset;
121 self.length = length;
122 self
123 }
124
125 pub fn metadata_replace(mut self, metadata_replace: bool) -> Self {
127 self.metadata_replace = metadata_replace;
128 self
129 }
130
131 pub fn version_id<T: Into<String>>(mut self, version_id: T) -> Self {
133 self.version_id = Some(version_id.into());
134 self
135 }
136
137 pub fn ssec(mut self, ssec: &SseCustomerKey) -> Self {
139 let mut header = ssec.headers();
140 header.extend(ssec.copy_headers());
141 self.ssec = Some(header);
142 self
143 }
144
145 pub fn match_etag(mut self, match_etag: Option<String>) -> Self {
146 self.match_etag = match_etag;
147 self
148 }
149
150 pub fn not_match_etag(mut self, not_match_etag: Option<String>) -> Self {
151 self.not_match_etag = not_match_etag;
152 self
153 }
154
155 pub fn modified_since(mut self, modified_since: Option<String>) -> Self {
156 self.modified_since = modified_since;
157 self
158 }
159
160 pub fn unmodified_since(mut self, unmodified_since: Option<String>) -> Self {
161 self.unmodified_since = unmodified_since;
162 self
163 }
164
165 pub(crate) fn args_headers(&self) -> HeaderMap {
166 let mut header = HeaderMap::new();
167 let mut copy_source =
168 urlencode(&format!("/{}/{}", self.bucket_name, self.object_name), true);
169 if let Some(version_id) = &self.version_id {
170 copy_source = copy_source + "?versionId=" + version_id;
171 }
172 header.insert("x-amz-copy-source", copy_source.parse().unwrap());
173 if let Some(value) = &self.match_etag {
174 header.insert("x-amz-copy-source-if-match", value.parse().unwrap());
175 }
176 if let Some(value) = &self.not_match_etag {
177 header.insert("x-amz-copy-source-if-none-match", value.parse().unwrap());
178 }
179 if self.metadata_replace {
180 header.insert("x-amz-metadata-directive", "REPLACE".parse().unwrap());
181 }
182 if let Some(value) = &self.modified_since {
183 header.insert(
184 "x-amz-copy-source-if-modified-since",
185 value.parse().unwrap(),
186 );
187 }
188 if let Some(value) = &self.unmodified_since {
189 header.insert(
190 "x-amz-copy-source-if-unmodified-since",
191 value.parse().unwrap(),
192 );
193 }
194 if self.offset > 0 || self.length > 0 {
195 let ranger = if self.length > 0 {
196 format!("bytes={}-{}", self.offset, self.offset + self.length - 1)
197 } else {
198 format!("bytes={}-", self.offset)
199 };
200 if let Ok(value) = ranger.parse() {
201 header.insert("x-amz-copy-source-range", value);
202 }
203 }
204 if let Some(ssec) = &self.ssec {
205 header.extend(ssec.clone());
206 for (k, v) in ssec {
207 header.insert(k, v.to_owned());
208 }
209 }
210 header
211 }
212}
213
214#[derive(Debug, Clone)]
227pub struct KeyArgs {
228 pub(crate) name: String,
229 pub(crate) version_id: Option<String>,
230 pub(crate) content_type: Option<String>,
231 pub(crate) ssec_headers: Option<HeaderMap>,
232 pub(crate) offset: usize,
233 pub(crate) length: usize,
234 pub(crate) extra_headers: Option<HeaderMap>,
235 pub(crate) metadata: HashMap<String, String>,
236}
237
238impl KeyArgs {
239 pub fn new<S: Into<String>>(name: S) -> Self {
240 Self {
241 name: name.into(),
242 extra_headers: None,
243 version_id: None,
244 content_type: None,
245 ssec_headers: None,
246 offset: 0,
247 length: 0,
248 metadata: Default::default(),
249 }
250 }
251
252 pub fn version_id(mut self, version_id: Option<String>) -> Self {
254 self.version_id = version_id;
255 self
256 }
257
258 pub fn content_type(mut self, content_type: Option<String>) -> Self {
260 self.content_type = content_type;
261 self
262 }
263
264 pub fn extra_headers(mut self, extra_headers: Option<HeaderMap>) -> Self {
266 self.extra_headers = extra_headers;
267 self
268 }
269
270 pub fn ssec(mut self, ssec: &SseCustomerKey) -> Self {
272 self.ssec_headers = Some(ssec.headers());
273 self
274 }
275
276 pub(crate) fn range(&self) -> Option<String> {
278 if self.offset > 0 || self.length > 0 {
279 Some(if self.length > 0 {
280 format!("bytes={}-{}", self.offset, self.offset + self.length - 1)
281 } else {
282 format!("bytes={}-", self.offset)
283 })
284 } else {
285 None
286 }
287 }
288
289 pub fn offset(mut self, offset: usize) -> Self {
294 self.offset = offset;
295 self
296 }
297
298 pub fn length(mut self, length: usize) -> Self {
303 self.length = length;
304 self
305 }
306
307 pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
317 self.metadata = metadata;
318 self
319 }
320
321 pub(crate) fn get_metadata_header(&self) -> Result<HeaderMap> {
323 let mut meta_header: HeaderMap = HeaderMap::new();
324 for (key, value) in &self.metadata {
325 let key = HeaderName::from_bytes(format!("x-amz-meta-{}", key).as_bytes())?;
326 meta_header.insert(key, value.parse()?);
327 }
328 Ok(meta_header)
329 }
330}
331
332impl<S> From<S> for KeyArgs
333where
334 S: Into<String>,
335{
336 fn from(name: S) -> Self {
337 Self::new(name)
338 }
339}
340
341#[derive(Debug, Clone)]
343pub struct ListMultipartUploadsArgs {
344 bucket_name: String,
345 delimiter: String,
346 encoding_type: String,
347 key_marker: Option<String>,
348 max_uploads: usize,
349 prefix: String,
350 upload_id_marker: Option<String>,
351 extra_headers: Option<HeaderMap>,
352 extra_query_params: Option<String>,
353 expected_bucket_owner: Option<String>,
354}
355
356impl ListMultipartUploadsArgs {
357 pub fn new(bucket_name: String) -> Self {
358 Self {
359 bucket_name,
360 delimiter: "".to_string(),
361 encoding_type: "".to_string(),
362 max_uploads: 1000,
363 prefix: "".to_string(),
364 key_marker: None,
365 upload_id_marker: None,
366 expected_bucket_owner: None,
367 extra_query_params: None,
368 extra_headers: None,
369 }
370 }
371
372 pub fn bucket_name(&self) -> &str {
374 &self.bucket_name
375 }
376
377 pub fn delimiter<T: Into<String>>(mut self, delimiter: T) -> Self {
379 self.delimiter = delimiter.into();
380 self
381 }
382
383 pub fn encoding_type<T: Into<String>>(mut self, encoding_type: T) -> Self {
386 self.encoding_type = encoding_type.into();
387 self
388 }
389
390 pub fn key_marker<T: Into<String>>(mut self, key_marker: T) -> Self {
391 self.key_marker = Some(key_marker.into());
392 self
393 }
394
395 pub fn upload_id_marker<T: Into<String>>(mut self, upload_id_marker: T) -> Self {
396 self.upload_id_marker = Some(upload_id_marker.into());
397 self
398 }
399
400 pub fn max_uploads(mut self, max_uploads: usize) -> Self {
401 self.max_uploads = max_uploads;
402 if self.max_uploads > 1000 {
403 self.max_uploads = 1000;
404 }
405 self
406 }
407
408 pub fn prefix<T: Into<String>>(mut self, prefix: T) -> Self {
409 self.prefix = prefix.into();
410 self
411 }
412
413 pub fn expected_bucket_owner<T: Into<String>>(mut self, expected_bucket_owner: T) -> Self {
415 self.expected_bucket_owner = Some(expected_bucket_owner.into());
416 self
417 }
418
419 pub fn extra_query_params(mut self, extra_query_params: Option<String>) -> Self {
421 self.extra_query_params = extra_query_params;
422 self
423 }
424
425 pub fn extra_headers(mut self, extra_headers: Option<HeaderMap>) -> Self {
427 self.extra_headers = extra_headers;
428 self
429 }
430
431 pub(crate) fn args_query_map(&self) -> QueryMap {
432 let mut querys: QueryMap = QueryMap::default();
433 querys.insert("uploads".to_string(), "".to_string());
434 querys.insert("delimiter".to_string(), self.delimiter.to_string());
435 querys.insert("max-uploads".to_string(), self.max_uploads.to_string());
436 querys.insert("prefix".to_string(), self.prefix.to_string());
437 querys.insert("encoding-type".to_string(), self.encoding_type.to_string());
438 if let Some(encoding_type) = &self.key_marker {
439 querys.insert("key-marker".to_string(), encoding_type.to_string());
440 }
441 if let Some(delimiter) = &self.upload_id_marker {
442 querys.insert("upload-id-marker".to_string(), delimiter.clone());
443 }
444 return querys;
445 }
446
447 pub(crate) fn args_headers(&self) -> HeaderMap {
448 let mut headermap = HeaderMap::new();
449 if let Some(owner) = &self.expected_bucket_owner {
450 if let Ok(val) = owner.parse() {
451 headermap.insert("x-amz-expected-bucket-owner", val);
452 }
453 }
454 headermap
455 }
456}
457
458pub struct ListObjectVersionsArgs {
459 pub delimiter: Option<String>,
460 pub encoding_type: Option<String>,
461 pub extra_headers: Option<HeaderMap>,
462 pub key_marker: Option<String>,
464 pub prefix: Option<String>,
465 pub max_keys: usize,
467 pub version_id_marker: Option<String>,
469}
470
471impl Default for ListObjectVersionsArgs {
472 fn default() -> Self {
473 Self {
474 extra_headers: None,
475 delimiter: None,
476 encoding_type: None,
477 max_keys: 1000,
478 prefix: None,
479 key_marker: None,
480 version_id_marker: None,
481 }
482 }
483}
484
485impl ListObjectVersionsArgs {
486 pub(crate) fn args_query_map(&self) -> QueryMap {
487 let mut querys: QueryMap = QueryMap::default();
488 querys.insert("versions".to_string(), "".to_string());
489 if let Some(delimiter) = &self.delimiter {
490 querys.insert("delimiter".to_string(), delimiter.clone());
491 }
492 if let Some(encoding_type) = &self.encoding_type {
493 querys.insert("encoding-type".to_string(), encoding_type.clone());
494 }
495 if let Some(key_marker) = &self.key_marker {
496 querys.insert("key-marker".to_string(), key_marker.clone());
497 }
498 if let Some(prefix) = &self.prefix {
499 querys.insert("prefix".to_string(), prefix.clone());
500 }
501 if let Some(version_id_marker) = &self.version_id_marker {
502 querys.insert("version-id-marker".to_string(), version_id_marker.clone());
503 }
504 querys.insert("max-keys".to_string(), format!("{}", self.max_keys));
505 querys
506 }
507}
508
509#[derive(Debug, Clone)]
517pub struct ListObjectsArgs {
518 pub(crate) continuation_token: Option<String>,
519 pub(crate) delimiter: Option<String>,
520 pub(crate) use_encoding_type: bool,
521 pub(crate) fetch_owner: bool,
522 pub(crate) start_after: Option<String>,
523 pub(crate) max_keys: usize,
524 pub(crate) prefix: Option<String>,
525 pub(crate) extra_headers: Option<HeaderMap>,
526}
527
528impl Default for ListObjectsArgs {
529 fn default() -> Self {
530 Self {
531 continuation_token: None,
532 delimiter: None,
533 fetch_owner: false,
534 max_keys: 1000,
535 prefix: None,
536 start_after: None,
537 use_encoding_type: false,
538 extra_headers: None,
539 }
540 }
541}
542
543impl ListObjectsArgs {
544 pub fn continuation_token<T: Into<String>>(mut self, token: T) -> Self {
545 self.continuation_token = Some(token.into());
546 self
547 }
548
549 pub fn delimiter<T: Into<String>>(mut self, delimiter: T) -> Self {
550 self.delimiter = Some(delimiter.into());
551 self
552 }
553
554 pub fn use_encoding_type(mut self, use_encoding_type: bool) -> Self {
555 self.use_encoding_type = use_encoding_type;
556 self
557 }
558
559 pub fn fetch_owner(mut self, fetch_owner: bool) -> Self {
560 self.fetch_owner = fetch_owner;
561 self
562 }
563
564 pub fn start_after<T: Into<String>>(mut self, start_after: T) -> Self {
565 self.start_after = Some(start_after.into());
566 self
567 }
568
569 pub fn max_keys(mut self, max_keys: usize) -> Self {
570 self.max_keys = max_keys;
571 if self.max_keys > 1000 {
572 self.max_keys = 1000;
573 }
574 self
575 }
576
577 pub fn prefix<T: Into<String>>(mut self, prefix: T) -> Self {
578 self.prefix = Some(prefix.into());
579 self
580 }
581
582 pub fn extra_headers(mut self, extra_headers: Option<HeaderMap>) -> Self {
584 self.extra_headers = extra_headers;
585 self
586 }
587
588 pub(crate) fn args_query_map(&self) -> QueryMap {
589 let mut querys: QueryMap = QueryMap::default();
590 querys.insert("list-type".to_string(), "2".to_string());
591
592 if self.use_encoding_type {
593 querys.insert("encoding-type".to_string(), "url".to_string());
594 }
595 if let Some(delimiter) = &self.delimiter {
596 querys.insert("delimiter".to_string(), delimiter.clone());
597 }
598 if let Some(token) = &self.continuation_token {
599 querys.insert("continuation-token".to_string(), token.clone());
600 }
601 if self.fetch_owner {
602 querys.insert("fetch-owner".to_string(), "true".to_string());
603 }
604 if let Some(prefix) = &self.prefix {
605 querys.insert("prefix".to_string(), prefix.clone());
606 }
607 if let Some(start_after) = &self.start_after {
608 querys.insert("start-after".to_string(), start_after.clone());
609 }
610 querys.insert("max-keys".to_string(), format!("{}", self.max_keys));
611 return querys;
612 }
613}
614
615#[derive(Debug, Clone)]
620pub struct MultipartUploadTask {
621 bucket: String,
622 key: String,
623 upload_id: String,
624 bucket_owner: Option<String>,
625 content_type: Option<String>,
626 ssec_header: Option<HeaderMap>,
627}
628
629impl From<InitiateMultipartUploadResult> for MultipartUploadTask {
630 fn from(i: InitiateMultipartUploadResult) -> Self {
631 Self::new(i.bucket, i.key, i.upload_id, None, None, None)
632 }
633}
634
635impl MultipartUploadTask {
636 pub fn new(
637 bucket: String,
638 key: String,
639 upload_id: String,
640 bucket_owner: Option<String>,
641 content_type: Option<String>,
642 ssec_header: Option<HeaderMap>,
643 ) -> Self {
644 Self {
645 bucket,
646 key,
647 upload_id,
648 bucket_owner,
649 content_type,
650 ssec_header,
651 }
652 }
653
654 pub fn bucket(&self) -> &str {
655 self.bucket.as_ref()
656 }
657
658 pub fn key(&self) -> &str {
659 self.key.as_ref()
660 }
661
662 pub fn upload_id(&self) -> &str {
663 self.upload_id.as_ref()
664 }
665
666 pub fn content_type(&self) -> Option<&String> {
667 self.content_type.as_ref()
668 }
669
670 pub fn bucket_owner(&self) -> Option<&String> {
671 self.bucket_owner.as_ref()
672 }
673
674 pub fn ssec_header(&self) -> Option<&HeaderMap> {
675 self.ssec_header.as_ref()
676 }
677
678 pub(crate) fn set_ssec_header(&mut self, ssec_header: Option<HeaderMap>) {
679 self.ssec_header = ssec_header;
680 }
681
682 pub(crate) fn set_bucket_owner(&mut self, bucket_owner: Option<String>) {
683 self.bucket_owner = bucket_owner;
684 }
685}
686
687#[derive(Debug, Clone, Default)]
692pub struct ObjectLockConfig {
693 mode: String,
695 duration: usize,
697 duration_unit: String,
699}
700
701impl ObjectLockConfig {
702 pub fn new(duration: usize, is_day: bool, is_governance: bool) -> Self {
703 let mut obj = Self::default();
704 obj.config(duration, is_day, is_governance);
705 obj
706 }
707
708 pub fn config(&mut self, duration: usize, is_day: bool, is_governance: bool) {
711 self.duration = duration;
712 self.duration_unit = (if is_day { "Days" } else { "Years" }).to_string();
713 self.mode = (if is_governance {
714 "GOVERNANCE"
715 } else {
716 "COMPLIANCE"
717 })
718 .to_string();
719 }
720
721 pub fn duration(&self) -> usize {
723 self.duration
724 }
725
726 pub fn mode(&self) -> &str {
728 self.mode.as_ref()
729 }
730
731 pub fn period(&self) -> &str {
733 self.duration_unit.as_ref()
734 }
735}
736
737impl ToXml for ObjectLockConfig {
738 fn to_xml(&self) -> crate::error::Result<String> {
739 let mut result =
740 "<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled>".to_string();
741 if !self.mode.is_empty() && !self.duration_unit.is_empty() {
742 result += "<Rule><DefaultRetention>";
743 result += &format!("<Mode>{}</Mode>", self.mode);
744 result += &format!(
745 "<{}>{}</{}>",
746 self.duration_unit, self.duration, self.duration_unit
747 );
748 result += "</DefaultRetention></Rule>";
749 }
750 result += "</ObjectLockConfiguration>";
751 Ok(result)
752 }
753}
754
755impl FromXml for ObjectLockConfig {
756 fn from_xml(value: String) -> crate::error::Result<Self> {
757 let obj = crate::xml::de::from_str::<ObjectLockConfiguration>(&value)?;
758 if let Some(rule) = obj.rule {
759 let mode = if rule.default_retention.mode == RetentionMode::GOVERNANCE {
760 "GOVERNANCE"
761 } else {
762 "COMPLIANCE"
763 };
764 if let Some(duration) = rule.default_retention.days {
765 Ok(Self {
766 mode: mode.to_owned(),
767 duration,
768 duration_unit: "Days".to_owned(),
769 })
770 } else if let Some(duration) = rule.default_retention.years {
771 Ok(Self {
772 mode: mode.to_owned(),
773 duration,
774 duration_unit: "Years".to_owned(),
775 })
776 } else {
777 Ok(Default::default())
778 }
779 } else {
780 Ok(Default::default())
781 }
782 }
783}
784
785#[derive(Clone)]
795pub struct PresignedArgs {
796 pub(crate) region: Option<String>,
797 pub(crate) bucket_name: String,
798 pub(crate) object_name: String,
799 pub(crate) version_id: Option<String>,
800 pub(crate) expires: usize,
801 pub(crate) request_date: Option<UtcTime>,
802 pub(crate) headers: Option<HeaderMap>,
803 pub(crate) querys: QueryMap,
804}
805
806impl PresignedArgs {
807 pub fn new<T1: Into<String>, T2: Into<String>>(bucket_name: T1, object_name: T2) -> Self {
808 Self {
809 region: None,
810 bucket_name: bucket_name.into(),
811 object_name: object_name.into(),
812 version_id: None,
813 expires: 604800,
814 request_date: None,
815 headers: None,
816 querys: QueryMap::new(),
817 }
818 }
819
820 pub fn region<T: Into<String>>(mut self, region: T) -> Self {
821 self.region = Some(region.into());
822 self
823 }
824
825 pub fn version_id<T: Into<String>>(mut self, version_id: T) -> Self {
826 self.version_id = Some(version_id.into());
827 self
828 }
829
830 pub fn regirequest_date(mut self, request_date: UtcTime) -> Self {
831 self.request_date = Some(request_date);
832 self
833 }
834
835 pub fn expires(mut self, expires: usize) -> Self {
836 self.expires = expires;
837 self
838 }
839
840 pub fn headers(mut self, header: HeaderMap) -> Self {
841 self.headers = Some(header);
842 self
843 }
844
845 pub fn header<K>(mut self, key: K, value: &str) -> Self
846 where
847 K: IntoHeaderName,
848 {
849 let mut headers = self.headers.unwrap_or(HeaderMap::new());
850 if let Ok(value) = value.parse() {
851 headers.insert(key, value);
852 }
853 self.headers = Some(headers);
854 self
855 }
856
857 pub fn querys(mut self, querys: QueryMap) -> Self {
858 self.querys = querys;
859 self
860 }
861
862 pub fn query<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
863 self.querys.insert(key.into(), value.into());
864 self
865 }
866
867 pub fn query_string(mut self, query_str: &str) -> Self {
868 self.querys.merge_str(query_str);
869 self
870 }
871
872 pub fn apply<F>(self, apply: F) -> Self
873 where
874 F: FnOnce(Self) -> Self,
875 {
876 apply(self)
877 }
878}
879
880#[derive(Debug, Clone)]
884pub struct Tags(HashMap<String, String>);
885
886impl Tags {
887 pub fn new() -> Self {
888 Self(HashMap::new())
889 }
890
891 pub fn to_query(&self) -> String {
892 self.0
893 .iter()
894 .map(|(key, value)| format!("{}={}", urlencode(key, false), urlencode(value, false)))
895 .collect::<Vec<String>>()
896 .join("&")
897 }
898
899 pub fn insert<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) -> &mut Self {
900 self.0.insert(key.into(), value.into());
901 self
902 }
903
904 pub fn into_map(self) -> HashMap<String, String> {
905 self.0
906 }
907}
908
909impl From<HashMap<String, String>> for Tags {
910 fn from(inner: HashMap<String, String>) -> Self {
911 Self(inner)
912 }
913}
914
915impl std::ops::Deref for Tags {
916 type Target = HashMap<String, String>;
917
918 fn deref(&self) -> &Self::Target {
919 &self.0
920 }
921}
922
923impl std::ops::DerefMut for Tags {
924 fn deref_mut(&mut self) -> &mut Self::Target {
925 &mut self.0
926 }
927}
928
929impl From<Tagging> for Tags {
930 fn from(tagging: Tagging) -> Self {
931 let mut map = HashMap::new();
932 for tag in tagging.tag_set.tags {
933 map.insert(tag.key, tag.value);
934 }
935 Self(map)
936 }
937}
938
939impl FromXml for Tags {
940 fn from_xml(v: String) -> crate::error::Result<Self> {
941 crate::xml::de::from_string::<Tagging>(v)
942 .map(Into::into)
943 .map_err(Into::into)
944 }
945}
946
947impl ToXml for Tags {
948 fn to_xml(&self) -> crate::error::Result<String> {
949 let mut result = "<Tagging><TagSet>".to_string();
950 for (key, value) in &self.0 {
951 result += &format!("<Tag><Key>{}</Key><Value>{}</Value></Tag>", key, value);
952 }
953 result += "</TagSet></Tagging>";
954 return Ok(result);
955 }
956}