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