s3 0.1.27

A lean, modern, unofficial S3-compatible client for Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
//! Public request and response types shared across operations.
//!
//! Callers should work with these stable domain types. Protocol-specific XML DTOs and parsing
//! helpers live in a private submodule so the public API stays focused on S3 concepts.
//!
//! Common entry points in this module include:
//!
//! - [`GetObjectOutput`](crate::types::GetObjectOutput) and
//!   [`BlockingGetObjectOutput`](crate::types::BlockingGetObjectOutput) for object downloads
//! - [`ListObjectsV2Output`](crate::types::ListObjectsV2Output) and
//!   [`Object`](crate::types::Object) for object listings
//! - [`ListBucketsOutput`](crate::types::ListBucketsOutput) and
//!   [`Bucket`](crate::types::Bucket) for bucket listings
//! - [`PresignedRequest`](crate::types::PresignedRequest) for presigned URL generation

use http::{HeaderMap, Method};
use url::Url;

#[cfg(any(feature = "checksums", feature = "async", feature = "blocking"))]
use crate::error::{Error, Result};
#[cfg(any(feature = "async", feature = "blocking"))]
use bytes::Bytes;

#[cfg(any(test, feature = "async", feature = "blocking"))]
pub(crate) mod xml;
#[cfg(feature = "async")]
/// Streaming response body for async operations.
pub type ByteStream =
    std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes>> + Send + 'static>>;

/// Fully resolved presigned request.
#[derive(Clone, Debug)]
pub struct PresignedRequest {
    /// HTTP method to use.
    pub method: Method,
    /// Fully signed request URL.
    pub url: Url,
    /// Headers that must accompany the request.
    pub headers: HeaderMap,
}

#[cfg(feature = "async")]
/// Output from a GET object request.
pub struct GetObjectOutput {
    /// Response body stream.
    pub body: ByteStream,
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Content length, if known.
    pub content_length: Option<u64>,
    /// Content type, if provided.
    pub content_type: Option<String>,
}

#[cfg(feature = "blocking")]
/// Blocking response body reader.
pub struct BlockingByteStream {
    inner: Box<dyn std::io::Read + 'static>,
}

#[cfg(feature = "blocking")]
impl BlockingByteStream {
    pub(crate) fn new<R>(reader: R) -> Self
    where
        R: std::io::Read + 'static,
    {
        Self {
            inner: Box::new(reader),
        }
    }
}

#[cfg(feature = "blocking")]
impl std::fmt::Debug for BlockingByteStream {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("BlockingByteStream")
            .field("inner", &"<reader>")
            .finish()
    }
}

#[cfg(feature = "blocking")]
impl std::io::Read for BlockingByteStream {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.inner.read(buf)
    }
}

#[cfg(feature = "blocking")]
/// Output from a blocking GET object request.
#[derive(Debug)]
pub struct BlockingGetObjectOutput {
    /// Response body reader.
    pub body: BlockingByteStream,
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Content length, if known.
    pub content_length: Option<u64>,
    /// Content type, if provided.
    pub content_type: Option<String>,
}

#[cfg(feature = "blocking")]
impl BlockingGetObjectOutput {
    /// Reads the full response body into memory.
    pub fn bytes(mut self) -> Result<Bytes> {
        use std::io::Read as _;

        let mut out = Vec::new();
        self.body
            .read_to_end(&mut out)
            .map_err(|e| Error::transport("failed to read response body", Some(Box::new(e))))?;
        Ok(Bytes::from(out))
    }

    /// Streams the response body into the provided writer.
    pub fn write_to<W>(mut self, writer: &mut W) -> Result<u64>
    where
        W: std::io::Write,
    {
        let bytes_copied = std::io::copy(&mut self.body, writer)
            .map_err(|e| Error::transport("failed to write response body", Some(Box::new(e))))?;
        Ok(bytes_copied)
    }
}

