Skip to main content

data_modelling_core/export/
odps.rs

1//! ODPS (Open Data Product Standard) exporter
2//!
3//! Exports ODPSDataProduct models to ODPS YAML format.
4
5use crate::export::ExportError;
6use crate::models::odps::*;
7
8/// ODPS exporter for generating ODPS YAML from ODPSDataProduct models
9pub struct ODPSExporter;
10
11impl ODPSExporter {
12    /// Export a Data Product to ODPS YAML format (instance method for WASM compatibility)
13    ///
14    /// # Arguments
15    ///
16    /// * `product` - The Data Product to export
17    ///
18    /// # Returns
19    ///
20    /// A Result containing the YAML string in ODPS format, or an ExportError
21    pub fn export(&self, product: &ODPSDataProduct) -> Result<String, ExportError> {
22        let yaml = Self::export_product(product);
23
24        // Validate exported YAML against ODPS schema (if feature enabled)
25        #[cfg(feature = "odps-validation")]
26        {
27            use crate::validation::schema::validate_odps_internal;
28            validate_odps_internal(&yaml).map_err(ExportError::ValidationError)?;
29        }
30
31        Ok(yaml)
32    }
33
34    /// Export a Data Product to ODPS YAML format
35    ///
36    /// # Arguments
37    ///
38    /// * `product` - The Data Product to export
39    ///
40    /// # Returns
41    ///
42    /// A YAML string in ODPS format
43    ///
44    /// # Example
45    ///
46    /// ```rust
47    /// use data_modelling_core::export::odps::ODPSExporter;
48    /// use data_modelling_core::models::odps::*;
49    ///
50    /// let product = ODPSDataProduct {
51    ///     api_version: "v1.0.0".to_string(),
52    ///     kind: "DataProduct".to_string(),
53    ///     id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
54    ///     name: Some("customer-data-product".to_string()),
55    ///     version: Some("1.0.0".to_string()),
56    ///     status: ODPSStatus::Active,
57    ///     domain: None,
58    ///     tenant: None,
59    ///     authoritative_definitions: None,
60    ///     description: None,
61    ///     custom_properties: None,
62    ///     tags: vec![],
63    ///     input_ports: None,
64    ///     output_ports: None,
65    ///     management_ports: None,
66    ///     support: None,
67    ///     team: None,
68    ///     product_created_ts: None,
69    ///     created_at: None,
70    ///     updated_at: None,
71    /// };
72    ///
73    /// let yaml = ODPSExporter::export_product(&product);
74    /// assert!(yaml.contains("apiVersion: v1.0.0"));
75    /// assert!(yaml.contains("kind: DataProduct"));
76    /// ```
77    pub fn export_product(product: &ODPSDataProduct) -> String {
78        // Use direct struct serialization - serde handles all field naming and optional fields
79        match serde_yaml::to_string(product) {
80            Ok(yaml) => yaml,
81            Err(e) => format!("# Error serializing product: {}\n", e),
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_export_product_basic() {
92        let product = ODPSDataProduct {
93            api_version: "v1.0.0".to_string(),
94            kind: "DataProduct".to_string(),
95            id: "test-id".to_string(),
96            name: Some("Test Product".to_string()),
97            version: Some("1.0.0".to_string()),
98            status: ODPSStatus::Active,
99            domain: Some("test-domain".to_string()),
100            tenant: None,
101            authoritative_definitions: None,
102            description: None,
103            custom_properties: None,
104            tags: vec![],
105            input_ports: None,
106            output_ports: None,
107            management_ports: None,
108            support: None,
109            team: None,
110            product_created_ts: None,
111            created_at: None,
112            updated_at: None,
113        };
114
115        let yaml = ODPSExporter::export_product(&product);
116        assert!(yaml.contains("apiVersion: v1.0.0"));
117        assert!(yaml.contains("kind: DataProduct"));
118        assert!(yaml.contains("id: test-id"));
119        assert!(yaml.contains("status: active"));
120        assert!(yaml.contains("name: Test Product"));
121        assert!(yaml.contains("domain: test-domain"));
122    }
123
124    #[test]
125    fn test_export_product_with_ports() {
126        let product = ODPSDataProduct {
127            api_version: "v1.0.0".to_string(),
128            kind: "DataProduct".to_string(),
129            id: "test-id".to_string(),
130            name: None,
131            version: None,
132            status: ODPSStatus::Draft,
133            domain: None,
134            tenant: None,
135            authoritative_definitions: None,
136            description: None,
137            custom_properties: None,
138            tags: vec![],
139            input_ports: Some(vec![ODPSInputPort {
140                name: "input-1".to_string(),
141                version: "1.0.0".to_string(),
142                contract_id: "contract-123".to_string(),
143                tags: vec![],
144                custom_properties: None,
145                authoritative_definitions: None,
146            }]),
147            output_ports: Some(vec![ODPSOutputPort {
148                name: "output-1".to_string(),
149                description: Some("Output port description".to_string()),
150                r#type: Some("dataset".to_string()),
151                version: "1.0.0".to_string(),
152                contract_id: Some("contract-456".to_string()),
153                sbom: None,
154                input_contracts: None,
155                tags: vec![],
156                custom_properties: None,
157                authoritative_definitions: None,
158            }]),
159            management_ports: None,
160            support: None,
161            team: None,
162            product_created_ts: None,
163            created_at: None,
164            updated_at: None,
165        };
166
167        let yaml = ODPSExporter::export_product(&product);
168        assert!(yaml.contains("inputPorts:"));
169        assert!(yaml.contains("name: input-1"));
170        assert!(yaml.contains("contractId: contract-123"));
171        assert!(yaml.contains("outputPorts:"));
172        assert!(yaml.contains("name: output-1"));
173    }
174
175    #[test]
176    fn test_export_product_with_team() {
177        let product = ODPSDataProduct {
178            api_version: "v1.0.0".to_string(),
179            kind: "DataProduct".to_string(),
180            id: "test-id".to_string(),
181            name: None,
182            version: None,
183            status: ODPSStatus::Active,
184            domain: None,
185            tenant: None,
186            authoritative_definitions: None,
187            description: None,
188            custom_properties: None,
189            tags: vec![],
190            input_ports: None,
191            output_ports: None,
192            management_ports: None,
193            support: None,
194            team: Some(ODPSTeam {
195                name: Some("Data Team".to_string()),
196                description: Some("The data team".to_string()),
197                members: Some(vec![ODPSTeamMember {
198                    username: "user@example.com".to_string(),
199                    name: Some("John Doe".to_string()),
200                    description: None,
201                    role: Some("Lead".to_string()),
202                    date_in: Some("2024-01-01".to_string()),
203                    date_out: None,
204                    replaced_by_username: None,
205                    tags: vec![],
206                    custom_properties: None,
207                    authoritative_definitions: None,
208                }]),
209                tags: vec![],
210                custom_properties: None,
211                authoritative_definitions: None,
212            }),
213            product_created_ts: None,
214            created_at: None,
215            updated_at: None,
216        };
217
218        let yaml = ODPSExporter::export_product(&product);
219        assert!(yaml.contains("team:"));
220        assert!(yaml.contains("name: Data Team"));
221        assert!(yaml.contains("members:"));
222        assert!(yaml.contains("username: user@example.com"));
223        assert!(yaml.contains("role: Lead"));
224    }
225}