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