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
213pub type ObjectClientResult<T, S, C> = Result<T, ObjectClientError<S, C>>;
215
216#[derive(Debug, Error, PartialEq, Eq)]
218#[non_exhaustive]
219pub enum GetObjectError {
220 #[error("The bucket does not exist")]
221 NoSuchBucket(ClientErrorMetadata),
222
223 #[error("The key does not exist")]
224 NoSuchKey(ClientErrorMetadata),
225
226 #[error("At least one of the preconditions specified did not hold")]
227 PreconditionFailed(ClientErrorMetadata),
228}
229
230#[derive(Debug, Default, Clone)]
232#[non_exhaustive]
233pub struct GetObjectParams {
234 pub range: Option<Range<u64>>,
235 pub if_match: Option<ETag>,
236 pub checksum_mode: Option<ChecksumMode>,
237}
238
239impl GetObjectParams {
240 pub fn new() -> Self {
242 Self::default()
243 }
244
245 pub fn range(mut self, value: Option<Range<u64>>) -> Self {
247 self.range = value;
248 self
249 }
250
251 pub fn if_match(mut self, value: Option<ETag>) -> Self {
253 self.if_match = value;
254 self
255 }
256
257 pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
259 self.checksum_mode = value;
260 self
261 }
262}
263
264#[derive(Debug)]
266#[non_exhaustive]
267pub struct ListObjectsResult {
268 pub objects: Vec<ObjectInfo>,
270
271 pub common_prefixes: Vec<String>,
274
275 pub next_continuation_token: Option<String>,
277}
278
279#[derive(Debug, Error, PartialEq, Eq)]
281#[non_exhaustive]
282pub enum ListObjectsError {
283 #[error("The bucket does not exist")]
284 NoSuchBucket,
285}
286
287#[derive(Debug, Default, Clone)]
289#[non_exhaustive]
290pub struct HeadObjectParams {
291 pub checksum_mode: Option<ChecksumMode>,
293}
294
295impl HeadObjectParams {
296 pub fn new() -> Self {
298 Self::default()
299 }
300
301 pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
303 self.checksum_mode = value;
304 self
305 }
306}
307
308#[non_exhaustive]
310#[derive(Clone, Debug, PartialEq)]
311pub enum ChecksumMode {
312 Enabled,
314}
315
316#[derive(Debug)]
318#[non_exhaustive]
319pub struct HeadObjectResult {
320 pub size: u64,
324
325 pub last_modified: OffsetDateTime,
327
328 pub etag: ETag,
330
331 pub storage_class: Option<String>,
338
339 pub restore_status: Option<RestoreStatus>,
342 pub checksum: Checksum,
347
348 pub sse_type: Option<String>,
350
351 pub sse_kms_key_id: Option<String>,
353}
354
355#[derive(Debug, Error, PartialEq, Eq)]
357#[non_exhaustive]
358pub enum HeadObjectError {
359 #[error("The object was not found")]
361 NotFound,
362}
363
364#[derive(Debug)]
369#[non_exhaustive]
370pub struct DeleteObjectResult {}
371
372#[derive(Debug, Error, PartialEq, Eq)]
374#[non_exhaustive]
375pub enum DeleteObjectError {
376 #[error("The bucket does not exist")]
377 NoSuchBucket,
378}
379
380#[derive(Debug)]
382#[non_exhaustive]
383pub struct CopyObjectResult {
384 }
386
387#[derive(Debug, Error, PartialEq, Eq)]
389#[non_exhaustive]
390pub enum CopyObjectError {
391 #[error("The object was not found")]
393 NotFound,
394
395 #[error("The source object of the COPY action is not in the active tier and is only stored in Amazon S3 Glacier.")]
396 ObjectNotInActiveTierError,
397}
398
399#[derive(Debug, Default, Clone)]
401#[non_exhaustive]
402pub struct CopyObjectParams {
403 }
405
406impl CopyObjectParams {
407 pub fn new() -> Self {
409 Self::default()
410 }
411}
412
413#[derive(Debug, Default)]
415pub struct GetObjectAttributesResult {
416 pub etag: Option<String>,
418
419 pub checksum: Option<Checksum>,
421
422 pub object_parts: Option<GetObjectAttributesParts>,
424
425 pub storage_class: Option<String>,
427
428 pub object_size: Option<u64>,
430}
431
432#[derive(Debug, Error, PartialEq, Eq)]
434#[non_exhaustive]
435pub enum GetObjectAttributesError {
436 #[error("The bucket does not exist")]
437 NoSuchBucket,
438
439 #[error("The key does not exist")]
440 NoSuchKey,
441}
442
443#[derive(Debug, Default, Clone)]
445#[non_exhaustive]
446pub struct RenameObjectParams {
447 pub if_none_match: Option<String>,
449 pub if_match: Option<ETag>,
451 pub if_source_match: Option<ETag>,
453 pub client_token: Option<String>,
455 pub custom_headers: Vec<(String, String)>,
457}
458
459impl RenameObjectParams {
460 pub fn new() -> Self {
462 Self::default()
463 }
464
465 pub fn if_none_match(mut self, value: Option<String>) -> Self {
467 self.if_none_match = value;
468 self
469 }
470
471 pub fn if_match(mut self, value: Option<ETag>) -> Self {
473 self.if_match = value;
474 self
475 }
476
477 pub fn if_source_match(mut self, value: Option<ETag>) -> Self {
479 self.if_source_match = value;
480 self
481 }
482
483 pub fn client_token(mut self, value: Option<String>) -> Self {
485 self.client_token = value;
486 self
487 }
488
489 pub fn custom_headers(mut self, value: Vec<(String, String)>) -> Self {
491 self.custom_headers = value;
492 self
493 }
494}
495
496#[derive(Debug, Eq, PartialEq)]
498#[non_exhaustive]
499pub struct RenameObjectResult {}
500
501#[derive(Debug, Eq, PartialEq)]
502#[non_exhaustive]
503pub enum RenamePreconditionTypes {
504 IfMatch,
505 IfNoneMatch,
506 Other,
507}
508
509#[derive(Debug, Error, PartialEq, Eq)]
511#[non_exhaustive]
512pub enum RenameObjectError {
513 #[error("The bucket does not exist")]
514 NoSuchBucket,
515 #[error("The destination key provided is too long")]
516 KeyTooLong,
517 #[error("The key was not found")]
518 KeyNotFound,
519 #[error("A Precondition")]
520 PreConditionFailed(RenamePreconditionTypes),
521 #[error("The service does not implement rename")]
522 NotImplementedError,
523 #[error("The service returned an Internal Error")]
524 InternalError,
525 #[error("You do not have access to this resource")]
526 AccessDenied,
527 #[error("Bad Request")]
528 BadRequest,
529}
530
531pub type ObjectMetadata = HashMap<String, String>;
532
533#[derive(Debug, Default, Clone)]
535#[non_exhaustive]
536pub struct PutObjectParams {
537 pub trailing_checksums: PutObjectTrailingChecksums,
539 pub storage_class: Option<String>,
541 pub server_side_encryption: Option<String>,
543 pub ssekms_key_id: Option<String>,
546 pub custom_headers: Vec<(String, String)>,
548 pub object_metadata: ObjectMetadata,
550}
551
552impl PutObjectParams {
553 pub fn new() -> Self {
555 Self::default()
556 }
557
558 pub fn trailing_checksums(mut self, value: PutObjectTrailingChecksums) -> Self {
560 self.trailing_checksums = value;
561 self
562 }
563
564 pub fn storage_class(mut self, value: String) -> Self {
566 self.storage_class = Some(value);
567 self
568 }
569
570 pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
572 self.server_side_encryption = value;
573 self
574 }
575
576 pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
578 self.ssekms_key_id = value;
579 self
580 }
581
582 pub fn add_custom_header(mut self, name: String, value: String) -> Self {
584 self.custom_headers.push((name, value));
585 self
586 }
587
588 pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
590 self.object_metadata = value;
591 self
592 }
593}
594
595#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
597pub enum PutObjectTrailingChecksums {
598 Enabled,
600 ReviewOnly,
602 #[default]
604 Disabled,
605}
606
607pub type UploadReview = mountpoint_s3_crt::s3::client::UploadReview;
609
610pub type UploadReviewPart = mountpoint_s3_crt::s3::client::UploadReviewPart;
612
613pub type ChecksumAlgorithm = mountpoint_s3_crt::s3::client::ChecksumAlgorithm;
615
616#[derive(Debug, Default, Clone)]
618#[non_exhaustive]
619pub struct PutObjectSingleParams {
620 pub checksum: Option<UploadChecksum>,
622 pub storage_class: Option<String>,
624 pub server_side_encryption: Option<String>,
626 pub ssekms_key_id: Option<String>,
629 pub if_match: Option<ETag>,
631 pub write_offset_bytes: Option<u64>,
633 pub custom_headers: Vec<(String, String)>,
635 pub object_metadata: ObjectMetadata,
637}
638
639impl PutObjectSingleParams {
640 pub fn new() -> Self {
642 Self::default()
643 }
644
645 pub fn new_for_append(offset: u64) -> Self {
647 Self::default().write_offset_bytes(offset)
648 }
649
650 pub fn checksum(mut self, value: Option<UploadChecksum>) -> Self {
652 self.checksum = value;
653 self
654 }
655
656 pub fn storage_class(mut self, value: String) -> Self {
658 self.storage_class = Some(value);
659 self
660 }
661
662 pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
664 self.server_side_encryption = value;
665 self
666 }
667
668 pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
670 self.ssekms_key_id = value;
671 self
672 }
673
674 pub fn if_match(mut self, value: Option<ETag>) -> Self {
676 self.if_match = value;
677 self
678 }
679
680 pub fn write_offset_bytes(mut self, value: u64) -> Self {
682 self.write_offset_bytes = Some(value);
683 self
684 }
685
686 pub fn add_custom_header(mut self, name: String, value: String) -> Self {
688 self.custom_headers.push((name, value));
689 self
690 }
691
692 pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
694 self.object_metadata = value;
695 self
696 }
697}
698
699#[derive(Debug, Clone)]
701#[non_exhaustive]
702pub enum UploadChecksum {
703 Crc64nvme(checksums::Crc64nvme),
704 Crc32c(checksums::Crc32c),
705 Crc32(checksums::Crc32),
706 Sha1(checksums::Sha1),
707 Sha256(checksums::Sha256),
708}
709
710impl UploadChecksum {
711 pub fn checksum_algorithm(&self) -> ChecksumAlgorithm {
713 match self {
714 UploadChecksum::Crc64nvme(_) => ChecksumAlgorithm::Crc64nvme,
715 UploadChecksum::Crc32c(_) => ChecksumAlgorithm::Crc32c,
716 UploadChecksum::Crc32(_) => ChecksumAlgorithm::Crc32,
717 UploadChecksum::Sha1(_) => ChecksumAlgorithm::Sha1,
718 UploadChecksum::Sha256(_) => ChecksumAlgorithm::Sha256,
719 }
720 }
721}
722
723pub trait ClientBackpressureHandle {
735 fn increment_read_window(&mut self, len: usize);
743
744 fn ensure_read_window(&mut self, desired_end_offset: u64);
746
747 fn read_window_end_offset(&self) -> u64;
750}
751
752#[cfg_attr(not(docsrs), async_trait)]
758pub trait GetObjectResponse:
759 Stream<Item = ObjectClientResult<GetBodyPart, GetObjectError, Self::ClientError>> + Send + Sync
760{
761 type BackpressureHandle: ClientBackpressureHandle + Clone + Send + Sync;
762 type ClientError: std::error::Error + Send + Sync + 'static;
763
764 fn backpressure_handle(&mut self) -> Option<&mut Self::BackpressureHandle>;
769
770 fn get_object_metadata(&self) -> ObjectMetadata;
772
773 fn get_object_checksum(&self) -> Result<Checksum, ObjectChecksumError>;
775}
776
777#[derive(Debug, Error)]
779pub enum ObjectChecksumError {
780 #[error("requested object checksums, but did not specify it in the request")]
781 DidNotRequestChecksums,
782 #[error("object checksum could not be retrieved from headers")]
783 HeadersError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
784}
785
786#[derive(Debug)]
789pub struct GetBodyPart {
790 pub offset: u64,
791 pub data: Bytes,
792}
793
794#[cfg_attr(not(docsrs), async_trait)]
805pub trait PutObjectRequest: Send {
806 type ClientError: std::error::Error + Send + Sync + 'static;
807
808 async fn write(&mut self, slice: &[u8]) -> ObjectClientResult<(), PutObjectError, Self::ClientError>;
810
811 async fn complete(self) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
813
814 async fn review_and_complete(
816 self,
817 review_callback: impl FnOnce(UploadReview) -> bool + Send + 'static,
818 ) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
819}
820
821#[derive(Debug)]
823#[non_exhaustive]
824pub struct PutObjectResult {
825 pub etag: ETag,
827 pub sse_type: Option<String>,
829 pub sse_kms_key_id: Option<String>,
831}
832
833#[derive(Debug, Error, PartialEq, Eq)]
835#[non_exhaustive]
836pub enum PutObjectError {
837 #[error("The bucket does not exist")]
838 NoSuchBucket,
839
840 #[error("The key does not exist")]
841 NoSuchKey,
842
843 #[error("Request body cannot be empty when write offset is specified")]
844 EmptyBody,
845
846 #[error("The offset does not match the current object size")]
847 InvalidWriteOffset,
848
849 #[error("The provided checksum does not match the data")]
850 BadChecksum,
851
852 #[error("The provided checksum is not valid or does not match the existing checksum algorithm")]
853 InvalidChecksumType,
854
855 #[error("At least one of the pre-conditions you specified did not hold")]
856 PreconditionFailed,
857
858 #[error("The server does not support the functionality required to fulfill the request")]
859 NotImplemented,
860}
861
862#[derive(Debug, Clone, Copy)]
868pub enum RestoreStatus {
869 InProgress,
872
873 Restored { expiry: SystemTime },
876}
877
878#[derive(Debug, Clone)]
883#[non_exhaustive]
884pub struct ObjectInfo {
885 pub key: String,
887
888 pub size: u64,
890
891 pub last_modified: OffsetDateTime,
893
894 pub storage_class: Option<String>,
898
899 pub restore_status: Option<RestoreStatus>,
902
903 pub etag: String,
905
906 pub checksum_algorithms: Vec<ChecksumAlgorithm>,
914}
915
916#[derive(Debug)]
919pub enum ObjectAttribute {
920 ETag,
922
923 Checksum,
925
926 ObjectParts,
928
929 StorageClass,
931
932 ObjectSize,
934}
935
936impl fmt::Display for ObjectAttribute {
937 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938 let attr_name = match self {
939 ObjectAttribute::ETag => "ETag",
940 ObjectAttribute::Checksum => "Checksum",
941 ObjectAttribute::ObjectParts => "ObjectParts",
942 ObjectAttribute::StorageClass => "StorageClass",
943 ObjectAttribute::ObjectSize => "ObjectSize",
944 };
945 write!(f, "{attr_name}")
946 }
947}
948
949#[derive(Clone, Debug, PartialEq, Eq)]
954pub struct Checksum {
955 pub checksum_crc64nvme: Option<String>,
957
958 pub checksum_crc32: Option<String>,
960
961 pub checksum_crc32c: Option<String>,
963
964 pub checksum_sha1: Option<String>,
966
967 pub checksum_sha256: Option<String>,
969}
970
971impl Checksum {
972 pub fn empty() -> Self {
974 Self {
975 checksum_crc64nvme: None,
976 checksum_crc32: None,
977 checksum_crc32c: None,
978 checksum_sha1: None,
979 checksum_sha256: None,
980 }
981 }
982
983 pub fn algorithms(&self) -> Vec<ChecksumAlgorithm> {
985 let mut algorithms = Vec::with_capacity(1);
987
988 let Self {
990 checksum_crc64nvme,
991 checksum_crc32,
992 checksum_crc32c,
993 checksum_sha1,
994 checksum_sha256,
995 } = &self;
996
997 if checksum_crc64nvme.is_some() {
998 algorithms.push(ChecksumAlgorithm::Crc64nvme);
999 }
1000 if checksum_crc32.is_some() {
1001 algorithms.push(ChecksumAlgorithm::Crc32);
1002 }
1003 if checksum_crc32c.is_some() {
1004 algorithms.push(ChecksumAlgorithm::Crc32c);
1005 }
1006 if checksum_sha1.is_some() {
1007 algorithms.push(ChecksumAlgorithm::Sha1);
1008 }
1009 if checksum_sha256.is_some() {
1010 algorithms.push(ChecksumAlgorithm::Sha256);
1011 }
1012
1013 algorithms
1014 }
1015}
1016
1017impl From<Option<UploadChecksum>> for Checksum {
1018 fn from(value: Option<UploadChecksum>) -> Self {
1019 let mut checksum = Checksum::empty();
1020 match value.as_ref() {
1021 Some(UploadChecksum::Crc64nvme(crc64)) => checksum.checksum_crc64nvme = Some(crc64nvme_to_base64(crc64)),
1022 Some(UploadChecksum::Crc32c(crc32c)) => checksum.checksum_crc32c = Some(crc32c_to_base64(crc32c)),
1023 Some(UploadChecksum::Crc32(crc32)) => checksum.checksum_crc32 = Some(crc32_to_base64(crc32)),
1024 Some(UploadChecksum::Sha1(sha1)) => checksum.checksum_sha1 = Some(sha1_to_base64(sha1)),
1025 Some(UploadChecksum::Sha256(sha256)) => checksum.checksum_sha256 = Some(sha256_to_base64(sha256)),
1026 None => {}
1027 };
1028 checksum
1029 }
1030}
1031
1032#[derive(Debug)]
1037pub struct GetObjectAttributesParts {
1038 pub is_truncated: Option<bool>,
1040
1041 pub max_parts: Option<usize>,
1043
1044 pub next_part_number_marker: Option<usize>,
1046
1047 pub part_number_marker: Option<usize>,
1049
1050 pub parts: Option<Vec<ObjectPart>>,
1052
1053 pub total_parts_count: Option<usize>,
1055}
1056
1057#[derive(Debug)]
1062pub struct ObjectPart {
1063 pub checksum: Option<Checksum>,
1065
1066 pub part_number: usize,
1068
1069 pub size: usize,
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076
1077 #[test]
1078 fn test_checksum_algorithm_one_set() {
1079 let checksum = Checksum {
1080 checksum_crc64nvme: None,
1081 checksum_crc32: None,
1082 checksum_crc32c: None,
1083 checksum_sha1: Some("checksum_sha1".to_string()),
1084 checksum_sha256: None,
1085 };
1086 assert_eq!(checksum.algorithms(), vec![ChecksumAlgorithm::Sha1]);
1087 }
1088
1089 #[test]
1090 fn test_checksum_algorithm_none_set() {
1091 let checksum = Checksum {
1092 checksum_crc64nvme: None,
1093 checksum_crc32: None,
1094 checksum_crc32c: None,
1095 checksum_sha1: None,
1096 checksum_sha256: None,
1097 };
1098 assert_eq!(checksum.algorithms(), vec![]);
1099 }
1100
1101 #[test]
1102 fn test_checksum_algorithm_many_set() {
1103 let checksum = Checksum {
1105 checksum_crc64nvme: None,
1106 checksum_crc32: None,
1107 checksum_crc32c: Some("checksum_crc32c".to_string()),
1108 checksum_sha1: Some("checksum_sha1".to_string()),
1109 checksum_sha256: None,
1110 };
1111 assert_eq!(
1112 checksum.algorithms(),
1113 vec![ChecksumAlgorithm::Crc32c, ChecksumAlgorithm::Sha1],
1114 );
1115 }
1116}