Skip to main content

data_modelling_core/export/
cads.rs

1//! CADS (Compute Asset Description Specification) exporter
2//!
3//! Exports CADSAsset models to CADS v1.0 YAML format.
4
5use crate::export::ExportError;
6use crate::models::cads::*;
7
8/// CADS exporter for generating CADS v1.0 YAML from CADSAsset models
9pub struct CADSExporter;
10
11impl CADSExporter {
12    /// Export a CADS asset to CADS v1.0 YAML format (instance method for WASM compatibility)
13    ///
14    /// # Arguments
15    ///
16    /// * `asset` - The CADS asset to export
17    ///
18    /// # Returns
19    ///
20    /// A Result containing the YAML string in CADS v1.0 format, or an ExportError
21    pub fn export(&self, asset: &CADSAsset) -> Result<String, ExportError> {
22        let yaml = Self::export_asset(asset);
23
24        // Validate exported YAML against CADS schema (if feature enabled)
25        #[cfg(feature = "schema-validation")]
26        {
27            use crate::validation::schema::validate_cads_internal;
28            validate_cads_internal(&yaml).map_err(ExportError::ValidationError)?;
29        }
30
31        Ok(yaml)
32    }
33
34    /// Export a CADS asset to CADS v1.0 YAML format
35    ///
36    /// # Arguments
37    ///
38    /// * `asset` - The CADS asset to export
39    ///
40    /// # Returns
41    ///
42    /// A YAML string in CADS v1.0 format
43    ///
44    /// # Example
45    ///
46    /// ```rust
47    /// use data_modelling_core::export::cads::CADSExporter;
48    /// use data_modelling_core::models::cads::*;
49    ///
50    /// let asset = CADSAsset {
51    ///     api_version: "v1.0".to_string(),
52    ///     kind: CADSKind::AIModel,
53    ///     id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
54    ///     name: "sentiment-analysis-model".to_string(),
55    ///     version: "1.0.0".to_string(),
56    ///     status: CADSStatus::Production,
57    ///     domain: None,
58    ///     tags: vec![],
59    ///     description: None,
60    ///     runtime: None,
61    ///     sla: None,
62    ///     pricing: None,
63    ///     team: None,
64    ///     risk: None,
65    ///     compliance: None,
66    ///     validation_profiles: None,
67    ///     bpmn_models: None,
68    ///     dmn_models: None,
69    ///     openapi_specs: None,
70    ///     custom_properties: None,
71    ///     created_at: None,
72    ///     updated_at: None,
73    /// };
74    ///
75    /// let yaml = CADSExporter::export_asset(&asset);
76    /// assert!(yaml.contains("apiVersion: v1.0"));
77    /// assert!(yaml.contains("kind: AIModel"));
78    /// ```
79    pub fn export_asset(asset: &CADSAsset) -> String {
80        // Use direct struct serialization - serde handles all field naming and optional fields
81        match serde_yaml::to_string(asset) {
82            Ok(yaml) => yaml,
83            Err(e) => format!("# Error serializing asset: {}\n", e),
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_export_asset_basic() {
94        let asset = CADSAsset {
95            api_version: "v1.0".to_string(),
96            kind: CADSKind::AIModel,
97            id: "test-id".to_string(),
98            name: "Test Model".to_string(),
99            version: "1.0.0".to_string(),
100            status: CADSStatus::Production,
101            domain: Some("ml".to_string()),
102            tags: vec![],
103            description: None,
104            runtime: None,
105            sla: None,
106            pricing: None,
107            team: None,
108            risk: None,
109            compliance: None,
110            validation_profiles: None,
111            bpmn_models: None,
112            dmn_models: None,
113            openapi_specs: None,
114            custom_properties: None,
115            created_at: None,
116            updated_at: None,
117        };
118
119        let yaml = CADSExporter::export_asset(&asset);
120        assert!(yaml.contains("apiVersion: v1.0"));
121        assert!(yaml.contains("kind: AIModel"));
122        assert!(yaml.contains("id: test-id"));
123        assert!(yaml.contains("status: production"));
124        assert!(yaml.contains("name: Test Model"));
125        assert!(yaml.contains("domain: ml"));
126    }
127
128    #[test]
129    fn test_export_asset_with_description() {
130        let asset = CADSAsset {
131            api_version: "v1.0".to_string(),
132            kind: CADSKind::MLPipeline,
133            id: "test-id".to_string(),
134            name: "Test Pipeline".to_string(),
135            version: "1.0.0".to_string(),
136            status: CADSStatus::Draft,
137            domain: None,
138            tags: vec![],
139            description: Some(CADSDescription {
140                purpose: Some("Data processing pipeline".to_string()),
141                limitations: Some("Max 1TB per day".to_string()),
142                usage: None,
143                external_links: None,
144            }),
145            runtime: None,
146            sla: None,
147            pricing: None,
148            team: None,
149            risk: None,
150            compliance: None,
151            validation_profiles: None,
152            bpmn_models: None,
153            dmn_models: None,
154            openapi_specs: None,
155            custom_properties: None,
156            created_at: None,
157            updated_at: None,
158        };
159
160        let yaml = CADSExporter::export_asset(&asset);
161        assert!(yaml.contains("description:"));
162        assert!(yaml.contains("purpose: Data processing pipeline"));
163        assert!(yaml.contains("limitations: Max 1TB per day"));
164    }
165
166    #[test]
167    fn test_export_asset_with_runtime() {
168        let asset = CADSAsset {
169            api_version: "v1.0".to_string(),
170            kind: CADSKind::Application,
171            id: "test-id".to_string(),
172            name: "Test App".to_string(),
173            version: "1.0.0".to_string(),
174            status: CADSStatus::Validated,
175            domain: None,
176            tags: vec![],
177            description: None,
178            runtime: Some(CADSRuntime {
179                environment: Some("kubernetes".to_string()),
180                endpoints: Some(vec!["https://api.example.com".to_string()]),
181                container: Some(CADSRuntimeContainer {
182                    image: Some("myapp:latest".to_string()),
183                }),
184                resources: Some(CADSRuntimeResources {
185                    cpu: Some("2".to_string()),
186                    memory: Some("4Gi".to_string()),
187                    gpu: None,
188                }),
189            }),
190            sla: None,
191            pricing: None,
192            team: None,
193            risk: None,
194            compliance: None,
195            validation_profiles: None,
196            bpmn_models: None,
197            dmn_models: None,
198            openapi_specs: None,
199            custom_properties: None,
200            created_at: None,
201            updated_at: None,
202        };
203
204        let yaml = CADSExporter::export_asset(&asset);
205        assert!(yaml.contains("runtime:"));
206        assert!(yaml.contains("environment: kubernetes"));
207        assert!(yaml.contains("container:"));
208        assert!(yaml.contains("image: myapp:latest"));
209    }
210
211    #[test]
212    fn test_export_asset_with_team() {
213        let asset = CADSAsset {
214            api_version: "v1.0".to_string(),
215            kind: CADSKind::AIModel,
216            id: "test-id".to_string(),
217            name: "Test Model".to_string(),
218            version: "1.0.0".to_string(),
219            status: CADSStatus::Production,
220            domain: None,
221            tags: vec![],
222            description: None,
223            runtime: None,
224            sla: None,
225            pricing: None,
226            team: Some(vec![CADSTeamMember {
227                role: "owner".to_string(),
228                name: "John Doe".to_string(),
229                contact: Some("user@example.com".to_string()),
230            }]),
231            risk: None,
232            compliance: None,
233            validation_profiles: None,
234            bpmn_models: None,
235            dmn_models: None,
236            openapi_specs: None,
237            custom_properties: None,
238            created_at: None,
239            updated_at: None,
240        };
241
242        let yaml = CADSExporter::export_asset(&asset);
243        assert!(yaml.contains("team:"));
244        assert!(yaml.contains("name: John Doe"));
245        assert!(yaml.contains("role: owner"));
246    }
247
248    #[test]
249    fn test_export_all_kinds() {
250        let kinds = vec![
251            (CADSKind::AIModel, "AIModel"),
252            (CADSKind::MLPipeline, "MLPipeline"),
253            (CADSKind::Application, "Application"),
254            (CADSKind::ETLPipeline, "ETLPipeline"),
255            (CADSKind::SourceSystem, "SourceSystem"),
256            (CADSKind::DestinationSystem, "DestinationSystem"),
257            (CADSKind::DataPipeline, "DataPipeline"),
258            (CADSKind::ETLProcess, "ETLProcess"),
259        ];
260
261        for (kind, expected_str) in kinds {
262            let asset = CADSAsset {
263                api_version: "v1.0".to_string(),
264                kind,
265                id: "test-id".to_string(),
266                name: "Test".to_string(),
267                version: "1.0.0".to_string(),
268                status: CADSStatus::Draft,
269                domain: None,
270                tags: vec![],
271                description: None,
272                runtime: None,
273                sla: None,
274                pricing: None,
275                team: None,
276                risk: None,
277                compliance: None,
278                validation_profiles: None,
279                bpmn_models: None,
280                dmn_models: None,
281                openapi_specs: None,
282                custom_properties: None,
283                created_at: None,
284                updated_at: None,
285            };
286
287            let yaml = CADSExporter::export_asset(&asset);
288            assert!(
289                yaml.contains(&format!("kind: {}", expected_str)),
290                "Expected kind '{}' not found in YAML",
291                expected_str
292            );
293        }
294    }
295}