value_log/
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
14impl std::fmt::Display for Version {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "{}", u8::from(*self))
17    }
18}
19
20impl From<Version> for u8 {
21    fn from(value: Version) -> Self {
22        match value {
23            Version::V1 => 1,
24        }
25    }
26}
27
28impl TryFrom<u8> for Version {
29    type Error = ();
30    fn try_from(value: u8) -> Result<Self, Self::Error> {
31        match value {
32            1 => Ok(Self::V1),
33            _ => Err(()),
34        }
35    }
36}
37
38const MAGIC_BYTES: [u8; 3] = [b'V', b'L', b'G'];
39
40impl Version {
41    // NOTE: Used in tests
42    #[allow(unused)]
43    pub(crate) fn len() -> u8 {
44        5
45    }
46
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<usize> {
64        writer.write_all(&MAGIC_BYTES)?;
65        writer.write_u8(u8::from(self))?;
66        Ok(5)
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'V', b'L', b'G', 1]);
81        Ok(())
82    }
83
84    #[test]
85    #[allow(clippy::expect_used)]
86    pub fn version_deserialize_success() {
87        let version = Version::parse_file_header(&[b'V', b'L', b'G', 1]);
88        assert_eq!(version, Some(Version::V1));
89    }
90
91    #[test]
92    #[allow(clippy::expect_used)]
93    pub fn version_deserialize_fail() {
94        let version = Version::parse_file_header(&[b'F', b'J', b'X', 1]);
95        assert!(version.is_none());
96    }
97
98    #[test]
99    #[allow(clippy::expect_used)]
100    pub fn version_serde_round_trip() {
101        let mut buf = vec![];
102        Version::V1.write_file_header(&mut buf).expect("can't fail");
103
104        let version = Version::parse_file_header(&buf);
105        assert_eq!(version, Some(Version::V1));
106    }
107
108    #[test]
109    #[allow(clippy::expect_used)]
110    pub fn version_len() {
111        let mut buf = vec![];
112        let size = Version::V1.write_file_header(&mut buf).expect("can't fail");
113        assert_eq!(Version::len() as usize, size);
114    }
115}