1use super::{ExportError, ExportResult};
7use crate::models::{Column, DataModel, Table};
8use serde_yaml;
9use std::collections::HashMap;
10
11fn get_ref_path_from_relationships(column: &Column) -> Option<String> {
14 column.relationships.iter().find_map(|rel| {
15 if rel.relationship_type == "foreignKey" {
16 Some(format!("#/{}", rel.to))
18 } else {
19 None
20 }
21 })
22}
23
24pub struct ODCLExporter;
26
27impl ODCLExporter {
28 pub fn export_table(table: &Table, _format: &str) -> String {
32 Self::export_odcs_v3_1_0_format(table)
34 }
35
36 fn json_to_yaml_value(json: &serde_json::Value) -> serde_yaml::Value {
38 match json {
39 serde_json::Value::Null => serde_yaml::Value::Null,
40 serde_json::Value::Bool(b) => serde_yaml::Value::Bool(*b),
41 serde_json::Value::Number(n) => {
42 if let Some(i) = n.as_i64() {
43 serde_yaml::Value::Number(serde_yaml::Number::from(i))
44 } else if let Some(f) = n.as_f64() {
45 serde_yaml::Value::Number(serde_yaml::Number::from(f))
46 } else {
47 serde_yaml::Value::String(n.to_string())
48 }
49 }
50 serde_json::Value::String(s) => serde_yaml::Value::String(s.clone()),
51 serde_json::Value::Array(arr) => {
52 let yaml_arr: Vec<serde_yaml::Value> =
53 arr.iter().map(Self::json_to_yaml_value).collect();
54 serde_yaml::Value::Sequence(yaml_arr)
55 }
56 serde_json::Value::Object(obj) => {
57 let mut yaml_map = serde_yaml::Mapping::new();
58 for (k, v) in obj {
59 yaml_map.insert(
60 serde_yaml::Value::String(k.clone()),
61 Self::json_to_yaml_value(v),
62 );
63 }
64 serde_yaml::Value::Mapping(yaml_map)
65 }
66 }
67 }
68
69 fn export_odcs_v3_1_0_format(table: &Table) -> String {
71 let mut yaml = serde_yaml::Mapping::new();
72
73 yaml.insert(
75 serde_yaml::Value::String("apiVersion".to_string()),
76 serde_yaml::Value::String("v3.1.0".to_string()),
77 );
78 yaml.insert(
79 serde_yaml::Value::String("kind".to_string()),
80 serde_yaml::Value::String("DataContract".to_string()),
81 );
82
83 yaml.insert(
85 serde_yaml::Value::String("id".to_string()),
86 serde_yaml::Value::String(table.id.to_string()),
87 );
88
89 yaml.insert(
91 serde_yaml::Value::String("name".to_string()),
92 serde_yaml::Value::String(table.name.clone()),
93 );
94
95 let version = table
97 .odcl_metadata
98 .get("version")
99 .and_then(|v| v.as_str())
100 .map(|s| s.to_string())
101 .unwrap_or_else(|| "1.0.0".to_string());
102 yaml.insert(
103 serde_yaml::Value::String("version".to_string()),
104 serde_yaml::Value::String(version),
105 );
106
107 if let Some(status) = table.odcl_metadata.get("status")
109 && !status.is_null()
110 {
111 yaml.insert(
112 serde_yaml::Value::String("status".to_string()),
113 Self::json_to_yaml_value(status),
114 );
115 }
116
117 if let Some(domain) = table.odcl_metadata.get("domain")
119 && !domain.is_null()
120 {
121 yaml.insert(
122 serde_yaml::Value::String("domain".to_string()),
123 Self::json_to_yaml_value(domain),
124 );
125 }
126
127 if let Some(data_product) = table.odcl_metadata.get("dataProduct")
129 && !data_product.is_null()
130 {
131 yaml.insert(
132 serde_yaml::Value::String("dataProduct".to_string()),
133 Self::json_to_yaml_value(data_product),
134 );
135 }
136
137 if let Some(tenant) = table.odcl_metadata.get("tenant")
139 && !tenant.is_null()
140 {
141 yaml.insert(
142 serde_yaml::Value::String("tenant".to_string()),
143 Self::json_to_yaml_value(tenant),
144 );
145 }
146
147 if let Some(description) = table.odcl_metadata.get("description")
149 && !description.is_null()
150 {
151 yaml.insert(
152 serde_yaml::Value::String("description".to_string()),
153 Self::json_to_yaml_value(description),
154 );
155 }
156
157 if !table.tags.is_empty() {
159 let tags_yaml: Vec<serde_yaml::Value> = table
160 .tags
161 .iter()
162 .map(|t| serde_yaml::Value::String(t.to_string()))
163 .collect();
164 yaml.insert(
165 serde_yaml::Value::String("tags".to_string()),
166 serde_yaml::Value::Sequence(tags_yaml),
167 );
168 }
169
170 if let Some(team) = table.odcl_metadata.get("team")
172 && !team.is_null()
173 {
174 yaml.insert(
175 serde_yaml::Value::String("team".to_string()),
176 Self::json_to_yaml_value(team),
177 );
178 }
179
180 if let Some(roles) = table.odcl_metadata.get("roles")
182 && !roles.is_null()
183 {
184 yaml.insert(
185 serde_yaml::Value::String("roles".to_string()),
186 Self::json_to_yaml_value(roles),
187 );
188 }
189
190 if let Some(pricing) = table.odcl_metadata.get("pricing")
192 && !pricing.is_null()
193 {
194 yaml.insert(
195 serde_yaml::Value::String("price".to_string()),
196 Self::json_to_yaml_value(pricing),
197 );
198 }
199
200 if let Some(terms) = table.odcl_metadata.get("terms")
202 && !terms.is_null()
203 {
204 yaml.insert(
205 serde_yaml::Value::String("terms".to_string()),
206 Self::json_to_yaml_value(terms),
207 );
208 }
209
210 if let Some(servers) = table.odcl_metadata.get("servers")
212 && !servers.is_null()
213 {
214 yaml.insert(
215 serde_yaml::Value::String("servers".to_string()),
216 Self::json_to_yaml_value(servers),
217 );
218 }
219
220 if let Some(servicelevels) = table.odcl_metadata.get("servicelevels")
222 && !servicelevels.is_null()
223 {
224 yaml.insert(
225 serde_yaml::Value::String("servicelevels".to_string()),
226 Self::json_to_yaml_value(servicelevels),
227 );
228 }
229
230 if let Some(links) = table.odcl_metadata.get("links")
232 && !links.is_null()
233 {
234 yaml.insert(
235 serde_yaml::Value::String("links".to_string()),
236 Self::json_to_yaml_value(links),
237 );
238 }
239
240 if let Some(infrastructure) = table.odcl_metadata.get("infrastructure")
242 && !infrastructure.is_null()
243 {
244 yaml.insert(
245 serde_yaml::Value::String("infrastructure".to_string()),
246 Self::json_to_yaml_value(infrastructure),
247 );
248 }
249
250 let mut schema_array = Vec::new();
252 let mut schema_obj = serde_yaml::Mapping::new();
253
254 schema_obj.insert(
255 serde_yaml::Value::String("name".to_string()),
256 serde_yaml::Value::String(table.name.clone()),
257 );
258
259 let mut properties = serde_yaml::Mapping::new();
261
262 fn build_nested_properties(
264 parent_name: &str,
265 all_columns: &[crate::models::Column],
266 json_to_yaml_fn: &dyn Fn(&serde_json::Value) -> serde_yaml::Value,
267 ) -> Option<serde_yaml::Mapping> {
268 let parent_prefix = format!("{}.", parent_name);
269 let nested_columns: Vec<&crate::models::Column> = all_columns
270 .iter()
271 .filter(|col| col.name.starts_with(&parent_prefix))
272 .collect();
273
274 if nested_columns.is_empty() {
275 return None;
276 }
277
278 let mut nested_props = serde_yaml::Mapping::new();
279
280 let mut child_map: std::collections::HashMap<String, Vec<&crate::models::Column>> =
282 std::collections::HashMap::new();
283
284 for nested_col in &nested_columns {
285 let Some(relative_name) = nested_col.name.strip_prefix(&parent_prefix) else {
287 continue;
288 };
289 if let Some(dot_pos) = relative_name.find('.') {
290 let child_name = &relative_name[..dot_pos];
291 child_map
292 .entry(child_name.to_string())
293 .or_default()
294 .push(nested_col);
295 } else {
296 child_map
298 .entry(relative_name.to_string())
299 .or_default()
300 .push(nested_col);
301 }
302 }
303
304 for (child_name, child_cols) in child_map {
306 let direct_child = child_cols.iter().find(|col| {
308 col.name
309 .strip_prefix(&parent_prefix)
310 .map(|rel_name| !rel_name.contains('.'))
311 .unwrap_or(false)
312 });
313
314 if let Some(child_col) = direct_child {
315 let mut child_prop = serde_yaml::Mapping::new();
316
317 let child_has_nested = child_cols.iter().any(|col| {
319 col.name
320 .strip_prefix(&parent_prefix)
321 .map(|rel| {
322 rel.starts_with(&format!("{}.", child_name)) && rel != child_name
323 })
324 .unwrap_or(false)
325 });
326
327 let data_type_upper = child_col.data_type.to_uppercase();
329 let is_array_object = data_type_upper.starts_with("ARRAY<")
330 && (data_type_upper.contains("OBJECT")
331 || data_type_upper.contains("STRUCT"));
332 let is_struct_or_object = data_type_upper == "STRUCT"
333 || data_type_upper == "OBJECT"
334 || data_type_upper.starts_with("STRUCT<");
335
336 let nested_props_map =
338 build_nested_properties(&child_col.name, all_columns, json_to_yaml_fn);
339
340 if is_array_object && (child_has_nested || nested_props_map.is_some()) {
341 child_prop.insert(
343 serde_yaml::Value::String("type".to_string()),
344 serde_yaml::Value::String("array".to_string()),
345 );
346
347 let mut items = serde_yaml::Mapping::new();
348 items.insert(
349 serde_yaml::Value::String("type".to_string()),
350 serde_yaml::Value::String("object".to_string()),
351 );
352
353 if let Some(nested_props) = nested_props_map {
355 items.insert(
356 serde_yaml::Value::String("properties".to_string()),
357 serde_yaml::Value::Mapping(nested_props),
358 );
359 }
360
361 child_prop.insert(
362 serde_yaml::Value::String("items".to_string()),
363 serde_yaml::Value::Mapping(items),
364 );
365 } else if is_struct_or_object || child_has_nested || nested_props_map.is_some()
366 {
367 child_prop.insert(
369 serde_yaml::Value::String("type".to_string()),
370 serde_yaml::Value::String("object".to_string()),
371 );
372
373 if let Some(nested_props) = nested_props_map {
375 child_prop.insert(
376 serde_yaml::Value::String("properties".to_string()),
377 serde_yaml::Value::Mapping(nested_props),
378 );
379 }
380 } else {
381 child_prop.insert(
383 serde_yaml::Value::String("type".to_string()),
384 serde_yaml::Value::String(child_col.data_type.clone().to_lowercase()),
385 );
386 }
387
388 if !child_col.nullable {
389 child_prop.insert(
390 serde_yaml::Value::String("required".to_string()),
391 serde_yaml::Value::Bool(true),
392 );
393 }
394
395 if !child_col.description.is_empty() {
396 child_prop.insert(
397 serde_yaml::Value::String("description".to_string()),
398 serde_yaml::Value::String(child_col.description.clone()),
399 );
400 }
401
402 if !child_col.quality.is_empty() {
404 let quality_array: Vec<serde_yaml::Value> = child_col
405 .quality
406 .iter()
407 .map(|rule| {
408 let mut rule_map = serde_yaml::Mapping::new();
409 for (k, v) in rule {
410 rule_map.insert(
411 serde_yaml::Value::String(k.clone()),
412 json_to_yaml_fn(v),
413 );
414 }
415 serde_yaml::Value::Mapping(rule_map)
416 })
417 .collect();
418 child_prop.insert(
419 serde_yaml::Value::String("quality".to_string()),
420 serde_yaml::Value::Sequence(quality_array),
421 );
422 }
423
424 if let Some(ref_path) = get_ref_path_from_relationships(child_col) {
426 child_prop.insert(
427 serde_yaml::Value::String("$ref".to_string()),
428 serde_yaml::Value::String(ref_path),
429 );
430 }
431
432 if !child_col.enum_values.is_empty() {
434 let enum_yaml: Vec<serde_yaml::Value> = child_col
435 .enum_values
436 .iter()
437 .map(|e| serde_yaml::Value::String(e.clone()))
438 .collect();
439 child_prop.insert(
440 serde_yaml::Value::String("enum".to_string()),
441 serde_yaml::Value::Sequence(enum_yaml),
442 );
443 }
444
445 if !child_col.constraints.is_empty() {
447 let constraints_yaml: Vec<serde_yaml::Value> = child_col
448 .constraints
449 .iter()
450 .map(|c| serde_yaml::Value::String(c.clone()))
451 .collect();
452 child_prop.insert(
453 serde_yaml::Value::String("constraints".to_string()),
454 serde_yaml::Value::Sequence(constraints_yaml),
455 );
456 }
457
458 if let Some(ref fk) = child_col.foreign_key {
460 let mut fk_map = serde_yaml::Mapping::new();
461 fk_map.insert(
462 serde_yaml::Value::String("table".to_string()),
463 serde_yaml::Value::String(fk.table_id.clone()),
464 );
465 fk_map.insert(
466 serde_yaml::Value::String("column".to_string()),
467 serde_yaml::Value::String(fk.column_name.clone()),
468 );
469 child_prop.insert(
470 serde_yaml::Value::String("foreignKey".to_string()),
471 serde_yaml::Value::Mapping(fk_map),
472 );
473 }
474
475 nested_props.insert(
476 serde_yaml::Value::String(child_name),
477 serde_yaml::Value::Mapping(child_prop),
478 );
479 }
480 }
481
482 if nested_props.is_empty() {
483 None
484 } else {
485 Some(nested_props)
486 }
487 }
488
489 for column in &table.columns {
490 if column.name.contains('.') {
492 continue;
493 }
494
495 let mut prop = serde_yaml::Mapping::new();
496
497 let has_nested = table.columns.iter().any(|col| {
499 col.name.starts_with(&format!("{}.", column.name)) && col.name != column.name
500 });
501
502 let data_type_upper = column.data_type.to_uppercase();
504 let is_array_object = data_type_upper.starts_with("ARRAY<")
505 && (data_type_upper.contains("OBJECT") || data_type_upper.contains("STRUCT"));
506 let is_struct_or_object = data_type_upper == "STRUCT"
507 || data_type_upper == "OBJECT"
508 || data_type_upper.starts_with("STRUCT<");
509
510 if has_nested {
512 let nested_props = build_nested_properties(
514 &column.name,
515 &table.columns,
516 &Self::json_to_yaml_value,
517 );
518
519 if is_array_object {
520 prop.insert(
522 serde_yaml::Value::String("type".to_string()),
523 serde_yaml::Value::String("array".to_string()),
524 );
525
526 let mut items = serde_yaml::Mapping::new();
527 items.insert(
528 serde_yaml::Value::String("type".to_string()),
529 serde_yaml::Value::String("object".to_string()),
530 );
531
532 if let Some(nested_props_map) = nested_props {
534 items.insert(
535 serde_yaml::Value::String("properties".to_string()),
536 serde_yaml::Value::Mapping(nested_props_map),
537 );
538 }
539
540 prop.insert(
541 serde_yaml::Value::String("items".to_string()),
542 serde_yaml::Value::Mapping(items),
543 );
544 } else if is_struct_or_object || nested_props.is_some() {
545 prop.insert(
547 serde_yaml::Value::String("type".to_string()),
548 serde_yaml::Value::String("object".to_string()),
549 );
550
551 if let Some(nested_props_map) = nested_props {
553 prop.insert(
554 serde_yaml::Value::String("properties".to_string()),
555 serde_yaml::Value::Mapping(nested_props_map),
556 );
557 }
558 } else {
559 prop.insert(
561 serde_yaml::Value::String("type".to_string()),
562 serde_yaml::Value::String(column.data_type.clone().to_lowercase()),
563 );
564 }
565 } else {
566 prop.insert(
568 serde_yaml::Value::String("type".to_string()),
569 serde_yaml::Value::String(column.data_type.clone().to_lowercase()),
570 );
571 }
572
573 if !column.nullable {
574 prop.insert(
575 serde_yaml::Value::String("required".to_string()),
576 serde_yaml::Value::Bool(true),
577 );
578 }
579
580 if column.primary_key {
581 prop.insert(
582 serde_yaml::Value::String("primaryKey".to_string()),
583 serde_yaml::Value::Bool(true),
584 );
585 }
586
587 if column.secondary_key {
588 prop.insert(
589 serde_yaml::Value::String("businessKey".to_string()),
590 serde_yaml::Value::Bool(true),
591 );
592 }
593
594 if !column.description.is_empty() {
595 prop.insert(
596 serde_yaml::Value::String("description".to_string()),
597 serde_yaml::Value::String(column.description.clone()),
598 );
599 }
600
601 if !column.quality.is_empty() {
603 let quality_array: Vec<serde_yaml::Value> = column
604 .quality
605 .iter()
606 .map(|rule| {
607 let mut rule_map = serde_yaml::Mapping::new();
608 for (k, v) in rule {
609 rule_map.insert(
610 serde_yaml::Value::String(k.clone()),
611 Self::json_to_yaml_value(v),
612 );
613 }
614 serde_yaml::Value::Mapping(rule_map)
615 })
616 .collect();
617 prop.insert(
618 serde_yaml::Value::String("quality".to_string()),
619 serde_yaml::Value::Sequence(quality_array),
620 );
621 }
622
623 if let Some(ref_path) = get_ref_path_from_relationships(column) {
625 prop.insert(
626 serde_yaml::Value::String("$ref".to_string()),
627 serde_yaml::Value::String(ref_path),
628 );
629 }
630
631 if !column.enum_values.is_empty() {
633 let enum_yaml: Vec<serde_yaml::Value> = column
634 .enum_values
635 .iter()
636 .map(|e| serde_yaml::Value::String(e.clone()))
637 .collect();
638 prop.insert(
639 serde_yaml::Value::String("enum".to_string()),
640 serde_yaml::Value::Sequence(enum_yaml),
641 );
642 }
643
644 if !column.constraints.is_empty() {
646 let constraints_yaml: Vec<serde_yaml::Value> = column
647 .constraints
648 .iter()
649 .map(|c| serde_yaml::Value::String(c.clone()))
650 .collect();
651 prop.insert(
652 serde_yaml::Value::String("constraints".to_string()),
653 serde_yaml::Value::Sequence(constraints_yaml),
654 );
655 }
656
657 if let Some(ref fk) = column.foreign_key {
659 let mut fk_map = serde_yaml::Mapping::new();
660 fk_map.insert(
661 serde_yaml::Value::String("table".to_string()),
662 serde_yaml::Value::String(fk.table_id.clone()),
663 );
664 fk_map.insert(
665 serde_yaml::Value::String("column".to_string()),
666 serde_yaml::Value::String(fk.column_name.clone()),
667 );
668 prop.insert(
669 serde_yaml::Value::String("foreignKey".to_string()),
670 serde_yaml::Value::Mapping(fk_map),
671 );
672 }
673
674 properties.insert(
675 serde_yaml::Value::String(column.name.clone()),
676 serde_yaml::Value::Mapping(prop),
677 );
678 }
679
680 schema_obj.insert(
681 serde_yaml::Value::String("properties".to_string()),
682 serde_yaml::Value::Mapping(properties),
683 );
684
685 schema_array.push(serde_yaml::Value::Mapping(schema_obj));
686 yaml.insert(
687 serde_yaml::Value::String("schema".to_string()),
688 serde_yaml::Value::Sequence(schema_array),
689 );
690
691 if !table.quality.is_empty() {
693 let quality_array: Vec<serde_yaml::Value> = table
694 .quality
695 .iter()
696 .map(|rule| {
697 let mut rule_map = serde_yaml::Mapping::new();
698 for (k, v) in rule {
699 rule_map.insert(
700 serde_yaml::Value::String(k.clone()),
701 Self::json_to_yaml_value(v),
702 );
703 }
704 serde_yaml::Value::Mapping(rule_map)
705 })
706 .collect();
707 yaml.insert(
708 serde_yaml::Value::String("quality".to_string()),
709 serde_yaml::Value::Sequence(quality_array),
710 );
711 }
712
713 let excluded_keys = [
715 "id",
716 "version",
717 "status",
718 "domain",
719 "dataProduct",
720 "tenant",
721 "description",
722 "team",
723 "roles",
724 "pricing",
725 "terms",
726 "servers",
727 "servicelevels",
728 "links",
729 "apiVersion",
730 "kind",
731 "info",
732 "dataContractSpecification",
733 ];
734
735 let mut custom_props = Vec::new();
736 for (key, value) in &table.odcl_metadata {
737 if !excluded_keys.contains(&key.as_str()) && !value.is_null() {
738 let mut prop = serde_yaml::Mapping::new();
739 prop.insert(
740 serde_yaml::Value::String("property".to_string()),
741 serde_yaml::Value::String(key.clone()),
742 );
743 prop.insert(
744 serde_yaml::Value::String("value".to_string()),
745 Self::json_to_yaml_value(value),
746 );
747 custom_props.push(serde_yaml::Value::Mapping(prop));
748 }
749 }
750
751 if let Some(ref db_type) = table.database_type {
753 let mut prop = serde_yaml::Mapping::new();
754 prop.insert(
755 serde_yaml::Value::String("property".to_string()),
756 serde_yaml::Value::String("databaseType".to_string()),
757 );
758 prop.insert(
759 serde_yaml::Value::String("value".to_string()),
760 serde_yaml::Value::String(format!("{:?}", db_type)),
761 );
762 custom_props.push(serde_yaml::Value::Mapping(prop));
763 }
764
765 if !table.medallion_layers.is_empty() {
767 let layers: Vec<serde_yaml::Value> = table
768 .medallion_layers
769 .iter()
770 .map(|l| serde_yaml::Value::String(format!("{:?}", l)))
771 .collect();
772 let mut prop = serde_yaml::Mapping::new();
773 prop.insert(
774 serde_yaml::Value::String("property".to_string()),
775 serde_yaml::Value::String("medallionLayers".to_string()),
776 );
777 prop.insert(
778 serde_yaml::Value::String("value".to_string()),
779 serde_yaml::Value::Sequence(layers),
780 );
781 custom_props.push(serde_yaml::Value::Mapping(prop));
782 }
783
784 if let Some(ref scd_pattern) = table.scd_pattern {
786 let mut prop = serde_yaml::Mapping::new();
787 prop.insert(
788 serde_yaml::Value::String("property".to_string()),
789 serde_yaml::Value::String("scdPattern".to_string()),
790 );
791 prop.insert(
792 serde_yaml::Value::String("value".to_string()),
793 serde_yaml::Value::String(format!("{:?}", scd_pattern)),
794 );
795 custom_props.push(serde_yaml::Value::Mapping(prop));
796 }
797
798 if let Some(ref dv_class) = table.data_vault_classification {
800 let mut prop = serde_yaml::Mapping::new();
801 prop.insert(
802 serde_yaml::Value::String("property".to_string()),
803 serde_yaml::Value::String("dataVaultClassification".to_string()),
804 );
805 prop.insert(
806 serde_yaml::Value::String("value".to_string()),
807 serde_yaml::Value::String(format!("{:?}", dv_class)),
808 );
809 custom_props.push(serde_yaml::Value::Mapping(prop));
810 }
811
812 if let Some(ref catalog) = table.catalog_name {
814 let mut prop = serde_yaml::Mapping::new();
815 prop.insert(
816 serde_yaml::Value::String("property".to_string()),
817 serde_yaml::Value::String("catalogName".to_string()),
818 );
819 prop.insert(
820 serde_yaml::Value::String("value".to_string()),
821 serde_yaml::Value::String(catalog.clone()),
822 );
823 custom_props.push(serde_yaml::Value::Mapping(prop));
824 }
825
826 if let Some(ref schema) = table.schema_name {
827 let mut prop = serde_yaml::Mapping::new();
828 prop.insert(
829 serde_yaml::Value::String("property".to_string()),
830 serde_yaml::Value::String("schemaName".to_string()),
831 );
832 prop.insert(
833 serde_yaml::Value::String("value".to_string()),
834 serde_yaml::Value::String(schema.clone()),
835 );
836 custom_props.push(serde_yaml::Value::Mapping(prop));
837 }
838
839 if !custom_props.is_empty() {
840 yaml.insert(
841 serde_yaml::Value::String("customProperties".to_string()),
842 serde_yaml::Value::Sequence(custom_props),
843 );
844 }
845
846 yaml.insert(
848 serde_yaml::Value::String("contractCreatedTs".to_string()),
849 serde_yaml::Value::String(table.created_at.to_rfc3339()),
850 );
851
852 serde_yaml::to_string(&yaml).unwrap_or_default()
853 }
854
855 pub fn export(
857 &self,
858 tables: &[Table],
859 _format: &str,
860 ) -> Result<HashMap<String, ExportResult>, ExportError> {
861 let mut exports = HashMap::new();
862 for table in tables {
863 let yaml = Self::export_odcs_v3_1_0_format(table);
865 exports.insert(
866 table.name.clone(),
867 ExportResult {
868 content: yaml,
869 format: "odcs_v3_1_0".to_string(),
870 },
871 );
872 }
873 Ok(exports)
874 }
875
876 pub fn export_model(
878 model: &DataModel,
879 table_ids: Option<&[uuid::Uuid]>,
880 _format: &str,
881 ) -> HashMap<String, String> {
882 let tables_to_export: Vec<&Table> = if let Some(ids) = table_ids {
883 model
884 .tables
885 .iter()
886 .filter(|t| ids.contains(&t.id))
887 .collect()
888 } else {
889 model.tables.iter().collect()
890 };
891
892 let mut exports = HashMap::new();
893 for table in tables_to_export {
894 let yaml = Self::export_odcs_v3_1_0_format(table);
896 exports.insert(table.name.clone(), yaml);
897 }
898
899 exports
900 }
901}
902
903#[cfg(test)]
904mod tests {
905 use super::*;
906 use crate::export::odcs::ODCSExporter;
907 use crate::models::{Column, Tag};
908
909 #[test]
910 fn test_export_odcs_v3_1_0_basic() {
911 let table = Table {
912 id: Table::generate_id("test_table", None, None, None),
913 name: "test_table".to_string(),
914 columns: vec![Column {
915 name: "id".to_string(),
916 data_type: "BIGINT".to_string(),
917 nullable: false,
918 primary_key: true,
919 description: "Primary key".to_string(),
920 ..Default::default()
921 }],
922 database_type: None,
923 catalog_name: None,
924 schema_name: None,
925 medallion_layers: Vec::new(),
926 scd_pattern: None,
927 data_vault_classification: None,
928 modeling_level: None,
929 tags: vec![Tag::Simple("test".to_string())],
930 odcl_metadata: HashMap::new(),
931 owner: None,
932 sla: None,
933 contact_details: None,
934 infrastructure_type: None,
935 notes: None,
936 position: None,
937 yaml_file_path: None,
938 drawio_cell_id: None,
939 quality: Vec::new(),
940 errors: Vec::new(),
941 created_at: chrono::Utc::now(),
942 updated_at: chrono::Utc::now(),
943 };
944
945 let yaml = ODCSExporter::export_table(&table, "odcs_v3_1_0");
946
947 assert!(yaml.contains("apiVersion: v3.1.0"));
948 assert!(yaml.contains("kind: DataContract"));
949 assert!(yaml.contains("name: test_table"));
950 assert!(yaml.contains("tags:"));
951 assert!(yaml.contains("- test"));
952 }
953}