oca_zip_resolver/
lib.rs

1use oca_rs::state::oca::OCA;
2use std::collections::HashMap;
3use std::fs;
4use std::io::BufReader;
5use std::io::Read;
6
7#[derive(Debug)]
8struct ResolvedFile {
9    meta: serde_json::Value,
10    files: HashMap<String, String>,
11}
12
13pub fn resolve_from_zip(path: &str) -> Result<OCA, String> {
14    let fname = std::path::Path::new(path);
15    let file =
16        fs::File::open(fname).map_err(|e| format!("Error while loading {path} file. {e}"))?;
17    let reader = BufReader::new(file);
18
19    let mut archive = zip::ZipArchive::new(reader).map_err(|err| err.to_string())?;
20
21    let mut resolved_file = ResolvedFile {
22        meta: serde_json::Value::Null,
23        files: HashMap::new(),
24    };
25
26    for i in 0..archive.len() {
27        let mut file = archive.by_index(i).unwrap();
28        if file.enclosed_name().is_none() {
29            return Err(format!("Entry {} has a suspicious path", file.name()));
30        };
31
32        if (*file.name()).contains('/') {
33            continue;
34        } else {
35            let mut buffer = String::new();
36
37            file.read_to_string(&mut buffer)
38                .map_err(|err| err.to_string())?;
39            if file.name().to_string().eq(&String::from("meta.json")) {
40                resolved_file.meta =
41                    serde_json::from_str(buffer.as_str()).map_err(|err| err.to_string())?;
42            } else {
43                resolved_file.files.insert(file.name().to_string(), buffer);
44            }
45        }
46    }
47
48    if let serde_json::Value::Null = resolved_file.meta {
49        return Err(format!(
50            "Malformed OCA Bundle ({path}). Missing meta.json file."
51        ));
52    }
53
54    let mut oca_option: Option<OCA> = None;
55    if let serde_json::Value::String(root_sai) = resolved_file
56        .meta
57        .get("root")
58        .ok_or("Missing 'root' attribute in meta.json file")
59        .map_err(|e| e.to_string())?
60    {
61        let root_filename = format!("{root_sai}.json");
62        let root_file_content = resolved_file.files.remove(&root_filename).ok_or(format!(
63            "Malformed OCA Bundle ({}). Missing {} file.",
64            path, &root_filename
65        ))?;
66        let data = format!(
67            r#"{{"capture_base": {}, "overlays": [{}] }}"#,
68            root_file_content,
69            resolved_file
70                .files
71                .values()
72                .cloned()
73                .collect::<Vec<String>>()
74                .join(",")
75        );
76
77        let oca_builder = oca_rs::controller::load_oca(&mut data.as_bytes()).unwrap();
78        oca_option = Some(oca_builder.finalize());
79    }
80
81    oca_option
82        .ok_or("Error while loading OCA Bundle")
83        .map_err(|e| e.to_string())
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    fn assets_dir_path() -> String {
91        format!("{}/assets", env!("CARGO_MANIFEST_DIR"))
92    }
93
94    #[test]
95    fn resolve_from_proper_flat_oca_bundle_is_ok() {
96        let common_assets_dir_path = format!("{}/../assets", env!("CARGO_MANIFEST_DIR"));
97        let path = format!("{common_assets_dir_path}/oca_bundle.zip");
98        let oca_result = resolve_from_zip(path.as_str());
99        assert!(oca_result.is_ok());
100    }
101
102    #[test]
103    fn resolve_from_proper_oca_bundle_with_dir_is_ok() {
104        let path = format!("{}/oca_bundle_with_dir.zip", assets_dir_path());
105        let oca_result = resolve_from_zip(path.as_str());
106        assert!(oca_result.is_ok());
107    }
108
109    #[test]
110    fn resolve_from_missing_oca_bundle_is_err() {
111        let path = format!("{}/missing_oca.zip", assets_dir_path());
112        let oca_result = resolve_from_zip(path.as_str());
113        assert!(oca_result.is_err());
114    }
115
116    #[test]
117    fn resolve_from_malformed_oca_bundle_is_err() {
118        let assets_dir_path = assets_dir_path();
119        let paths = vec![
120            format!("{}/missing_meta_file.zip", &assets_dir_path),
121            format!("{}/missing_root_file.zip", &assets_dir_path),
122        ];
123        for path in paths {
124            let oca_result = resolve_from_zip(path.as_str());
125            assert!(oca_result.is_err());
126            if let Err(e) = oca_result {
127                assert!(e.contains("Malformed OCA Bundle"));
128            }
129        }
130    }
131}