fjall/
version.rs

1// Copyright (c) 2024-present, fjall-rs
2// This source code is licensed under both the Apache 2.0 and MIT License
3// (found in the LICENSE-* files in the repository)
4
5use byteorder::WriteBytesExt;
6
7/// Disk format version
8#[derive(Copy, Clone, Debug, Eq, PartialEq)]
9pub enum Version {
10    /// Version for 1.x.x releases
11    V1,
12
13    /// Version for 2.x.x releases
14    V2,
15}
16
17impl std::fmt::Display for Version {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        write!(f, "{}", u8::from(*self))
20    }
21}
22
23impl From<Version> for u8 {
24    fn from(value: Version) -> Self {
25        match value {
26            Version::V1 => 1,
27            Version::V2 => 2,
28        }
29    }
30}
31
32impl TryFrom<u8> for Version {
33    type Error = ();
34
35    fn try_from(value: u8) -> Result<Self, Self::Error> {
36        match value {
37            1 => Ok(Self::V1),
38            2 => Ok(Self::V2),
39            _ => Err(()),
40        }
41    }
42}
43
44const MAGIC_BYTES: [u8; 3] = [b'F', b'J', b'L'];
45
46impl Version {
47    pub(crate) fn parse_file_header(bytes: &[u8]) -> Option<Self> {
48        let first_three = bytes.get(0..3)?;
49
50        if first_three == MAGIC_BYTES {
51            let version = *bytes.get(3)?;
52            let version = Self::try_from(version).ok()?;
53
54            Some(version)
55        } else {
56            None
57        }
58    }
59
60    pub(crate) fn write_file_header<W: std::io::Write>(
61        self,
62        writer: &mut W,
63    ) -> std::io::Result<()> {
64        writer.write_all(&MAGIC_BYTES)?;
65        writer.write_u8(u8::from(self))?;
66        Ok(())
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use test_log::test;
74
75    #[test]
76    #[allow(clippy::expect_used)]
77    pub fn version_serialize() -> crate::Result<()> {
78        let mut bytes = vec![];
79        Version::V1.write_file_header(&mut bytes)?;
80        assert_eq!(bytes, &[b'F', b'J', b'L', 1]);
81        Ok(())
82    }
83
84    #[test]
85    #[allow(clippy::expect_used)]
86    pub fn version_serialize_2() -> crate::Result<()> {
87        let mut bytes = vec![];
88        Version::V2.write_file_header(&mut bytes)?;
89        assert_eq!(bytes, &[b'F', b'J', b'L', 2]);
90        Ok(())
91    }
92
93    #[test]
94    #[allow(clippy::expect_used)]
95    pub fn version_deserialize_success() {
96        let version = Version::parse_file_header(&[b'F', b'J', b'L', 1]);
97        assert_eq!(version, Some(Version::V1));
98    }
99
100    #[test]
101    #[allow(clippy::expect_used)]
102    pub fn version_deserialize_success_2() {
103        let version = Version::parse_file_header(&[b'F', b'J', b'L', 2]);
104        assert_eq!(version, Some(Version::V2));
105    }
106
107    #[test]
108    #[allow(clippy::expect_used)]
109    pub fn version_deserialize_fail() {
110        let version = Version::parse_file_header(&[b'F', b'J', b'X', 1]);
111        assert!(version.is_none());
112    }
113
114    #[test]
115    #[allow(clippy::expect_used)]
116    pub fn version_serde_round_trip() {
117        let mut buf = vec![];
118        Version::V1.write_file_header(&mut buf).expect("can't fail");
119
120        let version = Version::parse_file_header(&buf);
121        assert_eq!(version, Some(Version::V1));
122    }
123
124    #[test]
125    #[allow(clippy::expect_used)]
126    pub fn version_len() {
127        let mut buf = vec![];
128        Version::V1.write_file_header(&mut buf).expect("can't fail");
129        assert_eq!(4, buf.len());
130    }
131}