1use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, PartialEq)]
15pub enum FileData {
16 Bytes(Vec<u8>),
18 Path(PathBuf),
21}
22
23impl FileData {
24 pub fn write_to(&self, dest: &Path) -> Result<(), std::io::Error> {
30 match self {
31 FileData::Bytes(data) => std::fs::write(dest, data),
32 FileData::Path(src) => {
33 match std::fs::rename(src, dest) {
35 Ok(()) => Ok(()),
36 Err(_) => {
37 std::fs::copy(src, dest)?;
39 let _ = std::fs::remove_file(src);
40 Ok(())
41 }
42 }
43 }
44 }
45 }
46
47 pub fn len(&self) -> Result<u64, std::io::Error> {
50 match self {
51 FileData::Bytes(data) => Ok(data.len() as u64),
52 FileData::Path(path) => std::fs::metadata(path).map(|m| m.len()),
53 }
54 }
55
56 pub fn is_empty(&self) -> Result<bool, std::io::Error> {
58 self.len().map(|n| n == 0)
59 }
60
61 pub fn copy_to(&self, dest: &Path) -> Result<(), std::io::Error> {
66 match self {
67 FileData::Bytes(data) => std::fs::write(dest, data),
68 FileData::Path(src) => {
69 std::fs::copy(src, dest)?;
70 Ok(())
71 }
72 }
73 }
74
75 pub fn into_bytes(self) -> Result<Vec<u8>, std::io::Error> {
78 match self {
79 FileData::Bytes(data) => Ok(data),
80 FileData::Path(path) => std::fs::read(path),
81 }
82 }
83
84 pub fn to_bytes(&self) -> Result<std::borrow::Cow<'_, [u8]>, std::io::Error> {
88 match self {
89 FileData::Bytes(data) => Ok(std::borrow::Cow::Borrowed(data)),
90 FileData::Path(path) => std::fs::read(path).map(std::borrow::Cow::Owned),
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_to_bytes_borrows_bytes_and_reads_a_path() {
101 let data = FileData::Bytes(b"in memory".to_vec());
102 assert!(matches!(
103 data.to_bytes().unwrap(),
104 std::borrow::Cow::Borrowed(b"in memory")
105 ));
106
107 let temp = tempfile::tempdir().unwrap();
108 let path = temp.path().join("on-disk.txt");
109 std::fs::write(&path, b"on disk").unwrap();
110 let data = FileData::Path(path);
111 assert_eq!(&*data.to_bytes().unwrap(), b"on disk");
112 assert!(
113 matches!(data, FileData::Path(_)),
114 "to_bytes leaves the reference in place"
115 );
116 }
117
118 #[test]
119 fn test_to_bytes_names_a_missing_file_error() {
120 let temp = tempfile::tempdir().unwrap();
121 assert!(
122 FileData::Path(temp.path().join("gone.txt"))
123 .to_bytes()
124 .is_err()
125 );
126 }
127
128 #[test]
129 fn test_filedata_bytes_write_to() {
130 let temp = tempfile::tempdir().unwrap();
131 let dir = temp.path().to_path_buf();
132
133 let data = FileData::Bytes(b"hello world".to_vec());
134 let dest = dir.join("output.txt");
135 data.write_to(&dest).unwrap();
136
137 assert_eq!(std::fs::read(&dest).unwrap(), b"hello world");
138 }
139
140 #[test]
141 fn test_filedata_path_write_to_renames() {
142 let temp = tempfile::tempdir().unwrap();
143 let dir = temp.path().to_path_buf();
144
145 let src = dir.join("source.bin");
146 std::fs::write(&src, b"file content").unwrap();
147
148 let data = FileData::Path(src.clone());
149 let dest = dir.join("moved.bin");
150 data.write_to(&dest).unwrap();
151
152 assert_eq!(std::fs::read(&dest).unwrap(), b"file content");
153 assert!(!src.exists());
155 }
156
157 #[test]
158 fn test_filedata_bytes_len() {
159 let data = FileData::Bytes(vec![0u8; 42]);
160 assert_eq!(data.len().unwrap(), 42);
161 }
162
163 #[test]
164 fn test_filedata_path_len() {
165 let temp = tempfile::tempdir().unwrap();
166 let dir = temp.path().to_path_buf();
167
168 let path = dir.join("sized.bin");
169 std::fs::write(&path, vec![0u8; 1024]).unwrap();
170
171 let data = FileData::Path(path);
172 assert_eq!(data.len().unwrap(), 1024);
173 }
174
175 #[test]
176 fn test_filedata_bytes_is_empty() {
177 assert!(FileData::Bytes(vec![]).is_empty().unwrap());
178 assert!(!FileData::Bytes(vec![1]).is_empty().unwrap());
179 }
180
181 #[test]
182 fn test_filedata_bytes_copy_to() {
183 let temp = tempfile::tempdir().unwrap();
184 let dir = temp.path().to_path_buf();
185
186 let data = FileData::Bytes(b"copy me".to_vec());
187 let dest = dir.join("copied.txt");
188 data.copy_to(&dest).unwrap();
189
190 assert_eq!(std::fs::read(&dest).unwrap(), b"copy me");
191 }
192
193 #[test]
194 fn test_filedata_path_copy_to_preserves_source() {
195 let temp = tempfile::tempdir().unwrap();
196 let dir = temp.path().to_path_buf();
197
198 let src = dir.join("source.bin");
199 std::fs::write(&src, b"original content").unwrap();
200
201 let data = FileData::Path(src.clone());
202 let dest = dir.join("copied.bin");
203 data.copy_to(&dest).unwrap();
204
205 assert_eq!(std::fs::read(&dest).unwrap(), b"original content");
206 assert!(src.exists(), "Source should still exist after copy_to");
207 }
208
209 #[test]
210 fn test_filedata_bytes_into_bytes() {
211 let data = FileData::Bytes(b"content".to_vec());
212 assert_eq!(data.into_bytes().unwrap(), b"content");
213 }
214
215 #[test]
216 fn test_filedata_path_into_bytes() {
217 let temp = tempfile::tempdir().unwrap();
218 let dir = temp.path().to_path_buf();
219
220 let path = dir.join("read-me.bin");
221 std::fs::write(&path, b"disk content").unwrap();
222
223 let data = FileData::Path(path);
224 assert_eq!(data.into_bytes().unwrap(), b"disk content");
225 }
226}