1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ChartSpec {
11 pub chart_type: String,
13 pub labels: Vec<String>,
15 pub datasets: Vec<ChartDataset>,
17 #[serde(default)]
19 pub title: Option<String>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ChartDataset {
25 pub label: String,
26 pub data: Vec<f64>,
27 #[serde(default)]
28 pub color: Option<String>,
29}
30
31impl ChartSpec {
32 pub fn simple(chart_type: &str, labels: Vec<String>, data: Vec<f64>) -> Self {
34 Self {
35 chart_type: chart_type.into(),
36 labels,
37 datasets: vec![ChartDataset {
38 label: "Data".into(),
39 data,
40 color: None,
41 }],
42 title: None,
43 }
44 }
45
46 pub fn is_valid_type(&self) -> bool {
48 matches!(
49 self.chart_type.as_str(),
50 "line" | "bar" | "pie" | "scatter" | "doughnut" | "radar" | "polarArea"
51 )
52 }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct TableSpec {
58 pub headers: Vec<String>,
60 pub rows: Vec<Vec<String>>,
62 #[serde(default)]
64 pub sortable: bool,
65}
66
67impl TableSpec {
68 pub fn new(headers: Vec<String>, rows: Vec<Vec<String>>) -> Self {
69 Self {
70 headers,
71 rows,
72 sortable: false,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct FormSpec {
80 pub fields: Vec<FormField>,
82 #[serde(default = "default_submit_text")]
84 pub submit_text: String,
85 #[serde(default)]
87 pub title: Option<String>,
88}
89
90fn default_submit_text() -> String {
91 "Submit".into()
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct FormField {
97 pub name: String,
99 pub label: String,
101 pub field_type: String,
103 #[serde(default)]
105 pub required: bool,
106 #[serde(default)]
108 pub placeholder: Option<String>,
109 #[serde(default)]
111 pub options: Vec<String>,
112 #[serde(default)]
114 pub default_value: Option<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct DiagramSpec {
120 pub source: String,
122 #[serde(default)]
124 pub title: Option<String>,
125}
126
127impl DiagramSpec {
128 pub fn new(source: &str) -> Self {
129 Self {
130 source: source.into(),
131 title: None,
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_chart_spec_simple() {
142 let chart = ChartSpec::simple(
143 "bar",
144 vec!["A".into(), "B".into(), "C".into()],
145 vec![1.0, 2.0, 3.0],
146 );
147 assert_eq!(chart.chart_type, "bar");
148 assert_eq!(chart.labels.len(), 3);
149 assert_eq!(chart.datasets.len(), 1);
150 assert_eq!(chart.datasets[0].data, vec![1.0, 2.0, 3.0]);
151 assert!(chart.is_valid_type());
152 }
153
154 #[test]
155 fn test_chart_spec_serialization() {
156 let chart = ChartSpec::simple("line", vec!["Jan".into(), "Feb".into()], vec![10.0, 20.0]);
157 let json = serde_json::to_string(&chart).unwrap();
158 let restored: ChartSpec = serde_json::from_str(&json).unwrap();
159 assert_eq!(restored.chart_type, "line");
160 assert_eq!(restored.datasets[0].data.len(), 2);
161 }
162
163 #[test]
164 fn test_chart_valid_types() {
165 for t in &[
166 "line",
167 "bar",
168 "pie",
169 "scatter",
170 "doughnut",
171 "radar",
172 "polarArea",
173 ] {
174 let c = ChartSpec::simple(t, vec![], vec![]);
175 assert!(c.is_valid_type(), "Expected {} to be valid", t);
176 }
177 let invalid = ChartSpec::simple("unknown", vec![], vec![]);
178 assert!(!invalid.is_valid_type());
179 }
180
181 #[test]
182 fn test_table_spec() {
183 let table = TableSpec::new(
184 vec!["Name".into(), "Age".into()],
185 vec![
186 vec!["Alice".into(), "30".into()],
187 vec!["Bob".into(), "25".into()],
188 ],
189 );
190 assert_eq!(table.headers.len(), 2);
191 assert_eq!(table.rows.len(), 2);
192 assert!(!table.sortable);
193 }
194
195 #[test]
196 fn test_table_spec_serialization() {
197 let table = TableSpec {
198 headers: vec!["Col1".into()],
199 rows: vec![vec!["Val1".into()]],
200 sortable: true,
201 };
202 let json = serde_json::to_string(&table).unwrap();
203 let restored: TableSpec = serde_json::from_str(&json).unwrap();
204 assert!(restored.sortable);
205 }
206
207 #[test]
208 fn test_form_spec() {
209 let form = FormSpec {
210 fields: vec![FormField {
211 name: "email".into(),
212 label: "Email".into(),
213 field_type: "email".into(),
214 required: true,
215 placeholder: Some("user@example.com".into()),
216 options: vec![],
217 default_value: None,
218 }],
219 submit_text: "Send".into(),
220 title: Some("Contact".into()),
221 };
222 assert_eq!(form.fields.len(), 1);
223 assert!(form.fields[0].required);
224 }
225
226 #[test]
227 fn test_form_spec_serialization() {
228 let form = FormSpec {
229 fields: vec![FormField {
230 name: "name".into(),
231 label: "Name".into(),
232 field_type: "text".into(),
233 required: false,
234 placeholder: None,
235 options: vec![],
236 default_value: Some("default".into()),
237 }],
238 submit_text: "Submit".into(),
239 title: None,
240 };
241 let json = serde_json::to_string(&form).unwrap();
242 let restored: FormSpec = serde_json::from_str(&json).unwrap();
243 assert_eq!(restored.fields[0].default_value, Some("default".into()));
244 }
245
246 #[test]
247 fn test_diagram_spec() {
248 let diagram = DiagramSpec::new("graph LR; A-->B; B-->C");
249 assert!(diagram.source.contains("graph LR"));
250 assert!(diagram.title.is_none());
251 }
252
253 #[test]
254 fn test_diagram_spec_serialization() {
255 let diagram = DiagramSpec {
256 source: "sequenceDiagram\n A->>B: Hello".into(),
257 title: Some("Sequence".into()),
258 };
259 let json = serde_json::to_string(&diagram).unwrap();
260 let restored: DiagramSpec = serde_json::from_str(&json).unwrap();
261 assert_eq!(restored.title, Some("Sequence".into()));
262 }
263}