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}