1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct Slot {
10 pub name: String,
12
13 pub prompt: String,
15
16 pub kind: SlotKind,
18
19 pub constraints: Option<SlotConstraints>,
21
22 pub required: bool,
24
25 pub default: Option<String>,
27
28 pub temperature: Option<f32>,
30}
31
32#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
34#[serde(rename_all = "snake_case")]
35pub enum SlotKind {
36 #[default]
38 Raw,
39
40 Function,
42
43 Class,
45
46 Html,
48
49 Css,
51
52 JavaScript,
54
55 Component,
57
58 Custom(String),
60}
61
62#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
64pub struct SlotConstraints {
65 pub max_lines: Option<usize>,
67
68 pub max_chars: Option<usize>,
70
71 pub required_imports: Vec<String>,
73
74 pub forbidden_patterns: Vec<String>,
76
77 pub language: Option<String>,
79
80 pub test_harness: Option<String>,
83
84 pub test_command: Option<String>,
86}
87
88impl Eq for Slot {}
89
90impl std::hash::Hash for Slot {
91 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
92 self.name.hash(state);
93 self.prompt.hash(state);
94 self.kind.hash(state);
95 self.constraints.hash(state);
96 self.required.hash(state);
97 self.default.hash(state);
98 if let Some(temp) = self.temperature {
99 temp.to_bits().hash(state);
100 }
101 }
102}
103
104impl Slot {
105 pub fn new(name: impl Into<String>, prompt: impl Into<String>) -> Self {
121 Self {
122 name: name.into(),
123 prompt: prompt.into(),
124 kind: SlotKind::default(),
125 constraints: None,
126 required: true,
127 default: None,
128 temperature: None,
129 }
130 }
131
132 pub fn with_temperature(mut self, temp: f32) -> Self {
134 self.temperature = Some(temp.clamp(0.0, 2.0));
135 self
136 }
137
138 pub fn with_kind(mut self, kind: SlotKind) -> Self {
140 self.kind = kind;
141 self
142 }
143
144 pub fn with_constraints(mut self, constraints: SlotConstraints) -> Self {
146 self.constraints = Some(constraints);
147 self
148 }
149
150 pub fn optional(mut self, default: impl Into<String>) -> Self {
152 self.required = false;
153 self.default = Some(default.into());
154 self
155 }
156
157 pub fn validate(&self, code: &str) -> Result<(), Vec<String>> {
159 let mut errors = Vec::new();
160
161 if let Some(ref constraints) = self.constraints {
162 if let Some(max) = constraints.max_lines {
164 let lines = code.lines().count();
165 if lines > max {
166 errors.push(format!("Code exceeds max lines: {} > {}", lines, max));
167 }
168 }
169
170 if let Some(max) = constraints.max_chars {
172 if code.len() > max {
173 errors.push(format!("Code exceeds max chars: {} > {}", code.len(), max));
174 }
175 }
176
177 for pattern in &constraints.forbidden_patterns {
179 if let Ok(re) = regex::Regex::new(pattern) {
180 if re.is_match(code) {
181 errors.push(format!("Code contains forbidden pattern: {}", pattern));
182 }
183 }
184 }
185 }
186
187 if errors.is_empty() {
188 Ok(())
189 } else {
190 Err(errors)
191 }
192 }
193}
194
195impl SlotConstraints {
196 pub fn new() -> Self {
198 Self::default()
199 }
200
201 pub fn max_lines(mut self, lines: usize) -> Self {
203 self.max_lines = Some(lines);
204 self
205 }
206
207 pub fn max_chars(mut self, chars: usize) -> Self {
209 self.max_chars = Some(chars);
210 self
211 }
212
213 pub fn language(mut self, lang: impl Into<String>) -> Self {
215 self.language = Some(lang.into());
216 self
217 }
218
219 pub fn require_import(mut self, import: impl Into<String>) -> Self {
221 self.required_imports.push(import.into());
222 self
223 }
224
225 pub fn forbid_pattern(mut self, pattern: impl Into<String>) -> Self {
227 self.forbidden_patterns.push(pattern.into());
228 self
229 }
230
231 pub fn test_harness(mut self, harness: impl Into<String>) -> Self {
233 self.test_harness = Some(harness.into());
234 self
235 }
236
237 pub fn test_command(mut self, command: impl Into<String>) -> Self {
239 self.test_command = Some(command.into());
240 self
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_slot_creation() {
250 let slot = Slot::new("test", "Generate a test");
251 assert_eq!(slot.name, "test");
252 assert_eq!(slot.prompt, "Generate a test");
253 assert!(slot.required);
254 }
255
256 #[test]
257 fn test_slot_validation() {
258 let slot = Slot::new("test", "")
259 .with_constraints(SlotConstraints::new().max_lines(5));
260
261 assert!(slot.validate("line1\nline2\nline3").is_ok());
262 assert!(slot.validate("1\n2\n3\n4\n5\n6").is_err());
263 }
264}