#[cfg(feature = "async")]
impl std::fmt::Debug for GetObjectOutput {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GetObjectOutput")
            .field("body", &"<stream>")
            .field("etag", &self.etag)
            .field("content_length", &self.content_length)
            .field("content_type", &self.content_type)
            .finish()
    }
}

#[cfg(feature = "async")]
impl GetObjectOutput {
    /// Collects the response body into memory.
    pub async fn bytes(self) -> Result<Bytes> {
        use futures_util::StreamExt as _;

        let mut out = Vec::new();
        let mut stream = self.body;
        while let Some(chunk) = stream.next().await {
            out.extend_from_slice(&chunk?);
        }
        Ok(Bytes::from(out))
    }

    /// Streams the response body into the provided writer.
    pub async fn write_to<W>(self, writer: &mut W) -> Result<u64>
    where
        W: futures_io::AsyncWrite + Unpin,
    {
        use futures_util::{StreamExt as _, io::AsyncWriteExt as _};

        let mut written = 0u64;
        let mut stream = self.body;
        while let Some(chunk) = stream.next().await {
            let chunk = chunk?;
            writer.write_all(&chunk).await.map_err(|e| {
                Error::transport("failed to write response body", Some(Box::new(e)))
            })?;
            written = written.saturating_add(chunk.len() as u64);
        }

        writer
            .flush()
            .await
            .map_err(|e| Error::transport("failed to flush writer", Some(Box::new(e))))?;

        Ok(written)
    }
}

/// Output from a HEAD object request.
#[derive(Debug)]
pub struct HeadObjectOutput {
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Content length, if known.
    pub content_length: Option<u64>,
    /// Content type, if provided.
    pub content_type: Option<String>,
}

/// Output from a PUT object request.
#[derive(Debug)]
pub struct PutObjectOutput {
    /// Entity tag, if provided.
    pub etag: Option<String>,
}

#[cfg(feature = "checksums")]
/// Supported checksum algorithms.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChecksumAlgorithm {
    /// CRC32 (ISO HDLC).
    Crc32,
    /// CRC32C (Castagnoli).
    Crc32c,
    /// SHA-1.
    Sha1,
    /// SHA-256.
    Sha256,
}

#[cfg(feature = "checksums")]
impl ChecksumAlgorithm {
    /// Returns the checksum header name for this algorithm.
    pub fn header_name(self) -> http::header::HeaderName {
        match self {
            Self::Crc32 => http::header::HeaderName::from_static("x-amz-checksum-crc32"),
            Self::Crc32c => http::header::HeaderName::from_static("x-amz-checksum-crc32c"),
            Self::Sha1 => http::header::HeaderName::from_static("x-amz-checksum-sha1"),
            Self::Sha256 => http::header::HeaderName::from_static("x-amz-checksum-sha256"),
        }
    }
}

#[cfg(feature = "checksums")]
/// Checksum value to send with a request.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Checksum {
    /// Checksum algorithm.
    pub algorithm: ChecksumAlgorithm,
    /// Base64-encoded checksum value.
    pub value: String,
}

#[cfg(feature = "checksums")]
impl Checksum {
    /// Creates a checksum with a pre-encoded value.
    pub fn new(algorithm: ChecksumAlgorithm, value: impl Into<String>) -> Self {
        Self {
            algorithm,
            value: value.into(),
        }
    }

