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