1use std::collections::HashMap;
2
3use hyper::{
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 {
373 &self.bucket_name
374 }
375
376 pub fn delimiter<T: Into<String>>(mut self, delimiter: T) -> Self {
377 self.delimiter = delimiter.into();
378 self
379 }
380
381 pub fn encoding_type<T: Into<String>>(mut self, encoding_type: T) -> Self {
382 self.encoding_type = encoding_type.into();
383 self
384 }
385
386 pub fn key_marker<T: Into<String>>(mut self, key_marker: T) -> Self {
387 self.key_marker = Some(key_marker.into());
388 self
389 }
390
391 pub fn upload_id_marker<T: Into<String>>(mut self, upload_id_marker: T) -> Self {
392 self.upload_id_marker = Some(upload_id_marker.into());
393 self
394 }
395
396 pub fn max_uploads(mut self, max_uploads: usize) -> Self {
397 self.max_uploads = max_uploads;
398 if self.max_uploads > 1000 {
399 self.max_uploads = 1000;
400 }
401 self
402 }
403
404 pub fn prefix<T: Into<String>>(mut self, prefix: T) -> Self {
405 self.prefix = prefix.into();
406 self
407 }
408
409 pub fn expected_bucket_owner<T: Into<String>>(mut self, expected_bucket_owner: T) -> Self {
410 self.expected_bucket_owner = Some(expected_bucket_owner.into());
411 self
412 }
413
414 pub fn extra_query_params(mut self, extra_query_params: Option<String>) -> Self {
416 self.extra_query_params = extra_query_params;
417 self
418 }
419
420 pub fn extra_headers(mut self, extra_headers: Option<HeaderMap>) -> Self {
422 self.extra_headers = extra_headers;
423 self
424 }
425
426 pub(crate) fn args_query_map(&self) -> QueryMap {
427 let mut querys: QueryMap = QueryMap::default();
428 querys.insert("uploads".to_string(), "".to_string());
429 querys.insert("delimiter".to_string(), self.delimiter.to_string());
430 querys.insert("max-uploads".to_string(), self.max_uploads.to_string());
431 querys.insert("prefix".to_string(), self.prefix.to_string());
432 querys.insert("encoding-type".to_string(), self.encoding_type.to_string());
433 if let Some(encoding_type) = &self.key_marker {
434 querys.insert("key-marker".to_string(), encoding_type.to_string());
435 }
436 if let Some(delimiter) = &self.upload_id_marker {
437 querys.insert("upload-id-marker".to_string(), delimiter.clone());
438 }
439 return querys;
440 }
441
442 pub(crate) fn args_headers(&self) -> HeaderMap {
443 let mut headermap = HeaderMap::new();
444 if let Some(owner) = &self.expected_bucket_owner {
445 if let Ok(val) = owner.parse() {
446 headermap.insert("x-amz-expected-bucket-owner", val);
447 }
448 }
449 headermap
450 }
451}
452
453pub struct ListObjectVersionsArgs {
454 pub delimiter: Option<String>,
455 pub encoding_type: Option<String>,
456 pub extra_headers: Option<HeaderMap>,
457 pub key_marker: Option<String>,
459 pub prefix: Option<String>,
460 pub max_keys: usize,
462 pub version_id_marker: Option<String>,
464}
465
466impl Default for ListObjectVersionsArgs {
467 fn default() -> Self {
468 Self {
469 extra_headers: None,
470 delimiter: None,
471 encoding_type: None,
472 max_keys: 1000,
473 prefix: None,
474 key_marker: None,
475 version_id_marker: None,
476 }
477 }
478}
479
480impl ListObjectVersionsArgs {
481 pub(crate) fn args_query_map(&self) -> QueryMap {
482 let mut querys: QueryMap = QueryMap::default();
483 querys.insert("versions".to_string(), "".to_string());
484 if let Some(delimiter) = &self.delimiter {
485 querys.insert("delimiter".to_string(), delimiter.clone());
486 }
487 if let Some(encoding_type) = &self.encoding_type {
488 querys.insert("encoding-type".to_string(), encoding_type.clone());
489 }
490 if let Some(key_marker) = &self.key_marker {
491 querys.insert("key-marker".to_string(), key_marker.clone());
492 }
493 if let Some(prefix) = &self.prefix {
494 querys.insert("prefix".to_string(), prefix.clone());
495 }
496 if let Some(version_id_marker) = &self.version_id_marker {
497 querys.insert("version-id-marker".to_string(), version_id_marker.clone());
498 }
499 querys.insert("max-keys".to_string(), format!("{}", self.max_keys));
500 querys
501 }
502}
503
504#[derive(Debug, Clone)]
512pub struct ListObjectsArgs {
513 pub(crate) continuation_token: Option<String>,
514 pub(crate) delimiter: Option<String>,
515 pub(crate) use_encoding_type: bool,
516 pub(crate) fetch_owner: bool,
517 pub(crate) start_after: Option<String>,
518 pub(crate) max_keys: usize,
519 pub(crate) prefix: Option<String>,
520 pub(crate) extra_headers: Option<HeaderMap>,
521}
522
523impl Default for ListObjectsArgs {
524 fn default() -> Self {
525 Self {
526 continuation_token: None,
527 delimiter: None,
528 fetch_owner: false,
529 max_keys: 1000,
530 prefix: None,
531 start_after: None,
532 use_encoding_type: false,
533 extra_headers: None,
534 }
535 }
536}
537
538impl ListObjectsArgs {
539 pub fn continuation_token<T: Into<String>>(mut self, token: T) -> Self {
540 self.continuation_token = Some(token.into());
541 self
542 }
543
544 pub fn delimiter<T: Into<String>>(mut self, delimiter: T) -> Self {
545 self.delimiter = Some(delimiter.into());
546 self
547 }
548
549 pub fn use_encoding_type(mut self, use_encoding_type: bool) -> Self {
550 self.use_encoding_type = use_encoding_type;
551 self
552 }
553
554 pub fn fetch_owner(mut self, fetch_owner: bool) -> Self {
555 self.fetch_owner = fetch_owner;
556 self
557 }
558
559 pub fn start_after<T: Into<String>>(mut self, start_after: T) -> Self {
560 self.start_after = Some(start_after.into());
561 self
562 }
563
564 pub fn max_keys(mut self, max_keys: usize) -> Self {
565 self.max_keys = max_keys;
566 if self.max_keys > 1000 {
567 self.max_keys = 1000;
568 }
569 self
570 }
571
572 pub fn prefix<T: Into<String>>(mut self, prefix: T) -> Self {
573 self.prefix = Some(prefix.into());
574 self
575 }
576
577 pub fn extra_headers(mut self, extra_headers: Option<HeaderMap>) -> Self {
579 self.extra_headers = extra_headers;
580 self
581 }
582
583 pub(crate) fn args_query_map(&self) -> QueryMap {
584 let mut querys: QueryMap = QueryMap::default();
585 querys.insert("list-type".to_string(), "2".to_string());
586
587 if self.use_encoding_type {
588 querys.insert("encoding-type".to_string(), "url".to_string());
589 }
590 if let Some(delimiter) = &self.delimiter {
591 querys.insert("delimiter".to_string(), delimiter.clone());
592 }
593 if let Some(token) = &self.continuation_token {
594 querys.insert("continuation-token".to_string(), token.clone());
595 }
596 if self.fetch_owner {
597 querys.insert("fetch-owner".to_string(), "true".to_string());
598 }
599 if let Some(prefix) = &self.prefix {
600 querys.insert("prefix".to_string(), prefix.clone());
601 }
602 if let Some(start_after) = &self.start_after {
603 querys.insert("start-after".to_string(), start_after.clone());
604 }
605 querys.insert("max-keys".to_string(), format!("{}", self.max_keys));
606 return querys;
607 }
608}
609
610#[derive(Debug, Clone)]
615pub struct MultipartUploadTask {
616 bucket: String,
617 key: String,
618 upload_id: String,
619 bucket_owner: Option<String>,
620 content_type: Option<String>,
621 ssec_header: Option<HeaderMap>,
622}
623
624impl From<InitiateMultipartUploadResult> for MultipartUploadTask {
625 fn from(i: InitiateMultipartUploadResult) -> Self {
626 Self::new(i.bucket, i.key, i.upload_id, None, None, None)
627 }
628}
629
630impl MultipartUploadTask {
631 pub fn new(
632 bucket: String,
633 key: String,
634 upload_id: String,
635 bucket_owner: Option<String>,
636 content_type: Option<String>,
637 ssec_header: Option<HeaderMap>,
638 ) -> Self {
639 Self {
640 bucket,
641 key,
642 upload_id,
643 bucket_owner,
644 content_type,
645 ssec_header,
646 }
647 }
648
649 pub fn bucket(&self) -> &str {
650 self.bucket.as_ref()
651 }
652
653 pub fn key(&self) -> &str {
654 self.key.as_ref()
655 }
656
657 pub fn upload_id(&self) -> &str {
658 self.upload_id.as_ref()
659 }
660
661 pub fn content_type(&self) -> Option<&String> {
662 self.content_type.as_ref()
663 }
664
665 pub fn bucket_owner(&self) -> Option<&String> {
666 self.bucket_owner.as_ref()
667 }
668
669 pub fn ssec_header(&self) -> Option<&HeaderMap> {
670 self.ssec_header.as_ref()
671 }
672
673 pub(crate) fn set_ssec(&mut self, ssec: SseCustomerKey) {
674 self.ssec_header = Some(ssec.headers());
675 }
676
677 pub(crate) fn set_ssec_header(&mut self, ssec_header: Option<HeaderMap>) {
678 self.ssec_header = ssec_header;
679 }
680
681 pub(crate) fn set_bucket_owner(&mut self, bucket_owner: Option<String>) {
682 self.bucket_owner = bucket_owner;
683 }
684}
685
686#[derive(Debug, Clone, Default)]
691pub struct ObjectLockConfig {
692 mode: String,
694 duration: usize,
696 duration_unit: String,
698}
699
700impl ObjectLockConfig {
701 pub fn new(duration: usize, is_day: bool, is_governance: bool) -> Self {
702 let mut obj = Self::default();
703 obj.config(duration, is_day, is_governance);
704 obj
705 }
706
707 pub fn config(&mut self, duration: usize, is_day: bool, is_governance: bool) {
710 self.duration = duration;
711 self.duration_unit = (if is_day { "Days" } else { "Years" }).to_string();
712 self.mode = (if is_governance {
713 "GOVERNANCE"
714 } else {
715 "COMPLIANCE"
716 })
717 .to_string();
718 }
719
720 pub fn duration(&self) -> usize {
722 self.duration
723 }
724
725 pub fn mode(&self) -> &str {
727 self.mode.as_ref()
728 }
729
730 pub fn period(&self) -> &str {
732 self.duration_unit.as_ref()
733 }
734}
735
736impl ToXml for ObjectLockConfig {
737 fn to_xml(&self) -> crate::error::Result<String> {
738 let mut result =
739 "<ObjectLockConfiguration><ObjectLockEnabled>Enabled</ObjectLockEnabled>".to_string();
740 if !self.mode.is_empty() && !self.duration_unit.is_empty() {
741 result += "<Rule><DefaultRetention>";
742 result += &format!("<Mode>{}</Mode>", self.mode);
743 result += &format!(
744 "<{}>{}</{}>",
745 self.duration_unit, self.duration, self.duration_unit
746 );
747 result += "</DefaultRetention></Rule>";
748 }
749 result += "</ObjectLockConfiguration>";
750 Ok(result)
751 }
752}
753
754impl FromXml for ObjectLockConfig {
755 fn from_xml(value: String) -> crate::error::Result<Self> {
756 let obj = crate::xml::de::from_str::<ObjectLockConfiguration>(&value)?;
757 if let Some(rule) = obj.rule {
758 let mode = if rule.default_retention.mode == RetentionMode::GOVERNANCE {
759 "GOVERNANCE"
760 } else {
761 "COMPLIANCE"
762 };
763 if let Some(duration) = rule.default_retention.days {
764 Ok(Self {
765 mode: mode.to_owned(),
766 duration,
767 duration_unit: "Days".to_owned(),
768 })
769 } else if let Some(duration) = rule.default_retention.years {
770 Ok(Self {
771 mode: mode.to_owned(),
772 duration,
773 duration_unit: "Years".to_owned(),
774 })
775 } else {
776 Ok(Default::default())
777 }
778 } else {
779 Ok(Default::default())
780 }
781 }
782}
783
784#[derive(Clone)]
794pub struct PresignedArgs {
795 pub(crate) region: Option<String>,
796 pub(crate) bucket_name: String,
797 pub(crate) object_name: String,
798 pub(crate) version_id: Option<String>,
799 pub(crate) expires: usize,
800 pub(crate) request_date: Option<UtcTime>,
801 pub(crate) headers: Option<HeaderMap>,
802 pub(crate) querys: QueryMap,
803}
804
805impl PresignedArgs {
806 pub fn new<T1: Into<String>, T2: Into<String>>(bucket_name: T1, object_name: T2) -> Self {
807 Self {
808 region: None,
809 bucket_name: bucket_name.into(),
810 object_name: object_name.into(),
811 version_id: None,
812 expires: 604800,
813 request_date: None,
814 headers: None,
815 querys: QueryMap::new(),
816 }
817 }
818
819 pub fn region<T: Into<String>>(mut self, region: T) -> Self {
820 self.region = Some(region.into());
821 self
822 }
823
824 pub fn version_id<T: Into<String>>(mut self, version_id: T) -> Self {
825 self.version_id = Some(version_id.into());
826 self
827 }
828
829 pub fn regirequest_date(mut self, request_date: UtcTime) -> Self {
830 self.request_date = Some(request_date);
831 self
832 }
833
834 pub fn expires(mut self, expires: usize) -> Self {
835 self.expires = expires;
836 self
837 }
838
839 pub fn headers(mut self, header: HeaderMap) -> Self {
840 self.headers = Some(header);
841 self
842 }
843
844 pub fn header<K>(mut self, key: K, value: &str) -> Self
845 where
846 K: IntoHeaderName,
847 {
848 let mut headers = self.headers.unwrap_or(HeaderMap::new());
849 if let Ok(value) = value.parse() {
850 headers.insert(key, value);
851 }
852 self.headers = Some(headers);
853 self
854 }
855
856 pub fn querys(mut self, querys: QueryMap) -> Self {
857 self.querys = querys;
858 self
859 }
860
861 pub fn query<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
862 self.querys.insert(key.into(), value.into());
863 self
864 }
865
866 pub fn query_string(mut self, query_str: &str) -> Self {
867 self.querys.merge_str(query_str);
868 self
869 }
870
871 pub fn apply<F>(self, apply: F) -> Self
872 where
873 F: FnOnce(Self) -> Self,
874 {
875 apply(self)
876 }
877}
878
879#[derive(Debug, Clone)]
883pub struct Tags(HashMap<String, String>);
884
885impl Tags {
886 pub fn new() -> Self {
887 Self(HashMap::new())
888 }
889
890 pub fn to_query(&self) -> String {
891 self.0
892 .iter()
893 .map(|(key, value)| format!("{}={}", urlencode(key, false), urlencode(value, false)))
894 .collect::<Vec<String>>()
895 .join("&")
896 }
897
898 pub fn insert<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) -> &mut Self {
899 self.0.insert(key.into(), value.into());
900 self
901 }
902
903 pub fn into_map(self) -> HashMap<String, String> {
904 self.0
905 }
906}
907
908impl From<HashMap<String, String>> for Tags {
909 fn from(inner: HashMap<String, String>) -> Self {
910 Self(inner)
911 }
912}
913
914impl std::ops::Deref for Tags {
915 type Target = HashMap<String, String>;
916
917 fn deref(&self) -> &Self::Target {
918 &self.0
919 }
920}
921
922impl std::ops::DerefMut for Tags {
923 fn deref_mut(&mut self) -> &mut Self::Target {
924 &mut self.0
925 }
926}
927
928impl From<Tagging> for Tags {
929 fn from(tagging: Tagging) -> Self {
930 let mut map = HashMap::new();
931 for tag in tagging.tag_set.tags {
932 map.insert(tag.key, tag.value);
933 }
934 Self(map)
935 }
936}
937
938impl FromXml for Tags {
939 fn from_xml(v: String) -> crate::error::Result<Self> {
940 crate::xml::de::from_string::<Tagging>(v)
941 .map(Into::into)
942 .map_err(Into::into)
943 }
944}
945
946impl ToXml for Tags {
947 fn to_xml(&self) -> crate::error::Result<String> {
948 let mut result = "<Tagging><TagSet>".to_string();
949 for (key, value) in &self.0 {
950 result += &format!("<Tag><Key>{}</Key><Value>{}</Value></Tag>", key, value);
951 }
952 result += "</TagSet></Tagging>";
953 return Ok(result);
954 }
955}