#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![deny(missing_docs)]
#[macro_use]
extern crate amplify;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::ops::{Deref, DerefMut};
use std::path::Path;
#[derive(Debug)]
pub struct BinFile<const MAGIC: u64, const VERSION: u16 = 1>(File);
impl<const MAGIC: u64, const VERSION: u16> Deref for BinFile<MAGIC, VERSION> {
type Target = File;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<const MAGIC: u64, const VERSION: u16> DerefMut for BinFile<MAGIC, VERSION> {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl<const MAGIC: u64, const VERSION: u16> Read for BinFile<MAGIC, VERSION> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.0.read(buf) }
}
impl<const MAGIC: u64, const VERSION: u16> Write for BinFile<MAGIC, VERSION> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.0.write(buf) }
fn flush(&mut self) -> io::Result<()> { self.0.flush() }
}
impl<const MAGIC: u64, const VERSION: u16> BinFile<MAGIC, VERSION> {
pub const MAGIC: u64 = MAGIC;
pub const VERSION: u16 = VERSION;
pub fn create(path: impl AsRef<Path>) -> io::Result<Self> {
let mut file = File::create(path)?;
file.write_all(&MAGIC.to_be_bytes())?;
file.write_all(&VERSION.to_be_bytes())?;
Ok(Self(file))
}
pub fn create_new(path: impl AsRef<Path>) -> io::Result<Self> {
let mut file = File::create_new(path)?;
file.write_all(&MAGIC.to_be_bytes())?;
file.write_all(&VERSION.to_be_bytes())?;
Ok(Self(file))
}
pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
let path = path.as_ref();
let mut file = Self(File::open(&path)?);
file.check(&path)?;
Ok(file)
}
pub fn open_rw(path: impl AsRef<Path>) -> io::Result<Self> {
let path = path.as_ref();
let mut file = Self(OpenOptions::new().read(true).write(true).open(&path)?);
file.check(&path)?;
Ok(file)
}
fn check(&mut self, filename: &Path) -> io::Result<()> {
let mut magic = [0u8; 8];
self.read_exact(&mut magic)?;
if magic != MAGIC.to_be_bytes() {
return Err(io::Error::other(BinFileError::InvalidMagic {
filename: filename.to_string_lossy().to_string(),
expected: MAGIC,
actual: u64::from_be_bytes(magic),
}));
}
let mut version = [0u8; 2];
self.read_exact(&mut version)?;
if version != VERSION.to_be_bytes() {
return Err(io::Error::other(BinFileError::InvalidVersion {
filename: filename.to_string_lossy().to_string(),
expected: VERSION,
actual: u16::from_be_bytes(version),
}));
}
Ok(())
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display(doc_comments)]
pub enum BinFileError {
InvalidMagic {
#[allow(missing_docs)]
filename: String,
#[allow(missing_docs)]
expected: u64,
#[allow(missing_docs)]
actual: u64,
},
InvalidVersion {
#[allow(missing_docs)]
filename: String,
#[allow(missing_docs)]
expected: u16,
#[allow(missing_docs)]
actual: u16,
},
}
#[cfg(test)]
mod tests {
use std::fs;
use super::*;
#[test]
fn create() {
const MY_MAGIC: u64 = u64::from_be_bytes(*b"MYMAGIC!");
let mut file = BinFile::<MY_MAGIC, 1>::create("target/test1").unwrap();
file.write_all(b"hello world").unwrap();
file.flush().unwrap();
let check = fs::read("target/test1").unwrap();
assert_eq!(check, b"MYMAGIC!\x00\x01hello world");
}
#[test]
fn create_new() {
const MY_MAGIC: u64 = u64::from_be_bytes(*b"MYMAGIC!");
fs::remove_file("target/test2").ok();
let mut file = BinFile::<MY_MAGIC, 1>::create_new("target/test2").unwrap();
file.write_all(b"hello world").unwrap();
file.flush().unwrap();
let fail = BinFile::<MY_MAGIC, 1>::create_new("target/test2");
assert_eq!(fail.unwrap_err().kind(), io::ErrorKind::AlreadyExists);
let check = fs::read("target/test2").unwrap();
assert_eq!(check, b"MYMAGIC!\x00\x01hello world");
}
#[test]
fn open_ro() {
const MY_MAGIC: u64 = u64::from_be_bytes(*b"MYMAGIC!");
let mut file = BinFile::<MY_MAGIC, 1>::create("target/test3").unwrap();
file.write_all(b"hello world").unwrap();
file.flush().unwrap();
let mut file = BinFile::<MY_MAGIC, 1>::open("target/test3").unwrap();
let mut buf = Vec::new();
let check = file.read_to_end(&mut buf).unwrap();
assert_eq!(check, 11);
assert_eq!(buf, b"hello world");
}
#[test]
fn open_rw() {
const MY_MAGIC: u64 = u64::from_be_bytes(*b"MYMAGIC!");
let mut file = BinFile::<MY_MAGIC, 1>::create("target/test5").unwrap();
file.write_all(b"hello world").unwrap();
file.flush().unwrap();
let mut file = BinFile::<MY_MAGIC, 1>::open_rw("target/test5").unwrap();
let mut buf = Vec::new();
let check = file.read_to_end(&mut buf).unwrap();
assert_eq!(check, 11);
assert_eq!(buf, b"hello world");
file.write_all(b"\nand hello again").unwrap();
}
#[test]
fn open_wrong_magic() {
const MY_MAGIC: u64 = u64::from_be_bytes(*b"MYMAGIC!");
let mut file = BinFile::<MY_MAGIC, 1>::create("target/test4").unwrap();
file.write_all(b"hello world").unwrap();
file.flush().unwrap();
let err = BinFile::<0xFFFFFF_FFFFFF, 1>::open("target/test4").unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::Other);
assert_eq!(err.downcast::<BinFileError>().unwrap(), BinFileError::InvalidMagic {
filename: "target/test4".to_string(),
expected: 0xFFFFFF_FFFFFF,
actual: MY_MAGIC,
});
}
#[test]
fn open_wrong_version() {
const MY_MAGIC: u64 = u64::from_be_bytes(*b"MYMAGIC!");
let mut file = BinFile::<MY_MAGIC, 1>::create("target/test5").unwrap();
file.write_all(b"hello world").unwrap();
file.flush().unwrap();
let err = BinFile::<MY_MAGIC, 0x0100>::open("target/test5").unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::Other);
assert_eq!(err.downcast::<BinFileError>().unwrap(), BinFileError::InvalidVersion {
filename: "target/test5".to_string(),
expected: 0x0100,
actual: 1,
});
}
}