konnektoren_core/challenges/package/
package_reader.rs1use crate::asset_loader::AssetLoader;
2use crate::challenges::{Package, PackageMetadata};
3use std::collections::HashMap;
4use std::io::{Cursor, Read};
5use zip::ZipArchive;
6
7pub struct PackageReader;
8
9impl PackageReader {
10 pub async fn download(url: &str) -> Result<Vec<u8>, String> {
11 let loader = AssetLoader::default();
12 loader.load_binary(url).await
13 }
14
15 pub fn read(package_data: &[u8]) -> Result<Package, String> {
16 let reader = Cursor::new(package_data);
17 let mut archive =
18 ZipArchive::new(reader).map_err(|e| format!("Failed to create ZIP archive: {}", e))?;
19
20 let mut files = HashMap::new();
21 let mut config = None;
22 let mut custom = None;
23
24 for i in 0..archive.len() {
25 let mut file = archive
26 .by_index(i)
27 .map_err(|e| format!("Failed to read file from archive: {}", e))?;
28 let file_name = file.name().to_string();
29
30 let mut contents = Vec::new();
31 file.read_to_end(&mut contents)
32 .map_err(|e| format!("Failed to read file contents: {}", e))?;
33
34 match file_name.as_str() {
35 "config.yml" => {
36 config = Some(
37 serde_yaml::from_slice(&contents)
38 .map_err(|e| format!("Failed to parse config.yml: {}", e))?,
39 );
40 }
41 "challenge.yml" => {
42 custom = Some(
43 serde_yaml::from_slice(&contents)
44 .map_err(|e| format!("Failed to parse challenge.yml: {}", e))?,
45 );
46 }
47 _ => {
48 files.insert(file_name, contents);
49 }
50 }
51 }
52
53 let config = config.ok_or_else(|| "Missing config.yml in package".to_string())?;
54 let custom = custom.unwrap_or_default();
55 let metadata = PackageMetadata { config, custom };
56
57 Ok(Package { metadata, files })
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use env_logger;
65 #[cfg(feature = "ssr")]
66 use std::env;
67
68 #[test]
69 fn test_package_reader() {
70 env_logger::init();
71 let package_data = include_bytes!("../../../assets/articles-pkg.zip");
72 let package = PackageReader::read(package_data).unwrap();
73
74 assert_eq!(package.files.len(), 5);
75 log::debug!("files {:?}", package.files.keys());
76 assert!(package.get_html_file().is_some());
77 assert!(package.get_css_file().is_some());
78 assert!(package.get_js_file().is_some());
79 assert!(package.get_results_file().is_some());
80 }
81
82 #[cfg(feature = "ssr")]
83 #[tokio::test]
84 async fn test_package_reader_ssr() {
85 use std::fs;
86 use tempfile::TempDir;
87
88 let temp_dir = TempDir::new().unwrap();
90 let build_dir_path = temp_dir.path().to_str().unwrap().to_string();
91
92 let test_zip_content = include_bytes!("../../../assets/articles-pkg.zip");
94 let test_zip_path = format!("{}/test_package.zip", build_dir_path);
95 fs::write(&test_zip_path, test_zip_content).unwrap();
96
97 let _guard = SetEnvVariableGuard::new("BUILD_DIR", Some(build_dir_path.clone()));
99
100 let package_data = fs::read(&test_zip_path).unwrap();
103 let package = PackageReader::read(&package_data).unwrap();
104
105 assert_eq!(package.files.len(), 5);
107 assert!(package.get_html_file().is_some());
108 assert!(package.get_css_file().is_some());
109 assert!(package.get_js_file().is_some());
110 assert!(package.get_results_file().is_some());
111 }
112
113 #[cfg(feature = "ssr")]
114 struct SetEnvVariableGuard {
115 name: String,
116 original_value: Option<String>,
117 }
118
119 #[cfg(feature = "ssr")]
120 impl SetEnvVariableGuard {
121 fn new(name: impl Into<String>, value: Option<String>) -> Self {
122 let name = name.into();
123 let original_value = env::var(&name).ok();
124
125 match &value {
126 Some(value) => unsafe { env::set_var(&name, value) },
127 None => unsafe { env::remove_var(&name) },
128 }
129
130 Self {
131 name,
132 original_value,
133 }
134 }
135 }
136
137 #[cfg(feature = "ssr")]
138 impl Drop for SetEnvVariableGuard {
139 fn drop(&mut self) {
140 match &self.original_value {
141 Some(value) => unsafe { env::set_var(&self.name, value) },
142 None => unsafe { env::remove_var(&self.name) },
143 }
144 }
145 }
146}