1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct InjectionContext {
14 pub project: Option<String>,
16
17 pub language: Option<String>,
19
20 pub framework: Option<String>,
22
23 pub style: Option<StyleGuide>,
25
26 pub surrounding_code: Option<String>,
28
29 pub available_imports: Vec<String>,
31
32 pub variables: HashMap<String, String>,
34
35 pub extra: HashMap<String, serde_json::Value>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct StyleGuide {
42 pub indent: IndentStyle,
44
45 pub max_line_length: Option<usize>,
47
48 pub semicolons: Option<bool>,
50
51 pub quote_style: Option<QuoteStyle>,
53
54 pub naming_convention: Option<NamingConvention>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub enum IndentStyle {
61 Spaces(u8),
63 Tabs,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum QuoteStyle {
71 Single,
72 Double,
73}
74
75#[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 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn with_project(mut self, project: impl Into<String>) -> Self {
93 self.project = Some(project.into());
94 self
95 }
96
97 pub fn with_language(mut self, language: impl Into<String>) -> Self {
99 self.language = Some(language.into());
100 self
101 }
102
103 pub fn with_framework(mut self, framework: impl Into<String>) -> Self {
105 self.framework = Some(framework.into());
106 self
107 }
108
109 pub fn with_style(mut self, style: StyleGuide) -> Self {
111 self.style = Some(style);
112 self
113 }
114
115 pub fn with_surrounding_code(mut self, code: impl Into<String>) -> Self {
117 self.surrounding_code = Some(code.into());
118 self
119 }
120
121 pub fn add_import(mut self, import: impl Into<String>) -> Self {
123 self.available_imports.push(import.into());
124 self
125 }
126
127 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 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}