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