Skip to main content

s3/
serde_types.rs

1#[derive(Deserialize, Debug)]
2pub struct InitiateMultipartUploadResponse {
3    #[serde(rename = "Bucket")]
4    _bucket: String,
5    #[serde(rename = "Key")]
6    pub key: String,
7    #[serde(rename = "UploadId")]
8    pub upload_id: String,
9}
10
11/// Owner information for the object
12#[derive(Deserialize, Debug, Clone)]
13pub struct Owner {
14    #[serde(rename = "DisplayName")]
15    /// Object owner's name.
16    pub display_name: Option<String>,
17    #[serde(rename = "ID")]
18    /// Object owner's ID.
19    pub id: String,
20}
21
22// <GetObjectAttributesOutput>
23//    <ETag>string</ETag>
24//    <Checksum>
25//       <ChecksumCRC32>string</ChecksumCRC32>
26//       <ChecksumCRC32C>string</ChecksumCRC32C>
27//       <ChecksumSHA1>string</ChecksumSHA1>
28//       <ChecksumSHA256>string</ChecksumSHA256>
29//    </Checksum>
30//    <ObjectParts>
31//       <IsTruncated>boolean</IsTruncated>
32//       <MaxParts>integer</MaxParts>
33//       <NextPartNumberMarker>integer</NextPartNumberMarker>
34//       <PartNumberMarker>integer</PartNumberMarker>
35//       <Part>
36//          <ChecksumCRC32>string</ChecksumCRC32>
37//          <ChecksumCRC32C>string</ChecksumCRC32C>
38//          <ChecksumSHA1>string</ChecksumSHA1>
39//          <ChecksumSHA256>string</ChecksumSHA256>
40//          <PartNumber>integer</PartNumber>
41//          <Size>long</Size>
42//       </Part>
43//       ...
44//       <PartsCount>integer</PartsCount>
45//    </ObjectParts>
46//    <StorageClass>string</StorageClass>
47//    <ObjectSize>long</ObjectSize>
48// </GetObjectAttributesOutput>
49#[derive(Deserialize, Debug)]
50pub struct GetObjectAttributesOutput {
51    #[serde(rename = "ETag")]
52    pub etag: String,
53    #[serde(rename = "Checksum")]
54    pub checksum: Checksum,
55    #[serde(rename = "ObjectParts")]
56    pub object_parts: ObjectParts,
57    #[serde(rename = "StorageClass")]
58    pub storage_class: String,
59    #[serde(rename = "ObjectSize")]
60    pub object_size: u64,
61}
62
63#[derive(Deserialize, Debug)]
64pub struct Checksum {
65    #[serde(rename = "ChecksumCRC32")]
66    pub checksum_crc32: String,
67    #[serde(rename = "ChecksumCRC32C")]
68    pub checksum_crc32c: String,
69    #[serde(rename = "ChecksumSHA1")]
70    pub checksum_sha1: String,
71    #[serde(rename = "ChecksumSHA256")]
72    pub checksum_sha256: String,
73}
74
75#[derive(Deserialize, Debug)]
76pub struct ObjectParts {
77    #[serde(rename = "IsTruncated")]
78    pub is_truncated: bool,
79    #[serde(rename = "MaxParts")]
80    pub max_parts: i32,
81    #[serde(rename = "NextPartNumberMarker")]
82    pub next_part_number_marker: i32,
83    #[serde(rename = "PartNumberMarker")]
84    pub part_number_marker: i32,
85    #[serde(rename = "Part")]
86    pub part: Vec<AttributesPart>,
87    #[serde(rename = "PartsCount")]
88    pub parts_count: u64,
89}
90
91#[derive(Deserialize, Debug)]
92pub struct AttributesPart {
93    #[serde(rename = "ChecksumCRC32")]
94    pub checksum_crc32: String,
95    #[serde(rename = "ChecksumCRC32C")]
96    pub checksum_crc32c: String,
97    #[serde(rename = "ChecksumSHA1")]
98    pub checksum_sha1: String,
99    #[serde(rename = "ChecksumSHA256")]
100    pub checksum_sha256: String,
101    #[serde(rename = "PartNumber")]
102    pub part_number: i32,
103    #[serde(rename = "Size")]
104    pub size: u64,
105}
106
107/// An individual object in a `ListBucketResult`
108#[derive(Deserialize, Debug, Clone)]
109pub struct Object {
110    #[serde(rename = "LastModified")]
111    /// Date and time the object was last modified.
112    pub last_modified: String,
113    #[serde(rename = "ETag")]
114    /// The entity tag is an MD5 hash of the object. The ETag only reflects changes to the
115    /// contents of an object, not its metadata.
116    pub e_tag: Option<String>,
117    #[serde(rename = "StorageClass")]
118    /// STANDARD | STANDARD_IA | REDUCED_REDUNDANCY | GLACIER
119    pub storage_class: Option<String>,
120    #[serde(rename = "Key")]
121    /// The object's key
122    pub key: String,
123    #[serde(rename = "Owner")]
124    /// Bucket owner
125    pub owner: Option<Owner>,
126    #[serde(rename = "Size")]
127    /// Size in bytes of the object.
128    pub size: u64,
129}
130
131/// An individual upload in a `ListMultipartUploadsResult`
132#[derive(Deserialize, Debug, Clone)]
133pub struct MultipartUpload {
134    #[serde(rename = "Initiated")]
135    /// Date and time the multipart upload was initiated
136    pub initiated: String,
137    #[serde(rename = "StorageClass")]
138    /// STANDARD | STANDARD_IA | REDUCED_REDUNDANCY | GLACIER
139    pub storage_class: String,
140    #[serde(rename = "Key")]
141    /// The object's key
142    pub key: String,
143    #[serde(rename = "Owner")]
144    /// Bucket owner
145    pub owner: Option<Owner>,
146    #[serde(rename = "UploadId")]
147    /// The identifier of the upload
148    pub id: String,
149}
150
151use std::fmt::{self};
152
153impl fmt::Display for CompleteMultipartUploadData {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        let mut parts = String::new();
156        for part in self.parts.clone() {
157            parts.push_str(&part.to_string())
158        }
159        write!(
160            f,
161            "<CompleteMultipartUpload>{}</CompleteMultipartUpload>",
162            parts
163        )
164    }
165}
166
167impl CompleteMultipartUploadData {
168    pub fn len(&self) -> usize {
169        self.to_string().len()
170    }
171
172    pub fn is_empty(&self) -> bool {
173        self.to_string().len() == 0
174    }
175}
176
177#[derive(Debug, Clone)]
178pub struct CompleteMultipartUploadData {
179    pub parts: Vec<Part>,
180}
181
182#[derive(Debug, Clone, Serialize)]
183pub struct Part {
184    #[serde(rename = "PartNumber")]
185    pub part_number: u32,
186    #[serde(rename = "ETag")]
187    pub etag: String,
188}
189
190impl fmt::Display for Part {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        write!(f, "<Part>").expect("Can't fail");
193        write!(f, "<PartNumber>{}</PartNumber>", self.part_number).expect("Can't fail");
194        write!(f, "<ETag>{}</ETag>", self.etag).expect("Can't fail");
195        write!(f, "</Part>")
196    }
197}
198
199#[derive(Deserialize, Debug, Clone)]
200pub struct BucketLocationResult {
201    #[serde(rename = "$value")]
202    pub region: String,
203}
204
205/// The parsed result of a s3 bucket listing
206///
207/// This accepts the ListBucketResult format returned for both ListObjects and ListObjectsV2
208#[derive(Deserialize, Debug, Clone)]
209pub struct ListBucketResult {
210    #[serde(rename = "Name")]
211    /// Name of the bucket.
212    pub name: String,
213    #[serde(rename = "Delimiter")]
214    /// A delimiter is a character you use to group keys.
215    pub delimiter: Option<String>,
216    #[serde(rename = "MaxKeys")]
217    /// Sets the maximum number of keys returned in the response body.
218    pub max_keys: Option<i32>,
219    #[serde(rename = "Prefix")]
220    /// Limits the response to keys that begin with the specified prefix.
221    pub prefix: Option<String>,
222    #[serde(rename = "ContinuationToken")] // for ListObjectsV2 request
223    #[serde(alias = "Marker")] // for ListObjects request
224    /// Indicates where in the bucket listing begins. It is included in the response if
225    /// it was sent with the request.
226    pub continuation_token: Option<String>,
227    #[serde(rename = "EncodingType")]
228    /// Specifies the encoding method to used
229    pub encoding_type: Option<String>,
230    #[serde(
231        default,
232        rename = "IsTruncated",
233        deserialize_with = "super::deserializer::bool_deserializer"
234    )]
235    ///  Specifies whether (true) or not (false) all of the results were returned.
236    ///  If the number of results exceeds that specified by MaxKeys, all of the results
237    ///  might not be returned.
238
239    /// When the response is truncated (that is, the IsTruncated element value in the response
240    /// is true), you can use the key name in in 'next_continuation_token' as a marker in the
241    /// subsequent request to get next set of objects. Amazon S3 lists objects in UTF-8 character
242    /// encoding in lexicographical order.
243    pub is_truncated: bool,
244    #[serde(rename = "NextContinuationToken", default)] // for ListObjectsV2 request
245    #[serde(alias = "NextMarker")] // for ListObjects request
246    pub next_continuation_token: Option<String>,
247    #[serde(rename = "Contents", default)]
248    /// Metadata about each object returned.
249    pub contents: Vec<Object>,
250    #[serde(rename = "CommonPrefixes", default)]
251    /// All of the keys rolled up into a common prefix count as a single return when
252    /// calculating the number of returns.
253    pub common_prefixes: Option<Vec<CommonPrefix>>,
254}
255
256/// The parsed result of a s3 bucket listing of uploads
257#[derive(Deserialize, Debug, Clone)]
258pub struct ListMultipartUploadsResult {
259    #[serde(rename = "Bucket")]
260    /// Name of the bucket.
261    pub name: String,
262    #[serde(rename = "NextKeyMarker")]
263    /// When the response is truncated (that is, the IsTruncated element value in the response
264    /// is true), you can use the key name in this field as a marker in the subsequent request
265    /// to get next set of objects. Amazon S3 lists objects in UTF-8 character encoding in
266    /// lexicographical order.
267    pub next_marker: Option<String>,
268    #[serde(rename = "Prefix")]
269    /// The prefix, present if the request contained a prefix too, shows the search root for the
270    /// uploads listed in this structure.
271    pub prefix: Option<String>,
272    #[serde(rename = "KeyMarker")]
273    /// Indicates where in the bucket listing begins.
274    pub marker: Option<String>,
275    #[serde(rename = "EncodingType")]
276    /// Specifies the encoding method to used
277    pub encoding_type: Option<String>,
278    #[serde(
279        rename = "IsTruncated",
280        deserialize_with = "super::deserializer::bool_deserializer"
281    )]
282    ///  Specifies whether (true) or not (false) all of the results were returned.
283    ///  If the number of results exceeds that specified by MaxKeys, all of the results
284    ///  might not be returned.
285    pub is_truncated: bool,
286    #[serde(rename = "Upload", default)]
287    /// Metadata about each upload returned.
288    pub uploads: Vec<MultipartUpload>,
289    #[serde(rename = "CommonPrefixes", default)]
290    /// All of the keys rolled up into a common prefix count as a single return when
291    /// calculating the number of returns.
292    pub common_prefixes: Option<Vec<CommonPrefix>>,
293}
294
295/// `CommonPrefix` is used to group keys
296#[derive(Deserialize, Debug, Clone)]
297pub struct CommonPrefix {
298    #[serde(rename = "Prefix")]
299    /// Keys that begin with the indicated prefix.
300    pub prefix: String,
301}
302
303// Taken from https://github.com/rusoto/rusoto
304#[derive(Deserialize, Debug, Default, Clone)]
305pub struct HeadObjectResult {
306    #[serde(rename = "AcceptRanges")]
307    /// Indicates that a range of bytes was specified.
308    pub accept_ranges: Option<String>,
309    #[serde(rename = "CacheControl")]
310    /// Specifies caching behavior along the request/reply chain.
311    pub cache_control: Option<String>,
312    #[serde(rename = "ContentDisposition")]
313    /// Specifies presentational information for the object.
314    pub content_disposition: Option<String>,
315    #[serde(rename = "ContentEncoding")]
316    /// Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to obtain the media-type referenced by the Content-Type header field.
317    pub content_encoding: Option<String>,
318    #[serde(rename = "ContentLanguage")]
319    /// The language the content is in.
320    pub content_language: Option<String>,
321    #[serde(rename = "ContentLength")]
322    /// Size of the body in bytes.
323    pub content_length: Option<i64>,
324    #[serde(rename = "ContentType")]
325    /// A standard MIME type describing the format of the object data.
326    pub content_type: Option<String>,
327    #[serde(rename = "DeleteMarker")]
328    /// Specifies whether the object retrieved was (true) or was not (false) a Delete Marker.
329    pub delete_marker: Option<bool>,
330    #[serde(rename = "ETag")]
331    /// An ETag is an opaque identifier assigned by a web server to a specific version of a resource found at a URL.
332    pub e_tag: Option<String>,
333    #[serde(rename = "Expiration")]
334    /// If the object expiration is configured, the response includes this header. It includes the expiry-date and rule-id key-value pairs providing object expiration information.
335    /// The value of the rule-id is URL encoded.
336    pub expiration: Option<String>,
337    #[serde(rename = "Expires")]
338    /// The date and time at which the object is no longer cacheable.
339    pub expires: Option<String>,
340    #[serde(rename = "LastModified")]
341    /// Last modified date of the object
342    pub last_modified: Option<String>,
343    #[serde(rename = "Metadata", default)]
344    /// A map of metadata to store with the object in S3.
345    pub metadata: Option<::std::collections::HashMap<String, String>>,
346    #[serde(rename = "MissingMeta")]
347    /// This is set to the number of metadata entries not returned in x-amz-meta headers. This can happen if you create metadata using an API like SOAP that supports more flexible metadata than
348    /// the REST API. For example, using SOAP, you can create metadata whose values are not legal HTTP headers.
349    pub missing_meta: Option<i64>,
350    #[serde(rename = "ObjectLockLegalHoldStatus")]
351    /// Specifies whether a legal hold is in effect for this object. This header is only returned if the requester has the s3:GetObjectLegalHold permission.
352    /// This header is not returned if the specified version of this object has never had a legal hold applied.
353    pub object_lock_legal_hold_status: Option<String>,
354    #[serde(rename = "ObjectLockMode")]
355    /// The Object Lock mode, if any, that's in effect for this object.
356    pub object_lock_mode: Option<String>,
357    #[serde(rename = "ObjectLockRetainUntilDate")]
358    /// The date and time when the Object Lock retention period expires.
359    /// This header is only returned if the requester has the s3:GetObjectRetention permission.
360    pub object_lock_retain_until_date: Option<String>,
361    #[serde(rename = "PartsCount")]
362    /// The count of parts this object has.
363    pub parts_count: Option<i64>,
364    #[serde(rename = "ReplicationStatus")]
365    /// If your request involves a bucket that is either a source or destination in a replication rule.
366    pub replication_status: Option<String>,
367    #[serde(rename = "RequestCharged")]
368    pub request_charged: Option<String>,
369    #[serde(rename = "Restore")]
370    /// If the object is an archived object (an object whose storage class is GLACIER), the response includes this header if either the archive restoration is in progress or an archive copy is already restored.
371    /// If an archive copy is already restored, the header value indicates when Amazon S3 is scheduled to delete the object copy.
372    pub restore: Option<String>,
373    #[serde(rename = "SseCustomerAlgorithm")]
374    /// If server-side encryption with a customer-provided encryption key was requested, the response will include this header confirming the encryption algorithm used.
375    pub sse_customer_algorithm: Option<String>,
376    #[serde(rename = "SseCustomerKeyMd5")]
377    /// If server-side encryption with a customer-provided encryption key was requested, the response will include this header to provide round-trip message integrity verification of the customer-provided encryption key.
378    pub sse_customer_key_md5: Option<String>,
379    #[serde(rename = "SsekmsKeyId")]
380    /// If present, specifies the ID of the AWS Key Management Service (AWS KMS) symmetric customer managed customer master key (CMK) that was used for the object.
381    pub ssekms_key_id: Option<String>,
382    #[serde(rename = "ServerSideEncryption")]
383    /// If the object is stored using server-side encryption either with an AWS KMS customer master key (CMK) or an Amazon S3-managed encryption key,
384    /// The response includes this header with the value of the server-side encryption algorithm used when storing this object in Amazon S3 (for example, AES256, aws:kms).
385    pub server_side_encryption: Option<String>,
386    #[serde(rename = "StorageClass")]
387    /// Provides storage class information of the object. Amazon S3 returns this header for all objects except for S3 Standard storage class objects.
388    pub storage_class: Option<String>,
389    #[serde(rename = "VersionId")]
390    /// Version of the object.
391    pub version_id: Option<String>,
392    #[serde(rename = "WebsiteRedirectLocation")]
393    /// If the bucket is configured as a website, redirects requests for this object to another object in the same bucket or to an external URL. Amazon S3 stores the value of this header in the object metadata.
394    pub website_redirect_location: Option<String>,
395}
396
397#[derive(Deserialize, Debug)]
398pub struct AwsError {
399    #[serde(rename = "Code")]
400    pub code: String,
401    #[serde(rename = "Message")]
402    pub message: String,
403    #[serde(rename = "RequestId")]
404    pub request_id: String,
405}
406
407/// Represents a single object identifier for the bulk delete request.
408#[derive(Debug, Clone)]
409pub struct ObjectIdentifier {
410    /// The key of the object to delete.
411    pub key: String,
412    /// The version ID of the object to delete (optional).
413    pub version_id: Option<String>,
414}
415
416impl ObjectIdentifier {
417    pub fn new(key: impl Into<String>) -> Self {
418        Self {
419            key: key.into(),
420            version_id: None,
421        }
422    }
423
424    pub fn with_version(key: impl Into<String>, version_id: impl Into<String>) -> Self {
425        Self {
426            key: key.into(),
427            version_id: Some(version_id.into()),
428        }
429    }
430}
431
432/// Request body for the DeleteObjects (bulk delete) API.
433#[derive(Debug, Clone)]
434pub struct DeleteObjectsRequest {
435    pub objects: Vec<ObjectIdentifier>,
436    /// If true, the response only includes errors (Quiet mode).
437    pub quiet: bool,
438}
439
440impl fmt::Display for DeleteObjectsRequest {
441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442        write!(
443            f,
444            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/">"#
445        )
446        .expect("Can't fail");
447        if self.quiet {
448            write!(f, "<Quiet>true</Quiet>").expect("Can't fail");
449        }
450        for obj in &self.objects {
451            let escaped_key = quick_xml::escape::escape(&obj.key);
452            write!(f, "<Object><Key>{}</Key>", escaped_key).expect("Can't fail");
453            if let Some(ref vid) = obj.version_id {
454                let escaped_vid = quick_xml::escape::escape(vid);
455                write!(f, "<VersionId>{}</VersionId>", escaped_vid).expect("Can't fail");
456            }
457            write!(f, "</Object>").expect("Can't fail");
458        }
459        write!(f, "</Delete>")
460    }
461}
462
463impl DeleteObjectsRequest {
464    pub fn len(&self) -> usize {
465        self.to_string().len()
466    }
467
468    pub fn is_empty(&self) -> bool {
469        self.objects.is_empty()
470    }
471}
472
473/// A single deleted object in the DeleteObjects response.
474#[derive(Deserialize, Debug, Clone)]
475pub struct DeletedObject {
476    #[serde(rename = "Key")]
477    pub key: String,
478    #[serde(rename = "VersionId")]
479    pub version_id: Option<String>,
480    #[serde(rename = "DeleteMarker")]
481    pub delete_marker: Option<bool>,
482    #[serde(rename = "DeleteMarkerVersionId")]
483    pub delete_marker_version_id: Option<String>,
484}
485
486/// A single error entry in the DeleteObjects response.
487#[derive(Deserialize, Debug, Clone)]
488pub struct DeleteError {
489    #[serde(rename = "Key")]
490    pub key: String,
491    #[serde(rename = "Code")]
492    pub code: String,
493    #[serde(rename = "Message")]
494    pub message: String,
495    #[serde(rename = "VersionId")]
496    pub version_id: Option<String>,
497}
498
499/// The parsed result of a DeleteObjects (bulk delete) response.
500#[derive(Deserialize, Debug, Clone)]
501pub struct DeleteObjectsResult {
502    #[serde(rename = "Deleted", default)]
503    pub deleted: Vec<DeletedObject>,
504    #[serde(rename = "Error", default)]
505    pub errors: Vec<DeleteError>,
506}
507
508#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
509#[serde(rename = "CORSConfiguration")]
510pub struct CorsConfiguration {
511    #[serde(rename = "CORSRule")]
512    rules: Vec<CorsRule>,
513}
514
515impl CorsConfiguration {
516    pub fn new(rules: Vec<CorsRule>) -> Self {
517        CorsConfiguration { rules }
518    }
519}
520
521impl fmt::Display for CorsConfiguration {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        let cors = quick_xml::se::to_string(&self).map_err(|_| fmt::Error)?;
524        let preamble = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
525        let cors = format!("{}{}", preamble, cors);
526        let cors = cors.replace(
527            "<CORSConfiguration>",
528            "<CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">",
529        );
530
531        write!(f, "{}", cors)
532    }
533}
534
535#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
536pub struct CorsRule {
537    #[serde(rename = "AllowedHeader")]
538    #[serde(skip_serializing_if = "Option::is_none")]
539    allowed_headers: Option<Vec<String>>,
540    #[serde(rename = "AllowedMethod")]
541    allowed_methods: Vec<String>,
542    #[serde(rename = "AllowedOrigin")]
543    allowed_origins: Vec<String>,
544    #[serde(rename = "ExposeHeader")]
545    #[serde(skip_serializing_if = "Option::is_none")]
546    expose_headers: Option<Vec<String>>,
547    #[serde(skip_serializing_if = "Option::is_none")]
548    #[serde(rename = "ID")]
549    id: Option<String>,
550    #[serde(rename = "MaxAgeSeconds")]
551    #[serde(skip_serializing_if = "Option::is_none")]
552    max_age_seconds: Option<u32>,
553}
554
555impl CorsRule {
556    pub fn new(
557        allowed_headers: Option<Vec<String>>,
558        allowed_methods: Vec<String>,
559        allowed_origins: Vec<String>,
560        expose_headers: Option<Vec<String>>,
561        id: Option<String>,
562        max_age_seconds: Option<u32>,
563    ) -> Self {
564        Self {
565            allowed_headers,
566            allowed_methods,
567            allowed_origins,
568            expose_headers,
569            id,
570            max_age_seconds,
571        }
572    }
573}
574
575#[derive(Serialize, Deserialize, Clone, Debug)]
576#[serde(rename = "LifecycleConfiguration")]
577pub struct BucketLifecycleConfiguration {
578    #[serde(rename = "Rule")]
579    pub rules: Vec<LifecycleRule>,
580}
581
582impl BucketLifecycleConfiguration {
583    pub fn new(rules: Vec<LifecycleRule>) -> Self {
584        BucketLifecycleConfiguration { rules }
585    }
586}
587
588#[derive(Serialize, Deserialize, Debug, Clone, Default)]
589pub struct LifecycleRule {
590    #[serde(
591        rename = "AbortIncompleteMultipartUpload",
592        skip_serializing_if = "Option::is_none"
593    )]
594    pub abort_incomplete_multipart_upload: Option<AbortIncompleteMultipartUpload>,
595
596    #[serde(rename = "Expiration", skip_serializing_if = "Option::is_none")]
597    pub expiration: Option<Expiration>,
598
599    #[serde(rename = "Filter", skip_serializing_if = "Option::is_none")]
600    pub filter: Option<LifecycleFilter>,
601
602    #[serde(rename = "ID", skip_serializing_if = "Option::is_none")]
603    pub id: Option<String>,
604
605    #[serde(
606        rename = "NoncurrentVersionExpiration",
607        skip_serializing_if = "Option::is_none"
608    )]
609    pub noncurrent_version_expiration: Option<NoncurrentVersionExpiration>,
610
611    #[serde(
612        rename = "NoncurrentVersionTransition",
613        skip_serializing_if = "Option::is_none"
614    )]
615    pub noncurrent_version_transition: Option<Vec<NoncurrentVersionTransition>>,
616
617    #[serde(rename = "Status")]
618    /// Valid Values: Enabled | Disabled
619    pub status: String,
620
621    #[serde(rename = "Transition", skip_serializing_if = "Option::is_none")]
622    pub transition: Option<Vec<Transition>>,
623}
624
625pub struct LifecycleRuleBuilder {
626    lifecycle_rule: LifecycleRule,
627}
628
629impl LifecycleRule {
630    pub fn builder(status: &str) -> LifecycleRuleBuilder {
631        LifecycleRuleBuilder::new(status)
632    }
633}
634
635impl LifecycleRuleBuilder {
636    pub fn new(status: &str) -> LifecycleRuleBuilder {
637        LifecycleRuleBuilder {
638            lifecycle_rule: LifecycleRule {
639                status: status.to_string(),
640                ..Default::default()
641            },
642        }
643    }
644
645    pub fn abort_incomplete_multipart_upload(
646        mut self,
647        abort_incomplete_multipart_upload: AbortIncompleteMultipartUpload,
648    ) -> LifecycleRuleBuilder {
649        self.lifecycle_rule.abort_incomplete_multipart_upload =
650            Some(abort_incomplete_multipart_upload);
651        self
652    }
653
654    pub fn expiration(mut self, expiration: Expiration) -> LifecycleRuleBuilder {
655        self.lifecycle_rule.expiration = Some(expiration);
656        self
657    }
658
659    pub fn filter(mut self, filter: LifecycleFilter) -> LifecycleRuleBuilder {
660        self.lifecycle_rule.filter = Some(filter);
661        self
662    }
663
664    pub fn id(mut self, id: &str) -> LifecycleRuleBuilder {
665        self.lifecycle_rule.id = Some(id.to_string());
666        self
667    }
668
669    pub fn noncurrent_version_expiration(
670        mut self,
671        noncurrent_version_expiration: NoncurrentVersionExpiration,
672    ) -> LifecycleRuleBuilder {
673        self.lifecycle_rule.noncurrent_version_expiration = Some(noncurrent_version_expiration);
674        self
675    }
676
677    pub fn noncurrent_version_transition(
678        mut self,
679        noncurrent_version_transition: Vec<NoncurrentVersionTransition>,
680    ) -> LifecycleRuleBuilder {
681        self.lifecycle_rule.noncurrent_version_transition = Some(noncurrent_version_transition);
682        self
683    }
684
685    pub fn transition(mut self, transition: Vec<Transition>) -> LifecycleRuleBuilder {
686        self.lifecycle_rule.transition = Some(transition);
687        self
688    }
689
690    pub fn build(self) -> LifecycleRule {
691        self.lifecycle_rule
692    }
693}
694#[derive(Serialize, Deserialize, Debug, Clone)]
695pub struct AbortIncompleteMultipartUpload {
696    #[serde(
697        rename = "DaysAfterInitiation",
698        skip_serializing_if = "Option::is_none"
699    )]
700    pub days_after_initiation: Option<i32>,
701}
702
703impl AbortIncompleteMultipartUpload {
704    pub fn new(days_after_initiation: Option<i32>) -> Self {
705        Self {
706            days_after_initiation,
707        }
708    }
709}
710
711#[derive(Serialize, Deserialize, Debug, Clone, Default)]
712pub struct Expiration {
713    /// Indicates at what date the object is to be moved or deleted. The date value must conform to the ISO 8601 format. The time is always midnight UTC.
714    #[serde(rename = "Date", skip_serializing_if = "Option::is_none")]
715    pub date: Option<String>,
716
717    #[serde(rename = "Days", skip_serializing_if = "Option::is_none")]
718    pub days: Option<u32>,
719
720    /// Indicates whether Amazon S3 will remove a delete marker with no noncurrent versions. If set to true, the delete marker will be expired; if set to false the policy takes no action. This cannot be specified with Days or Date in a Lifecycle Expiration Policy.
721    #[serde(
722        rename = "ExpiredObjectDeleteMarker",
723        skip_serializing_if = "Option::is_none"
724    )]
725    pub expired_object_delete_marker: Option<bool>,
726}
727
728impl Expiration {
729    pub fn new(
730        date: Option<String>,
731        days: Option<u32>,
732        expired_object_delete_marker: Option<bool>,
733    ) -> Self {
734        Self {
735            date,
736            days,
737            expired_object_delete_marker,
738        }
739    }
740}
741
742#[derive(Serialize, Deserialize, Debug, Clone, Default)]
743pub struct LifecycleFilter {
744    #[serde(rename = "And", skip_serializing_if = "Option::is_none")]
745    pub and: Option<And>,
746
747    #[serde(
748        rename = "ObjectSizeGreaterThan",
749        skip_serializing_if = "Option::is_none"
750    )]
751    pub object_size_greater_than: Option<i64>,
752
753    #[serde(rename = "ObjectSizeLessThan", skip_serializing_if = "Option::is_none")]
754    pub object_size_less_than: Option<i64>,
755
756    #[serde(rename = "Prefix", skip_serializing_if = "Option::is_none")]
757    pub prefix: Option<String>,
758
759    #[serde(rename = "Tag", skip_serializing_if = "Option::is_none")]
760    pub tag: Option<Tag>,
761}
762impl LifecycleFilter {
763    pub fn new(
764        and: Option<And>,
765        object_size_greater_than: Option<i64>,
766        object_size_less_than: Option<i64>,
767        prefix: Option<String>,
768        tag: Option<Tag>,
769    ) -> Self {
770        Self {
771            and,
772            object_size_greater_than,
773            object_size_less_than,
774            prefix,
775            tag,
776        }
777    }
778}
779
780#[derive(Serialize, Deserialize, Debug, Clone)]
781pub struct And {
782    #[serde(
783        rename = "ObjectSizeGreaterThan",
784        skip_serializing_if = "Option::is_none"
785    )]
786    pub object_size_greater_than: Option<i64>,
787
788    #[serde(rename = "ObjectSizeLessThan", skip_serializing_if = "Option::is_none")]
789    pub object_size_less_than: Option<i64>,
790
791    #[serde(rename = "Prefix", skip_serializing_if = "Option::is_none")]
792    pub prefix: Option<String>,
793
794    #[serde(rename = "Tag", skip_serializing_if = "Option::is_none")]
795    pub tags: Option<Vec<Tag>>,
796}
797
798impl And {
799    pub fn new(
800        object_size_greater_than: Option<i64>,
801        object_size_less_than: Option<i64>,
802        prefix: Option<String>,
803        tags: Option<Vec<Tag>>,
804    ) -> Self {
805        Self {
806            object_size_greater_than,
807            object_size_less_than,
808            prefix,
809            tags,
810        }
811    }
812}
813
814#[derive(Serialize, Deserialize, Debug, Clone)]
815pub struct Tag {
816    #[serde(rename = "Key")]
817    pub key: String,
818
819    #[serde(rename = "Value")]
820    pub value: String,
821}
822
823impl Tag {
824    pub fn new(key: &str, value: &str) -> Self {
825        Self {
826            key: key.to_string(),
827            value: value.to_string(),
828        }
829    }
830}
831
832#[derive(Serialize, Deserialize, Debug, Clone)]
833pub struct NoncurrentVersionExpiration {
834    #[serde(
835        rename = "NewerNoncurrentVersions",
836        skip_serializing_if = "Option::is_none"
837    )]
838    pub newer_noncurrent_versions: Option<i32>,
839
840    #[serde(rename = "NoncurrentDays", skip_serializing_if = "Option::is_none")]
841    pub noncurrent_days: Option<i32>,
842}
843
844impl NoncurrentVersionExpiration {
845    pub fn new(newer_noncurrent_versions: Option<i32>, noncurrent_days: Option<i32>) -> Self {
846        NoncurrentVersionExpiration {
847            newer_noncurrent_versions,
848            noncurrent_days,
849        }
850    }
851}
852
853#[derive(Serialize, Deserialize, Debug, Clone)]
854pub struct NoncurrentVersionTransition {
855    #[serde(
856        rename = "NewerNoncurrentVersions",
857        skip_serializing_if = "Option::is_none"
858    )]
859    pub newer_noncurrent_versions: Option<i32>,
860
861    #[serde(rename = "NoncurrentDays", skip_serializing_if = "Option::is_none")]
862    pub noncurrent_days: Option<i32>,
863
864    #[serde(rename = "StorageClass", skip_serializing_if = "Option::is_none")]
865    /// Valid Values: GLACIER | STANDARD_IA | ONEZONE_IA | INTELLIGENT_TIERING | DEEP_ARCHIVE | GLACIER_IR
866    pub storage_class: Option<String>,
867}
868
869impl NoncurrentVersionTransition {
870    pub fn new(
871        newer_noncurrent_versions: Option<i32>,
872        noncurrent_days: Option<i32>,
873        storage_class: Option<String>,
874    ) -> Self {
875        NoncurrentVersionTransition {
876            newer_noncurrent_versions,
877            noncurrent_days,
878            storage_class,
879        }
880    }
881}
882
883#[derive(Serialize, Deserialize, Debug, Clone)]
884pub struct Transition {
885    #[serde(rename = "Date", skip_serializing_if = "Option::is_none")]
886    pub date: Option<String>,
887
888    #[serde(rename = "Days", skip_serializing_if = "Option::is_none")]
889    pub days: Option<u32>,
890    /// Valid Values: GLACIER | STANDARD_IA | ONEZONE_IA | INTELLIGENT_TIERING | DEEP_ARCHIVE | GLACIER_IR
891    #[serde(rename = "StorageClass")]
892    pub storage_class: Option<String>,
893}
894
895impl Transition {
896    pub fn new(date: Option<String>, days: Option<u32>, storage_class: Option<String>) -> Self {
897        Transition {
898            date,
899            days,
900            storage_class,
901        }
902    }
903}
904
905#[cfg(test)]
906mod test {
907    use crate::serde_types::{
908        AbortIncompleteMultipartUpload, BucketLifecycleConfiguration, Expiration, LifecycleFilter,
909        LifecycleRule, NoncurrentVersionExpiration, NoncurrentVersionTransition, Transition,
910    };
911
912    use super::{
913        CorsConfiguration, CorsRule, DeleteObjectsRequest, DeleteObjectsResult, ObjectIdentifier,
914    };
915
916    #[test]
917    fn cors_config_serde() {
918        let rule = CorsRule {
919            allowed_headers: Some(vec!["Authorization".to_string(), "Header2".to_string()]),
920            allowed_methods: vec!["GET".to_string(), "DELETE".to_string()],
921            allowed_origins: vec!["*".to_string()],
922            expose_headers: None,
923            id: Some("lala".to_string()),
924            max_age_seconds: None,
925        };
926
927        let config = CorsConfiguration {
928            rules: vec![rule.clone(), rule],
929        };
930
931        let se = quick_xml::se::to_string(&config).unwrap();
932        assert_eq!(
933            se,
934            r#"<CORSConfiguration><CORSRule><AllowedHeader>Authorization</AllowedHeader><AllowedHeader>Header2</AllowedHeader><AllowedMethod>GET</AllowedMethod><AllowedMethod>DELETE</AllowedMethod><AllowedOrigin>*</AllowedOrigin><ID>lala</ID></CORSRule><CORSRule><AllowedHeader>Authorization</AllowedHeader><AllowedHeader>Header2</AllowedHeader><AllowedMethod>GET</AllowedMethod><AllowedMethod>DELETE</AllowedMethod><AllowedOrigin>*</AllowedOrigin><ID>lala</ID></CORSRule></CORSConfiguration>"#
935        )
936    }
937
938    #[test]
939    fn lifecycle_config_serde() {
940        let rule = LifecycleRule {
941            abort_incomplete_multipart_upload: Some(AbortIncompleteMultipartUpload {
942                days_after_initiation: Some(30),
943            }),
944            expiration: Some(Expiration {
945                date: Some("2024-06-017".to_string()),
946                days: Some(30),
947                expired_object_delete_marker: Some(true),
948            }),
949            filter: Some(LifecycleFilter {
950                and: None,
951                object_size_greater_than: Some(10),
952                object_size_less_than: Some(50),
953                prefix: None,
954                tag: None,
955            }),
956            id: Some("lala".to_string()),
957            noncurrent_version_expiration: Some(NoncurrentVersionExpiration {
958                newer_noncurrent_versions: Some(30),
959                noncurrent_days: Some(30),
960            }),
961            noncurrent_version_transition: Some(vec![NoncurrentVersionTransition {
962                newer_noncurrent_versions: Some(30),
963                noncurrent_days: Some(30),
964                storage_class: Some("GLACIER".to_string()),
965            }]),
966            status: "Enabled".to_string(),
967            transition: Some(vec![Transition {
968                date: Some("2024-06-017".to_string()),
969                days: Some(30),
970                storage_class: Some("GLACIER".to_string()),
971            }]),
972        };
973
974        let config = BucketLifecycleConfiguration { rules: vec![rule] };
975
976        let se = quick_xml::se::to_string(&config).unwrap();
977        assert_eq!(
978            se,
979            r#"<LifecycleConfiguration><Rule><AbortIncompleteMultipartUpload><DaysAfterInitiation>30</DaysAfterInitiation></AbortIncompleteMultipartUpload><Expiration><Date>2024-06-017</Date><Days>30</Days><ExpiredObjectDeleteMarker>true</ExpiredObjectDeleteMarker></Expiration><Filter><ObjectSizeGreaterThan>10</ObjectSizeGreaterThan><ObjectSizeLessThan>50</ObjectSizeLessThan></Filter><ID>lala</ID><NoncurrentVersionExpiration><NewerNoncurrentVersions>30</NewerNoncurrentVersions><NoncurrentDays>30</NoncurrentDays></NoncurrentVersionExpiration><NoncurrentVersionTransition><NewerNoncurrentVersions>30</NewerNoncurrentVersions><NoncurrentDays>30</NoncurrentDays><StorageClass>GLACIER</StorageClass></NoncurrentVersionTransition><Status>Enabled</Status><Transition><Date>2024-06-017</Date><Days>30</Days><StorageClass>GLACIER</StorageClass></Transition></Rule></LifecycleConfiguration>"#
980        )
981    }
982
983    #[test]
984    fn delete_objects_request_serialize() {
985        let request = DeleteObjectsRequest {
986            objects: vec![
987                ObjectIdentifier::new("file1.txt"),
988                ObjectIdentifier::new("file2.txt"),
989                ObjectIdentifier::with_version("file3.txt", "v1"),
990            ],
991            quiet: false,
992        };
993
994        let se = request.to_string();
995        assert_eq!(
996            se,
997            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Object><Key>file1.txt</Key></Object><Object><Key>file2.txt</Key></Object><Object><Key>file3.txt</Key><VersionId>v1</VersionId></Object></Delete>"#
998        )
999    }
1000
1001    #[test]
1002    fn delete_objects_request_serialize_quiet() {
1003        let request = DeleteObjectsRequest {
1004            objects: vec![ObjectIdentifier::new("file1.txt")],
1005            quiet: true,
1006        };
1007
1008        let se = request.to_string();
1009        assert_eq!(
1010            se,
1011            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Quiet>true</Quiet><Object><Key>file1.txt</Key></Object></Delete>"#
1012        )
1013    }
1014
1015    #[test]
1016    fn delete_objects_request_xml_escaping() {
1017        let request = DeleteObjectsRequest {
1018            objects: vec![ObjectIdentifier::new("file&name<>.txt")],
1019            quiet: false,
1020        };
1021
1022        let se = request.to_string();
1023        assert_eq!(
1024            se,
1025            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Object><Key>file&amp;name&lt;&gt;.txt</Key></Object></Delete>"#
1026        )
1027    }
1028
1029    #[test]
1030    fn delete_objects_request_len() {
1031        let request = DeleteObjectsRequest {
1032            objects: vec![ObjectIdentifier::new("file1.txt")],
1033            quiet: false,
1034        };
1035
1036        assert_eq!(request.len(), request.to_string().len());
1037        assert!(!request.is_empty());
1038    }
1039
1040    #[test]
1041    fn delete_objects_result_deserialize() {
1042        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1043<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1044  <Deleted>
1045    <Key>file1.txt</Key>
1046  </Deleted>
1047  <Deleted>
1048    <Key>file2.txt</Key>
1049    <DeleteMarker>true</DeleteMarker>
1050    <DeleteMarkerVersionId>abc123</DeleteMarkerVersionId>
1051  </Deleted>
1052  <Error>
1053    <Key>file3.txt</Key>
1054    <Code>AccessDenied</Code>
1055    <Message>Access Denied</Message>
1056  </Error>
1057</DeleteResult>"#;
1058
1059        let result: DeleteObjectsResult = quick_xml::de::from_str(xml).unwrap();
1060        assert_eq!(result.deleted.len(), 2);
1061        assert_eq!(result.deleted[0].key, "file1.txt");
1062        assert_eq!(result.deleted[1].key, "file2.txt");
1063        assert_eq!(result.deleted[1].delete_marker, Some(true));
1064        assert_eq!(
1065            result.deleted[1].delete_marker_version_id,
1066            Some("abc123".to_string())
1067        );
1068        assert_eq!(result.errors.len(), 1);
1069        assert_eq!(result.errors[0].key, "file3.txt");
1070        assert_eq!(result.errors[0].code, "AccessDenied");
1071        assert_eq!(result.errors[0].message, "Access Denied");
1072    }
1073
1074    #[test]
1075    fn delete_objects_result_deserialize_empty() {
1076        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1077<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1078</DeleteResult>"#;
1079
1080        let result: DeleteObjectsResult = quick_xml::de::from_str(xml).unwrap();
1081        assert_eq!(result.deleted.len(), 0);
1082        assert_eq!(result.errors.len(), 0);
1083    }
1084}