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