1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct Engine {
12 pub id: String,
13 pub description: Option<String>,
14 pub product_version: Option<String>,
15}
16
17#[derive(Debug, Clone, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct AppBundle {
21 pub id: String,
22 pub engine: String,
23 pub description: Option<String>,
24 pub version: Option<i32>,
25}
26
27#[derive(Debug, Clone, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct AppBundleDetails {
31 pub id: String,
32 pub engine: String,
33 pub description: Option<String>,
34 pub version: i32,
35 pub package: Option<String>,
36 pub upload_parameters: Option<UploadParameters>,
37}
38
39#[derive(Debug, Clone, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct UploadParameters {
42 pub endpoint_url: Option<String>,
43 pub form_data: Option<std::collections::HashMap<String, String>>,
44}
45
46#[derive(Debug, Clone, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct Activity {
50 pub id: String,
51 pub engine: String,
52 pub description: Option<String>,
53 pub version: Option<i32>,
54 pub command_line: Option<Vec<String>>,
55 pub app_bundles: Option<Vec<String>>,
56}
57
58#[derive(Debug, Clone, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct WorkItem {
62 pub id: String,
63 pub status: String,
64 pub progress: Option<String>,
65 pub report_url: Option<String>,
66 pub stats: Option<WorkItemStats>,
67}
68
69#[derive(Debug, Clone, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct WorkItemStats {
72 pub time_queued: Option<String>,
73 pub time_download_started: Option<String>,
74 pub time_instruction_started: Option<String>,
75 pub time_instruction_ended: Option<String>,
76 pub time_upload_ended: Option<String>,
77 pub time_finished: Option<String>,
78 pub bytes_downloaded: Option<i64>,
79 pub bytes_uploaded: Option<i64>,
80}
81
82#[derive(Debug, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct CreateAppBundleRequest {
86 pub id: String,
87 pub engine: String,
88 pub description: Option<String>,
89}
90
91#[derive(Debug, Serialize)]
93#[serde(rename_all = "camelCase")]
94pub struct CreateActivityRequest {
95 pub id: String,
96 pub engine: String,
97 pub command_line: Vec<String>,
98 pub app_bundles: Vec<String>,
99 pub parameters: std::collections::HashMap<String, ActivityParameter>,
100 pub description: Option<String>,
101}
102
103#[derive(Debug, Serialize)]
104#[serde(rename_all = "camelCase")]
105pub struct ActivityParameter {
106 pub verb: String,
107 pub local_name: Option<String>,
108 pub description: Option<String>,
109 pub required: Option<bool>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub zip: Option<bool>,
112}
113
114#[derive(Debug, Serialize)]
116#[serde(rename_all = "camelCase")]
117pub struct CreateWorkItemRequest {
118 pub activity_id: String,
119 pub arguments: std::collections::HashMap<String, WorkItemArgument>,
120}
121
122#[derive(Debug, Serialize)]
123#[serde(rename_all = "camelCase")]
124pub struct WorkItemArgument {
125 pub url: String,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub verb: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub headers: Option<std::collections::HashMap<String, String>>,
130}
131
132#[derive(Debug, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct PaginatedResponse<T> {
136 pub data: Vec<T>,
137 pub pagination_token: Option<String>,
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_engine_deserialization() {
146 let json = r#"{
147 "id": "Autodesk.Revit+2024",
148 "description": "Revit 2024 Engine",
149 "productVersion": "2024"
150 }"#;
151
152 let engine: Engine = serde_json::from_str(json).unwrap();
153 assert_eq!(engine.id, "Autodesk.Revit+2024");
154 assert_eq!(engine.description, Some("Revit 2024 Engine".to_string()));
155 }
156
157 #[test]
158 fn test_appbundle_deserialization() {
159 let json = r#"{
160 "id": "myapp.MyBundle+dev",
161 "engine": "Autodesk.Revit+2024",
162 "description": "My custom bundle",
163 "version": 1
164 }"#;
165
166 let bundle: AppBundle = serde_json::from_str(json).unwrap();
167 assert_eq!(bundle.id, "myapp.MyBundle+dev");
168 assert_eq!(bundle.engine, "Autodesk.Revit+2024");
169 }
170
171 #[test]
172 fn test_activity_deserialization() {
173 let json = r#"{
174 "id": "myapp.MyActivity+dev",
175 "engine": "Autodesk.Revit+2024",
176 "description": "My activity",
177 "version": 1
178 }"#;
179
180 let activity: Activity = serde_json::from_str(json).unwrap();
181 assert_eq!(activity.id, "myapp.MyActivity+dev");
182 }
183
184 #[test]
185 fn test_workitem_deserialization() {
186 let json = r#"{
187 "id": "workitem-id-123",
188 "status": "pending",
189 "progress": "0%"
190 }"#;
191
192 let workitem: WorkItem = serde_json::from_str(json).unwrap();
193 assert_eq!(workitem.id, "workitem-id-123");
194 assert_eq!(workitem.status, "pending");
195 }
196
197 #[test]
198 fn test_workitem_stats_deserialization() {
199 let json = r#"{
200 "id": "workitem-id-123",
201 "status": "success",
202 "stats": {
203 "bytesDownloaded": 1024,
204 "bytesUploaded": 2048
205 }
206 }"#;
207
208 let workitem: WorkItem = serde_json::from_str(json).unwrap();
209 assert!(workitem.stats.is_some());
210 let stats = workitem.stats.unwrap();
211 assert_eq!(stats.bytes_downloaded, Some(1024));
212 }
213
214 #[test]
215 fn test_create_appbundle_request_serialization() {
216 let request = CreateAppBundleRequest {
217 id: "MyBundle".to_string(),
218 engine: "Autodesk.Revit+2024".to_string(),
219 description: Some("Test bundle".to_string()),
220 };
221
222 let json = serde_json::to_value(&request).unwrap();
223 assert_eq!(json["id"], "MyBundle");
224 assert_eq!(json["engine"], "Autodesk.Revit+2024");
225 }
226
227 #[test]
228 fn test_create_activity_request_serialization() {
229 let mut parameters = std::collections::HashMap::new();
230 parameters.insert(
231 "input".to_string(),
232 ActivityParameter {
233 verb: "get".to_string(),
234 local_name: Some("input.rvt".to_string()),
235 description: None,
236 required: Some(true),
237 zip: None,
238 },
239 );
240
241 let request = CreateActivityRequest {
242 id: "MyActivity".to_string(),
243 engine: "Autodesk.Revit+2024".to_string(),
244 command_line: vec!["$(engine.path)\\revitcoreconsole.exe".to_string()],
245 app_bundles: vec!["myapp.MyBundle+dev".to_string()],
246 description: Some("Test activity".to_string()),
247 parameters,
248 };
249
250 let json = serde_json::to_value(&request).unwrap();
251 assert_eq!(json["id"], "MyActivity");
252 assert!(json["commandLine"].is_array());
253 }
254
255 #[test]
256 fn test_create_workitem_request_serialization() {
257 let mut arguments = std::collections::HashMap::new();
258 arguments.insert(
259 "input".to_string(),
260 WorkItemArgument {
261 url: "https://example.com/input.rvt".to_string(),
262 verb: Some("get".to_string()),
263 headers: None,
264 },
265 );
266
267 let request = CreateWorkItemRequest {
268 activity_id: "myapp.MyActivity+dev".to_string(),
269 arguments,
270 };
271
272 let json = serde_json::to_value(&request).unwrap();
273 assert_eq!(json["activityId"], "myapp.MyActivity+dev");
274 }
275
276 #[test]
277 fn test_paginated_response_deserialization() {
278 let json = r#"{
279 "paginationToken": "next-page-token",
280 "data": [
281 {"id": "item1", "engine": "engine1"},
282 {"id": "item2", "engine": "engine2"}
283 ]
284 }"#;
285
286 let response: PaginatedResponse<AppBundle> = serde_json::from_str(json).unwrap();
287 assert_eq!(
288 response.pagination_token,
289 Some("next-page-token".to_string())
290 );
291 assert_eq!(response.data.len(), 2);
292 }
293
294 #[test]
295 fn test_workitem_with_progress() {
296 let json = r#"{
297 "id": "workitem-id",
298 "status": "inprogress",
299 "progress": "50%"
300 }"#;
301
302 let workitem: WorkItem = serde_json::from_str(json).unwrap();
303 assert_eq!(workitem.status, "inprogress");
304 assert_eq!(workitem.progress, Some("50%".to_string()));
305 }
306
307 #[test]
308 fn test_workitem_with_report_url() {
309 let json = r#"{
310 "id": "workitem-id",
311 "status": "success",
312 "reportUrl": "https://example.com/report.txt"
313 }"#;
314
315 let workitem: WorkItem = serde_json::from_str(json).unwrap();
316 assert!(workitem.report_url.is_some());
317 }
318
319 #[test]
320 fn test_activity_parameter_serialization() {
321 let param = ActivityParameter {
322 verb: "get".to_string(),
323 local_name: Some("input.rvt".to_string()),
324 description: Some("Input file".to_string()),
325 required: Some(true),
326 zip: Some(false),
327 };
328
329 let json = serde_json::to_value(¶m).unwrap();
330 assert_eq!(json["verb"], "get");
331 assert_eq!(json["localName"], "input.rvt");
332 assert_eq!(json["required"], true);
333 }
334
335 #[test]
336 fn test_workitem_argument_with_headers() {
337 let mut headers = std::collections::HashMap::new();
338 headers.insert("Authorization".to_string(), "Bearer token".to_string());
339
340 let arg = WorkItemArgument {
341 url: "https://example.com/file.rvt".to_string(),
342 verb: Some("get".to_string()),
343 headers: Some(headers),
344 };
345
346 let json = serde_json::to_value(&arg).unwrap();
347 assert_eq!(json["url"], "https://example.com/file.rvt");
348 assert_eq!(json["headers"]["Authorization"], "Bearer token");
349 }
350
351 #[test]
352 fn test_engine_with_product_version() {
353 let json = r#"{
354 "id": "Autodesk.Revit+2024",
355 "productVersion": "2024"
356 }"#;
357
358 let engine: Engine = serde_json::from_str(json).unwrap();
359 assert_eq!(engine.id, "Autodesk.Revit+2024");
360 assert_eq!(engine.product_version, Some("2024".to_string()));
361 }
362
363 #[test]
364 fn test_paginated_workitem_response_deserialization() {
365 let json = r#"{
366 "paginationToken": "next-token-abc",
367 "data": [
368 {
369 "id": "wi-001",
370 "status": "success",
371 "progress": "100%",
372 "reportUrl": "https://example.com/report1.txt"
373 },
374 {
375 "id": "wi-002",
376 "status": "pending"
377 }
378 ]
379 }"#;
380
381 let response: PaginatedResponse<WorkItem> = serde_json::from_str(json).unwrap();
382 assert_eq!(
383 response.pagination_token,
384 Some("next-token-abc".to_string())
385 );
386 assert_eq!(response.data.len(), 2);
387 assert_eq!(response.data[0].id, "wi-001");
388 assert_eq!(response.data[0].status, "success");
389 assert!(response.data[0].report_url.is_some());
390 assert_eq!(response.data[1].id, "wi-002");
391 assert_eq!(response.data[1].status, "pending");
392 assert!(response.data[1].report_url.is_none());
393 }
394
395 #[test]
396 fn test_paginated_workitem_response_no_token() {
397 let json = r#"{
398 "data": [
399 {
400 "id": "wi-003",
401 "status": "inprogress",
402 "progress": "25%"
403 }
404 ]
405 }"#;
406
407 let response: PaginatedResponse<WorkItem> = serde_json::from_str(json).unwrap();
408 assert!(response.pagination_token.is_none());
409 assert_eq!(response.data.len(), 1);
410 assert_eq!(response.data[0].progress, Some("25%".to_string()));
411 }
412
413 #[test]
414 fn test_workitem_full_stats_deserialization() {
415 let json = r#"{
416 "id": "wi-full",
417 "status": "success",
418 "reportUrl": "https://example.com/report.txt",
419 "stats": {
420 "timeQueued": "2024-01-01T00:00:00Z",
421 "timeDownloadStarted": "2024-01-01T00:00:01Z",
422 "timeInstructionStarted": "2024-01-01T00:00:02Z",
423 "timeInstructionEnded": "2024-01-01T00:01:00Z",
424 "timeUploadEnded": "2024-01-01T00:01:05Z",
425 "timeFinished": "2024-01-01T00:01:06Z",
426 "bytesDownloaded": 5242880,
427 "bytesUploaded": 1048576
428 }
429 }"#;
430
431 let workitem: WorkItem = serde_json::from_str(json).unwrap();
432 assert_eq!(workitem.id, "wi-full");
433 assert_eq!(workitem.status, "success");
434 let stats = workitem.stats.unwrap();
435 assert_eq!(stats.time_queued, Some("2024-01-01T00:00:00Z".to_string()));
436 assert_eq!(stats.bytes_downloaded, Some(5242880));
437 assert_eq!(stats.bytes_uploaded, Some(1048576));
438 assert_eq!(
439 stats.time_finished,
440 Some("2024-01-01T00:01:06Z".to_string())
441 );
442 }
443
444 #[test]
445 fn test_upload_parameters_deserialization() {
446 let json = r#"{
447 "endpointUrl": "https://s3.amazonaws.com/da-uploads",
448 "formData": {
449 "key": "apps/myapp/bundle.zip",
450 "policy": "base64-encoded-policy",
451 "x-amz-signature": "sig123",
452 "x-amz-credential": "cred456",
453 "x-amz-date": "20240101T000000Z"
454 }
455 }"#;
456
457 let params: UploadParameters = serde_json::from_str(json).unwrap();
458 assert_eq!(
459 params.endpoint_url,
460 Some("https://s3.amazonaws.com/da-uploads".to_string())
461 );
462 let form_data = params.form_data.unwrap();
463 assert_eq!(form_data.len(), 5);
464 assert_eq!(
465 form_data.get("key"),
466 Some(&"apps/myapp/bundle.zip".to_string())
467 );
468 }
469
470 #[test]
471 fn test_upload_parameters_missing_endpoint() {
472 let json = r#"{}"#;
473 let params: UploadParameters = serde_json::from_str(json).unwrap();
474 assert!(params.endpoint_url.is_none());
475 assert!(params.form_data.is_none());
476 }
477
478 #[test]
479 fn test_appbundle_details_with_upload_params() {
480 let json = r#"{
481 "id": "myapp.MyBundle+dev",
482 "engine": "Autodesk.Revit+2024",
483 "version": 2,
484 "uploadParameters": {
485 "endpointUrl": "https://s3.amazonaws.com/upload",
486 "formData": {
487 "key": "upload-key"
488 }
489 }
490 }"#;
491
492 let details: AppBundleDetails = serde_json::from_str(json).unwrap();
493 assert_eq!(details.version, 2);
494 assert!(details.upload_parameters.is_some());
495 let params = details.upload_parameters.unwrap();
496 assert!(params.endpoint_url.is_some());
497 }
498}