    /// Computes a checksum from raw bytes.
    pub fn from_bytes(algorithm: ChecksumAlgorithm, bytes: impl AsRef<[u8]>) -> Self {
        use base64::Engine as _;

        let bytes = bytes.as_ref();
        let value = match algorithm {
            ChecksumAlgorithm::Crc32 => {
                const CRC32: crc::Crc<u32> = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
                let checksum = CRC32.checksum(bytes).to_be_bytes();
                base64::engine::general_purpose::STANDARD.encode(checksum)
            }
            ChecksumAlgorithm::Crc32c => {
                const CRC32C: crc::Crc<u32> = crc::Crc::<u32>::new(&crc::CRC_32_ISCSI);
                let checksum = CRC32C.checksum(bytes).to_be_bytes();
                base64::engine::general_purpose::STANDARD.encode(checksum)
            }
            ChecksumAlgorithm::Sha1 => {
                use sha1::Digest as _;

                let digest = sha1::Sha1::digest(bytes);
                base64::engine::general_purpose::STANDARD.encode(digest)
            }
            ChecksumAlgorithm::Sha256 => {
                use graviola::hashing::{Hash as _, Sha256};

                let digest = Sha256::hash(bytes);
                base64::engine::general_purpose::STANDARD.encode(digest.as_ref())
            }
        };

        Self { algorithm, value }
    }

    pub(crate) fn apply(&self, headers: &mut HeaderMap) -> Result<()> {
        let value = http::HeaderValue::from_str(&self.value)
            .map_err(|_| Error::invalid_config("invalid checksum header value"))?;
        headers.insert(self.algorithm.header_name(), value);
        Ok(())
    }
}

/// Output from a DELETE object request.
#[derive(Debug)]
pub struct DeleteObjectOutput;

/// Output from a multi-delete request.
#[derive(Debug)]
pub struct DeleteObjectsOutput {
    /// Successfully deleted objects.
    pub deleted: Vec<DeletedObject>,
    /// Per-object errors.
    pub errors: Vec<DeleteObjectError>,
}

/// Identifier for an object in delete requests.
#[derive(Clone, Debug)]
pub struct DeleteObjectIdentifier {
    /// Object key.
    pub key: String,
    /// Optional version id.
    pub version_id: Option<String>,
}

impl DeleteObjectIdentifier {
    /// Creates an identifier from an object key.
    pub fn new(key: impl Into<String>) -> Self {
        Self {
            key: key.into(),
            version_id: None,
        }
    }

    /// Sets the version id for this identifier.
    pub fn with_version_id(mut self, version_id: impl Into<String>) -> Self {
        self.version_id = Some(version_id.into());
        self
    }
}

/// Successfully deleted object metadata.
#[derive(Debug)]
pub struct DeletedObject {
    /// Object key, if reported.
    pub key: Option<String>,
    /// Version id, if reported.
    pub version_id: Option<String>,
    /// Whether a delete marker was created.
    pub delete_marker: Option<bool>,
    /// Delete marker version id, if reported.
    pub delete_marker_version_id: Option<String>,
}

/// Error metadata for a failed delete entry.
#[derive(Debug)]
pub struct DeleteObjectError {
    /// Object key, if reported.
    pub key: Option<String>,
    /// Version id, if reported.
    pub version_id: Option<String>,
    /// Error code, if reported.
    pub code: Option<String>,
    /// Error message, if reported.
    pub message: Option<String>,
}

/// Output from a copy object request.
#[derive(Debug)]
pub struct CopyObjectOutput {
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Last-modified timestamp, if provided.
    pub last_modified: Option<String>,
}

#[cfg(feature = "multipart")]
/// Output from initiating a multipart upload.
#[derive(Debug)]
pub struct CreateMultipartUploadOutput {
    /// Bucket name, if provided.
    pub bucket: Option<String>,
    /// Object key, if provided.
    pub key: Option<String>,
    /// Upload id to use for subsequent part uploads.
    pub upload_id: String,
}

#[cfg(feature = "multipart")]
/// Output from uploading a multipart part.
#[derive(Debug)]
pub struct UploadPartOutput {
    /// Entity tag for the uploaded part.
    pub etag: Option<String>,
}

#[cfg(feature = "multipart")]
/// Completed part descriptor for multipart completion.
#[derive(Clone, Debug)]
pub struct CompletedPart {
    /// Part number.
    pub part_number: u32,
    /// Part etag.
    pub etag: String,
}

