tsk/assets/
embedded.rs

1//! Embedded asset implementation using rust-embed
2//!
3//! This module provides the default asset manager that embeds dockerfiles
4//! and templates directly into the TSK binary at compile time.
5
6use anyhow::{Result, anyhow};
7use async_trait::async_trait;
8use rust_embed::RustEmbed;
9
10use super::AssetManager;
11
12#[derive(RustEmbed)]
13#[folder = "templates/"]
14#[prefix = "templates/"]
15struct Templates;
16
17#[derive(RustEmbed)]
18#[folder = "dockerfiles/"]
19#[prefix = "dockerfiles/"]
20struct Dockerfiles;
21
22/// Asset manager that serves embedded assets from the binary
23pub struct EmbeddedAssetManager;
24
25impl EmbeddedAssetManager {
26    /// Create a new embedded asset manager
27    pub fn new() -> Self {
28        Self
29    }
30}
31
32impl Default for EmbeddedAssetManager {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38#[async_trait]
39impl AssetManager for EmbeddedAssetManager {
40    fn get_template(&self, template_type: &str) -> Result<String> {
41        let filename = format!("templates/{template_type}.md");
42
43        Templates::get(&filename)
44            .ok_or_else(|| anyhow!("Template '{template_type}' not found"))
45            .and_then(|file| {
46                String::from_utf8(file.data.to_vec())
47                    .map_err(|e| anyhow!("Failed to decode template '{template_type}': {e}"))
48            })
49    }
50
51    fn get_dockerfile(&self, dockerfile_name: &str) -> Result<Vec<u8>> {
52        let path = format!("dockerfiles/{dockerfile_name}/Dockerfile");
53
54        Dockerfiles::get(&path)
55            .ok_or_else(|| anyhow!("Dockerfile '{dockerfile_name}' not found"))
56            .map(|file| file.data.to_vec())
57    }
58
59    fn get_dockerfile_file(&self, dockerfile_name: &str, file_path: &str) -> Result<Vec<u8>> {
60        let path = format!("dockerfiles/{dockerfile_name}/{file_path}");
61
62        Dockerfiles::get(&path)
63            .ok_or_else(|| {
64                anyhow!("File '{file_path}' not found in dockerfile '{dockerfile_name}'")
65            })
66            .map(|file| file.data.to_vec())
67    }
68
69    fn list_templates(&self) -> Vec<String> {
70        Templates::iter()
71            .filter_map(|path| {
72                if path.starts_with("templates/") && path.ends_with(".md") {
73                    path.strip_prefix("templates/")
74                        .and_then(|p| p.strip_suffix(".md"))
75                        .map(|s| s.to_string())
76                } else {
77                    None
78                }
79            })
80            .collect()
81    }
82
83    fn list_dockerfiles(&self) -> Vec<String> {
84        use std::collections::HashSet;
85
86        let mut dockerfiles = HashSet::new();
87
88        for path in Dockerfiles::iter() {
89            if path.starts_with("dockerfiles/") {
90                if let Some(remaining) = path.strip_prefix("dockerfiles/") {
91                    if let Some(dockerfile_name) = remaining.split('/').next() {
92                        dockerfiles.insert(dockerfile_name.to_string());
93                    }
94                }
95            }
96        }
97
98        let mut result: Vec<String> = dockerfiles.into_iter().collect();
99        result.sort();
100        result
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_embedded_asset_manager_creation() {
110        let _manager = EmbeddedAssetManager::new();
111    }
112
113    #[test]
114    fn test_get_template_success() {
115        let manager = EmbeddedAssetManager::new();
116
117        // Test getting a known template
118        let result = manager.get_template("feat");
119        assert!(result.is_ok());
120        let content = result.unwrap();
121        assert!(content.contains("Feature"));
122        assert!(content.contains("{{DESCRIPTION}}"));
123    }
124
125    #[test]
126    fn test_get_template_not_found() {
127        let manager = EmbeddedAssetManager::new();
128
129        // Test getting a non-existent template
130        let result = manager.get_template("nonexistent");
131        assert!(result.is_err());
132        assert!(result.unwrap_err().to_string().contains("not found"));
133    }
134
135    #[test]
136    fn test_list_templates() {
137        let manager = EmbeddedAssetManager::new();
138
139        let templates = manager.list_templates();
140        assert!(!templates.is_empty());
141
142        // Check that known templates are included
143        assert!(templates.contains(&"feat".to_string()));
144        assert!(templates.contains(&"fix".to_string()));
145        assert!(templates.contains(&"doc".to_string()));
146        assert!(templates.contains(&"plan".to_string()));
147        assert!(templates.contains(&"refactor".to_string()));
148    }
149
150    #[test]
151    fn test_get_dockerfile_success() {
152        let manager = EmbeddedAssetManager::new();
153
154        // Test getting the tsk-base Dockerfile
155        let result = manager.get_dockerfile("tsk-base");
156        assert!(result.is_ok());
157        let content = result.unwrap();
158        assert!(!content.is_empty());
159
160        // Convert to string to check content
161        let content_str = String::from_utf8_lossy(&content);
162        assert!(content_str.contains("FROM"));
163    }
164
165    #[test]
166    fn test_get_dockerfile_not_found() {
167        let manager = EmbeddedAssetManager::new();
168
169        // Test getting a non-existent dockerfile
170        let result = manager.get_dockerfile("nonexistent");
171        assert!(result.is_err());
172        assert!(result.unwrap_err().to_string().contains("not found"));
173    }
174
175    #[test]
176    fn test_list_dockerfiles() {
177        let manager = EmbeddedAssetManager::new();
178
179        let dockerfiles = manager.list_dockerfiles();
180        assert!(!dockerfiles.is_empty());
181
182        // Check that known dockerfiles are included
183        assert!(dockerfiles.contains(&"tsk-base".to_string()));
184        assert!(dockerfiles.contains(&"tsk-proxy".to_string()));
185    }
186
187    #[test]
188    fn test_get_dockerfile_file_success() {
189        let manager = EmbeddedAssetManager::new();
190
191        // Test getting squid.conf from tsk-proxy
192        let result = manager.get_dockerfile_file("tsk-proxy", "squid.conf");
193        assert!(result.is_ok());
194        let content = result.unwrap();
195        assert!(!content.is_empty());
196
197        // Convert to string to check content
198        let content_str = String::from_utf8_lossy(&content);
199        assert!(content_str.contains("http_access"));
200    }
201
202    #[test]
203    fn test_get_dockerfile_file_not_found() {
204        let manager = EmbeddedAssetManager::new();
205
206        // Test getting a non-existent file
207        let result = manager.get_dockerfile_file("tsk-base", "nonexistent.txt");
208        assert!(result.is_err());
209        assert!(result.unwrap_err().to_string().contains("not found"));
210    }
211}