1use crate::error::{CrousError, Result};
19
20pub const MAGIC: &[u8; 7] = b"CROUSv1";
22
23pub const HEADER_SIZE: usize = 8;
25
26pub const FLAGS_NONE: u8 = 0x00;
28pub const FLAGS_HAS_INDEX: u8 = 0x02;
30pub const FLAGS_HAS_SCHEMA: u8 = 0x04;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct FileHeader {
36 pub flags: u8,
38}
39
40impl FileHeader {
41 pub fn new(flags: u8) -> Self {
43 Self { flags }
44 }
45
46 pub fn encode(&self) -> [u8; HEADER_SIZE] {
48 let mut buf = [0u8; HEADER_SIZE];
49 buf[..7].copy_from_slice(MAGIC);
50 buf[7] = self.flags;
51 buf
52 }
53
54 pub fn decode(data: &[u8]) -> Result<Self> {
56 if data.len() < HEADER_SIZE {
57 return Err(CrousError::UnexpectedEof(data.len()));
58 }
59 if &data[..7] != MAGIC {
60 return Err(CrousError::InvalidMagic);
61 }
62 Ok(Self { flags: data[7] })
63 }
64
65 pub fn has_index(&self) -> bool {
67 self.flags & FLAGS_HAS_INDEX != 0
68 }
69
70 pub fn has_schema(&self) -> bool {
72 self.flags & FLAGS_HAS_SCHEMA != 0
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn header_roundtrip() {
82 let hdr = FileHeader::new(FLAGS_HAS_INDEX | FLAGS_HAS_SCHEMA);
83 let bytes = hdr.encode();
84 assert_eq!(&bytes[..7], b"CROUSv1");
85 assert_eq!(bytes[7], 0x06);
86 let decoded = FileHeader::decode(&bytes).unwrap();
87 assert_eq!(decoded, hdr);
88 assert!(decoded.has_index());
89 assert!(decoded.has_schema());
90 }
91
92 #[test]
93 fn header_invalid_magic() {
94 let bad = b"CROUSv2\x00";
95 assert!(matches!(
96 FileHeader::decode(bad),
97 Err(CrousError::InvalidMagic)
98 ));
99 }
100
101 #[test]
102 fn header_too_short() {
103 let short = b"CROUS";
104 assert!(FileHeader::decode(short).is_err());
105 }
106}