#[cfg(feature = "multipart")]
/// Output from completing a multipart upload.
#[derive(Debug)]
pub struct CompleteMultipartUploadOutput {
    /// Object location, if provided.
    pub location: Option<String>,
    /// Bucket name, if provided.
    pub bucket: Option<String>,
    /// Object key, if provided.
    pub key: Option<String>,
    /// Entity tag, if provided.
    pub etag: Option<String>,
}

#[cfg(feature = "multipart")]
/// Output from aborting a multipart upload.
#[derive(Debug)]
pub struct AbortMultipartUploadOutput;

#[cfg(feature = "multipart")]
/// Output from listing multipart parts.
#[derive(Debug)]
pub struct ListPartsOutput {
    /// Bucket name, if provided.
    pub bucket: Option<String>,
    /// Object key, if provided.
    pub key: Option<String>,
    /// Upload id, if provided.
    pub upload_id: Option<String>,
    /// Whether the listing is truncated.
    pub is_truncated: bool,
    /// Marker for the current page.
    pub part_number_marker: Option<u32>,
    /// Marker for the next page.
    pub next_part_number_marker: Option<u32>,
    /// Maximum number of parts requested.
    pub max_parts: Option<u32>,
    /// Listed parts.
    pub parts: Vec<Part>,
}

#[cfg(feature = "multipart")]
/// Metadata for a multipart part.
#[derive(Debug)]
pub struct Part {
    /// Part number.
    pub part_number: u32,
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Size in bytes.
    pub size: u64,
    /// Last-modified timestamp, if provided.
    pub last_modified: Option<String>,
}

#[cfg(feature = "multipart")]
/// Output from copying a multipart part.
#[derive(Debug)]
pub struct UploadPartCopyOutput {
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Last-modified timestamp, if provided.
    pub last_modified: Option<String>,
}

/// Output from a ListObjectsV2 request.
#[derive(Debug)]
pub struct ListObjectsV2Output {
    /// Bucket name.
    pub name: String,
    /// Prefix filter, if any.
    pub prefix: Option<String>,
    /// Delimiter used for grouping, if any.
    pub delimiter: Option<String>,
    /// Whether the listing is truncated.
    pub is_truncated: bool,
    /// Number of keys returned, if reported.
    pub key_count: Option<u32>,
    /// Maximum number of keys requested.
    pub max_keys: Option<u32>,
    /// Continuation token used for this response, if any.
    pub continuation_token: Option<String>,
    /// Continuation token for the next page, if any.
    pub next_continuation_token: Option<String>,
    /// Listed objects.
    pub contents: Vec<Object>,
    /// Common prefixes when using delimiters.
    pub common_prefixes: Vec<String>,
}

/// Object metadata returned by list operations.
#[derive(Debug)]
pub struct Object {
    /// Object key.
    pub key: String,
    /// Object size in bytes.
    pub size: u64,
    /// Entity tag, if provided.
    pub etag: Option<String>,
    /// Last-modified timestamp, if provided.
    pub last_modified: Option<String>,
    /// Storage class, if provided.
    pub storage_class: Option<String>,
}

/// Output from listing buckets.
#[derive(Debug)]
pub struct ListBucketsOutput {
    /// Owner information, if provided.
    pub owner: Option<BucketOwner>,
    /// Buckets returned in the response.
    pub buckets: Vec<Bucket>,
}

/// Bucket owner metadata.
#[derive(Debug)]
pub struct BucketOwner {
    /// Owner id, if provided.
    pub id: Option<String>,
    /// Owner display name, if provided.
    pub display_name: Option<String>,
}

/// Bucket listing entry.
#[derive(Debug)]
pub struct Bucket {
    /// Bucket name.
    pub name: String,
    /// Creation date, if provided.
    pub creation_date: Option<String>,
}

