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, use this key marker in the subsequent request. For
264    /// general-purpose buckets, also pass [`Self::next_upload_id_marker`] when present.
265    pub next_marker: Option<String>,
266    #[serde(rename = "NextUploadIdMarker")]
267    /// Upload ID cursor for the next page. Some S3-compatible services, including directory
268    /// buckets, omit this field and continue with `NextKeyMarker` alone.
269    pub next_upload_id_marker: Option<String>,
270    #[serde(rename = "Prefix")]
271    /// The prefix, present if the request contained a prefix too, shows the search root for the
272    /// uploads listed in this structure.
273    pub prefix: Option<String>,
274    #[serde(rename = "KeyMarker")]
275    /// Indicates where in the bucket listing begins.
276    pub marker: Option<String>,
277    #[serde(rename = "UploadIdMarker")]
278    /// Upload ID cursor supplied in the request, when present.
279    pub upload_id_marker: Option<String>,
280    #[serde(rename = "EncodingType")]
281    /// Specifies the encoding method to used
282    pub encoding_type: Option<String>,
283    #[serde(
284        rename = "IsTruncated",
285        deserialize_with = "super::deserializer::bool_deserializer"
286    )]
287    ///  Specifies whether (true) or not (false) all of the results were returned.
288    ///  If the number of results exceeds that specified by MaxKeys, all of the results
289    ///  might not be returned.
290    pub is_truncated: bool,
291    #[serde(rename = "Upload", default)]
292    /// Metadata about each upload returned.
293    pub uploads: Vec<MultipartUpload>,
294    #[serde(rename = "CommonPrefixes", default)]
295    /// All of the keys rolled up into a common prefix count as a single return when
296    /// calculating the number of returns.
297    pub common_prefixes: Option<Vec<CommonPrefix>>,
298}
299
300/// `CommonPrefix` is used to group keys
301#[derive(Deserialize, Debug, Clone)]
302pub struct CommonPrefix {
303    #[serde(rename = "Prefix")]
304    /// Keys that begin with the indicated prefix.
305    pub prefix: String,
306}
307
308// Taken from https://github.com/rusoto/rusoto
309#[derive(Deserialize, Debug, Default, Clone)]
310pub struct HeadObjectResult {
311    #[serde(rename = "AcceptRanges")]
312    /// Indicates that a range of bytes was specified.
313    pub accept_ranges: Option<String>,
314    #[serde(rename = "CacheControl")]
315    /// Specifies caching behavior along the request/reply chain.
316    pub cache_control: Option<String>,
317    #[serde(rename = "ContentDisposition")]
318    /// Specifies presentational information for the object.
319    pub content_disposition: Option<String>,
320    #[serde(rename = "ContentEncoding")]
321    /// 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.
322    pub content_encoding: Option<String>,
323    #[serde(rename = "ContentLanguage")]
324    /// The language the content is in.
325    pub content_language: Option<String>,
326    #[serde(rename = "ContentLength")]
327    /// Size of the body in bytes.
328    pub content_length: Option<i64>,
329    #[serde(rename = "ContentType")]
330    /// A standard MIME type describing the format of the object data.
331    pub content_type: Option<String>,
332    #[serde(rename = "DeleteMarker")]
333    /// Specifies whether the object retrieved was (true) or was not (false) a Delete Marker.
334    pub delete_marker: Option<bool>,
335    #[serde(rename = "ETag")]
336    /// An ETag is an opaque identifier assigned by a web server to a specific version of a resource found at a URL.
337    pub e_tag: Option<String>,
338    #[serde(rename = "Expiration")]
339    /// 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.
340    /// The value of the rule-id is URL encoded.
341    pub expiration: Option<String>,
342    #[serde(rename = "Expires")]
343    /// The date and time at which the object is no longer cacheable.
344    pub expires: Option<String>,
345    #[serde(rename = "LastModified")]
346    /// Last modified date of the object
347    pub last_modified: Option<String>,
348    #[serde(rename = "Metadata", default)]
349    /// A map of metadata to store with the object in S3.
350    pub metadata: Option<::std::collections::HashMap<String, String>>,
351    #[serde(rename = "MissingMeta")]
352    /// 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
353    /// the REST API. For example, using SOAP, you can create metadata whose values are not legal HTTP headers.
354    pub missing_meta: Option<i64>,
355    #[serde(rename = "ObjectLockLegalHoldStatus")]
356    /// Specifies whether a legal hold is in effect for this object. This header is only returned if the requester has the s3:GetObjectLegalHold permission.
357    /// This header is not returned if the specified version of this object has never had a legal hold applied.
358    pub object_lock_legal_hold_status: Option<String>,
359    #[serde(rename = "ObjectLockMode")]
360    /// The Object Lock mode, if any, that's in effect for this object.
361    pub object_lock_mode: Option<String>,
362    #[serde(rename = "ObjectLockRetainUntilDate")]
363    /// The date and time when the Object Lock retention period expires.
364    /// This header is only returned if the requester has the s3:GetObjectRetention permission.
365    pub object_lock_retain_until_date: Option<String>,
366    #[serde(rename = "PartsCount")]
367    /// The count of parts this object has.
368    pub parts_count: Option<i64>,
369    #[serde(rename = "ReplicationStatus")]
370    /// If your request involves a bucket that is either a source or destination in a replication rule.
371    pub replication_status: Option<String>,
372    #[serde(rename = "RequestCharged")]
373    pub request_charged: Option<String>,
374    #[serde(rename = "Restore")]
375    /// 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.
376    /// If an archive copy is already restored, the header value indicates when Amazon S3 is scheduled to delete the object copy.
377    pub restore: Option<String>,
378    #[serde(rename = "SseCustomerAlgorithm")]
379    /// If server-side encryption with a customer-provided encryption key was requested, the response will include this header confirming the encryption algorithm used.
380    pub sse_customer_algorithm: Option<String>,
381    #[serde(rename = "SseCustomerKeyMd5")]
382    /// 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.
383    pub sse_customer_key_md5: Option<String>,
384    #[serde(rename = "SsekmsKeyId")]
385    /// 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.
386    pub ssekms_key_id: Option<String>,
387    #[serde(rename = "ServerSideEncryption")]
388    /// 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,
389    /// 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).
390    pub server_side_encryption: Option<String>,
391    #[serde(rename = "StorageClass")]
392    /// Provides storage class information of the object. Amazon S3 returns this header for all objects except for S3 Standard storage class objects.
393    pub storage_class: Option<String>,
394    #[serde(rename = "VersionId")]
395    /// Version of the object.
396    pub version_id: Option<String>,
397    #[serde(rename = "WebsiteRedirectLocation")]
398    /// 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.
399    pub website_redirect_location: Option<String>,
400}
401
402#[derive(Deserialize, Debug)]
403pub struct AwsError {
404    #[serde(rename = "Code")]
405    pub code: String,
406    #[serde(rename = "Message")]
407    pub message: String,
408    #[serde(rename = "RequestId")]
409    pub request_id: String,
410}
411
412/// Represents a single object identifier for the bulk delete request.
413#[derive(Debug, Clone)]
414pub struct ObjectIdentifier {
415    /// The key of the object to delete.
416    pub key: String,
417    /// The version ID of the object to delete (optional).
418    pub version_id: Option<String>,
419}
420
421impl ObjectIdentifier {
422    pub fn new(key: impl Into<String>) -> Self {
423        Self {
424            key: key.into(),
425            version_id: None,
426        }
427    }
428
429    pub fn with_version(key: impl Into<String>, version_id: impl Into<String>) -> Self {
430        Self {
431            key: key.into(),
432            version_id: Some(version_id.into()),
433        }
434    }
435}
436
437/// Request body for the DeleteObjects (bulk delete) API.
438#[derive(Debug, Clone)]
439pub struct DeleteObjectsRequest {
440    pub objects: Vec<ObjectIdentifier>,
441    /// If true, the response only includes errors (Quiet mode).
442    pub quiet: bool,
443}
444
445impl fmt::Display for DeleteObjectsRequest {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        write!(
448            f,
449            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/">"#
450        )
451        .expect("Can't fail");
452        if self.quiet {
453            write!(f, "<Quiet>true</Quiet>").expect("Can't fail");
454        }
455        for obj in &self.objects {
456            let escaped_key = quick_xml::escape::escape(&obj.key);
457            write!(f, "<Object><Key>{}</Key>", escaped_key).expect("Can't fail");
458            if let Some(ref vid) = obj.version_id {
459                let escaped_vid = quick_xml::escape::escape(vid);
460                write!(f, "<VersionId>{}</VersionId>", escaped_vid).expect("Can't fail");
461            }
462            write!(f, "</Object>").expect("Can't fail");
463        }
464        write!(f, "</Delete>")
465    }
466}
467
468impl DeleteObjectsRequest {
469    pub fn len(&self) -> usize {
470        self.to_string().len()
471    }
472
473    pub fn is_empty(&self) -> bool {
474        self.objects.is_empty()
475    }
476}
477
478/// A single deleted object in the DeleteObjects response.
479#[derive(Deserialize, Debug, Clone)]
480pub struct DeletedObject {
481    #[serde(rename = "Key")]
482    pub key: String,
483    #[serde(rename = "VersionId")]
484    pub version_id: Option<String>,
485    #[serde(rename = "DeleteMarker")]
486    pub delete_marker: Option<bool>,
487    #[serde(rename = "DeleteMarkerVersionId")]
488    pub delete_marker_version_id: Option<String>,
489}
490
491/// A single error entry in the DeleteObjects response.
492#[derive(Deserialize, Debug, Clone)]
493pub struct DeleteError {
494    #[serde(rename = "Key")]
495    pub key: String,
496    #[serde(rename = "Code")]
497    pub code: String,
498    #[serde(rename = "Message")]
499    pub message: String,
500    #[serde(rename = "VersionId")]
501    pub version_id: Option<String>,
502}
503
504/// The parsed result of a DeleteObjects (bulk delete) response.
505#[derive(Deserialize, Debug, Clone)]
506pub struct DeleteObjectsResult {
507    #[serde(rename = "Deleted", default)]
508    pub deleted: Vec<DeletedObject>,
509    #[serde(rename = "Error", default)]
510    pub errors: Vec<DeleteError>,
511}
512
513#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
514#[serde(rename = "CORSConfiguration")]
515pub struct CorsConfiguration {
516    #[serde(rename = "CORSRule")]
517    rules: Vec<CorsRule>,
518}
519
520impl CorsConfiguration {
521    pub fn new(rules: Vec<CorsRule>) -> Self {
522        CorsConfiguration { rules }
523    }
524}
525
526impl fmt::Display for CorsConfiguration {
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        let cors = quick_xml::se::to_string(&self).map_err(|_| fmt::Error)?;
529        let preamble = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
530        let cors = format!("{}{}", preamble, cors);
531        let cors = cors.replace(
532            "<CORSConfiguration>",
533            "<CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">",
534        );
535
536        write!(f, "{}", cors)
537    }
538}
539
540#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
541pub struct CorsRule {
542    #[serde(rename = "AllowedHeader")]
543    #[serde(skip_serializing_if = "Option::is_none")]
544    allowed_headers: Option<Vec<String>>,
545    #[serde(rename = "AllowedMethod")]
546    allowed_methods: Vec<String>,
547    #[serde(rename = "AllowedOrigin")]
548    allowed_origins: Vec<String>,
549    #[serde(rename = "ExposeHeader")]
550    #[serde(skip_serializing_if = "Option::is_none")]
551    expose_headers: Option<Vec<String>>,
552    #[serde(skip_serializing_if = "Option::is_none")]
553    #[serde(rename = "ID")]
554    id: Option<String>,
555    #[serde(rename = "MaxAgeSeconds")]
556    #[serde(skip_serializing_if = "Option::is_none")]
557    max_age_seconds: Option<u32>,
558}
559
560impl CorsRule {
561    pub fn new(
562        allowed_headers: Option<Vec<String>>,
563        allowed_methods: Vec<String>,
564        allowed_origins: Vec<String>,
565        expose_headers: Option<Vec<String>>,
566        id: Option<String>,
567        max_age_seconds: Option<u32>,
568    ) -> Self {
569        Self {
570            allowed_headers,
571            allowed_methods,
572            allowed_origins,
573            expose_headers,
574            id,
575            max_age_seconds,
576        }
577    }
578}
579
580#[derive(Serialize, Deserialize, Clone, Debug)]
581#[serde(rename = "LifecycleConfiguration")]
582pub struct BucketLifecycleConfiguration {
583    #[serde(rename = "Rule")]
584    pub rules: Vec<LifecycleRule>,
585}
586
587impl BucketLifecycleConfiguration {
588    pub fn new(rules: Vec<LifecycleRule>) -> Self {
589        BucketLifecycleConfiguration { rules }
590    }
591}
592
593#[derive(Serialize, Deserialize, Debug, Clone, Default)]
594pub struct LifecycleRule {
595    #[serde(
596        rename = "AbortIncompleteMultipartUpload",
597        skip_serializing_if = "Option::is_none"
598    )]
599    pub abort_incomplete_multipart_upload: Option<AbortIncompleteMultipartUpload>,
600
601    #[serde(rename = "Expiration", skip_serializing_if = "Option::is_none")]
602    pub expiration: Option<Expiration>,
603
604    #[serde(rename = "Filter", skip_serializing_if = "Option::is_none")]
605    pub filter: Option<LifecycleFilter>,
606
607    #[serde(rename = "ID", skip_serializing_if = "Option::is_none")]
608    pub id: Option<String>,
609
610    #[serde(
611        rename = "NoncurrentVersionExpiration",
612        skip_serializing_if = "Option::is_none"
613    )]
614    pub noncurrent_version_expiration: Option<NoncurrentVersionExpiration>,
615
616    #[serde(
617        rename = "NoncurrentVersionTransition",
618        skip_serializing_if = "Option::is_none"
619    )]
620    pub noncurrent_version_transition: Option<Vec<NoncurrentVersionTransition>>,
621
622    #[serde(rename = "Status")]
623    /// Valid Values: Enabled | Disabled
624    pub status: String,
625
626    #[serde(rename = "Transition", skip_serializing_if = "Option::is_none")]
627    pub transition: Option<Vec<Transition>>,
628}
629
630pub struct LifecycleRuleBuilder {
631    lifecycle_rule: LifecycleRule,
632}
633
634impl LifecycleRule {
635    pub fn builder(status: &str) -> LifecycleRuleBuilder {
636        LifecycleRuleBuilder::new(status)
637    }
638}
639
640impl LifecycleRuleBuilder {
641    pub fn new(status: &str) -> LifecycleRuleBuilder {
642        LifecycleRuleBuilder {
643            lifecycle_rule: LifecycleRule {
644                status: status.to_string(),
645                ..Default::default()
646            },
647        }
648    }
649
650    pub fn abort_incomplete_multipart_upload(
651        mut self,
652        abort_incomplete_multipart_upload: AbortIncompleteMultipartUpload,
653    ) -> LifecycleRuleBuilder {
654        self.lifecycle_rule.abort_incomplete_multipart_upload =
655            Some(abort_incomplete_multipart_upload);
656        self
657    }
658
659    pub fn expiration(mut self, expiration: Expiration) -> LifecycleRuleBuilder {
660        self.lifecycle_rule.expiration = Some(expiration);
661        self
662    }
663
664    pub fn filter(mut self, filter: LifecycleFilter) -> LifecycleRuleBuilder {
665        self.lifecycle_rule.filter = Some(filter);
666        self
667    }
668
669    pub fn id(mut self, id: &str) -> LifecycleRuleBuilder {
670        self.lifecycle_rule.id = Some(id.to_string());
671        self
672    }
673
674    pub fn noncurrent_version_expiration(
675        mut self,
676        noncurrent_version_expiration: NoncurrentVersionExpiration,
677    ) -> LifecycleRuleBuilder {
678        self.lifecycle_rule.noncurrent_version_expiration = Some(noncurrent_version_expiration);
679        self
680    }
681
682    pub fn noncurrent_version_transition(
683        mut self,
684        noncurrent_version_transition: Vec<NoncurrentVersionTransition>,
685    ) -> LifecycleRuleBuilder {
686        self.lifecycle_rule.noncurrent_version_transition = Some(noncurrent_version_transition);
687        self
688    }
689
690    pub fn transition(mut self, transition: Vec<Transition>) -> LifecycleRuleBuilder {
691        self.lifecycle_rule.transition = Some(transition);
692        self
693    }
694
695    pub fn build(self) -> LifecycleRule {
696        self.lifecycle_rule
697    }
698}
699#[derive(Serialize, Deserialize, Debug, Clone)]
700pub struct AbortIncompleteMultipartUpload {
701    #[serde(
702        rename = "DaysAfterInitiation",
703        skip_serializing_if = "Option::is_none"
704    )]
705    pub days_after_initiation: Option<i32>,
706}
707
708impl AbortIncompleteMultipartUpload {
709    pub fn new(days_after_initiation: Option<i32>) -> Self {
710        Self {
711            days_after_initiation,
712        }
713    }
714}
715
716#[derive(Serialize, Deserialize, Debug, Clone, Default)]
717pub struct Expiration {
718    /// 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.
719    #[serde(rename = "Date", skip_serializing_if = "Option::is_none")]
720    pub date: Option<String>,
721
722    #[serde(rename = "Days", skip_serializing_if = "Option::is_none")]
723    pub days: Option<u32>,
724
725    /// 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.
726    #[serde(
727        rename = "ExpiredObjectDeleteMarker",
728        skip_serializing_if = "Option::is_none"
729    )]
730    pub expired_object_delete_marker: Option<bool>,
731}
732
733impl Expiration {
734    pub fn new(
735        date: Option<String>,
736        days: Option<u32>,
737        expired_object_delete_marker: Option<bool>,
738    ) -> Self {
739        Self {
740            date,
741            days,
742            expired_object_delete_marker,
743        }
744    }
745}
746
747#[derive(Serialize, Deserialize, Debug, Clone, Default)]
748pub struct LifecycleFilter {
749    #[serde(rename = "And", skip_serializing_if = "Option::is_none")]
750    pub and: Option<And>,
751
752    #[serde(
753        rename = "ObjectSizeGreaterThan",
754        skip_serializing_if = "Option::is_none"
755    )]
756    pub object_size_greater_than: Option<i64>,
757
758    #[serde(rename = "ObjectSizeLessThan", skip_serializing_if = "Option::is_none")]
759    pub object_size_less_than: Option<i64>,
760
761    #[serde(rename = "Prefix", skip_serializing_if = "Option::is_none")]
762    pub prefix: Option<String>,
763
764    #[serde(rename = "Tag", skip_serializing_if = "Option::is_none")]
765    pub tag: Option<Tag>,
766}
767impl LifecycleFilter {
768    pub fn new(
769        and: Option<And>,
770        object_size_greater_than: Option<i64>,
771        object_size_less_than: Option<i64>,
772        prefix: Option<String>,
773        tag: Option<Tag>,
774    ) -> Self {
775        Self {
776            and,
777            object_size_greater_than,
778            object_size_less_than,
779            prefix,
780            tag,
781        }
782    }
783}
784
785#[derive(Serialize, Deserialize, Debug, Clone)]
786pub struct And {
787    #[serde(
788        rename = "ObjectSizeGreaterThan",
789        skip_serializing_if = "Option::is_none"
790    )]
791    pub object_size_greater_than: Option<i64>,
792
793    #[serde(rename = "ObjectSizeLessThan", skip_serializing_if = "Option::is_none")]
794    pub object_size_less_than: Option<i64>,
795
796    #[serde(rename = "Prefix", skip_serializing_if = "Option::is_none")]
797    pub prefix: Option<String>,
798
799    #[serde(rename = "Tag", skip_serializing_if = "Option::is_none")]
800    pub tags: Option<Vec<Tag>>,
801}
802
803impl And {
804    pub fn new(
805        object_size_greater_than: Option<i64>,
806        object_size_less_than: Option<i64>,
807        prefix: Option<String>,
808        tags: Option<Vec<Tag>>,
809    ) -> Self {
810        Self {
811            object_size_greater_than,
812            object_size_less_than,
813            prefix,
814            tags,
815        }
816    }
817}
818
819#[derive(Serialize, Deserialize, Debug, Clone)]
820pub struct Tag {
821    #[serde(rename = "Key")]
822    pub key: String,
823
824    #[serde(rename = "Value")]
825    pub value: String,
826}
827
828impl Tag {
829    pub fn new(key: &str, value: &str) -> Self {
830        Self {
831            key: key.to_string(),
832            value: value.to_string(),
833        }
834    }
835}
836
837#[derive(Serialize, Deserialize, Debug, Clone)]
838pub struct NoncurrentVersionExpiration {
839    #[serde(
840        rename = "NewerNoncurrentVersions",
841        skip_serializing_if = "Option::is_none"
842    )]
843    pub newer_noncurrent_versions: Option<i32>,
844
845    #[serde(rename = "NoncurrentDays", skip_serializing_if = "Option::is_none")]
846    pub noncurrent_days: Option<i32>,
847}
848
849impl NoncurrentVersionExpiration {
850    pub fn new(newer_noncurrent_versions: Option<i32>, noncurrent_days: Option<i32>) -> Self {
851        NoncurrentVersionExpiration {
852            newer_noncurrent_versions,
853            noncurrent_days,
854        }
855    }
856}
857
858#[derive(Serialize, Deserialize, Debug, Clone)]
859pub struct NoncurrentVersionTransition {
860    #[serde(
861        rename = "NewerNoncurrentVersions",
862        skip_serializing_if = "Option::is_none"
863    )]
864    pub newer_noncurrent_versions: Option<i32>,
865
866    #[serde(rename = "NoncurrentDays", skip_serializing_if = "Option::is_none")]
867    pub noncurrent_days: Option<i32>,
868
869    #[serde(rename = "StorageClass", skip_serializing_if = "Option::is_none")]
870    /// Valid Values: GLACIER | STANDARD_IA | ONEZONE_IA | INTELLIGENT_TIERING | DEEP_ARCHIVE | GLACIER_IR
871    pub storage_class: Option<String>,
872}
873
874impl NoncurrentVersionTransition {
875    pub fn new(
876        newer_noncurrent_versions: Option<i32>,
877        noncurrent_days: Option<i32>,
878        storage_class: Option<String>,
879    ) -> Self {
880        NoncurrentVersionTransition {
881            newer_noncurrent_versions,
882            noncurrent_days,
883            storage_class,
884        }
885    }
886}
887
888#[derive(Serialize, Deserialize, Debug, Clone)]
889pub struct Transition {
890    #[serde(rename = "Date", skip_serializing_if = "Option::is_none")]
891    pub date: Option<String>,
892
893    #[serde(rename = "Days", skip_serializing_if = "Option::is_none")]
894    pub days: Option<u32>,
895    /// Valid Values: GLACIER | STANDARD_IA | ONEZONE_IA | INTELLIGENT_TIERING | DEEP_ARCHIVE | GLACIER_IR
896    #[serde(rename = "StorageClass")]
897    pub storage_class: Option<String>,
898}
899
900impl Transition {
901    pub fn new(date: Option<String>, days: Option<u32>, storage_class: Option<String>) -> Self {
902        Transition {
903            date,
904            days,
905            storage_class,
906        }
907    }
908}
909
910#[cfg(test)]
911mod test {
912    use crate::serde_types::{
913        AbortIncompleteMultipartUpload, BucketLifecycleConfiguration, Expiration, LifecycleFilter,
914        LifecycleRule, NoncurrentVersionExpiration, NoncurrentVersionTransition, Transition,
915    };
916
917    use super::{
918        CorsConfiguration, CorsRule, DeleteObjectsRequest, DeleteObjectsResult, ObjectIdentifier,
919    };
920
921    #[test]
922    fn cors_config_serde() {
923        let rule = CorsRule {
924            allowed_headers: Some(vec!["Authorization".to_string(), "Header2".to_string()]),
925            allowed_methods: vec!["GET".to_string(), "DELETE".to_string()],
926            allowed_origins: vec!["*".to_string()],
927            expose_headers: None,
928            id: Some("lala".to_string()),
929            max_age_seconds: None,
930        };
931
932        let config = CorsConfiguration {
933            rules: vec![rule.clone(), rule],
934        };
935
936        let se = quick_xml::se::to_string(&config).unwrap();
937        assert_eq!(
938            se,
939            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>"#
940        )
941    }
942
943    #[test]
944    fn lifecycle_config_serde() {
945        let rule = LifecycleRule {
946            abort_incomplete_multipart_upload: Some(AbortIncompleteMultipartUpload {
947                days_after_initiation: Some(30),
948            }),
949            expiration: Some(Expiration {
950                date: Some("2024-06-017".to_string()),
951                days: Some(30),
952                expired_object_delete_marker: Some(true),
953            }),
954            filter: Some(LifecycleFilter {
955                and: None,
956                object_size_greater_than: Some(10),
957                object_size_less_than: Some(50),
958                prefix: None,
959                tag: None,
960            }),
961            id: Some("lala".to_string()),
962            noncurrent_version_expiration: Some(NoncurrentVersionExpiration {
963                newer_noncurrent_versions: Some(30),
964                noncurrent_days: Some(30),
965            }),
966            noncurrent_version_transition: Some(vec![NoncurrentVersionTransition {
967                newer_noncurrent_versions: Some(30),
968                noncurrent_days: Some(30),
969                storage_class: Some("GLACIER".to_string()),
970            }]),
971            status: "Enabled".to_string(),
972            transition: Some(vec![Transition {
973                date: Some("2024-06-017".to_string()),
974                days: Some(30),
975                storage_class: Some("GLACIER".to_string()),
976            }]),
977        };
978
979        let config = BucketLifecycleConfiguration { rules: vec![rule] };
980
981        let se = quick_xml::se::to_string(&config).unwrap();
982        assert_eq!(
983            se,
984            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>"#
985        )
986    }
987
988    #[test]
989    fn delete_objects_request_serialize() {
990        let request = DeleteObjectsRequest {
991            objects: vec![
992                ObjectIdentifier::new("file1.txt"),
993                ObjectIdentifier::new("file2.txt"),
994                ObjectIdentifier::with_version("file3.txt", "v1"),
995            ],
996            quiet: false,
997        };
998
999        let se = request.to_string();
1000        assert_eq!(
1001            se,
1002            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>"#
1003        )
1004    }
1005
1006    #[test]
1007    fn delete_objects_request_serialize_quiet() {
1008        let request = DeleteObjectsRequest {
1009            objects: vec![ObjectIdentifier::new("file1.txt")],
1010            quiet: true,
1011        };
1012
1013        let se = request.to_string();
1014        assert_eq!(
1015            se,
1016            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Quiet>true</Quiet><Object><Key>file1.txt</Key></Object></Delete>"#
1017        )
1018    }
1019
1020    #[test]
1021    fn delete_objects_request_xml_escaping() {
1022        let request = DeleteObjectsRequest {
1023            objects: vec![ObjectIdentifier::new("file&name<>.txt")],
1024            quiet: false,
1025        };
1026
1027        let se = request.to_string();
1028        assert_eq!(
1029            se,
1030            r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Object><Key>file&amp;name&lt;&gt;.txt</Key></Object></Delete>"#
1031        )
1032    }
1033
1034    #[test]
1035    fn delete_objects_request_len() {
1036        let request = DeleteObjectsRequest {
1037            objects: vec![ObjectIdentifier::new("file1.txt")],
1038            quiet: false,
1039        };
1040
1041        assert_eq!(request.len(), request.to_string().len());
1042        assert!(!request.is_empty());
1043    }
1044
1045    #[test]
1046    fn delete_objects_result_deserialize() {
1047        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1048<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1049  <Deleted>
1050    <Key>file1.txt</Key>
1051  </Deleted>
1052  <Deleted>
1053    <Key>file2.txt</Key>
1054    <DeleteMarker>true</DeleteMarker>
1055    <DeleteMarkerVersionId>abc123</DeleteMarkerVersionId>
1056  </Deleted>
1057  <Error>
1058    <Key>file3.txt</Key>
1059    <Code>AccessDenied</Code>
1060    <Message>Access Denied</Message>
1061  </Error>
1062</DeleteResult>"#;
1063
1064        let result: DeleteObjectsResult = quick_xml::de::from_str(xml).unwrap();
1065        assert_eq!(result.deleted.len(), 2);
1066        assert_eq!(result.deleted[0].key, "file1.txt");
1067        assert_eq!(result.deleted[1].key, "file2.txt");
1068        assert_eq!(result.deleted[1].delete_marker, Some(true));
1069        assert_eq!(
1070            result.deleted[1].delete_marker_version_id,
1071            Some("abc123".to_string())
1072        );
1073        assert_eq!(result.errors.len(), 1);
1074        assert_eq!(result.errors[0].key, "file3.txt");
1075        assert_eq!(result.errors[0].code, "AccessDenied");
1076        assert_eq!(result.errors[0].message, "Access Denied");
1077    }
1078
1079    #[test]
1080    fn delete_objects_result_deserialize_empty() {
1081        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1082<DeleteResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1083</DeleteResult>"#;
1084
1085        let result: DeleteObjectsResult = quick_xml::de::from_str(xml).unwrap();
1086        assert_eq!(result.deleted.len(), 0);
1087        assert_eq!(result.errors.len(), 0);
1088    }
1089}