Skip to main content

raps_da/
types.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2025 Dmytro Yemelianov
3
4//! Type definitions for the Design Automation API module.
5
6use serde::{Deserialize, Serialize};
7
8/// Engine information
9#[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/// AppBundle information
18#[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/// AppBundle details (full)
28#[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/// Activity information
47#[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/// WorkItem information
59#[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/// Request to create an AppBundle
83#[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/// Request to create an Activity
92#[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/// Request to create a WorkItem
115#[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/// Paginated response
133#[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(&param).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}