data_modelling_sdk/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::*;
7use serde_yaml;
8
9/// ODPS exporter for generating ODPS YAML from ODPSDataProduct models
10pub struct ODPSExporter;
11
12impl ODPSExporter {
13    /// Export a Data Product to ODPS YAML format (instance method for WASM compatibility)
14    ///
15    /// # Arguments
16    ///
17    /// * `product` - The Data Product to export
18    ///
19    /// # Returns
20    ///
21    /// A Result containing the YAML string in ODPS format, or an ExportError
22    pub fn export(&self, product: &ODPSDataProduct) -> Result<String, ExportError> {
23        let yaml = Self::export_product(product);
24
25        // Validate exported YAML against ODPS schema (if feature enabled)
26        #[cfg(feature = "odps-validation")]
27        {
28            #[cfg(feature = "cli")]
29            {
30                use crate::cli::validation::validate_odps_internal;
31                validate_odps_internal(&yaml).map_err(ExportError::ValidationError)?;
32            }
33            #[cfg(not(feature = "cli"))]
34            {
35                // Inline validation when CLI feature is not enabled
36                use jsonschema::Validator;
37                use serde_json::Value;
38
39                let schema_content = include_str!("../../schemas/odps-json-schema-latest.json");
40                let schema: Value = serde_json::from_str(schema_content).map_err(|e| {
41                    ExportError::ValidationError(format!("Failed to load ODPS schema: {}", e))
42                })?;
43
44                let validator = Validator::new(&schema).map_err(|e| {
45                    ExportError::ValidationError(format!("Failed to compile ODPS schema: {}", e))
46                })?;
47
48                let data: Value = serde_yaml::from_str(&yaml).map_err(|e| {
49                    ExportError::ValidationError(format!("Failed to parse YAML: {}", e))
50                })?;
51
52                if let Err(error) = validator.validate(&data) {
53                    return Err(ExportError::ValidationError(format!(
54                        "ODPS validation failed: {}",
55                        error
56                    )));
57                }
58            }
59        }
60
61        Ok(yaml)
62    }
63
64    /// Export a Data Product to ODPS YAML format
65    ///
66    /// # Arguments
67    ///
68    /// * `product` - The Data Product to export
69    ///
70    /// # Returns
71    ///
72    /// A YAML string in ODPS format
73    ///
74    /// # Example
75    ///
76    /// ```rust
77    /// use data_modelling_sdk::export::odps::ODPSExporter;
78    /// use data_modelling_sdk::models::odps::*;
79    ///
80    /// let product = ODPSDataProduct {
81    ///     api_version: "v1.0.0".to_string(),
82    ///     kind: "DataProduct".to_string(),
83    ///     id: "550e8400-e29b-41d4-a716-446655440000".to_string(),
84    ///     name: Some("customer-data-product".to_string()),
85    ///     version: Some("1.0.0".to_string()),
86    ///     status: ODPSStatus::Active,
87    ///     domain: None,
88    ///     tenant: None,
89    ///     authoritative_definitions: None,
90    ///     description: None,
91    ///     custom_properties: None,
92    ///     tags: vec![],
93    ///     input_ports: None,
94    ///     output_ports: None,
95    ///     management_ports: None,
96    ///     support: None,
97    ///     team: None,
98    ///     product_created_ts: None,
99    ///     created_at: None,
100    ///     updated_at: None,
101    /// };
102    ///
103    /// let yaml = ODPSExporter::export_product(&product);
104    /// assert!(yaml.contains("apiVersion: v1.0.0"));
105    /// assert!(yaml.contains("kind: DataProduct"));
106    /// ```
107    pub fn export_product(product: &ODPSDataProduct) -> String {
108        let mut yaml = serde_yaml::Mapping::new();
109
110        // Required fields
111        yaml.insert(
112            serde_yaml::Value::String("apiVersion".to_string()),
113            serde_yaml::Value::String(product.api_version.clone()),
114        );
115
116        yaml.insert(
117            serde_yaml::Value::String("kind".to_string()),
118            serde_yaml::Value::String(product.kind.clone()),
119        );
120
121        yaml.insert(
122            serde_yaml::Value::String("id".to_string()),
123            serde_yaml::Value::String(product.id.clone()),
124        );
125
126        let status_str = match product.status {
127            ODPSStatus::Proposed => "proposed",
128            ODPSStatus::Draft => "draft",
129            ODPSStatus::Active => "active",
130            ODPSStatus::Deprecated => "deprecated",
131            ODPSStatus::Retired => "retired",
132        };
133        yaml.insert(
134            serde_yaml::Value::String("status".to_string()),
135            serde_yaml::Value::String(status_str.to_string()),
136        );
137
138        // Optional fields
139        if let Some(name) = &product.name {
140            yaml.insert(
141                serde_yaml::Value::String("name".to_string()),
142                serde_yaml::Value::String(name.clone()),
143            );
144        }
145
146        if let Some(version) = &product.version {
147            yaml.insert(
148                serde_yaml::Value::String("version".to_string()),
149                serde_yaml::Value::String(version.clone()),
150            );
151        }
152
153        if let Some(domain) = &product.domain {
154            yaml.insert(
155                serde_yaml::Value::String("domain".to_string()),
156                serde_yaml::Value::String(domain.clone()),
157            );
158        }
159
160        if let Some(tenant) = &product.tenant {
161            yaml.insert(
162                serde_yaml::Value::String("tenant".to_string()),
163                serde_yaml::Value::String(tenant.clone()),
164            );
165        }
166
167        if !product.tags.is_empty() {
168            let tags_yaml: Vec<serde_yaml::Value> = product
169                .tags
170                .iter()
171                .map(|t| serde_yaml::Value::String(t.to_string()))
172                .collect();
173            yaml.insert(
174                serde_yaml::Value::String("tags".to_string()),
175                serde_yaml::Value::Sequence(tags_yaml),
176            );
177        }
178
179        if let Some(description) = &product.description {
180            let mut desc_map = serde_yaml::Mapping::new();
181            if let Some(purpose) = &description.purpose {
182                desc_map.insert(
183                    serde_yaml::Value::String("purpose".to_string()),
184                    serde_yaml::Value::String(purpose.clone()),
185                );
186            }
187            if let Some(limitations) = &description.limitations {
188                desc_map.insert(
189                    serde_yaml::Value::String("limitations".to_string()),
190                    serde_yaml::Value::String(limitations.clone()),
191                );
192            }
193            if let Some(usage) = &description.usage {
194                desc_map.insert(
195                    serde_yaml::Value::String("usage".to_string()),
196                    serde_yaml::Value::String(usage.clone()),
197                );
198            }
199            if let Some(auth_defs) = &description.authoritative_definitions {
200                let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
201                if !defs_yaml.is_empty() {
202                    desc_map.insert(
203                        serde_yaml::Value::String("authoritativeDefinitions".to_string()),
204                        serde_yaml::Value::Sequence(defs_yaml),
205                    );
206                }
207            }
208            if let Some(custom_props) = &description.custom_properties {
209                let props_yaml = Self::serialize_custom_properties(custom_props);
210                if !props_yaml.is_empty() {
211                    desc_map.insert(
212                        serde_yaml::Value::String("customProperties".to_string()),
213                        serde_yaml::Value::Sequence(props_yaml),
214                    );
215                }
216            }
217            if !desc_map.is_empty() {
218                yaml.insert(
219                    serde_yaml::Value::String("description".to_string()),
220                    serde_yaml::Value::Mapping(desc_map),
221                );
222            }
223        }
224
225        if let Some(auth_defs) = &product.authoritative_definitions {
226            // Preserve empty arrays (include even if empty)
227            let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
228            yaml.insert(
229                serde_yaml::Value::String("authoritativeDefinitions".to_string()),
230                serde_yaml::Value::Sequence(defs_yaml),
231            );
232        }
233
234        if let Some(custom_props) = &product.custom_properties {
235            // Preserve empty arrays (include even if empty)
236            let props_yaml = Self::serialize_custom_properties(custom_props);
237            yaml.insert(
238                serde_yaml::Value::String("customProperties".to_string()),
239                serde_yaml::Value::Sequence(props_yaml),
240            );
241        }
242
243        if let Some(input_ports) = &product.input_ports {
244            // Preserve empty arrays (include even if empty)
245            let ports_yaml: Vec<serde_yaml::Value> = input_ports
246                .iter()
247                .map(|port| {
248                    let mut port_map = serde_yaml::Mapping::new();
249                    port_map.insert(
250                        serde_yaml::Value::String("name".to_string()),
251                        serde_yaml::Value::String(port.name.clone()),
252                    );
253                    port_map.insert(
254                        serde_yaml::Value::String("version".to_string()),
255                        serde_yaml::Value::String(port.version.clone()),
256                    );
257                    port_map.insert(
258                        serde_yaml::Value::String("contractId".to_string()),
259                        serde_yaml::Value::String(port.contract_id.clone()),
260                    );
261                    if !port.tags.is_empty() {
262                        let tags_yaml: Vec<serde_yaml::Value> = port
263                            .tags
264                            .iter()
265                            .map(|t| serde_yaml::Value::String(t.to_string()))
266                            .collect();
267                        port_map.insert(
268                            serde_yaml::Value::String("tags".to_string()),
269                            serde_yaml::Value::Sequence(tags_yaml),
270                        );
271                    }
272                    if let Some(custom_props) = &port.custom_properties {
273                        let props_yaml = Self::serialize_custom_properties(custom_props);
274                        if !props_yaml.is_empty() {
275                            port_map.insert(
276                                serde_yaml::Value::String("customProperties".to_string()),
277                                serde_yaml::Value::Sequence(props_yaml),
278                            );
279                        }
280                    }
281                    if let Some(auth_defs) = &port.authoritative_definitions {
282                        let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
283                        if !defs_yaml.is_empty() {
284                            port_map.insert(
285                                serde_yaml::Value::String("authoritativeDefinitions".to_string()),
286                                serde_yaml::Value::Sequence(defs_yaml),
287                            );
288                        }
289                    }
290                    serde_yaml::Value::Mapping(port_map)
291                })
292                .collect();
293            // Always include inputPorts, even if empty, to preserve structure
294            yaml.insert(
295                serde_yaml::Value::String("inputPorts".to_string()),
296                serde_yaml::Value::Sequence(ports_yaml),
297            );
298        }
299
300        if let Some(output_ports) = &product.output_ports {
301            let ports_yaml: Vec<serde_yaml::Value> = output_ports
302                .iter()
303                .map(|port| {
304                    let mut port_map = serde_yaml::Mapping::new();
305                    port_map.insert(
306                        serde_yaml::Value::String("name".to_string()),
307                        serde_yaml::Value::String(port.name.clone()),
308                    );
309                    port_map.insert(
310                        serde_yaml::Value::String("version".to_string()),
311                        serde_yaml::Value::String(port.version.clone()),
312                    );
313                    if let Some(description) = &port.description {
314                        port_map.insert(
315                            serde_yaml::Value::String("description".to_string()),
316                            serde_yaml::Value::String(description.clone()),
317                        );
318                    }
319                    if let Some(r#type) = &port.r#type {
320                        port_map.insert(
321                            serde_yaml::Value::String("type".to_string()),
322                            serde_yaml::Value::String(r#type.clone()),
323                        );
324                    }
325                    if let Some(contract_id) = &port.contract_id {
326                        port_map.insert(
327                            serde_yaml::Value::String("contractId".to_string()),
328                            serde_yaml::Value::String(contract_id.clone()),
329                        );
330                    }
331                    if let Some(sbom) = &port.sbom {
332                        let sbom_yaml: Vec<serde_yaml::Value> = sbom
333                            .iter()
334                            .map(|s| {
335                                let mut sbom_map = serde_yaml::Mapping::new();
336                                sbom_map.insert(
337                                    serde_yaml::Value::String("url".to_string()),
338                                    serde_yaml::Value::String(s.url.clone()),
339                                );
340                                if let Some(r#type) = &s.r#type {
341                                    sbom_map.insert(
342                                        serde_yaml::Value::String("type".to_string()),
343                                        serde_yaml::Value::String(r#type.clone()),
344                                    );
345                                }
346                                serde_yaml::Value::Mapping(sbom_map)
347                            })
348                            .collect();
349                        port_map.insert(
350                            serde_yaml::Value::String("sbom".to_string()),
351                            serde_yaml::Value::Sequence(sbom_yaml),
352                        );
353                    }
354                    if let Some(input_contracts) = &port.input_contracts {
355                        let contracts_yaml: Vec<serde_yaml::Value> = input_contracts
356                            .iter()
357                            .map(|contract| {
358                                let mut contract_map = serde_yaml::Mapping::new();
359                                contract_map.insert(
360                                    serde_yaml::Value::String("id".to_string()),
361                                    serde_yaml::Value::String(contract.id.clone()),
362                                );
363                                contract_map.insert(
364                                    serde_yaml::Value::String("version".to_string()),
365                                    serde_yaml::Value::String(contract.version.clone()),
366                                );
367                                serde_yaml::Value::Mapping(contract_map)
368                            })
369                            .collect();
370                        port_map.insert(
371                            serde_yaml::Value::String("inputContracts".to_string()),
372                            serde_yaml::Value::Sequence(contracts_yaml),
373                        );
374                    }
375                    if !port.tags.is_empty() {
376                        let tags_yaml: Vec<serde_yaml::Value> = port
377                            .tags
378                            .iter()
379                            .map(|t| serde_yaml::Value::String(t.to_string()))
380                            .collect();
381                        port_map.insert(
382                            serde_yaml::Value::String("tags".to_string()),
383                            serde_yaml::Value::Sequence(tags_yaml),
384                        );
385                    }
386                    if let Some(custom_props) = &port.custom_properties {
387                        let props_yaml = Self::serialize_custom_properties(custom_props);
388                        if !props_yaml.is_empty() {
389                            port_map.insert(
390                                serde_yaml::Value::String("customProperties".to_string()),
391                                serde_yaml::Value::Sequence(props_yaml),
392                            );
393                        }
394                    }
395                    if let Some(auth_defs) = &port.authoritative_definitions {
396                        let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
397                        if !defs_yaml.is_empty() {
398                            port_map.insert(
399                                serde_yaml::Value::String("authoritativeDefinitions".to_string()),
400                                serde_yaml::Value::Sequence(defs_yaml),
401                            );
402                        }
403                    }
404                    serde_yaml::Value::Mapping(port_map)
405                })
406                .collect();
407            yaml.insert(
408                serde_yaml::Value::String("outputPorts".to_string()),
409                serde_yaml::Value::Sequence(ports_yaml),
410            );
411        }
412
413        if let Some(management_ports) = &product.management_ports {
414            let ports_yaml: Vec<serde_yaml::Value> = management_ports
415                .iter()
416                .map(|port| {
417                    let mut port_map = serde_yaml::Mapping::new();
418                    port_map.insert(
419                        serde_yaml::Value::String("name".to_string()),
420                        serde_yaml::Value::String(port.name.clone()),
421                    );
422                    port_map.insert(
423                        serde_yaml::Value::String("content".to_string()),
424                        serde_yaml::Value::String(port.content.clone()),
425                    );
426                    if let Some(r#type) = &port.r#type {
427                        port_map.insert(
428                            serde_yaml::Value::String("type".to_string()),
429                            serde_yaml::Value::String(r#type.clone()),
430                        );
431                    }
432                    if let Some(url) = &port.url {
433                        port_map.insert(
434                            serde_yaml::Value::String("url".to_string()),
435                            serde_yaml::Value::String(url.clone()),
436                        );
437                    }
438                    if let Some(channel) = &port.channel {
439                        port_map.insert(
440                            serde_yaml::Value::String("channel".to_string()),
441                            serde_yaml::Value::String(channel.clone()),
442                        );
443                    }
444                    if let Some(description) = &port.description {
445                        port_map.insert(
446                            serde_yaml::Value::String("description".to_string()),
447                            serde_yaml::Value::String(description.clone()),
448                        );
449                    }
450                    if !port.tags.is_empty() {
451                        let tags_yaml: Vec<serde_yaml::Value> = port
452                            .tags
453                            .iter()
454                            .map(|t| serde_yaml::Value::String(t.to_string()))
455                            .collect();
456                        port_map.insert(
457                            serde_yaml::Value::String("tags".to_string()),
458                            serde_yaml::Value::Sequence(tags_yaml),
459                        );
460                    }
461                    if let Some(custom_props) = &port.custom_properties {
462                        let props_yaml = Self::serialize_custom_properties(custom_props);
463                        if !props_yaml.is_empty() {
464                            port_map.insert(
465                                serde_yaml::Value::String("customProperties".to_string()),
466                                serde_yaml::Value::Sequence(props_yaml),
467                            );
468                        }
469                    }
470                    if let Some(auth_defs) = &port.authoritative_definitions {
471                        let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
472                        if !defs_yaml.is_empty() {
473                            port_map.insert(
474                                serde_yaml::Value::String("authoritativeDefinitions".to_string()),
475                                serde_yaml::Value::Sequence(defs_yaml),
476                            );
477                        }
478                    }
479                    serde_yaml::Value::Mapping(port_map)
480                })
481                .collect();
482            yaml.insert(
483                serde_yaml::Value::String("managementPorts".to_string()),
484                serde_yaml::Value::Sequence(ports_yaml),
485            );
486        }
487
488        if let Some(support) = &product.support {
489            let support_yaml: Vec<serde_yaml::Value> = support
490                .iter()
491                .map(|s| {
492                    let mut support_map = serde_yaml::Mapping::new();
493                    support_map.insert(
494                        serde_yaml::Value::String("channel".to_string()),
495                        serde_yaml::Value::String(s.channel.clone()),
496                    );
497                    support_map.insert(
498                        serde_yaml::Value::String("url".to_string()),
499                        serde_yaml::Value::String(s.url.clone()),
500                    );
501                    if let Some(description) = &s.description {
502                        support_map.insert(
503                            serde_yaml::Value::String("description".to_string()),
504                            serde_yaml::Value::String(description.clone()),
505                        );
506                    }
507                    if let Some(tool) = &s.tool {
508                        support_map.insert(
509                            serde_yaml::Value::String("tool".to_string()),
510                            serde_yaml::Value::String(tool.clone()),
511                        );
512                    }
513                    if let Some(scope) = &s.scope {
514                        support_map.insert(
515                            serde_yaml::Value::String("scope".to_string()),
516                            serde_yaml::Value::String(scope.clone()),
517                        );
518                    }
519                    if let Some(invitation_url) = &s.invitation_url {
520                        support_map.insert(
521                            serde_yaml::Value::String("invitationUrl".to_string()),
522                            serde_yaml::Value::String(invitation_url.clone()),
523                        );
524                    }
525                    if !s.tags.is_empty() {
526                        let tags_yaml: Vec<serde_yaml::Value> = s
527                            .tags
528                            .iter()
529                            .map(|t| serde_yaml::Value::String(t.to_string()))
530                            .collect();
531                        support_map.insert(
532                            serde_yaml::Value::String("tags".to_string()),
533                            serde_yaml::Value::Sequence(tags_yaml),
534                        );
535                    }
536                    if let Some(custom_props) = &s.custom_properties {
537                        let props_yaml = Self::serialize_custom_properties(custom_props);
538                        if !props_yaml.is_empty() {
539                            support_map.insert(
540                                serde_yaml::Value::String("customProperties".to_string()),
541                                serde_yaml::Value::Sequence(props_yaml),
542                            );
543                        }
544                    }
545                    if let Some(auth_defs) = &s.authoritative_definitions {
546                        let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
547                        if !defs_yaml.is_empty() {
548                            support_map.insert(
549                                serde_yaml::Value::String("authoritativeDefinitions".to_string()),
550                                serde_yaml::Value::Sequence(defs_yaml),
551                            );
552                        }
553                    }
554                    serde_yaml::Value::Mapping(support_map)
555                })
556                .collect();
557            yaml.insert(
558                serde_yaml::Value::String("support".to_string()),
559                serde_yaml::Value::Sequence(support_yaml),
560            );
561        }
562
563        if let Some(team) = &product.team {
564            let mut team_map = serde_yaml::Mapping::new();
565            if let Some(name) = &team.name {
566                team_map.insert(
567                    serde_yaml::Value::String("name".to_string()),
568                    serde_yaml::Value::String(name.clone()),
569                );
570            }
571            if let Some(description) = &team.description {
572                team_map.insert(
573                    serde_yaml::Value::String("description".to_string()),
574                    serde_yaml::Value::String(description.clone()),
575                );
576            }
577            if let Some(members) = &team.members {
578                let members_yaml: Vec<serde_yaml::Value> = members
579                    .iter()
580                    .map(|member| {
581                        let mut member_map = serde_yaml::Mapping::new();
582                        member_map.insert(
583                            serde_yaml::Value::String("username".to_string()),
584                            serde_yaml::Value::String(member.username.clone()),
585                        );
586                        if let Some(name) = &member.name {
587                            member_map.insert(
588                                serde_yaml::Value::String("name".to_string()),
589                                serde_yaml::Value::String(name.clone()),
590                            );
591                        }
592                        if let Some(description) = &member.description {
593                            member_map.insert(
594                                serde_yaml::Value::String("description".to_string()),
595                                serde_yaml::Value::String(description.clone()),
596                            );
597                        }
598                        if let Some(role) = &member.role {
599                            member_map.insert(
600                                serde_yaml::Value::String("role".to_string()),
601                                serde_yaml::Value::String(role.clone()),
602                            );
603                        }
604                        if let Some(date_in) = &member.date_in {
605                            member_map.insert(
606                                serde_yaml::Value::String("dateIn".to_string()),
607                                serde_yaml::Value::String(date_in.clone()),
608                            );
609                        }
610                        if let Some(date_out) = &member.date_out {
611                            member_map.insert(
612                                serde_yaml::Value::String("dateOut".to_string()),
613                                serde_yaml::Value::String(date_out.clone()),
614                            );
615                        }
616                        if let Some(replaced_by) = &member.replaced_by_username {
617                            member_map.insert(
618                                serde_yaml::Value::String("replacedByUsername".to_string()),
619                                serde_yaml::Value::String(replaced_by.clone()),
620                            );
621                        }
622                        if !member.tags.is_empty() {
623                            let tags_yaml: Vec<serde_yaml::Value> = member
624                                .tags
625                                .iter()
626                                .map(|t| serde_yaml::Value::String(t.to_string()))
627                                .collect();
628                            member_map.insert(
629                                serde_yaml::Value::String("tags".to_string()),
630                                serde_yaml::Value::Sequence(tags_yaml),
631                            );
632                        }
633                        if let Some(custom_props) = &member.custom_properties {
634                            let props_yaml = Self::serialize_custom_properties(custom_props);
635                            if !props_yaml.is_empty() {
636                                member_map.insert(
637                                    serde_yaml::Value::String("customProperties".to_string()),
638                                    serde_yaml::Value::Sequence(props_yaml),
639                                );
640                            }
641                        }
642                        if let Some(auth_defs) = &member.authoritative_definitions {
643                            let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
644                            if !defs_yaml.is_empty() {
645                                member_map.insert(
646                                    serde_yaml::Value::String(
647                                        "authoritativeDefinitions".to_string(),
648                                    ),
649                                    serde_yaml::Value::Sequence(defs_yaml),
650                                );
651                            }
652                        }
653                        serde_yaml::Value::Mapping(member_map)
654                    })
655                    .collect();
656                team_map.insert(
657                    serde_yaml::Value::String("members".to_string()),
658                    serde_yaml::Value::Sequence(members_yaml),
659                );
660            }
661            if !team.tags.is_empty() {
662                let tags_yaml: Vec<serde_yaml::Value> = team
663                    .tags
664                    .iter()
665                    .map(|t| serde_yaml::Value::String(t.to_string()))
666                    .collect();
667                team_map.insert(
668                    serde_yaml::Value::String("tags".to_string()),
669                    serde_yaml::Value::Sequence(tags_yaml),
670                );
671            }
672            if let Some(custom_props) = &team.custom_properties {
673                let props_yaml = Self::serialize_custom_properties(custom_props);
674                if !props_yaml.is_empty() {
675                    team_map.insert(
676                        serde_yaml::Value::String("customProperties".to_string()),
677                        serde_yaml::Value::Sequence(props_yaml),
678                    );
679                }
680            }
681            if let Some(auth_defs) = &team.authoritative_definitions {
682                let defs_yaml = Self::serialize_authoritative_definitions(auth_defs);
683                if !defs_yaml.is_empty() {
684                    team_map.insert(
685                        serde_yaml::Value::String("authoritativeDefinitions".to_string()),
686                        serde_yaml::Value::Sequence(defs_yaml),
687                    );
688                }
689            }
690            if !team_map.is_empty() {
691                yaml.insert(
692                    serde_yaml::Value::String("team".to_string()),
693                    serde_yaml::Value::Mapping(team_map),
694                );
695            }
696        }
697
698        if let Some(product_created_ts) = &product.product_created_ts {
699            yaml.insert(
700                serde_yaml::Value::String("productCreatedTs".to_string()),
701                serde_yaml::Value::String(product_created_ts.clone()),
702            );
703        }
704
705        // Serialize to YAML string
706        serde_yaml::to_string(&serde_yaml::Value::Mapping(yaml))
707            .unwrap_or_else(|_| String::from(""))
708    }
709
710    /// Serialize authoritative definitions
711    fn serialize_authoritative_definitions(
712        defs: &[ODPSAuthoritativeDefinition],
713    ) -> Vec<serde_yaml::Value> {
714        defs.iter()
715            .map(|def| {
716                let mut def_map = serde_yaml::Mapping::new();
717                def_map.insert(
718                    serde_yaml::Value::String("type".to_string()),
719                    serde_yaml::Value::String(def.r#type.clone()),
720                );
721                def_map.insert(
722                    serde_yaml::Value::String("url".to_string()),
723                    serde_yaml::Value::String(def.url.clone()),
724                );
725                if let Some(description) = &def.description {
726                    def_map.insert(
727                        serde_yaml::Value::String("description".to_string()),
728                        serde_yaml::Value::String(description.clone()),
729                    );
730                }
731                serde_yaml::Value::Mapping(def_map)
732            })
733            .collect()
734    }
735
736    /// Serialize custom properties
737    fn serialize_custom_properties(props: &[ODPSCustomProperty]) -> Vec<serde_yaml::Value> {
738        props
739            .iter()
740            .map(|prop| {
741                let mut prop_map = serde_yaml::Mapping::new();
742                prop_map.insert(
743                    serde_yaml::Value::String("property".to_string()),
744                    serde_yaml::Value::String(prop.property.clone()),
745                );
746                prop_map.insert(
747                    serde_yaml::Value::String("value".to_string()),
748                    Self::json_to_yaml_value(&prop.value),
749                );
750                if let Some(description) = &prop.description {
751                    prop_map.insert(
752                        serde_yaml::Value::String("description".to_string()),
753                        serde_yaml::Value::String(description.clone()),
754                    );
755                }
756                serde_yaml::Value::Mapping(prop_map)
757            })
758            .collect()
759    }
760
761    /// Helper to convert serde_json::Value to serde_yaml::Value
762    fn json_to_yaml_value(json: &serde_json::Value) -> serde_yaml::Value {
763        match json {
764            serde_json::Value::Null => serde_yaml::Value::Null,
765            serde_json::Value::Bool(b) => serde_yaml::Value::Bool(*b),
766            serde_json::Value::Number(n) => {
767                if let Some(i) = n.as_i64() {
768                    serde_yaml::Value::Number(serde_yaml::Number::from(i))
769                } else if let Some(f) = n.as_f64() {
770                    serde_yaml::Value::Number(serde_yaml::Number::from(f))
771                } else {
772                    serde_yaml::Value::String(n.to_string())
773                }
774            }
775            serde_json::Value::String(s) => serde_yaml::Value::String(s.clone()),
776            serde_json::Value::Array(arr) => {
777                let yaml_arr: Vec<serde_yaml::Value> =
778                    arr.iter().map(Self::json_to_yaml_value).collect();
779                serde_yaml::Value::Sequence(yaml_arr)
780            }
781            serde_json::Value::Object(obj) => {
782                let mut yaml_map = serde_yaml::Mapping::new();
783                for (k, v) in obj {
784                    yaml_map.insert(
785                        serde_yaml::Value::String(k.clone()),
786                        Self::json_to_yaml_value(v),
787                    );
788                }
789                serde_yaml::Value::Mapping(yaml_map)
790            }
791        }
792    }
793}