1use std::sync::Arc;
13
14pub trait FileSource: Send + Sync {
18 fn read(&self, path: &str) -> Option<Vec<u8>>;
23}
24
25#[allow(dead_code)]
29pub struct DiskFileSource;
30
31impl FileSource for DiskFileSource {
32 fn read(&self, path: &str) -> Option<Vec<u8>> {
33 std::fs::read(path).ok()
34 }
35}
36
37pub(crate) type FileSourceHandle = Arc<dyn FileSource>;
40
41#[allow(dead_code)]
45pub fn set_file_source(lua: &mlua::Lua, source: Arc<dyn FileSource>) {
46 lua.set_app_data::<FileSourceHandle>(source);
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use std::sync::Arc;
53
54 struct InMemSource(Vec<(String, Vec<u8>)>);
55 impl FileSource for InMemSource {
56 fn read(&self, path: &str) -> Option<Vec<u8>> {
57 self.0
58 .iter()
59 .find(|(p, _)| p == path)
60 .map(|(_, b)| b.clone())
61 }
62 }
63
64 #[test]
65 fn disk_source_reads_real_file() {
66 let tmp = tempfile::NamedTempFile::new().unwrap();
67 std::fs::write(tmp.path(), b"hello").unwrap();
68 let src = DiskFileSource;
69 let bytes = src.read(tmp.path().to_str().unwrap()).unwrap();
70 assert_eq!(bytes, b"hello");
71 }
72
73 #[test]
74 fn disk_source_returns_none_for_missing() {
75 let src = DiskFileSource;
76 assert!(src.read("/definitely/does/not/exist").is_none());
77 }
78
79 #[test]
80 fn in_mem_source_returns_registered_path() {
81 let src = InMemSource(vec![("hi.txt".into(), b"hello".to_vec())]);
82 assert_eq!(src.read("hi.txt").unwrap(), b"hello");
83 assert!(src.read("missing").is_none());
84 }
85
86 #[test]
87 fn set_file_source_stores_handle_in_app_data() {
88 let lua = mlua::Lua::new();
89 let src: Arc<dyn FileSource> = Arc::new(DiskFileSource);
90 set_file_source(&lua, src);
91 assert!(lua.app_data_ref::<FileSourceHandle>().is_some());
92 }
93}