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}
244
245impl GetObjectParams {
246 pub fn new() -> Self {
248 Self::default()
249 }
250
251 pub fn range(mut self, value: Option<Range<u64>>) -> Self {
253 self.range = value;
254 self
255 }
256
257 pub fn if_match(mut self, value: Option<ETag>) -> Self {
259 self.if_match = value;
260 self
261 }
262
263 pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
265 self.checksum_mode = value;
266 self
267 }
268}
269
270#[derive(Debug)]
272#[non_exhaustive]
273pub struct ListObjectsResult {
274 pub objects: Vec<ObjectInfo>,
276
277 pub common_prefixes: Vec<String>,
280
281 pub next_continuation_token: Option<String>,
283}
284
285#[derive(Debug, Error, PartialEq, Eq)]
287#[non_exhaustive]
288pub enum ListObjectsError {
289 #[error("The bucket does not exist")]
290 NoSuchBucket,
291}
292
293#[derive(Debug, Default, Clone)]
295#[non_exhaustive]
296pub struct HeadObjectParams {
297 pub checksum_mode: Option<ChecksumMode>,
299}
300
301impl HeadObjectParams {
302 pub fn new() -> Self {
304 Self::default()
305 }
306
307 pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
309 self.checksum_mode = value;
310 self
311 }
312}
313
314#[non_exhaustive]
316#[derive(Clone, Debug, PartialEq)]
317pub enum ChecksumMode {
318 Enabled,
320}
321
322#[derive(Debug)]
324#[non_exhaustive]
325pub struct HeadObjectResult {
326 pub size: u64,
330
331 pub last_modified: OffsetDateTime,
333
334 pub etag: ETag,
336
337 pub storage_class: Option<String>,
344
345 pub restore_status: Option<RestoreStatus>,
348 pub checksum: Checksum,
353
354 pub sse_type: Option<String>,
356
357 pub sse_kms_key_id: Option<String>,
359}
360
361#[derive(Debug, Error, PartialEq, Eq)]
363#[non_exhaustive]
364pub enum HeadObjectError {
365 #[error("The object was not found")]
367 NotFound,
368}
369
370#[derive(Debug)]
375#[non_exhaustive]
376pub struct DeleteObjectResult {}
377
378#[derive(Debug, Error, PartialEq, Eq)]
380#[non_exhaustive]
381pub enum DeleteObjectError {
382 #[error("The bucket does not exist")]
383 NoSuchBucket,
384}
385
386#[derive(Debug)]
388#[non_exhaustive]
389pub struct CopyObjectResult {
390 }
392
393#[derive(Debug, Error, PartialEq, Eq)]
395#[non_exhaustive]
396pub enum CopyObjectError {
397 #[error("The object was not found")]
399 NotFound,
400
401 #[error("The source object of the COPY action is not in the active tier and is only stored in Amazon S3 Glacier.")]
402 ObjectNotInActiveTierError,
403}
404
405#[derive(Debug, Default, Clone)]
407#[non_exhaustive]
408pub struct CopyObjectParams {
409 }
411
412impl CopyObjectParams {
413 pub fn new() -> Self {
415 Self::default()
416 }
417}
418
419#[derive(Debug, Default)]
421pub struct GetObjectAttributesResult {
422 pub etag: Option<String>,
424
425 pub checksum: Option<Checksum>,
427
428 pub object_parts: Option<GetObjectAttributesParts>,
430
431 pub storage_class: Option<String>,
433
434 pub object_size: Option<u64>,
436}
437
438#[derive(Debug, Error, PartialEq, Eq)]
440#[non_exhaustive]
441pub enum GetObjectAttributesError {
442 #[error("The bucket does not exist")]
443 NoSuchBucket,
444
445 #[error("The key does not exist")]
446 NoSuchKey,
447}
448
449#[derive(Debug, Default, Clone)]
451#[non_exhaustive]
452pub struct RenameObjectParams {
453 pub if_none_match: Option<String>,
455 pub if_match: Option<ETag>,
457 pub if_source_match: Option<ETag>,
459 pub client_token: Option<String>,
461 pub custom_headers: Vec<(String, String)>,
463}
464
465impl RenameObjectParams {
466 pub fn new() -> Self {
468 Self::default()
469 }
470
471 pub fn if_none_match(mut self, value: Option<String>) -> Self {
473 self.if_none_match = value;
474 self
475 }
476
477 pub fn if_match(mut self, value: Option<ETag>) -> Self {
479 self.if_match = value;
480 self
481 }
482
483 pub fn if_source_match(mut self, value: Option<ETag>) -> Self {
485 self.if_source_match = value;
486 self
487 }
488
489 pub fn client_token(mut self, value: Option<String>) -> Self {
491 self.client_token = value;
492 self
493 }
494
495 pub fn custom_headers(mut self, value: Vec<(String, String)>) -> Self {
497 self.custom_headers = value;
498 self
499 }
500}
501
502#[derive(Debug, Eq, PartialEq)]
504#[non_exhaustive]
505pub struct RenameObjectResult {}
506
507#[derive(Debug, Eq, PartialEq)]
508#[non_exhaustive]
509pub enum RenamePreconditionTypes {
510 IfMatch,
511 IfNoneMatch,
512 Other,
513}
514
515#[derive(Debug, Error, PartialEq, Eq)]
517#[non_exhaustive]
518pub enum RenameObjectError {
519 #[error("The bucket does not exist")]
520 NoSuchBucket,
521 #[error("The destination key provided is too long")]
522 KeyTooLong,
523 #[error("The key was not found")]
524 KeyNotFound,
525 #[error("A Precondition")]
526 PreConditionFailed(RenamePreconditionTypes),
527 #[error("The service does not implement rename")]
528 NotImplementedError,
529 #[error("The service returned an Internal Error")]
530 InternalError,
531 #[error("You do not have access to this resource")]
532 AccessDenied,
533 #[error("Bad Request")]
534 BadRequest,
535}
536
537pub type ObjectMetadata = HashMap<String, String>;
538
539#[derive(Debug, Default, Clone)]
541#[non_exhaustive]
542pub struct PutObjectParams {
543 pub trailing_checksums: PutObjectTrailingChecksums,
545 pub storage_class: Option<String>,
547 pub server_side_encryption: Option<String>,
549 pub ssekms_key_id: Option<String>,
552 pub custom_headers: Vec<(String, String)>,
554 pub object_metadata: ObjectMetadata,
556}
557
558impl PutObjectParams {
559 pub fn new() -> Self {
561 Self::default()
562 }
563
564 pub fn trailing_checksums(mut self, value: PutObjectTrailingChecksums) -> Self {
566 self.trailing_checksums = value;
567 self
568 }
569
570 pub fn storage_class(mut self, value: String) -> Self {
572 self.storage_class = Some(value);
573 self
574 }
575
576 pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
578 self.server_side_encryption = value;
579 self
580 }
581
582 pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
584 self.ssekms_key_id = value;
585 self
586 }
587
588 pub fn add_custom_header(mut self, name: String, value: String) -> Self {
590 self.custom_headers.push((name, value));
591 self
592 }
593
594 pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
596 self.object_metadata = value;
597 self
598 }
599}
600
601#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
603pub enum PutObjectTrailingChecksums {
604 Enabled,
606 ReviewOnly,
608 #[default]
610 Disabled,
611}
612
613pub type UploadReview = mountpoint_s3_crt::s3::client::UploadReview;
615
616pub type UploadReviewPart = mountpoint_s3_crt::s3::client::UploadReviewPart;
618
619pub type ChecksumAlgorithm = mountpoint_s3_crt::s3::client::ChecksumAlgorithm;
621
622#[derive(Debug, Default, Clone)]
624#[non_exhaustive]
625pub struct PutObjectSingleParams {
626 pub checksum: Option<UploadChecksum>,
628 pub storage_class: Option<String>,
630 pub server_side_encryption: Option<String>,
632 pub ssekms_key_id: Option<String>,
635 pub if_match: Option<ETag>,
637 pub write_offset_bytes: Option<u64>,
639 pub custom_headers: Vec<(String, String)>,
641 pub object_metadata: ObjectMetadata,
643}
644
645impl PutObjectSingleParams {
646 pub fn new() -> Self {
648 Self::default()
649 }
650
651 pub fn new_for_append(offset: u64) -> Self {
653 Self::default().write_offset_bytes(offset)
654 }
655
656 pub fn checksum(mut self, value: Option<UploadChecksum>) -> Self {
658 self.checksum = value;
659 self
660 }
661
662 pub fn storage_class(mut self, value: String) -> Self {
664 self.storage_class = Some(value);
665 self
666 }
667
668 pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
670 self.server_side_encryption = value;
671 self
672 }
673
674 pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
676 self.ssekms_key_id = value;
677 self
678 }
679
680 pub fn if_match(mut self, value: Option<ETag>) -> Self {
682 self.if_match = value;
683 self
684 }
685
686 pub fn write_offset_bytes(mut self, value: u64) -> Self {
688 self.write_offset_bytes = Some(value);
689 self
690 }
691
692 pub fn add_custom_header(mut self, name: String, value: String) -> Self {
694 self.custom_headers.push((name, value));
695 self
696 }
697
698 pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
700 self.object_metadata = value;
701 self
702 }
703}
704
705#[derive(Debug, Clone)]
707#[non_exhaustive]
708pub enum UploadChecksum {
709 Crc64nvme(checksums::Crc64nvme),
710 Crc32c(checksums::Crc32c),
711 Crc32(checksums::Crc32),
712 Sha1(checksums::Sha1),
713 Sha256(checksums::Sha256),
714}
715
716impl UploadChecksum {
717 pub fn checksum_algorithm(&self) -> ChecksumAlgorithm {
719 match self {
720 UploadChecksum::Crc64nvme(_) => ChecksumAlgorithm::Crc64nvme,
721 UploadChecksum::Crc32c(_) => ChecksumAlgorithm::Crc32c,
722 UploadChecksum::Crc32(_) => ChecksumAlgorithm::Crc32,
723 UploadChecksum::Sha1(_) => ChecksumAlgorithm::Sha1,
724 UploadChecksum::Sha256(_) => ChecksumAlgorithm::Sha256,
725 }
726 }
727}
728
729pub trait ClientBackpressureHandle {
741 fn increment_read_window(&mut self, len: usize);
749
750 fn ensure_read_window(&mut self, desired_end_offset: u64);
752
753 fn read_window_end_offset(&self) -> u64;
756}
757
758#[cfg_attr(not(docsrs), async_trait)]
764pub trait GetObjectResponse:
765 Stream<Item = ObjectClientResult<GetBodyPart, GetObjectError, Self::ClientError>> + Send + Sync
766{
767 type BackpressureHandle: ClientBackpressureHandle + Clone + Send + Sync;
768 type ClientError: std::error::Error + Send + Sync + 'static;
769
770 fn backpressure_handle(&mut self) -> Option<&mut Self::BackpressureHandle>;
775
776 fn get_object_metadata(&self) -> ObjectMetadata;
778
779 fn get_object_checksum(&self) -> Result<Checksum, ObjectChecksumError>;
781}
782
783#[derive(Debug, Error)]
785pub enum ObjectChecksumError {
786 #[error("requested object checksums, but did not specify it in the request")]
787 DidNotRequestChecksums,
788 #[error("object checksum could not be retrieved from headers")]
789 HeadersError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
790}
791
792#[derive(Debug)]
795pub struct GetBodyPart {
796 pub offset: u64,
797 pub data: Bytes,
798}
799
800#[cfg_attr(not(docsrs), async_trait)]
811pub trait PutObjectRequest: Send {
812 type ClientError: std::error::Error + Send + Sync + 'static;
813
814 async fn write(&mut self, slice: &[u8]) -> ObjectClientResult<(), PutObjectError, Self::ClientError>;
816
817 async fn complete(self) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
819
820 async fn review_and_complete(
822 self,
823 review_callback: impl FnOnce(UploadReview) -> bool + Send + 'static,
824 ) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
825}
826
827#[derive(Debug)]
829#[non_exhaustive]
830pub struct PutObjectResult {
831 pub etag: ETag,
833 pub sse_type: Option<String>,
835 pub sse_kms_key_id: Option<String>,
837}
838
839#[derive(Debug, Error, PartialEq, Eq)]
841#[non_exhaustive]
842pub enum PutObjectError {
843 #[error("The bucket does not exist")]
844 NoSuchBucket,
845
846 #[error("The key does not exist")]
847 NoSuchKey,
848
849 #[error("Request body cannot be empty when write offset is specified")]
850 EmptyBody,
851
852 #[error("The offset does not match the current object size")]
853 InvalidWriteOffset,
854
855 #[error("The provided checksum does not match the data")]
856 BadChecksum,
857
858 #[error("The provided checksum is not valid or does not match the existing checksum algorithm")]
859 InvalidChecksumType,
860
861 #[error("At least one of the pre-conditions you specified did not hold")]
862 PreconditionFailed,
863
864 #[error("The server does not support the functionality required to fulfill the request")]
865 NotImplemented,
866}
867
868#[derive(Debug, Clone, Copy)]
874pub enum RestoreStatus {
875 InProgress,
878
879 Restored { expiry: SystemTime },
882}
883
884#[derive(Debug, Clone)]
889#[non_exhaustive]
890pub struct ObjectInfo {
891 pub key: String,
893
894 pub size: u64,
896
897 pub last_modified: OffsetDateTime,
899
900 pub storage_class: Option<String>,
904
905 pub restore_status: Option<RestoreStatus>,
908
909 pub etag: String,
911
912 pub checksum_algorithms: Vec<ChecksumAlgorithm>,
920}
921
922#[derive(Debug)]
925pub enum ObjectAttribute {
926 ETag,
928
929 Checksum,
931
932 ObjectParts,
934
935 StorageClass,
937
938 ObjectSize,
940}
941
942impl fmt::Display for ObjectAttribute {
943 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
944 let attr_name = match self {
945 ObjectAttribute::ETag => "ETag",
946 ObjectAttribute::Checksum => "Checksum",
947 ObjectAttribute::ObjectParts => "ObjectParts",
948 ObjectAttribute::StorageClass => "StorageClass",
949 ObjectAttribute::ObjectSize => "ObjectSize",
950 };
951 write!(f, "{attr_name}")
952 }
953}
954
955#[derive(Clone, Debug, PartialEq, Eq)]
960pub struct Checksum {
961 pub checksum_crc64nvme: Option<String>,
963
964 pub checksum_crc32: Option<String>,
966
967 pub checksum_crc32c: Option<String>,
969
970 pub checksum_sha1: Option<String>,
972
973 pub checksum_sha256: Option<String>,
975}
976
977impl Checksum {
978 pub fn empty() -> Self {
980 Self {
981 checksum_crc64nvme: None,
982 checksum_crc32: None,
983 checksum_crc32c: None,
984 checksum_sha1: None,
985 checksum_sha256: None,
986 }
987 }
988
989 pub fn algorithms(&self) -> Vec<ChecksumAlgorithm> {
991 let mut algorithms = Vec::with_capacity(1);
993
994 let Self {
996 checksum_crc64nvme,
997 checksum_crc32,
998 checksum_crc32c,
999 checksum_sha1,
1000 checksum_sha256,
1001 } = &self;
1002
1003 if checksum_crc64nvme.is_some() {
1004 algorithms.push(ChecksumAlgorithm::Crc64nvme);
1005 }
1006 if checksum_crc32.is_some() {
1007 algorithms.push(ChecksumAlgorithm::Crc32);
1008 }
1009 if checksum_crc32c.is_some() {
1010 algorithms.push(ChecksumAlgorithm::Crc32c);
1011 }
1012 if checksum_sha1.is_some() {
1013 algorithms.push(ChecksumAlgorithm::Sha1);
1014 }
1015 if checksum_sha256.is_some() {
1016 algorithms.push(ChecksumAlgorithm::Sha256);
1017 }
1018
1019 algorithms
1020 }
1021}
1022
1023impl From<Option<UploadChecksum>> for Checksum {
1024 fn from(value: Option<UploadChecksum>) -> Self {
1025 let mut checksum = Checksum::empty();
1026 match value.as_ref() {
1027 Some(UploadChecksum::Crc64nvme(crc64)) => checksum.checksum_crc64nvme = Some(crc64nvme_to_base64(crc64)),
1028 Some(UploadChecksum::Crc32c(crc32c)) => checksum.checksum_crc32c = Some(crc32c_to_base64(crc32c)),
1029 Some(UploadChecksum::Crc32(crc32)) => checksum.checksum_crc32 = Some(crc32_to_base64(crc32)),
1030 Some(UploadChecksum::Sha1(sha1)) => checksum.checksum_sha1 = Some(sha1_to_base64(sha1)),
1031 Some(UploadChecksum::Sha256(sha256)) => checksum.checksum_sha256 = Some(sha256_to_base64(sha256)),
1032 None => {}
1033 };
1034 checksum
1035 }
1036}
1037
1038#[derive(Debug)]
1043pub struct GetObjectAttributesParts {
1044 pub is_truncated: Option<bool>,
1046
1047 pub max_parts: Option<usize>,
1049
1050 pub next_part_number_marker: Option<usize>,
1052
1053 pub part_number_marker: Option<usize>,
1055
1056 pub parts: Option<Vec<ObjectPart>>,
1058
1059 pub total_parts_count: Option<usize>,
1061}
1062
1063#[derive(Debug)]
1068pub struct ObjectPart {
1069 pub checksum: Option<Checksum>,
1071
1072 pub part_number: usize,
1074
1075 pub size: usize,
1077}
1078
1079#[cfg(test)]
1080mod tests {
1081 use super::*;
1082
1083 #[test]
1084 fn test_checksum_algorithm_one_set() {
1085 let checksum = Checksum {
1086 checksum_crc64nvme: None,
1087 checksum_crc32: None,
1088 checksum_crc32c: None,
1089 checksum_sha1: Some("checksum_sha1".to_string()),
1090 checksum_sha256: None,
1091 };
1092 assert_eq!(checksum.algorithms(), vec![ChecksumAlgorithm::Sha1]);
1093 }
1094
1095 #[test]
1096 fn test_checksum_algorithm_none_set() {
1097 let checksum = Checksum {
1098 checksum_crc64nvme: None,
1099 checksum_crc32: None,
1100 checksum_crc32c: None,
1101 checksum_sha1: None,
1102 checksum_sha256: None,
1103 };
1104 assert_eq!(checksum.algorithms(), vec![]);
1105 }
1106
1107 #[test]
1108 fn test_checksum_algorithm_many_set() {
1109 let checksum = Checksum {
1111 checksum_crc64nvme: None,
1112 checksum_crc32: None,
1113 checksum_crc32c: Some("checksum_crc32c".to_string()),
1114 checksum_sha1: Some("checksum_sha1".to_string()),
1115 checksum_sha256: None,
1116 };
1117 assert_eq!(
1118 checksum.algorithms(),
1119 vec![ChecksumAlgorithm::Crc32c, ChecksumAlgorithm::Sha1],
1120 );
1121 }
1122}