elf_magic/
codegen.rs

1use crate::domain::{ElfMagicError, SolanaProgram};
2use minijinja::{context, Environment};
3
4/// Template for the generated lib.rs file
5const LIB_TEMPLATE: &str = r#"
6// This file is auto-generated by elf-magic
7// DO NOT EDIT MANUALLY - Changes will be overwritten
8// Generated at: {{ timestamp }}
9
10{% for constant in constants -%}
11/// ELF binary for the {{ constant.program_name }} Solana program
12pub const {{ constant.constant_name }}: &[u8] = include_bytes!(env!("{{ constant.env_var }}"));
13
14{% endfor -%}
15
16/// Get all available Solana program ELF binaries
17/// Returns a vector of (program_name, elf_bytes) tuples
18pub fn elves() -> Vec<(&'static str, &'static [u8])> {
19    vec![
20{%- for constant in constants %}
21        ("{{ constant.program_name }}", {{ constant.constant_name }}),
22{%- endfor %}
23    ]
24}
25"#;
26
27/// Render the complete lib.rs file content from Solana programs
28///
29/// This is the main entry point for code generation - takes domain objects
30/// and produces the final formatted Rust code using templates.
31pub fn render_lib_file(programs: &[SolanaProgram]) -> Result<String, ElfMagicError> {
32    // Convert programs to template data
33    let template_data = programs
34        .iter()
35        .map(|program| {
36            serde_json::json!({
37                "constant_name": program.constant_name(),
38                "env_var": program.env_var_name(),
39                "program_name": program.name.clone()
40            })
41        })
42        .collect::<Vec<_>>();
43
44    // Create minijinja environment and render template
45    let mut env = Environment::new();
46
47    env.add_template("lib", LIB_TEMPLATE)
48        .map_err(|e| ElfMagicError::CodeGeneration(format!("Failed to add template: {}", e)))?;
49
50    let template = env
51        .get_template("lib")
52        .map_err(|e| ElfMagicError::CodeGeneration(format!("Failed to get template: {}", e)))?;
53
54    let rendered_content = template
55        .render(context! {
56            constants => template_data,
57            timestamp => chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string()
58        })
59        .map_err(|e| ElfMagicError::CodeGeneration(format!("Failed to render template: {}", e)))?;
60
61    Ok(rendered_content)
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::path::PathBuf;
68
69    #[test]
70    fn test_render_lib_file() {
71        // Create test programs
72        let programs = vec![
73            SolanaProgram {
74                name: "token_manager".to_string(),
75                path: PathBuf::from("programs/token-manager"),
76                manifest_path: PathBuf::from("programs/token-manager/Cargo.toml"),
77            },
78            SolanaProgram {
79                name: "governance".to_string(),
80                path: PathBuf::from("programs/governance"),
81                manifest_path: PathBuf::from("programs/governance/Cargo.toml"),
82            },
83        ];
84
85        // Test template rendering
86        let result = render_lib_file(&programs);
87        assert!(result.is_ok());
88
89        let rendered_content = result.unwrap();
90        // println!("{}", rendered_content);
91
92        // Verify key content is present
93        assert!(rendered_content.contains("auto-generated by elf-magic"));
94        assert!(rendered_content.contains("TOKEN_MANAGER_ELF"));
95        assert!(rendered_content.contains("GOVERNANCE_ELF"));
96        assert!(rendered_content
97            .contains("include_bytes!(env!(\"PROGRAM_TOKEN_MANAGER_ELF_MAGIC_PATH\"))"));
98        assert!(rendered_content
99            .contains("include_bytes!(env!(\"PROGRAM_GOVERNANCE_ELF_MAGIC_PATH\"))"));
100        assert!(rendered_content.contains("pub fn elves()"));
101        assert!(rendered_content.contains("(\"token_manager\", TOKEN_MANAGER_ELF)"));
102        assert!(rendered_content.contains("(\"governance\", GOVERNANCE_ELF)"));
103    }
104
105    #[test]
106    fn test_render_lib_file_empty_constants() {
107        let programs: Vec<SolanaProgram> = Vec::new();
108
109        let result = render_lib_file(&programs);
110        assert!(result.is_ok());
111
112        let rendered_content = result.unwrap();
113
114        // Should still generate valid file with empty vector
115        assert!(rendered_content.contains("auto-generated by elf-magic"));
116        assert!(rendered_content.contains("pub fn elves()"));
117        assert!(rendered_content.contains("vec!["));
118        assert!(rendered_content.contains("]"));
119
120        // Should not contain any constants
121        assert!(!rendered_content.contains("pub const"));
122        assert!(!rendered_content.contains("include_bytes!"));
123    }
124
125    #[test]
126    fn test_render_lib_file_no_template_artifacts() {
127        // Test that template rendering produces clean output
128        let programs = vec![SolanaProgram {
129            name: "test_program".to_string(),
130            path: PathBuf::from("programs/test-program"),
131            manifest_path: PathBuf::from("programs/test-program/Cargo.toml"),
132        }];
133
134        let result = render_lib_file(&programs);
135        assert!(result.is_ok());
136
137        let rendered_content = result.unwrap();
138
139        // Verify the content looks like valid Rust
140        assert!(rendered_content.contains("pub const TEST_PROGRAM_ELF"));
141        assert!(rendered_content.contains("include_bytes!"));
142        assert!(rendered_content.contains("pub fn elves()"));
143
144        // Should not have any obvious template syntax left
145        assert!(!rendered_content.contains("{{"));
146        assert!(!rendered_content.contains("}}"));
147        assert!(!rendered_content.contains("{%"));
148        assert!(!rendered_content.contains("%}"));
149    }
150}