solana_runtime/snapshot_utils/
archive_format.rs

1use {
2    std::{fmt, str::FromStr},
3    strum::Display,
4};
5
6// SUPPORTED_ARCHIVE_COMPRESSION lists the compression types that can be
7// specified on the command line. "zstd" and "lz4" are valid whereas "gzip",
8// "bz2", "tar" and "none" have been deprecated. Thus, all newly created
9// snapshots will either use "zstd" or "lz4". By keeping the deprecated types
10// in the ArchiveFormat enum, pre-existing snapshot archives with the
11// deprecated compression types can still be read.
12pub const SUPPORTED_ARCHIVE_COMPRESSION: &[&str] = &["zstd", "lz4"];
13pub const DEFAULT_ARCHIVE_COMPRESSION: &str = "zstd";
14
15pub const TAR_BZIP2_EXTENSION: &str = "tar.bz2";
16pub const TAR_GZIP_EXTENSION: &str = "tar.gz";
17pub const TAR_ZSTD_EXTENSION: &str = "tar.zst";
18pub const TAR_LZ4_EXTENSION: &str = "tar.lz4";
19pub const TAR_EXTENSION: &str = "tar";
20
21/// The different archive formats used for snapshots
22#[derive(Copy, Clone, Debug, Eq, PartialEq, Display)]
23pub enum ArchiveFormat {
24    TarBzip2,
25    TarGzip,
26    TarZstd,
27    TarLz4,
28    Tar,
29}
30
31impl ArchiveFormat {
32    /// Get the file extension for the ArchiveFormat
33    pub fn extension(&self) -> &str {
34        match self {
35            ArchiveFormat::TarBzip2 => TAR_BZIP2_EXTENSION,
36            ArchiveFormat::TarGzip => TAR_GZIP_EXTENSION,
37            ArchiveFormat::TarZstd => TAR_ZSTD_EXTENSION,
38            ArchiveFormat::TarLz4 => TAR_LZ4_EXTENSION,
39            ArchiveFormat::Tar => TAR_EXTENSION,
40        }
41    }
42
43    pub fn from_cli_arg(archive_format_str: &str) -> Option<ArchiveFormat> {
44        match archive_format_str {
45            "zstd" => Some(ArchiveFormat::TarZstd),
46            "lz4" => Some(ArchiveFormat::TarLz4),
47            _ => None,
48        }
49    }
50}
51
52// Change this to `impl<S: AsRef<str>> TryFrom<S> for ArchiveFormat [...]`
53// once this Rust bug is fixed: https://github.com/rust-lang/rust/issues/50133
54impl TryFrom<&str> for ArchiveFormat {
55    type Error = ParseError;
56
57    fn try_from(extension: &str) -> Result<Self, Self::Error> {
58        match extension {
59            TAR_BZIP2_EXTENSION => Ok(ArchiveFormat::TarBzip2),
60            TAR_GZIP_EXTENSION => Ok(ArchiveFormat::TarGzip),
61            TAR_ZSTD_EXTENSION => Ok(ArchiveFormat::TarZstd),
62            TAR_LZ4_EXTENSION => Ok(ArchiveFormat::TarLz4),
63            TAR_EXTENSION => Ok(ArchiveFormat::Tar),
64            _ => Err(ParseError::InvalidExtension(extension.to_string())),
65        }
66    }
67}
68
69impl FromStr for ArchiveFormat {
70    type Err = ParseError;
71
72    fn from_str(extension: &str) -> Result<Self, Self::Err> {
73        Self::try_from(extension)
74    }
75}
76
77#[derive(Debug, Clone, Eq, PartialEq)]
78pub enum ParseError {
79    InvalidExtension(String),
80}
81
82impl fmt::Display for ParseError {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        match self {
85            ParseError::InvalidExtension(extension) => {
86                write!(f, "Invalid archive extension: {extension}")
87            }
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use {super::*, std::iter::zip};
95    const INVALID_EXTENSION: &str = "zip";
96
97    #[test]
98    fn test_extension() {
99        assert_eq!(ArchiveFormat::TarBzip2.extension(), TAR_BZIP2_EXTENSION);
100        assert_eq!(ArchiveFormat::TarGzip.extension(), TAR_GZIP_EXTENSION);
101        assert_eq!(ArchiveFormat::TarZstd.extension(), TAR_ZSTD_EXTENSION);
102        assert_eq!(ArchiveFormat::TarLz4.extension(), TAR_LZ4_EXTENSION);
103        assert_eq!(ArchiveFormat::Tar.extension(), TAR_EXTENSION);
104    }
105
106    #[test]
107    fn test_try_from() {
108        assert_eq!(
109            ArchiveFormat::try_from(TAR_BZIP2_EXTENSION),
110            Ok(ArchiveFormat::TarBzip2)
111        );
112        assert_eq!(
113            ArchiveFormat::try_from(TAR_GZIP_EXTENSION),
114            Ok(ArchiveFormat::TarGzip)
115        );
116        assert_eq!(
117            ArchiveFormat::try_from(TAR_ZSTD_EXTENSION),
118            Ok(ArchiveFormat::TarZstd)
119        );
120        assert_eq!(
121            ArchiveFormat::try_from(TAR_LZ4_EXTENSION),
122            Ok(ArchiveFormat::TarLz4)
123        );
124        assert_eq!(
125            ArchiveFormat::try_from(TAR_EXTENSION),
126            Ok(ArchiveFormat::Tar)
127        );
128        assert_eq!(
129            ArchiveFormat::try_from(INVALID_EXTENSION),
130            Err(ParseError::InvalidExtension(INVALID_EXTENSION.to_string()))
131        );
132    }
133
134    #[test]
135    fn test_from_str() {
136        assert_eq!(
137            ArchiveFormat::from_str(TAR_BZIP2_EXTENSION),
138            Ok(ArchiveFormat::TarBzip2)
139        );
140        assert_eq!(
141            ArchiveFormat::from_str(TAR_GZIP_EXTENSION),
142            Ok(ArchiveFormat::TarGzip)
143        );
144        assert_eq!(
145            ArchiveFormat::from_str(TAR_ZSTD_EXTENSION),
146            Ok(ArchiveFormat::TarZstd)
147        );
148        assert_eq!(
149            ArchiveFormat::from_str(TAR_LZ4_EXTENSION),
150            Ok(ArchiveFormat::TarLz4)
151        );
152        assert_eq!(
153            ArchiveFormat::from_str(TAR_EXTENSION),
154            Ok(ArchiveFormat::Tar)
155        );
156        assert_eq!(
157            ArchiveFormat::from_str(INVALID_EXTENSION),
158            Err(ParseError::InvalidExtension(INVALID_EXTENSION.to_string()))
159        );
160    }
161
162    #[test]
163    fn test_from_cli_arg() {
164        let golden = [Some(ArchiveFormat::TarZstd), Some(ArchiveFormat::TarLz4)];
165
166        for (arg, expected) in zip(SUPPORTED_ARCHIVE_COMPRESSION.iter(), golden.into_iter()) {
167            assert_eq!(ArchiveFormat::from_cli_arg(arg), expected);
168        }
169
170        assert_eq!(ArchiveFormat::from_cli_arg("bad"), None);
171    }
172}