1use std::fmt::{self, Debug};
2use std::ops::Range;
3use std::time::SystemTime;
4
5use async_trait::async_trait;
6use auto_impl::auto_impl;
7use bytes::Bytes;
8use futures::Stream;
9use mountpoint_s3_crt::s3::client::BufferPoolUsageStats;
10use std::collections::HashMap;
11use thiserror::Error;
12use time::OffsetDateTime;
13
14use crate::checksums::{
15 self, crc32_to_base64, crc32c_to_base64, crc64nvme_to_base64, sha1_to_base64, sha256_to_base64,
16};
17use crate::error_metadata::{ClientErrorMetadata, ProvideErrorMetadata};
18
19mod etag;
20pub use etag::ETag;
21
22#[cfg_attr(not(docsrs), async_trait)]
30#[auto_impl(Arc)]
31pub trait ObjectClient {
32 type GetObjectResponse: GetObjectResponse<ClientError = Self::ClientError>;
33 type PutObjectRequest: PutObjectRequest<ClientError = Self::ClientError>;
34 type ClientError: std::error::Error + ProvideErrorMetadata + Send + Sync + 'static;
35
36 fn read_part_size(&self) -> usize;
38
39 fn write_part_size(&self) -> usize;
41
42 fn initial_read_window_size(&self) -> Option<usize>;
45
46 fn mem_usage_stats(&self) -> Option<BufferPoolUsageStats>;
49
50 async fn delete_object(
54 &self,
55 bucket: &str,
56 key: &str,
57 ) -> ObjectClientResult<DeleteObjectResult, DeleteObjectError, Self::ClientError>;
58
59 async fn copy_object(
67 &self,
68 source_bucket: &str,
69 source_key: &str,
70 destination_bucket: &str,
71 destination_key: &str,
72 params: &CopyObjectParams,
73 ) -> ObjectClientResult<CopyObjectResult, CopyObjectError, Self::ClientError>;
74
75 async fn get_object(
78 &self,
79 bucket: &str,
80 key: &str,
81 params: &GetObjectParams,
82 ) -> ObjectClientResult<Self::GetObjectResponse, GetObjectError, Self::ClientError>;
83
84 async fn list_objects(
86 &self,
87 bucket: &str,
88 continuation_token: Option<&str>,
89 delimiter: &str,
90 max_keys: usize,
91 prefix: &str,
92 ) -> ObjectClientResult<ListObjectsResult, ListObjectsError, Self::ClientError>;
93
94 async fn head_object(
96 &self,
97 bucket: &str,
98 key: &str,
99 params: &HeadObjectParams,
100 ) -> ObjectClientResult<HeadObjectResult, HeadObjectError, Self::ClientError>;
101
102 async fn put_object(
105 &self,
106 bucket: &str,
107 key: &str,
108 params: &PutObjectParams,
109 ) -> ObjectClientResult<Self::PutObjectRequest, PutObjectError, Self::ClientError>;
110
111 async fn put_object_single<'a>(
113 &self,
114 bucket: &str,
115 key: &str,
116 params: &PutObjectSingleParams,
117 contents: impl AsRef<[u8]> + Send + 'a,
118 ) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
119
120 async fn get_object_attributes(
122 &self,
123 bucket: &str,
124 key: &str,
125 max_parts: Option<usize>,
126 part_number_marker: Option<usize>,
127 object_attributes: &[ObjectAttribute],
128 ) -> ObjectClientResult<GetObjectAttributesResult, GetObjectAttributesError, Self::ClientError>;
129
130 async fn rename_object(
132 &self,
133 bucket: &str,
134 src_key: &str,
135 dest_key: &str,
136 params: &RenameObjectParams,
137 ) -> ObjectClientResult<RenameObjectResult, RenameObjectError, Self::ClientError>;
138}
139
140#[derive(Debug, Error, PartialEq)]
155pub enum ObjectClientError<S, C> {
156 #[error("Service error")]
158 ServiceError(#[source] S),
159
160 #[error("Client error")]
163 ClientError(#[from] C),
164}
165
166impl<S, C> ProvideErrorMetadata for ObjectClientError<S, C>
167where
168 S: ProvideErrorMetadata,
169 C: ProvideErrorMetadata,
170{
171 fn meta(&self) -> ClientErrorMetadata {
172 match self {
173 Self::ClientError(err) => err.meta(),
174 Self::ServiceError(err) => err.meta(),
175 }
176 }
177}
178
179impl ProvideErrorMetadata for GetObjectError {
180 fn meta(&self) -> ClientErrorMetadata {
181 match self {
182 GetObjectError::NoSuchBucket(client_error_metadata) => client_error_metadata.clone(),
183 GetObjectError::NoSuchKey(client_error_metadata) => client_error_metadata.clone(),
184 GetObjectError::PreconditionFailed(client_error_metadata) => client_error_metadata.clone(),
185 }
186 }
187}
188
189impl ProvideErrorMetadata for ListObjectsError {
190 fn meta(&self) -> ClientErrorMetadata {
191 Default::default()
192 }
193}
194
195impl ProvideErrorMetadata for HeadObjectError {
196 fn meta(&self) -> ClientErrorMetadata {
197 Default::default()
198 }
199}
200
201impl ProvideErrorMetadata for DeleteObjectError {
202 fn meta(&self) -> ClientErrorMetadata {
203 Default::default()
204 }
205}
206
207impl ProvideErrorMetadata for RenameObjectError {
208 fn meta(&self) -> ClientErrorMetadata {
209 Default::default()
210 }
211}
212
213impl ProvideErrorMetadata for PutObjectError {
214 fn meta(&self) -> ClientErrorMetadata {
215 Default::default()
216 }
217}
218
219pub type ObjectClientResult<T, S, C> = Result<T, ObjectClientError<S, C>>;
221
222#[derive(Debug, Error, PartialEq, Eq)]
224#[non_exhaustive]
225pub enum GetObjectError {
226 #[error("The bucket does not exist")]
227 NoSuchBucket(ClientErrorMetadata),
228
229 #[error("The key does not exist")]
230 NoSuchKey(ClientErrorMetadata),
231
232 #[error("At least one of the preconditions specified did not hold")]
233 PreconditionFailed(ClientErrorMetadata),
234}
235
236#[derive(Debug, Default, Clone)]
238#[non_exhaustive]
239pub struct GetObjectParams {
240 pub range: Option<Range<u64>>,
241 pub if_match: Option<ETag>,
242 pub checksum_mode: Option<ChecksumMode>,
243 pub custom_id: Option<u64>,
246}
247
248impl GetObjectParams {
249 pub fn new() -> Self {
251 Self::default()
252 }
253
254 pub fn range(mut self, value: Option<Range<u64>>) -> Self {
256 self.range = value;
257 self
258 }
259
260 pub fn if_match(mut self, value: Option<ETag>) -> Self {
262 self.if_match = value;
263 self
264 }
265
266 pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
268 self.checksum_mode = value;
269 self
270 }
271
272 pub fn custom_id(mut self, value: Option<u64>) -> Self {
275 self.custom_id = value;
276 self
277 }
278}
279
280#[derive(Debug)]
282#[non_exhaustive]
283pub struct ListObjectsResult {
284 pub objects: Vec<ObjectInfo>,
286
287 pub common_prefixes: Vec<String>,
290
291 pub next_continuation_token: Option<String>,
293}
294
295#[derive(Debug, Error, PartialEq, Eq)]
297#[non_exhaustive]
298pub enum ListObjectsError {
299 #[error("The bucket does not exist")]
300 NoSuchBucket,
301}
302
303#[derive(Debug, Default, Clone)]
305#[non_exhaustive]
306pub struct HeadObjectParams {
307 pub checksum_mode: Option<ChecksumMode>,
309}
310
311impl HeadObjectParams {
312 pub fn new() -> Self {
314 Self::default()
315 }
316
317 pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
319 self.checksum_mode = value;
320 self
321 }
322}
323
324#[non_exhaustive]
326#[derive(Clone, Debug, PartialEq)]
327pub enum ChecksumMode {
328 Enabled,
330}
331
332#[derive(Debug)]
334#[non_exhaustive]
335pub struct HeadObjectResult {
336 pub size: u64,
340
341 pub last_modified: OffsetDateTime,
343
344 pub etag: ETag,
346
347 pub storage_class: Option<String>,
354
355 pub restore_status: Option<RestoreStatus>,
358 pub checksum: Checksum,
363
364 pub sse_type: Option<String>,
366
367 pub sse_kms_key_id: Option<String>,
369}
370
371#[derive(Debug, Error, PartialEq, Eq)]
373#[non_exhaustive]
374pub enum HeadObjectError {
375 #[error("The object was not found")]
377 NotFound,
378}
379
380#[derive(Debug)]
385#[non_exhaustive]
386pub struct DeleteObjectResult {}
387
388#[derive(Debug, Error, PartialEq, Eq)]
390#[non_exhaustive]
391pub enum DeleteObjectError {
392 #[error("The bucket does not exist")]
393 NoSuchBucket,
394}
395
396#[derive(Debug)]
398#[non_exhaustive]
399pub struct CopyObjectResult {
400 }
402
403#[derive(Debug, Error, PartialEq, Eq)]
405#[non_exhaustive]
406pub enum CopyObjectError {
407 #[error("The object was not found")]
409 NotFound,
410
411 #[error("The source object of the COPY action is not in the active tier and is only stored in Amazon S3 Glacier.")]
412 ObjectNotInActiveTierError,
413}
414
415#[derive(Debug, Default, Clone)]
417#[non_exhaustive]
418pub struct CopyObjectParams {
419 }
421
422impl CopyObjectParams {
423 pub fn new() -> Self {
425 Self::default()
426 }
427}
428
429#[derive(Debug, Default)]
431pub struct GetObjectAttributesResult {
432 pub etag: Option<String>,
434
435 pub checksum: Option<Checksum>,
437
438 pub object_parts: Option<GetObjectAttributesParts>,
440
441 pub storage_class: Option<String>,
443
444 pub object_size: Option<u64>,
446}
447
448#[derive(Debug, Error, PartialEq, Eq)]
450#[non_exhaustive]
451pub enum GetObjectAttributesError {
452 #[error("The bucket does not exist")]
453 NoSuchBucket,
454
455 #[error("The key does not exist")]
456 NoSuchKey,
457}
458
459#[derive(Debug, Default, Clone)]
461#[non_exhaustive]
462pub struct RenameObjectParams {
463 pub if_none_match: Option<String>,
465 pub if_match: Option<ETag>,
467 pub if_source_match: Option<ETag>,
469 pub client_token: Option<String>,
471 pub custom_headers: Vec<(String, String)>,
473}
474
475impl RenameObjectParams {
476 pub fn new() -> Self {
478 Self::default()
479 }
480
481 pub fn if_none_match(mut self, value: Option<String>) -> Self {
483 self.if_none_match = value;
484 self
485 }
486
487 pub fn if_match(mut self, value: Option<ETag>) -> Self {
489 self.if_match = value;
490 self
491 }
492
493 pub fn if_source_match(mut self, value: Option<ETag>) -> Self {
495 self.if_source_match = value;
496 self
497 }
498
499 pub fn client_token(mut self, value: Option<String>) -> Self {
501 self.client_token = value;
502 self
503 }
504
505 pub fn custom_headers(mut self, value: Vec<(String, String)>) -> Self {
507 self.custom_headers = value;
508 self
509 }
510}
511
512#[derive(Debug, Eq, PartialEq)]
514#[non_exhaustive]
515pub struct RenameObjectResult {}
516
517#[derive(Debug, Eq, PartialEq)]
518#[non_exhaustive]
519pub enum RenamePreconditionTypes {
520 IfMatch,
521 IfNoneMatch,
522 Other,
523}
524
525#[derive(Debug, Error, PartialEq, Eq)]
527#[non_exhaustive]
528pub enum RenameObjectError {
529 #[error("The bucket does not exist")]
530 NoSuchBucket,
531 #[error("The destination key provided is too long")]
532 KeyTooLong,
533 #[error("The key was not found")]
534 KeyNotFound,
535 #[error("A Precondition")]
536 PreConditionFailed(RenamePreconditionTypes),
537 #[error("The service does not implement rename")]
538 NotImplementedError,
539 #[error("The service returned an Internal Error")]
540 InternalError,
541 #[error("You do not have access to this resource")]
542 AccessDenied,
543 #[error("Bad Request")]
544 BadRequest,
545}
546
547pub type ObjectMetadata = HashMap<String, String>;
548
549#[derive(Debug, Default, Clone)]
551#[non_exhaustive]
552pub struct PutObjectParams {
553 pub trailing_checksums: PutObjectTrailingChecksums,
555 pub storage_class: Option<String>,
557 pub server_side_encryption: Option<String>,
559 pub ssekms_key_id: Option<String>,
562 pub custom_headers: Vec<(String, String)>,
564 pub object_metadata: ObjectMetadata,
566 pub custom_id: Option<u64>,
569}
570
571impl PutObjectParams {
572 pub fn new() -> Self {
574 Self::default()
575 }
576
577 pub fn trailing_checksums(mut self, value: PutObjectTrailingChecksums) -> Self {
579 self.trailing_checksums = value;
580 self
581 }
582
583 pub fn storage_class(mut self, value: String) -> Self {
585 self.storage_class = Some(value);
586 self
587 }
588
589 pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
591 self.server_side_encryption = value;
592 self
593 }
594
595 pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
597 self.ssekms_key_id = value;
598 self
599 }
600
601 pub fn add_custom_header(mut self, name: String, value: String) -> Self {
603 self.custom_headers.push((name, value));
604 self
605 }
606
607 pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
609 self.object_metadata = value;
610 self
611 }
612
613 pub fn custom_id(mut self, value: Option<u64>) -> Self {
616 self.custom_id = value;
617 self
618 }
619}
620
621#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
623pub enum PutObjectTrailingChecksums {
624 Enabled,
626 ReviewOnly,
628 #[default]
630 Disabled,
631}
632
633pub type UploadReview = mountpoint_s3_crt::s3::client::UploadReview;
635
636pub type UploadReviewPart = mountpoint_s3_crt::s3::client::UploadReviewPart;
638
639pub type ChecksumAlgorithm = mountpoint_s3_crt::s3::client::ChecksumAlgorithm;
641
642#[derive(Debug, Default, Clone)]
644#[non_exhaustive]
645pub struct PutObjectSingleParams {
646 pub checksum: Option<UploadChecksum>,
648 pub storage_class: Option<String>,
650 pub server_side_encryption: Option<String>,
652 pub ssekms_key_id: Option<String>,
655 pub if_match: Option<ETag>,
657 pub write_offset_bytes: Option<u64>,
659 pub custom_headers: Vec<(String, String)>,
661 pub object_metadata: ObjectMetadata,
663}
664
665impl PutObjectSingleParams {
666 pub fn new() -> Self {
668 Self::default()
669 }
670
671 pub fn new_for_append(offset: u64) -> Self {
673 Self::default().write_offset_bytes(offset)
674 }
675
676 pub fn checksum(mut self, value: Option<UploadChecksum>) -> Self {
678 self.checksum = value;
679 self
680 }
681
682 pub fn storage_class(mut self, value: String) -> Self {
684 self.storage_class = Some(value);
685 self
686 }
687
688 pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
690 self.server_side_encryption = value;
691 self
692 }
693
694 pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
696 self.ssekms_key_id = value;
697 self
698 }
699
700 pub fn if_match(mut self, value: Option<ETag>) -> Self {
702 self.if_match = value;
703 self
704 }
705
706 pub fn write_offset_bytes(mut self, value: u64) -> Self {
708 self.write_offset_bytes = Some(value);
709 self
710 }
711
712 pub fn add_custom_header(mut self, name: String, value: String) -> Self {
714 self.custom_headers.push((name, value));
715 self
716 }
717
718 pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
720 self.object_metadata = value;
721 self
722 }
723}
724
725#[derive(Debug, Clone)]
727#[non_exhaustive]
728pub enum UploadChecksum {
729 Crc64nvme(checksums::Crc64nvme),
730 Crc32c(checksums::Crc32c),
731 Crc32(checksums::Crc32),
732 Sha1(checksums::Sha1),
733 Sha256(checksums::Sha256),
734}
735
736impl UploadChecksum {
737 pub fn checksum_algorithm(&self) -> ChecksumAlgorithm {
739 match self {
740 UploadChecksum::Crc64nvme(_) => ChecksumAlgorithm::Crc64nvme,
741 UploadChecksum::Crc32c(_) => ChecksumAlgorithm::Crc32c,
742 UploadChecksum::Crc32(_) => ChecksumAlgorithm::Crc32,
743 UploadChecksum::Sha1(_) => ChecksumAlgorithm::Sha1,
744 UploadChecksum::Sha256(_) => ChecksumAlgorithm::Sha256,
745 }
746 }
747}
748
749pub trait ClientBackpressureHandle {
761 fn increment_read_window(&mut self, len: usize);
769
770 fn ensure_read_window(&mut self, desired_end_offset: u64);
772
773 fn read_window_end_offset(&self) -> u64;
776}
777
778#[cfg_attr(not(docsrs), async_trait)]
784pub trait GetObjectResponse:
785 Stream<Item = ObjectClientResult<GetBodyPart, GetObjectError, Self::ClientError>> + Send + Sync
786{
787 type BackpressureHandle: ClientBackpressureHandle + Clone + Send + Sync;
788 type ClientError: std::error::Error + Send + Sync + 'static;
789
790 fn backpressure_handle(&mut self) -> Option<&mut Self::BackpressureHandle>;
795
796 fn get_object_metadata(&self) -> ObjectMetadata;
798
799 fn get_object_checksum(&self) -> Result<Checksum, ObjectChecksumError>;
801}
802
803#[derive(Debug, Error)]
805pub enum ObjectChecksumError {
806 #[error("requested object checksums, but did not specify it in the request")]
807 DidNotRequestChecksums,
808 #[error("object checksum could not be retrieved from headers")]
809 HeadersError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
810}
811
812#[derive(Debug)]
815pub struct GetBodyPart {
816 pub offset: u64,
817 pub data: Bytes,
818}
819
820#[cfg_attr(not(docsrs), async_trait)]
831pub trait PutObjectRequest: Send {
832 type ClientError: std::error::Error + Send + Sync + 'static;
833
834 async fn write(&mut self, slice: &[u8]) -> ObjectClientResult<(), PutObjectError, Self::ClientError>;
836
837 async fn complete(self) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
839
840 async fn review_and_complete(
842 self,
843 review_callback: impl FnOnce(UploadReview) -> bool + Send + 'static,
844 ) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
845}
846
847#[derive(Debug)]
849#[non_exhaustive]
850pub struct PutObjectResult {
851 pub etag: ETag,
853 pub sse_type: Option<String>,
855 pub sse_kms_key_id: Option<String>,
857}
858
859#[derive(Debug, Error, PartialEq, Eq)]
861#[non_exhaustive]
862pub enum PutObjectError {
863 #[error("The bucket does not exist")]
864 NoSuchBucket,
865
866 #[error("The key does not exist")]
867 NoSuchKey,
868
869 #[error("Request body cannot be empty when write offset is specified")]
870 EmptyBody,
871
872 #[error("The offset does not match the current object size")]
873 InvalidWriteOffset,
874
875 #[error("The provided checksum does not match the data")]
876 BadChecksum,
877
878 #[error("The provided checksum is not valid or does not match the existing checksum algorithm")]
879 InvalidChecksumType,
880
881 #[error("At least one of the pre-conditions you specified did not hold")]
882 PreconditionFailed,
883
884 #[error("The server does not support the functionality required to fulfill the request")]
885 NotImplemented,
886}
887
888#[derive(Debug, Clone, Copy)]
894pub enum RestoreStatus {
895 InProgress,
898
899 Restored { expiry: SystemTime },
902}
903
904#[derive(Debug, Clone)]
909#[non_exhaustive]
910pub struct ObjectInfo {
911 pub key: String,
913
914 pub size: u64,
916
917 pub last_modified: OffsetDateTime,
919
920 pub storage_class: Option<String>,
924
925 pub restore_status: Option<RestoreStatus>,
928
929 pub etag: String,
931
932 pub checksum_algorithms: Vec<ChecksumAlgorithm>,
940}
941
942#[derive(Debug)]
945pub enum ObjectAttribute {
946 ETag,
948
949 Checksum,
951
952 ObjectParts,
954
955 StorageClass,
957
958 ObjectSize,
960}
961
962impl fmt::Display for ObjectAttribute {
963 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
964 let attr_name = match self {
965 ObjectAttribute::ETag => "ETag",
966 ObjectAttribute::Checksum => "Checksum",
967 ObjectAttribute::ObjectParts => "ObjectParts",
968 ObjectAttribute::StorageClass => "StorageClass",
969 ObjectAttribute::ObjectSize => "ObjectSize",
970 };
971 write!(f, "{attr_name}")
972 }
973}
974
975#[derive(Clone, Debug, PartialEq, Eq)]
980pub struct Checksum {
981 pub checksum_crc64nvme: Option<String>,
983
984 pub checksum_crc32: Option<String>,
986
987 pub checksum_crc32c: Option<String>,
989
990 pub checksum_sha1: Option<String>,
992
993 pub checksum_sha256: Option<String>,
995}
996
997impl Checksum {
998 pub fn empty() -> Self {
1000 Self {
1001 checksum_crc64nvme: None,
1002 checksum_crc32: None,
1003 checksum_crc32c: None,
1004 checksum_sha1: None,
1005 checksum_sha256: None,
1006 }
1007 }
1008
1009 pub fn algorithms(&self) -> Vec<ChecksumAlgorithm> {
1011 let mut algorithms = Vec::with_capacity(1);
1013
1014 let Self {
1016 checksum_crc64nvme,
1017 checksum_crc32,
1018 checksum_crc32c,
1019 checksum_sha1,
1020 checksum_sha256,
1021 } = &self;
1022
1023 if checksum_crc64nvme.is_some() {
1024 algorithms.push(ChecksumAlgorithm::Crc64nvme);
1025 }
1026 if checksum_crc32.is_some() {
1027 algorithms.push(ChecksumAlgorithm::Crc32);
1028 }
1029 if checksum_crc32c.is_some() {
1030 algorithms.push(ChecksumAlgorithm::Crc32c);
1031 }
1032 if checksum_sha1.is_some() {
1033 algorithms.push(ChecksumAlgorithm::Sha1);
1034 }
1035 if checksum_sha256.is_some() {
1036 algorithms.push(ChecksumAlgorithm::Sha256);
1037 }
1038
1039 algorithms
1040 }
1041}
1042
1043impl From<Option<UploadChecksum>> for Checksum {
1044 fn from(value: Option<UploadChecksum>) -> Self {
1045 let mut checksum = Checksum::empty();
1046 match value.as_ref() {
1047 Some(UploadChecksum::Crc64nvme(crc64)) => checksum.checksum_crc64nvme = Some(crc64nvme_to_base64(crc64)),
1048 Some(UploadChecksum::Crc32c(crc32c)) => checksum.checksum_crc32c = Some(crc32c_to_base64(crc32c)),
1049 Some(UploadChecksum::Crc32(crc32)) => checksum.checksum_crc32 = Some(crc32_to_base64(crc32)),
1050 Some(UploadChecksum::Sha1(sha1)) => checksum.checksum_sha1 = Some(sha1_to_base64(sha1)),
1051 Some(UploadChecksum::Sha256(sha256)) => checksum.checksum_sha256 = Some(sha256_to_base64(sha256)),
1052 None => {}
1053 };
1054 checksum
1055 }
1056}
1057
1058#[derive(Debug)]
1063pub struct GetObjectAttributesParts {
1064 pub is_truncated: Option<bool>,
1066
1067 pub max_parts: Option<usize>,
1069
1070 pub next_part_number_marker: Option<usize>,
1072
1073 pub part_number_marker: Option<usize>,
1075
1076 pub parts: Option<Vec<ObjectPart>>,
1078
1079 pub total_parts_count: Option<usize>,
1081}
1082
1083#[derive(Debug)]
1088pub struct ObjectPart {
1089 pub checksum: Option<Checksum>,
1091
1092 pub part_number: usize,
1094
1095 pub size: usize,
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101 use super::*;
1102
1103 #[test]
1104 fn test_checksum_algorithm_one_set() {
1105 let checksum = Checksum {
1106 checksum_crc64nvme: None,
1107 checksum_crc32: None,
1108 checksum_crc32c: None,
1109 checksum_sha1: Some("checksum_sha1".to_string()),
1110 checksum_sha256: None,
1111 };
1112 assert_eq!(checksum.algorithms(), vec![ChecksumAlgorithm::Sha1]);
1113 }
1114
1115 #[test]
1116 fn test_checksum_algorithm_none_set() {
1117 let checksum = Checksum {
1118 checksum_crc64nvme: None,
1119 checksum_crc32: None,
1120 checksum_crc32c: None,
1121 checksum_sha1: None,
1122 checksum_sha256: None,
1123 };
1124 assert_eq!(checksum.algorithms(), vec![]);
1125 }
1126
1127 #[test]
1128 fn test_checksum_algorithm_many_set() {
1129 let checksum = Checksum {
1131 checksum_crc64nvme: None,
1132 checksum_crc32: None,
1133 checksum_crc32c: Some("checksum_crc32c".to_string()),
1134 checksum_sha1: Some("checksum_sha1".to_string()),
1135 checksum_sha256: None,
1136 };
1137 assert_eq!(
1138 checksum.algorithms(),
1139 vec![ChecksumAlgorithm::Crc32c, ChecksumAlgorithm::Sha1],
1140 );
1141 }
1142}