1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Serialize)]
6pub struct ApiResponse<T> {
7 pub data: T,
8}
9
10#[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#[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#[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#[derive(Deserialize)]
46pub struct CreateEventRequest {
47 #[serde(rename = "type")]
48 pub event_type: String,
49 pub data: String,
50}
51
52#[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#[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#[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#[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#[derive(Deserialize)]
97pub struct SwitchProjectRequest {
98 pub project_path: String,
99}
100
101#[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}