1use std::{fs, path::Path};
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::Error;
6
7#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
11#[serde(tag = "mode", rename_all = "kebab-case")]
12pub enum Config {
13 #[serde(rename = "magic")]
14 Magic, #[serde(rename = "laser-eyes")]
17 LaserEyes {
18 workspaces: Vec<LaserEyesWorkspaceConfig>,
19 },
20
21 #[serde(rename = "permissive")]
22 Permissive {
23 workspaces: Vec<PermissiveWorkspaceConfig>,
24 #[serde(default)]
25 global_deny: Vec<String>,
26 },
27}
28
29impl Config {
30 pub fn load(manifest_dir: &Path) -> Result<Self, Error> {
31 let manifest_path = manifest_dir.join("Cargo.toml");
32 let content = fs::read_to_string(&manifest_path).map_err(|e| {
33 let message = format!("Failed to read Cargo.toml: {}", e);
34 Error::Config(message)
35 })?;
36
37 let toml_value: toml::Value = toml::from_str(&content).map_err(|e| {
38 let message = format!("Invalid TOML in Cargo.toml: {}", e);
39 Error::Config(message)
40 })?;
41
42 let metadata = toml_value
44 .get("package")
45 .and_then(|p| p.get("metadata"))
46 .and_then(|m| m.get("elf-magic"));
47
48 match metadata {
49 Some(config_value) => {
50 let json_value = serde_json::to_value(config_value).map_err(|e| {
51 let message = format!("Failed to convert config: {}", e);
52 Error::Config(message)
53 })?;
54 serde_json::from_value(json_value).map_err(|e| {
55 let message = format!("Invalid elf-magic config: {}", e);
56 Error::Config(message)
57 })
58 }
59 None => Ok(Config::Magic),
60 }
61 }
62}
63
64impl Default for Config {
65 fn default() -> Self {
66 Self::Magic
67 }
68}
69
70#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
72pub struct LaserEyesWorkspaceConfig {
73 pub manifest_path: String,
74 pub only: Vec<String>,
75}
76
77#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
79pub struct PermissiveWorkspaceConfig {
80 pub manifest_path: String,
81 #[serde(default)]
82 #[serde(alias = "exclude")]
83 pub deny: Vec<String>,
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use std::fs;
90 use tempfile::TempDir;
91
92 fn create_temp_manifest(content: &str) -> (TempDir, std::path::PathBuf) {
93 let temp_dir = TempDir::new().unwrap();
94 let manifest_path = temp_dir.path().join("Cargo.toml");
95 fs::write(&manifest_path, content).unwrap();
96 let path = temp_dir.path().to_path_buf();
97 (temp_dir, path)
98 }
99
100 #[test]
101 fn test_load_config_magic_mode_default() {
102 let manifest_content = r#"
103[package]
104name = "test-package"
105version = "0.1.0"
106edition = "2021"
107"#;
108
109 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
110 let config = Config::load(&manifest_dir).unwrap();
111
112 match config {
113 Config::Magic => {}
114 _ => panic!("Expected Magic mode"),
115 }
116 }
117
118 #[test]
119 fn test_load_config_magic_mode_explicit() {
120 let manifest_content = r#"
121[package]
122name = "test-package"
123version = "0.1.0"
124edition = "2021"
125
126[package.metadata.elf-magic]
127mode = "magic"
128"#;
129
130 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
131 let config = Config::load(&manifest_dir).unwrap();
132
133 match config {
134 Config::Magic => {}
135 _ => panic!("Expected Magic mode"),
136 }
137 }
138
139 #[test]
140 fn test_load_config_permissive_mode() {
141 let manifest_content = r#"
142[package]
143name = "test-package"
144version = "0.1.0"
145edition = "2021"
146
147[package.metadata.elf-magic]
148mode = "permissive"
149workspaces = [
150 { manifest_path = "./Cargo.toml" },
151 { manifest_path = "examples/basic/Cargo.toml", deny = ["target:test*"] }
152]
153"#;
154
155 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
156 let config = Config::load(&manifest_dir).unwrap();
157
158 match config {
159 Config::Permissive {
160 workspaces,
161 global_deny,
162 } => {
163 assert_eq!(workspaces.len(), 2);
164 assert_eq!(workspaces[0].manifest_path, "./Cargo.toml");
165 assert_eq!(workspaces[0].deny.len(), 0);
166 assert_eq!(workspaces[1].manifest_path, "examples/basic/Cargo.toml");
167 assert_eq!(workspaces[1].deny, vec!["target:test*"]);
168 assert_eq!(global_deny.len(), 0); }
170 _ => panic!("Expected Permissive mode"),
171 }
172 }
173
174 #[test]
175 fn test_load_config_permissive_mode_with_exclude_alias() {
176 let manifest_content = r#"
177[package]
178name = "test-package"
179version = "0.1.0"
180edition = "2021"
181
182[package.metadata.elf-magic]
183mode = "permissive"
184workspaces = [
185 { manifest_path = "./Cargo.toml", exclude = ["target:test*", "package:dev*"] }
186]
187"#;
188
189 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
190 let config = Config::load(&manifest_dir).unwrap();
191
192 match config {
193 Config::Permissive {
194 workspaces,
195 global_deny,
196 } => {
197 assert_eq!(workspaces.len(), 1);
198 assert_eq!(workspaces[0].deny, vec!["target:test*", "package:dev*"]);
199 assert_eq!(global_deny.len(), 0); }
201 _ => panic!("Expected Permissive mode"),
202 }
203 }
204
205 #[test]
206 fn test_load_config_missing_file() {
207 let temp_dir = TempDir::new().unwrap();
208 let non_existent = temp_dir.path().join("missing");
209
210 let result = Config::load(&non_existent);
211 assert!(result.is_err());
212 assert!(result
213 .unwrap_err()
214 .to_string()
215 .contains("Failed to read Cargo.toml"));
216 }
217
218 #[test]
219 fn test_load_config_invalid_toml() {
220 let invalid_toml = r#"
221[package
222name = "invalid"
223"#;
224
225 let (_temp_dir, manifest_dir) = create_temp_manifest(invalid_toml);
226 let result = Config::load(&manifest_dir);
227
228 assert!(result.is_err());
229 assert!(result.unwrap_err().to_string().contains("Invalid TOML"));
230 }
231
232 #[test]
233 fn test_load_config_invalid_elf_magic_config() {
234 let manifest_content = r#"
235[package]
236name = "test-package"
237version = "0.1.0"
238edition = "2021"
239
240[package.metadata.elf-magic]
241mode = "invalid-mode"
242"#;
243
244 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
245 let result = Config::load(&manifest_dir);
246
247 assert!(result.is_err());
248 assert!(result
249 .unwrap_err()
250 .to_string()
251 .contains("Invalid elf-magic config"));
252 }
253
254 #[test]
255 fn test_config_default() {
256 let config = Config::default();
257 match config {
258 Config::Magic => {}
259 _ => panic!("Default should be Magic mode"),
260 }
261 }
262
263 #[test]
264 fn test_workspace_config_defaults() {
265 let manifest_content = r#"
266[package]
267name = "test-package"
268version = "0.1.0"
269edition = "2021"
270
271[package.metadata.elf-magic]
272mode = "permissive"
273workspaces = [
274 { manifest_path = "./Cargo.toml" }
275]
276"#;
277
278 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
279 let config = Config::load(&manifest_dir).unwrap();
280
281 match config {
282 Config::Permissive {
283 workspaces,
284 global_deny,
285 } => {
286 assert_eq!(workspaces[0].deny.len(), 0); assert_eq!(global_deny.len(), 0); }
289 _ => panic!("Expected Permissive mode"),
290 }
291 }
292
293 #[test]
294 fn test_load_config_with_global_exclude() {
295 let manifest_content = r#"
296[package]
297name = "test-package"
298version = "0.1.0"
299edition = "2021"
300
301[package.metadata.elf-magic]
302mode = "permissive"
303global_deny = ["package:apl-token", "package:apl-associated-token-account"]
304workspaces = [
305 { manifest_path = "./Cargo.toml" },
306 { manifest_path = "examples/escrow/Cargo.toml", deny = ["target:test*"] }
307]
308"#;
309
310 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
311 let config = Config::load(&manifest_dir).unwrap();
312
313 match config {
314 Config::Permissive {
315 workspaces,
316 global_deny,
317 } => {
318 assert_eq!(workspaces.len(), 2);
319 assert_eq!(
320 global_deny,
321 vec!["package:apl-token", "package:apl-associated-token-account"]
322 );
323
324 assert_eq!(workspaces[0].manifest_path, "./Cargo.toml");
326 assert_eq!(workspaces[0].deny.len(), 0);
327
328 assert_eq!(workspaces[1].manifest_path, "examples/escrow/Cargo.toml");
330 assert_eq!(workspaces[1].deny, vec!["target:test*"]);
331 }
332 _ => panic!("Expected Permissive mode"),
333 }
334 }
335
336 #[test]
337 fn test_load_config_laser_eyes_mode() {
338 let manifest_content = r#"
339[package]
340name = "test-package"
341version = "0.1.0"
342edition = "2021"
343
344[package.metadata.elf-magic]
345mode = "laser-eyes"
346workspaces = [
347 { manifest_path = "./Cargo.toml", only = ["target:token_manager", "target:governance"] },
348 { manifest_path = "examples/defi/Cargo.toml", only = ["target:swap*", "package:my-*-program"] }
349]
350"#;
351
352 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
353 let config = Config::load(&manifest_dir).unwrap();
354
355 match config {
356 Config::LaserEyes { workspaces } => {
357 assert_eq!(workspaces.len(), 2);
358
359 assert_eq!(workspaces[0].manifest_path, "./Cargo.toml");
361 assert_eq!(
362 workspaces[0].only,
363 vec!["target:token_manager", "target:governance"]
364 );
365
366 assert_eq!(workspaces[1].manifest_path, "examples/defi/Cargo.toml");
368 assert_eq!(
369 workspaces[1].only,
370 vec!["target:swap*", "package:my-*-program"]
371 );
372 }
373 _ => panic!("Expected LaserEyes mode"),
374 }
375 }
376
377 #[test]
378 fn test_load_config_laser_eyes_mode_single_workspace() {
379 let manifest_content = r#"
380[package]
381name = "test-package"
382version = "0.1.0"
383edition = "2021"
384
385[package.metadata.elf-magic]
386mode = "laser-eyes"
387workspaces = [
388 { manifest_path = "./Cargo.toml", only = ["target:my_program"] }
389]
390"#;
391
392 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
393 let config = Config::load(&manifest_dir).unwrap();
394
395 match config {
396 Config::LaserEyes { workspaces } => {
397 assert_eq!(workspaces.len(), 1);
398 assert_eq!(workspaces[0].manifest_path, "./Cargo.toml");
399 assert_eq!(workspaces[0].only, vec!["target:my_program"]);
400 }
401 _ => panic!("Expected LaserEyes mode"),
402 }
403 }
404
405 #[test]
406 fn test_load_config_laser_eyes_mode_empty_include() {
407 let manifest_content = r#"
408[package]
409name = "test-package"
410version = "0.1.0"
411edition = "2021"
412
413[package.metadata.elf-magic]
414mode = "laser-eyes"
415workspaces = [
416 { manifest_path = "./Cargo.toml", only = [] }
417]
418"#;
419
420 let (_temp_dir, manifest_dir) = create_temp_manifest(manifest_content);
421 let config = Config::load(&manifest_dir).unwrap();
422
423 match config {
424 Config::LaserEyes { workspaces } => {
425 assert_eq!(workspaces.len(), 1);
426 assert_eq!(workspaces[0].manifest_path, "./Cargo.toml");
427 assert_eq!(workspaces[0].only.len(), 0);
428 }
429 _ => panic!("Expected LaserEyes mode"),
430 }
431 }
432}