1use std::io;
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
5pub enum Body {
6 InMemory(Vec<u8>),
7 OnDisk(PathBuf),
8}
9
10impl Body {
11 pub fn read_all(&self) -> io::Result<Vec<u8>> {
12 match self {
13 Self::InMemory(b) => Ok(b.clone()),
14 Self::OnDisk(p) => std::fs::read(p),
15 }
16 }
17
18 pub fn read_string(&self) -> io::Result<String> {
19 let bytes = self.read_all()?;
20 String::from_utf8(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
21 }
22
23 pub fn len_hint(&self) -> Option<u64> {
24 match self {
25 Self::InMemory(b) => Some(b.len() as u64),
26 Self::OnDisk(p) => std::fs::metadata(p).ok().map(|m| m.len()),
27 }
28 }
29
30 pub fn from_bytes(bytes: Vec<u8>) -> Self {
31 Self::InMemory(bytes)
32 }
33
34 pub fn from_string(s: String) -> Self {
35 Self::InMemory(s.into_bytes())
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use std::io::Write;
43
44 #[test]
45 fn in_memory_read_all_returns_bytes() {
46 let body = Body::InMemory(b"hi".to_vec());
47 assert_eq!(body.read_all().unwrap(), b"hi");
48 }
49
50 #[test]
51 fn in_memory_read_string_returns_utf8() {
52 let body = Body::InMemory(b"hi".to_vec());
53 assert_eq!(body.read_string().unwrap(), "hi");
54 }
55
56 #[test]
57 fn in_memory_read_string_invalid_utf8_errors() {
58 let body = Body::InMemory(vec![0xFF, 0xFE]);
59 let err = body.read_string().unwrap_err();
60 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
61 }
62
63 #[test]
64 fn on_disk_round_trip() {
65 let dir = std::env::temp_dir().join(format!("awsim-body-test-{}", std::process::id()));
66 std::fs::create_dir_all(&dir).unwrap();
67 let path = dir.join("blob.bin");
68 let mut f = std::fs::File::create(&path).unwrap();
69 f.write_all(b"hello world").unwrap();
70 drop(f);
71
72 let body = Body::OnDisk(path.clone());
73 assert_eq!(body.read_all().unwrap(), b"hello world");
74 assert_eq!(body.read_string().unwrap(), "hello world");
75 assert_eq!(body.len_hint(), Some(11));
76
77 let _ = std::fs::remove_file(&path);
78 let _ = std::fs::remove_dir(&dir);
79 }
80
81 #[test]
82 fn len_hint_in_memory() {
83 let body = Body::InMemory(vec![0; 42]);
84 assert_eq!(body.len_hint(), Some(42));
85 }
86
87 #[test]
88 fn len_hint_on_disk_missing_returns_none() {
89 let body = Body::OnDisk(PathBuf::from("/nonexistent/awsim/path/does/not/exist"));
90 assert_eq!(body.len_hint(), None);
91 }
92
93 #[test]
94 fn from_bytes_constructs_in_memory() {
95 let body = Body::from_bytes(b"abc".to_vec());
96 match body {
97 Body::InMemory(b) => assert_eq!(b, b"abc"),
98 _ => panic!("expected InMemory"),
99 }
100 }
101
102 #[test]
103 fn from_string_constructs_in_memory() {
104 let body = Body::from_string("abc".to_string());
105 assert_eq!(body.read_string().unwrap(), "abc");
106 }
107}