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(alias = "type", alias = "event_type")]
48 pub event_type: String,
49 pub data: String,
50}
51
52#[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#[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#[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#[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#[derive(Deserialize)]
101pub struct SwitchProjectRequest {
102 pub project_path: String,
103}
104
105#[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}