/// Output from a HEAD bucket request.
#[derive(Debug)]
pub struct HeadBucketOutput {
    /// Bucket region, if provided.
    pub region: Option<String>,
}

/// Output from a create bucket request.
#[derive(Debug)]
pub struct CreateBucketOutput;

/// Output from a delete bucket request.
#[derive(Debug)]
pub struct DeleteBucketOutput;

/// Bucket versioning configuration.
#[derive(Clone, Debug, Default)]
pub struct BucketVersioningConfiguration {
    /// Versioning status.
    pub status: Option<BucketVersioningStatus>,
    /// MFA delete status.
    pub mfa_delete: Option<BucketMfaDeleteStatus>,
}

/// Versioning status values.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BucketVersioningStatus {
    /// Enable versioning.
    Enabled,
    /// Suspend versioning.
    Suspended,
}

/// MFA delete status values.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BucketMfaDeleteStatus {
    /// Enable MFA delete.
    Enabled,
    /// Disable MFA delete.
    Disabled,
}

/// Output from updating bucket versioning.
#[derive(Debug)]
pub struct PutBucketVersioningOutput;

/// Bucket lifecycle configuration.
#[derive(Clone, Debug, Default)]
pub struct BucketLifecycleConfiguration {
    /// Lifecycle rules.
    pub rules: Vec<BucketLifecycleRule>,
}

/// Lifecycle rule definition.
#[derive(Clone, Debug)]
pub struct BucketLifecycleRule {
    /// Optional rule id.
    pub id: Option<String>,
    /// Rule status.
    pub status: BucketLifecycleStatus,
    /// Prefix filter.
    pub prefix: Option<String>,
    /// Expiration in days.
    pub expiration_days: Option<u32>,
    /// Expiration date (ISO 8601), if provided.
    pub expiration_date: Option<String>,
}

/// Lifecycle rule status values.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BucketLifecycleStatus {
    /// Enable the rule.
    #[default]
    Enabled,
    /// Disable the rule.
    Disabled,
}

/// Output from updating bucket lifecycle configuration.
#[derive(Debug)]
pub struct PutBucketLifecycleOutput;

/// Output from deleting bucket lifecycle configuration.
#[derive(Debug)]
pub struct DeleteBucketLifecycleOutput;

/// Bucket CORS configuration.
#[derive(Clone, Debug, Default)]
pub struct BucketCorsConfiguration {
    /// CORS rules.
    pub rules: Vec<BucketCorsRule>,
}

/// Bucket CORS rule definition.
#[derive(Clone, Debug)]
pub struct BucketCorsRule {
    /// Optional rule id.
    pub id: Option<String>,
    /// Allowed origins.
    pub allowed_origins: Vec<String>,
    /// Allowed methods.
    pub allowed_methods: Vec<CorsMethod>,
    /// Allowed headers.
    pub allowed_headers: Vec<String>,
    /// Exposed headers.
    pub expose_headers: Vec<String>,
    /// Max age in seconds.
    pub max_age_seconds: Option<u32>,
}

/// Allowed CORS method.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CorsMethod {
    /// HTTP GET.
    Get,
    /// HTTP PUT.
    Put,
    /// HTTP POST.
    Post,
    /// HTTP DELETE.
    Delete,
    /// HTTP HEAD.
    Head,
    /// Custom method.
    Other(String),
}

impl CorsMethod {
    /// Returns the wire value for this method.
    pub fn as_str(&self) -> &str {
        match self {
            Self::Get => "GET",
            Self::Put => "PUT",
            Self::Post => "POST",
            Self::Delete => "DELETE",
            Self::Head => "HEAD",
            Self::Other(v) => v.as_str(),
        }
    }
}

/// Output from updating bucket CORS configuration.
#[derive(Debug)]
pub struct PutBucketCorsOutput;

/// Output from deleting bucket CORS configuration.
#[derive(Debug)]
pub struct DeleteBucketCorsOutput;

