remozipsy 0.2.0

Remote Zip Sync - sync remote zip to local fs
Documentation
use core::fmt::Debug;

#[derive(Debug, thiserror::Error)]
pub enum Error<R: Debug, F: Debug> {
    #[error("Remote Error: {0}")]
    Remote(R),
    #[error("Filesystem Error: {0}")]
    FileSystem(F),
    #[error("Internal error while joining background tasks")]
    JoinError,
    #[error("Remote zip invalid, invalid local header signature while trying to read: {file_name}")]
    InvalidLocalHeaderSignature { file_name: String },
    #[error(
        "The calculated byte range to download a batch is too short. Was downloading file: {file_name}. Storage \
         contains {storage_size} bytes, but {expected_compressed_size} are necessary"
    )]
    InsufficientDownloadRange {
        file_name: String,
        storage_size: u64,
        expected_compressed_size: u64,
    },
    #[error("Overlapping filesizes from download detected")]
    OverlappingBytesForDifferentFiles,
    #[error("Invalid UTF-8 filename. This code requires filenames to match UTF-8")]
    InvalidUtf8Filename,
    #[error(
        "The bytes length: {bytes_cnt} passed to unzip doesn't match the file '{file_name}' compressed size: \
         {expected_compressed_size}"
    )]
    WrongBytesLength {
        file_name: String,
        bytes_cnt: u64,
        expected_compressed_size: u64,
    },
    #[error("Invalid compression method found, the field contains the value: {0}")]
    InvalidCompressionMethod(u16),
    #[error("Unsupported compression method found: '{0}', value {1}")]
    UnsupportedCompressionMethod(String, u16),
    #[error("The compression method returned an error decompressing the file")]
    CompressionError,
    #[error("The remote file hash: {remote} doesn't match its calculated one: {calculated}")]
    InvalidHash { remote: u32, calculated: u32 },
}

impl<R: Debug, F: Debug> Clone for Error<R, F>
where
    R: Clone,
    F: Clone,
{
    fn clone(&self) -> Self {
        match self {
            Error::Remote(r) => Error::Remote(r.clone()),
            Error::FileSystem(f) => Error::FileSystem(f.clone()),
            Error::JoinError => Error::JoinError,
            Error::InvalidLocalHeaderSignature { file_name } => Error::InvalidLocalHeaderSignature {
                file_name: file_name.clone(),
            },
            Error::InsufficientDownloadRange {
                file_name,
                storage_size,
                expected_compressed_size,
            } => Error::InsufficientDownloadRange {
                file_name: file_name.clone(),
                storage_size: *storage_size,
                expected_compressed_size: *expected_compressed_size,
            },
            Error::OverlappingBytesForDifferentFiles => Error::OverlappingBytesForDifferentFiles,
            Error::InvalidUtf8Filename => Error::InvalidUtf8Filename,
            Error::WrongBytesLength {
                file_name,
                bytes_cnt,
                expected_compressed_size,
            } => Error::WrongBytesLength {
                file_name: file_name.clone(),
                bytes_cnt: *bytes_cnt,
                expected_compressed_size: *expected_compressed_size,
            },
            Error::InvalidCompressionMethod(e) => Error::InvalidCompressionMethod(*e),
            Error::UnsupportedCompressionMethod(n, v) => Error::UnsupportedCompressionMethod(n.clone(), *v),
            Error::CompressionError => Error::CompressionError,
            Error::InvalidHash { remote, calculated } => Error::InvalidHash {
                remote:     *remote,
                calculated: *calculated,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_clone_variants() {
        let err1 = Error::<String, ()>::Remote("error".to_string());
        let err2 = Error::<(), String>::FileSystem("fs error".to_string());
        let err3 = Error::<(), ()>::JoinError;
        let err4 = Error::<(), ()>::InvalidLocalHeaderSignature {
            file_name: "file".to_string(),
        };
        let err5 = Error::<(), ()>::InsufficientDownloadRange {
            file_name: "file".to_string(),
            storage_size: 10,
            expected_compressed_size: 20,
        };
        let err6 = Error::<(), ()>::OverlappingBytesForDifferentFiles;
        let err7 = Error::<(), ()>::InvalidUtf8Filename;
        let err8 = Error::<(), ()>::WrongBytesLength {
            file_name: "file".to_string(),
            bytes_cnt: 10,
            expected_compressed_size: 20,
        };
        let err9 = Error::<(), ()>::InvalidCompressionMethod(0);
        let err10 = Error::<(), ()>::UnsupportedCompressionMethod("".to_string(), 0);
        let err11 = Error::<(), ()>::CompressionError;
        let err12 = Error::<(), ()>::InvalidHash {
            remote:     0,
            calculated: 0,
        };

        assert!(matches!(err1.clone(), Error::<String, ()>::Remote(_)));
        assert!(matches!(err2.clone(), Error::<(), String>::FileSystem(_)));
        assert!(matches!(err3.clone(), Error::<(), ()>::JoinError));
        assert!(matches!(
            err4.clone(),
            Error::<(), ()>::InvalidLocalHeaderSignature { .. }
        ));
        assert!(matches!(
            err5.clone(),
            Error::<(), ()>::InsufficientDownloadRange { .. }
        ));
        assert!(matches!(
            err6.clone(),
            Error::<(), ()>::OverlappingBytesForDifferentFiles
        ));
        assert!(matches!(err7.clone(), Error::<(), ()>::InvalidUtf8Filename));
        assert!(matches!(err8.clone(), Error::<(), ()>::WrongBytesLength { .. }));
        assert!(matches!(err9.clone(), Error::<(), ()>::InvalidCompressionMethod(_)));
        assert!(matches!(
            err10.clone(),
            Error::<(), ()>::UnsupportedCompressionMethod(_, _)
        ));
        assert!(matches!(err11.clone(), Error::<(), ()>::CompressionError));
        assert!(matches!(err12.clone(), Error::<(), ()>::InvalidHash { .. }));
    }
}