konnektoren_core/
asset_loader.rs1use std::path::PathBuf;
2
3#[cfg(feature = "csr")]
4use gloo::net::http::Request;
5
6#[derive(Debug, Clone)]
7pub enum AssetLoader {
8 Url { base_url: String },
10 File { base_dirs: Vec<PathBuf> },
12}
13
14impl Default for AssetLoader {
15 fn default() -> Self {
16 #[cfg(feature = "csr")]
17 {
18 AssetLoader::Url {
20 base_url: "/assets/".to_string(),
21 }
22 }
23
24 #[cfg(all(feature = "ssr", not(feature = "csr")))]
25 {
26 let mut base_dirs = Vec::new();
28
29 if let Ok(build_dir) = std::env::var("BUILD_DIR") {
31 base_dirs.push(PathBuf::from(build_dir));
32 }
33
34 base_dirs.push(PathBuf::from("./"));
36 base_dirs.push(PathBuf::from("assets"));
37
38 AssetLoader::File { base_dirs }
39 }
40
41 #[cfg(not(any(feature = "csr", feature = "ssr")))]
42 {
43 AssetLoader::File {
45 base_dirs: vec![PathBuf::from("./")],
46 }
47 }
48 }
49}
50
51impl AssetLoader {
52 #[cfg(feature = "csr")]
54 pub fn new_url(base_url: impl Into<String>) -> Self {
55 AssetLoader::Url {
56 base_url: base_url.into(),
57 }
58 }
59
60 pub fn new_file(base_dirs: Vec<PathBuf>) -> Self {
62 AssetLoader::File { base_dirs }
63 }
64
65 pub fn from_dir(dir: impl Into<PathBuf>) -> Self {
67 AssetLoader::File {
68 base_dirs: vec![dir.into()],
69 }
70 }
71
72 pub async fn load_binary(&self, path: &str) -> Result<Vec<u8>, String> {
74 match self {
75 #[cfg(feature = "csr")]
76 AssetLoader::Url { base_url } => {
77 let normalized_path = path.trim_start_matches('/');
79 let base_url = if base_url.ends_with('/') {
80 base_url.to_string()
81 } else {
82 format!("{}/", base_url)
83 };
84
85 let url = format!("{}{}", base_url, normalized_path);
86
87 let response = Request::get(&url)
88 .send()
89 .await
90 .map_err(|e| format!("Failed to send request to {}: {}", url, e))?;
91
92 if response.status() != 200 {
93 return Err(format!(
94 "Failed to load asset {}: status {}",
95 url,
96 response.status()
97 ));
98 }
99
100 response
101 .binary()
102 .await
103 .map_err(|e| format!("Failed to read response from {}: {}", url, e))
104 }
105
106 AssetLoader::File { base_dirs } => {
107 for base_dir in base_dirs {
109 let file_path = base_dir.join(path);
110 if file_path.exists() {
111 return std::fs::read(&file_path).map_err(|e| {
112 format!("Failed to read file {}: {}", file_path.display(), e)
113 });
114 }
115 }
116
117 let path_buf = PathBuf::from(path);
119 if path_buf.is_absolute() && path_buf.exists() {
120 return std::fs::read(&path_buf)
121 .map_err(|e| format!("Failed to read file {}: {}", path_buf.display(), e));
122 }
123
124 Err(format!("File not found: {}", path))
125 }
126
127 #[allow(unreachable_patterns)]
128 _ => Err("Asset loading is not available in this configuration".to_string()),
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 #[cfg(feature = "csr")]
136 use crate::asset_loader::AssetLoader;
137
138 #[cfg(feature = "csr")]
139 use wasm_bindgen_test::*;
140
141 #[cfg(feature = "csr")]
142 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
143
144 #[cfg(all(test, feature = "ssr", not(feature = "csr")))]
145 const KONNEKTOREN_YAML_CONTENT: &str = include_str!("../assets/konnektoren.yml");
146
147 #[cfg(all(test, feature = "ssr", not(feature = "csr")))]
149 #[tokio::test]
150 async fn test_load_konnektoren_yaml_ssr() {
151 use std::io::Write;
152 use tempfile::TempDir;
153 let temp_dir = TempDir::new().unwrap();
155
156 let assets_dir = temp_dir.path().join("assets");
158 std::fs::create_dir_all(&assets_dir).unwrap();
159
160 let yaml_path = assets_dir.join("konnektoren.yml");
162 let mut file = std::fs::File::create(&yaml_path).unwrap();
163 file.write_all(KONNEKTOREN_YAML_CONTENT.as_bytes()).unwrap();
164
165 std::env::set_var("BUILD_DIR", temp_dir.path().to_str().unwrap());
167
168 let asset_loader = AssetLoader::default();
170
171 let content = asset_loader
173 .load_binary("assets/konnektoren.yml")
174 .await
175 .unwrap();
176
177 let content_str = String::from_utf8(content).unwrap();
179 assert!(content_str.contains("id: \"konnektoren\""));
180 assert!(content_str.contains("name: \"Konnektoren\""));
181 assert!(content_str.contains("lang: \"de\""));
182 }
183
184 #[cfg(all(test, feature = "ssr", not(feature = "csr")))]
186 #[tokio::test]
187 async fn test_load_konnektoren_yaml_file_loader() {
188 use std::io::Write;
189 use tempfile::TempDir;
190
191 let temp_dir = TempDir::new().unwrap();
193
194 let assets_dir = temp_dir.path().join("assets");
196 std::fs::create_dir_all(&assets_dir).unwrap();
197
198 let yaml_path = assets_dir.join("konnektoren.yml");
200 let mut file = std::fs::File::create(&yaml_path).unwrap();
201 file.write_all(KONNEKTOREN_YAML_CONTENT.as_bytes()).unwrap();
202
203 let asset_loader = AssetLoader::from_dir(temp_dir.path());
205
206 let content = asset_loader
208 .load_binary("assets/konnektoren.yml")
209 .await
210 .unwrap();
211
212 let content_str = String::from_utf8(content).unwrap();
214 assert!(content_str.contains("id: \"konnektoren\""));
215 assert!(content_str.contains("name: \"Konnektoren\""));
216 assert!(content_str.contains("lang: \"de\""));
217 }
218
219 #[cfg(feature = "csr")]
221 #[wasm_bindgen_test]
222 async fn test_load_konnektoren_yaml_csr() {
223 let asset_loader = AssetLoader::default();
227
228 let result = asset_loader.load_binary("assets/konnektoren.yml").await;
231
232 match result {
236 Ok(_) => {
237 println!("Successfully loaded the file (integration environment)");
238 }
239 Err(e) => {
240 println!("Expected error in test environment: {}", e);
241 assert!(e.contains("assets/konnektoren.yml"));
243 }
244 }
245 }
246
247 #[cfg(feature = "csr")]
249 #[wasm_bindgen_test]
250 async fn test_custom_url_base_csr() {
251 let asset_loader = AssetLoader::new_url("https://example.com/static");
252
253 let result = asset_loader.load_binary("assets/konnektoren.yml").await;
255
256 match result {
257 Ok(_) => {
258 println!("Successfully loaded the file (integration environment)");
259 }
260 Err(e) => {
261 println!("Expected error in test environment: {}", e);
262 assert!(e.contains("https://example.com/static/assets/konnektoren.yml"));
264 }
265 }
266 }
267}