intent_engine/dashboard/
models.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4/// API response wrapper
5#[derive(Serialize)]
6pub struct ApiResponse<T> {
7    pub data: T,
8}
9
10/// API error response
11#[derive(Serialize)]
12pub struct ApiError {
13    pub code: String,
14    pub message: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub details: Option<Value>,
17}
18
19/// Create task request
20#[derive(Deserialize)]
21pub struct CreateTaskRequest {
22    pub name: String,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub spec: Option<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub priority: Option<i32>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub parent_id: Option<i64>,
29}
30
31/// Update task request
32#[derive(Deserialize)]
33pub struct UpdateTaskRequest {
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub name: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub spec: Option<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub priority: Option<i32>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub status: Option<String>,
42}
43
44/// Create event request
45#[derive(Deserialize)]
46pub struct CreateEventRequest {
47    #[serde(rename = "type")]
48    pub event_type: String,
49    pub data: String,
50}
51
52/// Update event request
53#[derive(Deserialize)]
54pub struct UpdateEventRequest {
55    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
56    pub event_type: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub data: Option<String>,
59}
60
61/// Spawn subtask request
62#[derive(Deserialize)]
63pub struct SpawnSubtaskRequest {
64    pub name: String,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub spec: Option<String>,
67}
68
69/// Query parameters for task list
70#[derive(Deserialize)]
71pub struct TaskListQuery {
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub status: Option<String>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub parent: Option<String>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub sort_by: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub limit: Option<i64>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub offset: Option<i64>,
82}
83
84/// Query parameters for event list
85#[derive(Deserialize)]
86pub struct EventListQuery {
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub event_type: Option<String>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub since: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub limit: Option<i32>,
93}
94
95/// Switch project request
96#[derive(Deserialize)]
97pub struct SwitchProjectRequest {
98    pub project_path: String,
99}
100
101/// Query parameters for search
102#[derive(Deserialize)]
103pub struct SearchQuery {
104    pub query: String,
105    #[serde(default = "default_true")]
106    pub include_tasks: bool,
107    #[serde(default = "default_true")]
108    pub include_events: bool,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub limit: Option<i64>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub offset: Option<i64>,
113}
114
115fn default_true() -> bool {
116    true
117}
118
119#[derive(Serialize)]
120pub struct PaginatedResponse<T> {
121    pub items: Vec<T>,
122    pub total_count: i64,
123    pub has_more: bool,
124    pub limit: i64,
125    pub offset: i64,
126}
127
128use crate::db::models::SearchResult;
129
130#[derive(Serialize)]
131pub struct SearchResponse {
132    pub results: Vec<SearchResult>,
133    pub total_tasks: i64,
134    pub total_events: i64,
135    pub has_more: bool,
136    pub limit: i64,
137    pub offset: i64,
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_create_task_request_deserialization() {
146        let json = r#"{"name":"Test Task","spec":"Test spec","priority":1}"#;
147        let req: CreateTaskRequest = serde_json::from_str(json).unwrap();
148        assert_eq!(req.name, "Test Task");
149        assert_eq!(req.spec, Some("Test spec".to_string()));
150        assert_eq!(req.priority, Some(1));
151    }
152
153    #[test]
154    fn test_api_response_serialization() {
155        let response = ApiResponse { data: "test" };
156        let json = serde_json::to_string(&response).unwrap();
157        assert!(json.contains("\"data\""));
158    }
159
160    #[test]
161    fn test_api_error_serialization() {
162        let error = ApiError {
163            code: "TEST_ERROR".to_string(),
164            message: "Test message".to_string(),
165            details: None,
166        };
167        let json = serde_json::to_string(&error).unwrap();
168        assert!(json.contains("TEST_ERROR"));
169        assert!(!json.contains("details"));
170    }
171
172    #[test]
173    fn test_search_query_defaults() {
174        let json = r#"{"query":"test"}"#;
175        let query: SearchQuery = serde_json::from_str(json).unwrap();
176        assert_eq!(query.query, "test");
177        assert!(query.include_tasks);
178        assert!(query.include_events);
179    }
180
181    #[test]
182    fn test_update_task_request_deserialization() {
183        let json = r#"{"name":"Updated","spec":"New spec","status":"done"}"#;
184        let req: UpdateTaskRequest = serde_json::from_str(json).unwrap();
185        assert_eq!(req.name, Some("Updated".to_string()));
186        assert_eq!(req.spec, Some("New spec".to_string()));
187        assert_eq!(req.status, Some("done".to_string()));
188    }
189
190    #[test]
191    fn test_create_event_request_deserialization() {
192        let json = r#"{"type":"decision","data":"Made a decision"}"#;
193        let req: CreateEventRequest = serde_json::from_str(json).unwrap();
194        assert_eq!(req.event_type, "decision");
195        assert_eq!(req.data, "Made a decision");
196    }
197
198    #[test]
199    fn test_update_event_request_deserialization() {
200        let json = r#"{"type":"milestone","data":"Updated data"}"#;
201        let req: UpdateEventRequest = serde_json::from_str(json).unwrap();
202        assert_eq!(req.event_type, Some("milestone".to_string()));
203        assert_eq!(req.data, Some("Updated data".to_string()));
204    }
205
206    #[test]
207    fn test_spawn_subtask_request_deserialization() {
208        let json = r#"{"name":"Subtask","spec":"Subtask spec"}"#;
209        let req: SpawnSubtaskRequest = serde_json::from_str(json).unwrap();
210        assert_eq!(req.name, "Subtask");
211        assert_eq!(req.spec, Some("Subtask spec".to_string()));
212    }
213
214    #[test]
215    fn test_task_list_query_deserialization() {
216        let json = r#"{"status":"doing","parent":"null"}"#;
217        let query: TaskListQuery = serde_json::from_str(json).unwrap();
218        assert_eq!(query.status, Some("doing".to_string()));
219        assert_eq!(query.parent, Some("null".to_string()));
220    }
221
222    #[test]
223    fn test_task_list_query_with_pagination() {
224        let json = r#"{"status":"doing","sort_by":"priority","limit":50,"offset":10}"#;
225        let query: TaskListQuery = serde_json::from_str(json).unwrap();
226        assert_eq!(query.status, Some("doing".to_string()));
227        assert_eq!(query.sort_by, Some("priority".to_string()));
228        assert_eq!(query.limit, Some(50));
229        assert_eq!(query.offset, Some(10));
230    }
231
232    #[test]
233    fn test_event_list_query_deserialization() {
234        let json = r#"{"event_type":"decision","since":"7d","limit":10}"#;
235        let query: EventListQuery = serde_json::from_str(json).unwrap();
236        assert_eq!(query.event_type, Some("decision".to_string()));
237        assert_eq!(query.since, Some("7d".to_string()));
238        assert_eq!(query.limit, Some(10));
239    }
240
241    #[test]
242    fn test_switch_project_request_deserialization() {
243        let json = r#"{"project_path":"/path/to/project"}"#;
244        let req: SwitchProjectRequest = serde_json::from_str(json).unwrap();
245        assert_eq!(req.project_path, "/path/to/project");
246    }
247
248    #[test]
249    fn test_api_error_with_details() {
250        let details = serde_json::json!({"field": "name", "issue": "too short"});
251        let error = ApiError {
252            code: "VALIDATION_ERROR".to_string(),
253            message: "Validation failed".to_string(),
254            details: Some(details),
255        };
256        let json = serde_json::to_string(&error).unwrap();
257        assert!(json.contains("VALIDATION_ERROR"));
258        assert!(json.contains("details"));
259        assert!(json.contains("field"));
260    }
261
262    #[test]
263    fn test_search_query_with_overrides() {
264        let json = r#"{"query":"test","include_tasks":false,"include_events":true,"limit":20}"#;
265        let query: SearchQuery = serde_json::from_str(json).unwrap();
266        assert_eq!(query.query, "test");
267        assert!(!query.include_tasks);
268        assert!(query.include_events);
269        assert_eq!(query.limit, Some(20));
270    }
271}