oca_zip_resolver 0.2.14

Lib for resolving OCA Bundle from zip file to OCA struct
Documentation
use oca_rs::state::oca::OCA;
use std::collections::HashMap;
use std::fs;
use std::io::BufReader;
use std::io::Read;

#[derive(Debug)]
struct ResolvedFile {
    meta: serde_json::Value,
    files: HashMap<String, String>,
}

pub fn resolve_from_zip(path: &str) -> Result<OCA, String> {
    let fname = std::path::Path::new(path);
    let file =
        fs::File::open(fname).map_err(|e| format!("Error while loading {path} file. {e}"))?;
    let reader = BufReader::new(file);

    let mut archive = zip::ZipArchive::new(reader).map_err(|err| err.to_string())?;

    let mut resolved_file = ResolvedFile {
        meta: serde_json::Value::Null,
        files: HashMap::new(),
    };

    for i in 0..archive.len() {
        let mut file = archive.by_index(i).unwrap();
        if file.enclosed_name().is_none() {
            return Err(format!("Entry {} has a suspicious path", file.name()));
        };

        if (*file.name()).contains('/') {
            continue;
        } else {
            let mut buffer = String::new();

            file.read_to_string(&mut buffer)
                .map_err(|err| err.to_string())?;
            if file.name().to_string().eq(&String::from("meta.json")) {
                resolved_file.meta =
                    serde_json::from_str(buffer.as_str()).map_err(|err| err.to_string())?;
            } else {
                resolved_file.files.insert(file.name().to_string(), buffer);
            }
        }
    }

    if let serde_json::Value::Null = resolved_file.meta {
        return Err(format!(
            "Malformed OCA Bundle ({path}). Missing meta.json file."
        ));
    }

    let mut oca_option: Option<OCA> = None;
    if let serde_json::Value::String(root_sai) = resolved_file
        .meta
        .get("root")
        .ok_or("Missing 'root' attribute in meta.json file")
        .map_err(|e| e.to_string())?
    {
        let root_filename = format!("{root_sai}.json");
        let root_file_content = resolved_file.files.remove(&root_filename).ok_or(format!(
            "Malformed OCA Bundle ({}). Missing {} file.",
            path, &root_filename
        ))?;
        let data = format!(
            r#"{{"capture_base": {}, "overlays": [{}] }}"#,
            root_file_content,
            resolved_file
                .files
                .values()
                .cloned()
                .collect::<Vec<String>>()
                .join(",")
        );

        let oca_builder = oca_rs::controller::load_oca(&mut data.as_bytes()).unwrap();
        oca_option = Some(oca_builder.finalize());
    }

    oca_option
        .ok_or("Error while loading OCA Bundle")
        .map_err(|e| e.to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    fn assets_dir_path() -> String {
        format!("{}/assets", env!("CARGO_MANIFEST_DIR"))
    }

    #[test]
    fn resolve_from_proper_flat_oca_bundle_is_ok() {
        let common_assets_dir_path = format!("{}/../assets", env!("CARGO_MANIFEST_DIR"));
        let path = format!("{common_assets_dir_path}/oca_bundle.zip");
        let oca_result = resolve_from_zip(path.as_str());
        assert!(oca_result.is_ok());
    }

    #[test]
    fn resolve_from_proper_oca_bundle_with_dir_is_ok() {
        let path = format!("{}/oca_bundle_with_dir.zip", assets_dir_path());
        let oca_result = resolve_from_zip(path.as_str());
        assert!(oca_result.is_ok());
    }

    #[test]
    fn resolve_from_missing_oca_bundle_is_err() {
        let path = format!("{}/missing_oca.zip", assets_dir_path());
        let oca_result = resolve_from_zip(path.as_str());
        assert!(oca_result.is_err());
    }

    #[test]
    fn resolve_from_malformed_oca_bundle_is_err() {
        let assets_dir_path = assets_dir_path();
        let paths = vec![
            format!("{}/missing_meta_file.zip", &assets_dir_path),
            format!("{}/missing_root_file.zip", &assets_dir_path),
        ];
        for path in paths {
            let oca_result = resolve_from_zip(path.as_str());
            assert!(oca_result.is_err());
            if let Err(e) = oca_result {
                assert!(e.contains("Malformed OCA Bundle"));
            }
        }
    }
}