aether_core/
context.rs

1//! Injection context for code generation.
2//!
3//! Provides context information to AI for better code generation.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Context for AI code injection.
9///
10/// This provides additional information to the AI model to help generate
11/// more relevant and accurate code.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct InjectionContext {
14    /// Project name or identifier.
15    pub project: Option<String>,
16
17    /// Target language (e.g., "rust", "html", "typescript").
18    pub language: Option<String>,
19
20    /// Framework being used (e.g., "react", "vue", "actix-web").
21    pub framework: Option<String>,
22
23    /// Coding style preferences.
24    pub style: Option<StyleGuide>,
25
26    /// Surrounding code context.
27    pub surrounding_code: Option<String>,
28
29    /// Import statements available.
30    pub available_imports: Vec<String>,
31
32    /// Custom variables for template expansion.
33    pub variables: HashMap<String, String>,
34
35    /// Additional metadata.
36    pub extra: HashMap<String, serde_json::Value>,
37}
38
39/// Coding style preferences.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct StyleGuide {
42    /// Indentation style (spaces or tabs).
43    pub indent: IndentStyle,
44
45    /// Maximum line length.
46    pub max_line_length: Option<usize>,
47
48    /// Whether to use semicolons (for JS/TS).
49    pub semicolons: Option<bool>,
50
51    /// Quote style for strings.
52    pub quote_style: Option<QuoteStyle>,
53
54    /// Naming convention.
55    pub naming_convention: Option<NamingConvention>,
56}
57
58/// Indentation style.
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub enum IndentStyle {
61    /// Use spaces with specified count.
62    Spaces(u8),
63    /// Use tabs.
64    Tabs,
65}
66
67/// Quote style for strings.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum QuoteStyle {
71    Single,
72    Double,
73}
74
75/// Naming convention.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum NamingConvention {
79    CamelCase,
80    PascalCase,
81    SnakeCase,
82    KebabCase,
83}
84
85impl InjectionContext {
86    /// Create a new empty context.
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Set the project name.
92    pub fn with_project(mut self, project: impl Into<String>) -> Self {
93        self.project = Some(project.into());
94        self
95    }
96
97    /// Set the target language.
98    pub fn with_language(mut self, language: impl Into<String>) -> Self {
99        self.language = Some(language.into());
100        self
101    }
102
103    /// Set the framework.
104    pub fn with_framework(mut self, framework: impl Into<String>) -> Self {
105        self.framework = Some(framework.into());
106        self
107    }
108
109    /// Set the style guide.
110    pub fn with_style(mut self, style: StyleGuide) -> Self {
111        self.style = Some(style);
112        self
113    }
114
115    /// Add surrounding code context.
116    pub fn with_surrounding_code(mut self, code: impl Into<String>) -> Self {
117        self.surrounding_code = Some(code.into());
118        self
119    }
120
121    /// Add an available import.
122    pub fn add_import(mut self, import: impl Into<String>) -> Self {
123        self.available_imports.push(import.into());
124        self
125    }
126
127    /// Set a variable.
128    pub fn set_variable(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
129        self.variables.insert(key.into(), value.into());
130        self
131    }
132
133    /// Convert context to a prompt string for AI.
134    pub fn to_prompt(&self) -> String {
135        let mut parts = Vec::new();
136
137        if let Some(ref project) = self.project {
138            parts.push(format!("Project: {}", project));
139        }
140
141        if let Some(ref lang) = self.language {
142            parts.push(format!("Language: {}", lang));
143        }
144
145        if let Some(ref fw) = self.framework {
146            parts.push(format!("Framework: {}", fw));
147        }
148
149        if let Some(ref style) = self.style {
150            let mut style_parts = Vec::new();
151            match &style.indent {
152                IndentStyle::Spaces(n) => style_parts.push(format!("{} spaces indent", n)),
153                IndentStyle::Tabs => style_parts.push("tabs indent".to_string()),
154            }
155            if let Some(max) = style.max_line_length {
156                style_parts.push(format!("max {} chars per line", max));
157            }
158            if !style_parts.is_empty() {
159                parts.push(format!("Style: {}", style_parts.join(", ")));
160            }
161        }
162
163        if !self.available_imports.is_empty() {
164            parts.push(format!("Available imports: {}", self.available_imports.join(", ")));
165        }
166
167        if let Some(ref code) = self.surrounding_code {
168            parts.push(format!("Surrounding code:\n```\n{}\n```", code));
169        }
170
171        parts.join("\n")
172    }
173}
174
175impl Default for StyleGuide {
176    fn default() -> Self {
177        Self {
178            indent: IndentStyle::Spaces(4),
179            max_line_length: Some(100),
180            semicolons: None,
181            quote_style: None,
182            naming_convention: None,
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_context_builder() {
193        let ctx = InjectionContext::new()
194            .with_project("my-app")
195            .with_language("typescript")
196            .with_framework("react");
197
198        assert_eq!(ctx.project, Some("my-app".to_string()));
199        assert_eq!(ctx.language, Some("typescript".to_string()));
200        assert_eq!(ctx.framework, Some("react".to_string()));
201    }
202
203    #[test]
204    fn test_context_to_prompt() {
205        let ctx = InjectionContext::new()
206            .with_project("test")
207            .with_language("rust");
208
209        let prompt = ctx.to_prompt();
210        assert!(prompt.contains("Project: test"));
211        assert!(prompt.contains("Language: rust"));
212    }
213}