use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq)]
pub enum FileData {
Bytes(Vec<u8>),
Path(PathBuf),
}
impl FileData {
pub fn write_to(&self, dest: &Path) -> Result<(), std::io::Error> {
match self {
FileData::Bytes(data) => std::fs::write(dest, data),
FileData::Path(src) => {
match std::fs::rename(src, dest) {
Ok(()) => Ok(()),
Err(_) => {
std::fs::copy(src, dest)?;
let _ = std::fs::remove_file(src);
Ok(())
}
}
}
}
}
pub fn len(&self) -> Result<u64, std::io::Error> {
match self {
FileData::Bytes(data) => Ok(data.len() as u64),
FileData::Path(path) => std::fs::metadata(path).map(|m| m.len()),
}
}
pub fn is_empty(&self) -> Result<bool, std::io::Error> {
self.len().map(|n| n == 0)
}
pub fn copy_to(&self, dest: &Path) -> Result<(), std::io::Error> {
match self {
FileData::Bytes(data) => std::fs::write(dest, data),
FileData::Path(src) => {
std::fs::copy(src, dest)?;
Ok(())
}
}
}
pub fn into_bytes(self) -> Result<Vec<u8>, std::io::Error> {
match self {
FileData::Bytes(data) => Ok(data),
FileData::Path(path) => std::fs::read(path),
}
}
pub fn to_bytes(&self) -> Result<std::borrow::Cow<'_, [u8]>, std::io::Error> {
match self {
FileData::Bytes(data) => Ok(std::borrow::Cow::Borrowed(data)),
FileData::Path(path) => std::fs::read(path).map(std::borrow::Cow::Owned),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_bytes_borrows_bytes_and_reads_a_path() {
let data = FileData::Bytes(b"in memory".to_vec());
assert!(matches!(
data.to_bytes().unwrap(),
std::borrow::Cow::Borrowed(b"in memory")
));
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("on-disk.txt");
std::fs::write(&path, b"on disk").unwrap();
let data = FileData::Path(path);
assert_eq!(&*data.to_bytes().unwrap(), b"on disk");
assert!(
matches!(data, FileData::Path(_)),
"to_bytes leaves the reference in place"
);
}
#[test]
fn test_to_bytes_names_a_missing_file_error() {
let temp = tempfile::tempdir().unwrap();
assert!(
FileData::Path(temp.path().join("gone.txt"))
.to_bytes()
.is_err()
);
}
#[test]
fn test_filedata_bytes_write_to() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path().to_path_buf();
let data = FileData::Bytes(b"hello world".to_vec());
let dest = dir.join("output.txt");
data.write_to(&dest).unwrap();
assert_eq!(std::fs::read(&dest).unwrap(), b"hello world");
}
#[test]
fn test_filedata_path_write_to_renames() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path().to_path_buf();
let src = dir.join("source.bin");
std::fs::write(&src, b"file content").unwrap();
let data = FileData::Path(src.clone());
let dest = dir.join("moved.bin");
data.write_to(&dest).unwrap();
assert_eq!(std::fs::read(&dest).unwrap(), b"file content");
assert!(!src.exists());
}
#[test]
fn test_filedata_bytes_len() {
let data = FileData::Bytes(vec![0u8; 42]);
assert_eq!(data.len().unwrap(), 42);
}
#[test]
fn test_filedata_path_len() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path().to_path_buf();
let path = dir.join("sized.bin");
std::fs::write(&path, vec![0u8; 1024]).unwrap();
let data = FileData::Path(path);
assert_eq!(data.len().unwrap(), 1024);
}
#[test]
fn test_filedata_bytes_is_empty() {
assert!(FileData::Bytes(vec![]).is_empty().unwrap());
assert!(!FileData::Bytes(vec![1]).is_empty().unwrap());
}
#[test]
fn test_filedata_bytes_copy_to() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path().to_path_buf();
let data = FileData::Bytes(b"copy me".to_vec());
let dest = dir.join("copied.txt");
data.copy_to(&dest).unwrap();
assert_eq!(std::fs::read(&dest).unwrap(), b"copy me");
}
#[test]
fn test_filedata_path_copy_to_preserves_source() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path().to_path_buf();
let src = dir.join("source.bin");
std::fs::write(&src, b"original content").unwrap();
let data = FileData::Path(src.clone());
let dest = dir.join("copied.bin");
data.copy_to(&dest).unwrap();
assert_eq!(std::fs::read(&dest).unwrap(), b"original content");
assert!(src.exists(), "Source should still exist after copy_to");
}
#[test]
fn test_filedata_bytes_into_bytes() {
let data = FileData::Bytes(b"content".to_vec());
assert_eq!(data.into_bytes().unwrap(), b"content");
}
#[test]
fn test_filedata_path_into_bytes() {
let temp = tempfile::tempdir().unwrap();
let dir = temp.path().to_path_buf();
let path = dir.join("read-me.bin");
std::fs::write(&path, b"disk content").unwrap();
let data = FileData::Path(path);
assert_eq!(data.into_bytes().unwrap(), b"disk content");
}
}