/// Bucket tag set.
#[derive(Clone, Debug, Default)]
pub struct BucketTagging {
    /// Tags associated with the bucket.
    pub tags: Vec<Tag>,
}

/// Tag key/value pair.
#[derive(Clone, Debug)]
pub struct Tag {
    /// Tag key.
    pub key: String,
    /// Tag value.
    pub value: String,
}

/// Output from updating bucket tags.
#[derive(Debug)]
pub struct PutBucketTaggingOutput;

/// Output from deleting bucket tags.
#[derive(Debug)]
pub struct DeleteBucketTaggingOutput;

/// Bucket encryption configuration.
#[derive(Clone, Debug, Default)]
pub struct BucketEncryptionConfiguration {
    /// Encryption rules.
    pub rules: Vec<BucketEncryptionRule>,
}

/// Bucket encryption rule definition.
#[derive(Clone, Debug)]
pub struct BucketEncryptionRule {
    /// Default server-side encryption settings.
    pub apply: ApplyServerSideEncryptionByDefault,
    /// Whether bucket keys are enabled.
    pub bucket_key_enabled: Option<bool>,
}

/// Default server-side encryption settings.
#[derive(Clone, Debug)]
pub struct ApplyServerSideEncryptionByDefault {
    /// Server-side encryption algorithm.
    pub sse_algorithm: SseAlgorithm,
    /// KMS master key id, if applicable.
    pub kms_master_key_id: Option<String>,
}

/// Server-side encryption algorithm.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SseAlgorithm {
    /// AES-256.
    Aes256,
    /// AWS KMS.
    AwsKms,
    /// AWS KMS with DSSE.
    AwsKmsDsse,
    /// Custom algorithm.
    Other(String),
}

impl SseAlgorithm {
    /// Returns the wire value for this algorithm.
    pub fn as_str(&self) -> &str {
        match self {
            Self::Aes256 => "AES256",
            Self::AwsKms => "aws:kms",
            Self::AwsKmsDsse => "aws:kms:dsse",
            Self::Other(v) => v.as_str(),
        }
    }
}

/// Output from updating bucket encryption configuration.
#[derive(Debug)]
pub struct PutBucketEncryptionOutput;

/// Output from deleting bucket encryption configuration.
#[derive(Debug)]
pub struct DeleteBucketEncryptionOutput;

/// Bucket public access block configuration.
#[derive(Clone, Debug, Default)]
pub struct BucketPublicAccessBlockConfiguration {
    /// Block public ACLs.
    pub block_public_acls: bool,
    /// Ignore public ACLs.
    pub ignore_public_acls: bool,
    /// Block public bucket policies.
    pub block_public_policy: bool,
    /// Restrict public buckets.
    pub restrict_public_buckets: bool,
}

/// Output from updating public access block settings.
#[derive(Debug)]
pub struct PutBucketPublicAccessBlockOutput;

/// Output from deleting public access block settings.
#[derive(Debug)]
pub struct DeleteBucketPublicAccessBlockOutput;

#[cfg(all(test, feature = "checksums"))]
mod checksum_tests {
    use super::{Checksum, ChecksumAlgorithm};

    #[test]
    fn from_bytes_matches_known_vectors() {
        let bytes = b"hello";

        assert_eq!(
            Checksum::from_bytes(ChecksumAlgorithm::Sha256, bytes).value,
            "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
        );
        assert_eq!(
            Checksum::from_bytes(ChecksumAlgorithm::Sha1, bytes).value,
            "qvTGHdzF6KLavt4PO0gs2a6pQ00="
        );
        assert_eq!(
            Checksum::from_bytes(ChecksumAlgorithm::Crc32, bytes).value,
            "NhCmhg=="
        );
        assert_eq!(
            Checksum::from_bytes(ChecksumAlgorithm::Crc32c, bytes).value,
            "mnG7TA=="
        );
    }
}