1use crate::schema::*;
6
7#[derive(Debug, Clone)]
9pub struct ValidationError {
10 pub path: String,
11 pub message: String,
12}
13
14impl std::fmt::Display for ValidationError {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "{}: {}", self.path, self.message)
17 }
18}
19
20impl std::error::Error for ValidationError {}
21
22pub trait Validate {
24 fn validate(&self, path: &str) -> Vec<ValidationError>;
25}
26
27impl Validate for UiResponse {
28 fn validate(&self, path: &str) -> Vec<ValidationError> {
29 let mut errors = Vec::new();
30
31 if self.components.is_empty() {
32 errors.push(ValidationError {
33 path: path.to_string(),
34 message: "UiResponse must have at least one component".to_string(),
35 });
36 }
37
38 for (i, component) in self.components.iter().enumerate() {
39 errors.extend(component.validate(&format!("{}.components[{}]", path, i)));
40 }
41
42 errors
43 }
44}
45
46impl Validate for Text {
47 fn validate(&self, path: &str) -> Vec<ValidationError> {
48 let mut errors = Vec::new();
49 if self.content.is_empty() {
50 errors.push(ValidationError {
51 path: format!("{}.content", path),
52 message: "Text content cannot be empty".to_string(),
53 });
54 }
55 errors
56 }
57}
58
59impl Validate for Button {
60 fn validate(&self, path: &str) -> Vec<ValidationError> {
61 let mut errors = Vec::new();
62 if self.label.is_empty() {
63 errors.push(ValidationError {
64 path: format!("{}.label", path),
65 message: "Button label cannot be empty".to_string(),
66 });
67 }
68 if self.action_id.is_empty() {
69 errors.push(ValidationError {
70 path: format!("{}.action_id", path),
71 message: "Button action_id cannot be empty".to_string(),
72 });
73 }
74 errors
75 }
76}
77
78impl Validate for TextInput {
79 fn validate(&self, path: &str) -> Vec<ValidationError> {
80 let mut errors = Vec::new();
81 if self.name.is_empty() {
82 errors.push(ValidationError {
83 path: format!("{}.name", path),
84 message: "TextInput name cannot be empty".to_string(),
85 });
86 }
87 if let (Some(min), Some(max)) = (self.min_length, self.max_length) {
88 if min > max {
89 errors.push(ValidationError {
90 path: format!("{}.min_length", path),
91 message: "min_length cannot be greater than max_length".to_string(),
92 });
93 }
94 }
95 errors
96 }
97}
98
99impl Validate for NumberInput {
100 fn validate(&self, path: &str) -> Vec<ValidationError> {
101 let mut errors = Vec::new();
102 if self.name.is_empty() {
103 errors.push(ValidationError {
104 path: format!("{}.name", path),
105 message: "NumberInput name cannot be empty".to_string(),
106 });
107 }
108 if let (Some(min), Some(max)) = (self.min, self.max) {
109 if min > max {
110 errors.push(ValidationError {
111 path: format!("{}.min", path),
112 message: "min cannot be greater than max".to_string(),
113 });
114 }
115 }
116 errors
117 }
118}
119
120impl Validate for Select {
121 fn validate(&self, path: &str) -> Vec<ValidationError> {
122 let mut errors = Vec::new();
123 if self.name.is_empty() {
124 errors.push(ValidationError {
125 path: format!("{}.name", path),
126 message: "Select name cannot be empty".to_string(),
127 });
128 }
129 if self.options.is_empty() {
130 errors.push(ValidationError {
131 path: format!("{}.options", path),
132 message: "Select must have at least one option".to_string(),
133 });
134 }
135 errors
136 }
137}
138
139impl Validate for Table {
140 fn validate(&self, path: &str) -> Vec<ValidationError> {
141 let mut errors = Vec::new();
142 if self.columns.is_empty() {
143 errors.push(ValidationError {
144 path: format!("{}.columns", path),
145 message: "Table must have at least one column".to_string(),
146 });
147 }
148 errors
149 }
150}
151
152impl Validate for Chart {
153 fn validate(&self, path: &str) -> Vec<ValidationError> {
154 let mut errors = Vec::new();
155 if self.data.is_empty() {
156 errors.push(ValidationError {
157 path: format!("{}.data", path),
158 message: "Chart must have data".to_string(),
159 });
160 }
161 if self.y_keys.is_empty() {
162 errors.push(ValidationError {
163 path: format!("{}.y_keys", path),
164 message: "Chart must have at least one y_key".to_string(),
165 });
166 }
167 errors
168 }
169}
170
171impl Validate for Card {
172 fn validate(&self, path: &str) -> Vec<ValidationError> {
173 let mut errors = Vec::new();
174 for (i, child) in self.content.iter().enumerate() {
175 errors.extend(child.validate(&format!("{}.content[{}]", path, i)));
176 }
177 if let Some(footer) = &self.footer {
178 for (i, child) in footer.iter().enumerate() {
179 errors.extend(child.validate(&format!("{}.footer[{}]", path, i)));
180 }
181 }
182 errors
183 }
184}
185
186impl Validate for Modal {
187 fn validate(&self, path: &str) -> Vec<ValidationError> {
188 let mut errors = Vec::new();
189 for (i, child) in self.content.iter().enumerate() {
190 errors.extend(child.validate(&format!("{}.content[{}]", path, i)));
191 }
192 errors
193 }
194}
195
196impl Validate for Stack {
197 fn validate(&self, path: &str) -> Vec<ValidationError> {
198 let mut errors = Vec::new();
199 for (i, child) in self.children.iter().enumerate() {
200 errors.extend(child.validate(&format!("{}.children[{}]", path, i)));
201 }
202 errors
203 }
204}
205
206impl Validate for Grid {
207 fn validate(&self, path: &str) -> Vec<ValidationError> {
208 let mut errors = Vec::new();
209 for (i, child) in self.children.iter().enumerate() {
210 errors.extend(child.validate(&format!("{}.children[{}]", path, i)));
211 }
212 errors
213 }
214}
215
216impl Validate for Tabs {
217 fn validate(&self, path: &str) -> Vec<ValidationError> {
218 let mut errors = Vec::new();
219 if self.tabs.is_empty() {
220 errors.push(ValidationError {
221 path: format!("{}.tabs", path),
222 message: "Tabs must have at least one tab".to_string(),
223 });
224 }
225 errors
226 }
227}
228
229impl Validate for Component {
230 fn validate(&self, path: &str) -> Vec<ValidationError> {
231 match self {
232 Component::Text(t) => t.validate(path),
233 Component::Button(b) => b.validate(path),
234 Component::TextInput(t) => t.validate(path),
235 Component::NumberInput(n) => n.validate(path),
236 Component::Select(s) => s.validate(path),
237 Component::Table(t) => t.validate(path),
238 Component::Chart(c) => c.validate(path),
239 Component::Card(c) => c.validate(path),
240 Component::Modal(m) => m.validate(path),
241 Component::Stack(s) => s.validate(path),
242 Component::Grid(g) => g.validate(path),
243 Component::Tabs(t) => t.validate(path),
244 _ => Vec::new(),
246 }
247 }
248}
249
250pub fn validate_ui_response(ui: &UiResponse) -> Result<(), Vec<ValidationError>> {
252 let errors = ui.validate("UiResponse");
253 if errors.is_empty() { Ok(()) } else { Err(errors) }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_empty_response_fails() {
262 let ui = UiResponse::new(vec![]);
263 let result = validate_ui_response(&ui);
264 assert!(result.is_err());
265 }
266
267 #[test]
268 fn test_valid_text_passes() {
269 let ui = UiResponse::new(vec![Component::Text(Text {
270 id: None,
271 content: "Hello".to_string(),
272 variant: TextVariant::Body,
273 })]);
274 let result = validate_ui_response(&ui);
275 assert!(result.is_ok());
276 }
277
278 #[test]
279 fn test_empty_button_label_fails() {
280 let ui = UiResponse::new(vec![Component::Button(Button {
281 id: None,
282 label: "".to_string(),
283 action_id: "click".to_string(),
284 variant: ButtonVariant::Primary,
285 disabled: false,
286 icon: None,
287 })]);
288 let result = validate_ui_response(&ui);
289 assert!(result.is_err());
290 }
291}